larkin 0.2.70

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 (144) hide show
  1. package/CONTRIBUTING.md +48 -0
  2. package/LICENSE +201 -0
  3. package/README.md +122 -0
  4. package/SECURITY.md +14 -0
  5. package/artifacts/release/THIRD_PARTY_NOTICES.txt +5488 -0
  6. package/dist/agent/agent-cli-capabilities.cjs +43 -0
  7. package/dist/agent/agent-cli-capabilities.mjs +35 -0
  8. package/dist/agent/agent-state-store.cjs +961 -0
  9. package/dist/agent/agent-state-store.mjs +923 -0
  10. package/dist/agent/agent-transport.cjs +201 -0
  11. package/dist/agent/agent-transport.mjs +194 -0
  12. package/dist/agent/config-cli-contract.cjs +19 -0
  13. package/dist/agent/config-cli-contract.mjs +16 -0
  14. package/dist/agent/context-prompt.cjs +159 -0
  15. package/dist/agent/context-prompt.mjs +155 -0
  16. package/dist/agent/freshness-gate.mjs +24 -0
  17. package/dist/agent/history-projection.cjs +72 -0
  18. package/dist/agent/history-projection.mjs +65 -0
  19. package/dist/agent/host-reminder-orchestrator.cjs +289 -0
  20. package/dist/agent/host-reminder-orchestrator.mjs +252 -0
  21. package/dist/agent/inbox-projection.cjs +92 -0
  22. package/dist/agent/inbox-projection.mjs +85 -0
  23. package/dist/agent/interaction-state-machine.cjs +710 -0
  24. package/dist/agent/interaction-state-machine.mjs +705 -0
  25. package/dist/agent/reminder-routes.cjs +192 -0
  26. package/dist/agent/reminder-routes.mjs +156 -0
  27. package/dist/agent/reminder-store.cjs +220 -0
  28. package/dist/agent/reminder-store.mjs +205 -0
  29. package/dist/agent/transport-business-context.cjs +486 -0
  30. package/dist/agent/transport-business-context.mjs +446 -0
  31. package/dist/agent/transport-shell.cjs +45 -0
  32. package/dist/agent/transport-shell.mjs +39 -0
  33. package/dist/app/agent-cli.mjs +596 -0
  34. package/dist/app/agent-config.mjs +633 -0
  35. package/dist/app/agent-lark-cli-workspace.cjs +95 -0
  36. package/dist/app/agent-lark-cli-workspace.mjs +85 -0
  37. package/dist/app/agent-readiness.cjs +42 -0
  38. package/dist/app/agent-readiness.mjs +38 -0
  39. package/dist/app/binary-entry.mjs +86 -0
  40. package/dist/app/cli.mjs +176 -0
  41. package/dist/app/dashboard.mjs +205 -0
  42. package/dist/app/internal-command.cjs +57 -0
  43. package/dist/app/internal-command.mjs +48 -0
  44. package/dist/app/lark-channel-secret.mjs +22 -0
  45. package/dist/app/lark-cli.mjs +887 -0
  46. package/dist/app/lark.mjs +45 -0
  47. package/dist/app/local-control.mjs +728 -0
  48. package/dist/app/official-lark-cli.cjs +138 -0
  49. package/dist/app/official-lark-cli.mjs +128 -0
  50. package/dist/app/pi-auth-cli.mjs +67 -0
  51. package/dist/app/run.mjs +364 -0
  52. package/dist/app/runtime-agent-config.mjs +416 -0
  53. package/dist/app/runtime-model-directory.mjs +78 -0
  54. package/dist/app/runtime-process.mjs +113 -0
  55. package/dist/app/session-cli.mjs +83 -0
  56. package/dist/app/setup-dashboard.mjs +26 -0
  57. package/dist/app/setup.mjs +166 -0
  58. package/dist/app/telemetry.mjs +83 -0
  59. package/dist/dashboard/dashboard-assets.mjs +21 -0
  60. package/dist/dashboard/dashboard-avatar.mjs +128 -0
  61. package/dist/dashboard/dashboard-config-controller.mjs +525 -0
  62. package/dist/dashboard/dashboard-lifecycle.cjs +97 -0
  63. package/dist/dashboard/dashboard-lifecycle.mjs +88 -0
  64. package/dist/dashboard/dashboard-template.mjs +29 -0
  65. package/dist/dashboard/dashboard-view-model.mjs +556 -0
  66. package/dist/dashboard/dashboard-workspace.mjs +104 -0
  67. package/dist/dashboard/web/assets/dashboard.css +3 -0
  68. package/dist/dashboard/web/assets/dashboard.js +49 -0
  69. package/dist/feishu/channel-transport.cjs +151 -0
  70. package/dist/feishu/channel-transport.mjs +148 -0
  71. package/dist/feishu/document-comment.cjs +120 -0
  72. package/dist/feishu/document-comment.mjs +109 -0
  73. package/dist/feishu/host-business-state.cjs +477 -0
  74. package/dist/feishu/host-business-state.mjs +462 -0
  75. package/dist/feishu/host-channel-business.cjs +161 -0
  76. package/dist/feishu/host-channel-business.mjs +157 -0
  77. package/dist/feishu/host-processing-eye.cjs +185 -0
  78. package/dist/feishu/host-processing-eye.mjs +181 -0
  79. package/dist/feishu/host-shell.cjs +1425 -0
  80. package/dist/feishu/host-shell.mjs +1385 -0
  81. package/dist/feishu/im-freshness-adapter.mjs +89 -0
  82. package/dist/feishu/interaction-orchestrator.cjs +279 -0
  83. package/dist/feishu/interaction-orchestrator.mjs +242 -0
  84. package/dist/feishu/lark-passthrough.mjs +70 -0
  85. package/dist/feishu/message-policy.cjs +111 -0
  86. package/dist/feishu/message-policy.mjs +98 -0
  87. package/dist/feishu/outbound-transport.cjs +221 -0
  88. package/dist/feishu/outbound-transport.mjs +215 -0
  89. package/dist/platform/build-info.cjs +25 -0
  90. package/dist/platform/build-info.mjs +18 -0
  91. package/dist/platform/callback-capability.cjs +301 -0
  92. package/dist/platform/callback-capability.mjs +252 -0
  93. package/dist/platform/check-bun-version.cjs +12 -0
  94. package/dist/platform/config.cjs +751 -0
  95. package/dist/platform/config.mjs +694 -0
  96. package/dist/platform/process-inspect.cjs +89 -0
  97. package/dist/platform/process-state.cjs +237 -0
  98. package/dist/platform/process-state.mjs +223 -0
  99. package/dist/platform/release-artifacts.mjs +61 -0
  100. package/dist/platform/root-layout.cjs +111 -0
  101. package/dist/platform/root-layout.mjs +72 -0
  102. package/dist/platform/telemetry-config.cjs +64 -0
  103. package/dist/platform/telemetry-config.mjs +56 -0
  104. package/dist/platform/telemetry-spool.cjs +661 -0
  105. package/dist/platform/telemetry-spool.mjs +653 -0
  106. package/dist/platform/telemetry-tracing.cjs +602 -0
  107. package/dist/platform/telemetry-tracing.mjs +596 -0
  108. package/dist/platform/telemetry-uploader.cjs +150 -0
  109. package/dist/platform/telemetry-uploader.mjs +146 -0
  110. package/dist/platform/workspace-service.cjs +469 -0
  111. package/dist/platform/workspace-service.mjs +430 -0
  112. package/dist/runtime/builtin-pi-assets.mjs +33 -0
  113. package/dist/runtime/claude-model-catalog.cjs +102 -0
  114. package/dist/runtime/claude-model-catalog.mjs +99 -0
  115. package/dist/runtime/codex-model-catalog.cjs +108 -0
  116. package/dist/runtime/codex-model-catalog.mjs +105 -0
  117. package/dist/runtime/pi-bash-timeout-extension.mjs +60 -0
  118. package/dist/runtime/pi-bash-timeout-injection.mjs +60 -0
  119. package/dist/runtime/pi-bash-timeout.bundle.js +43 -0
  120. package/dist/runtime/pi-model-catalog.cjs +116 -0
  121. package/dist/runtime/pi-model-catalog.mjs +75 -0
  122. package/dist/runtime/pi-official-auth.mjs +480 -0
  123. package/dist/runtime/pi-provider-config.cjs +252 -0
  124. package/dist/runtime/pi-provider-config.mjs +239 -0
  125. package/dist/runtime/pi-rpc-client.cjs +255 -0
  126. package/dist/runtime/pi-rpc-client.mjs +251 -0
  127. package/dist/runtime/pi-subagent-injection.mjs +129 -0
  128. package/dist/runtime/pi-subagents.bundle.js +8990 -0
  129. package/dist/runtime/runtime-adapters.mjs +1019 -0
  130. package/dist/runtime/runtime-contracts.cjs +2 -0
  131. package/dist/runtime/runtime-contracts.mjs +1 -0
  132. package/dist/runtime/runtime-host.cjs +930 -0
  133. package/dist/runtime/runtime-host.mjs +922 -0
  134. package/dist/runtime/runtime-model-catalog.cjs +43 -0
  135. package/dist/runtime/runtime-model-catalog.mjs +40 -0
  136. package/dist/runtime/runtime-readiness.cjs +177 -0
  137. package/dist/runtime/runtime-readiness.mjs +133 -0
  138. package/dist/setup/bot-register.mjs +655 -0
  139. package/dist/setup/grant-scopes.mjs +133 -0
  140. package/dist/setup/run-credential-preflight.mjs +89 -0
  141. package/dist/setup/setup-agent-choice.mjs +172 -0
  142. package/dist/setup/setup-bind.mjs +337 -0
  143. package/dist/setup/setup-binding.mjs +52 -0
  144. package/package.json +112 -0
@@ -0,0 +1,446 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import { createAgentStateStore } from "./agent-state-store.mjs";
5
+ import { createChannelTransport } from "../feishu/channel-transport.mjs";
6
+ import * as larkinConfig from "../platform/config.mjs";
7
+ import { parseFeishuText, projectFeishuHistoryEnvelope, sortVisibleHistory, } from "./history-projection.mjs";
8
+ import { projectInboxEvents } from "./inbox-projection.mjs";
9
+ import { signatureDescription } from "../feishu/message-policy.mjs";
10
+ import { createOutboundTransport } from "../feishu/outbound-transport.mjs";
11
+ import { createReminderRoutes } from "./reminder-routes.mjs";
12
+ import { isImageMime, looksLikeMarkdown, resolveMappedChatId, safeConversationExcerpt } from "./transport-shell.mjs";
13
+ import { managedOfficialLarkCli } from "../app/agent-lark-cli-workspace.mjs";
14
+ export function formatFeishuError(error, grantLink = null) {
15
+ if (!error)
16
+ return null;
17
+ const parts = [];
18
+ if (error.message)
19
+ parts.push(error.message);
20
+ if (error.missing_scopes?.length)
21
+ parts.push(`missing_scopes: ${error.missing_scopes.join(", ")}`);
22
+ const tag = [error.type, error.subtype].filter(Boolean).join("/");
23
+ const meta = [tag, error.code != null ? `code ${error.code}` : "", error.log_id ? `log_id ${error.log_id}` : ""].filter(Boolean).join(" | ");
24
+ if (meta)
25
+ parts.push(`(${meta})`);
26
+ let output = `飞书接口报错:${parts.join(" ")}`;
27
+ const isScope = error.code !== 230027
28
+ && (error.subtype === "app_scope_not_applied" || error.code === 99991672 || Boolean(error.missing_scopes?.length));
29
+ if (error.code === 230027) {
30
+ output += "\n\n[系统提示] 请检查 bot/app 是否已加入目标 chat/thread(membership),并确认该目标允许当前 app 执行操作(target authorization);这不是 OAuth scope apply 错误。";
31
+ }
32
+ if (isScope) {
33
+ output += grantLink
34
+ ? `\n\n授权链接(请 app owner 本人打开确认,约50分钟内有效,确认后即生效):\n${grantLink}`
35
+ : "\n\n[系统提示] 当前没有可用的授权链接。请勿自行拼造或猜测任何 scope-apply / 开发者后台链接(那类链接对本应用无效)。只需如实说明缺少上述 scope、需有开发者后台权限者去补充;有可用链接时系统会在此处给出。";
36
+ }
37
+ return output;
38
+ }
39
+ export function createTransportBusinessContext(env = process.env) {
40
+ const { config } = larkinConfig.loadConfig(env);
41
+ const selectedAgent = larkinConfig.selectAgent(config, env);
42
+ const stateStore = createAgentStateStore(config.larkinHome, selectedAgent.agentId);
43
+ if (path.resolve(stateStore.paths.root) !== path.resolve(selectedAgent.stateDir)) {
44
+ throw new Error(`Agent ${selectedAgent.agentId} state store 路径不一致`);
45
+ }
46
+ if (!config.serverId)
47
+ throw new Error("config.serverId 未配置");
48
+ const paths = stateStore.paths;
49
+ const attachmentDir = path.join(paths.root, "attachments");
50
+ const attachmentIndexFile = path.join(paths.root, "attachments.json");
51
+ const transportLogFile = path.join(paths.root, "transport.log");
52
+ const dryRun = env.LARKIN_FEISHU_DRYRUN === "1";
53
+ const agentId = selectedAgent.agentId;
54
+ const agentName = selectedAgent.name;
55
+ const agentRuntime = selectedAgent.runtime;
56
+ const agentModel = selectedAgent.model;
57
+ const agentEffort = selectedAgent.effort ?? null;
58
+ const serverId = config.serverId;
59
+ const unknownCreatedAt = "1970-01-01T00:00:00.000Z";
60
+ const log = (...parts) => {
61
+ const line = `[feishu-transport] ${parts.join(" ")}\n`;
62
+ process.stderr.write(line);
63
+ try {
64
+ fs.appendFileSync(transportLogFile, `${new Date().toISOString()} ${line}`);
65
+ }
66
+ catch { /* best effort */ }
67
+ };
68
+ const logRequest = (method, requestPath, body) => {
69
+ try {
70
+ fs.appendFileSync(transportLogFile, `${new Date().toISOString()} REQ ${method} ${requestPath} body=${JSON.stringify(body).slice(0, 300)}\n`);
71
+ }
72
+ catch { /* best effort */ }
73
+ };
74
+ let signatureCache = null;
75
+ const senderSignature = (senderId) => {
76
+ if (!senderId)
77
+ return null;
78
+ try {
79
+ const mtime = fs.statSync(paths.senderProfiles).mtimeMs;
80
+ if (!signatureCache || signatureCache.mtime !== mtime) {
81
+ signatureCache = { mtime, data: JSON.parse(fs.readFileSync(paths.senderProfiles, "utf8")) };
82
+ }
83
+ return signatureCache.data[senderId]?.description || null;
84
+ }
85
+ catch {
86
+ return null;
87
+ }
88
+ };
89
+ let identityCache;
90
+ const botIdentity = () => {
91
+ if (identityCache !== undefined)
92
+ return identityCache;
93
+ try {
94
+ identityCache = JSON.parse(fs.readFileSync(paths.botIdentity, "utf8"));
95
+ }
96
+ catch {
97
+ identityCache = null;
98
+ }
99
+ return identityCache;
100
+ };
101
+ const appendConversation = (item) => {
102
+ try {
103
+ fs.appendFileSync(paths.conversation, `${JSON.stringify({ ...item, text: safeConversationExcerpt(item.text) })}\n`);
104
+ }
105
+ catch (error) {
106
+ log(`conversation 写失败: ${error.message}`);
107
+ }
108
+ };
109
+ const loadMap = () => {
110
+ try {
111
+ return JSON.parse(fs.readFileSync(paths.map, "utf8"));
112
+ }
113
+ catch {
114
+ return {};
115
+ }
116
+ };
117
+ const resolveChatId = (target) => resolveMappedChatId(loadMap(), target);
118
+ const larkArgs = (...rest) => rest;
119
+ const larkCli = (args, cwd) => {
120
+ const managed = managedOfficialLarkCli(selectedAgent, process.env);
121
+ const options = {
122
+ encoding: "utf8",
123
+ env: managed.env,
124
+ ...(cwd ? { cwd } : {}),
125
+ };
126
+ const result = spawnSync(managed.command.command, [...managed.command.argsPrefix, ...larkArgs(...args)], options);
127
+ return { code: result.status, stdout: result.stdout || "", stderr: result.stderr || "" };
128
+ };
129
+ const larkJsonOut = (args) => {
130
+ let result;
131
+ try {
132
+ result = larkCli(args);
133
+ }
134
+ catch {
135
+ return null;
136
+ }
137
+ const clean = (value) => value.split("\n").filter((line) => !/^\[page|fetching/.test(line.trim())).join("\n");
138
+ for (const value of [result.stdout, result.stderr]) {
139
+ try {
140
+ const parsed = JSON.parse(clean(value));
141
+ if (parsed && typeof parsed === "object")
142
+ return parsed;
143
+ }
144
+ catch { /* try next stream */ }
145
+ }
146
+ return null;
147
+ };
148
+ const memberData = (chatId) => {
149
+ const result = larkJsonOut(["im", "+chat-members-list", "--chat-id", chatId, "--json"]);
150
+ return result?.ok && result.data ? result.data : null;
151
+ };
152
+ const userIds = new Map();
153
+ const chatUserIdSet = (chatId) => {
154
+ if (userIds.has(chatId))
155
+ return userIds.get(chatId) ?? null;
156
+ const result = larkJsonOut(["im", "+chat-members-list", "--chat-id", chatId, "--member-id-type", "user_id", "--json"]);
157
+ const set = result?.ok && result.data
158
+ ? new Set((result.data.users || []).flatMap((user) => user.member_id ? [user.member_id] : []))
159
+ : null;
160
+ userIds.set(chatId, set);
161
+ return set;
162
+ };
163
+ const memberNames = (chatId) => {
164
+ const names = {};
165
+ const data = memberData(chatId);
166
+ if (!data)
167
+ return names;
168
+ for (const user of data.users || [])
169
+ if (user.member_id)
170
+ names[user.member_id] = user.name || user.member_id;
171
+ for (const bot of data.bots || []) {
172
+ const name = bot.name || bot.member_id || "bot";
173
+ if (bot.member_id)
174
+ names[bot.member_id] = name;
175
+ if (bot.app_id)
176
+ names[bot.app_id] = name;
177
+ }
178
+ return names;
179
+ };
180
+ const pairCache = new Map();
181
+ const memberNameIdPairs = (chatId) => {
182
+ const cached = pairCache.get(chatId);
183
+ if (cached)
184
+ return cached;
185
+ const byName = {};
186
+ for (const [id, name] of Object.entries(memberNames(chatId))) {
187
+ if (!name || name === id)
188
+ continue;
189
+ if (!byName[name] || (id.startsWith("ou_") && !byName[name].startsWith("ou_")))
190
+ byName[name] = id;
191
+ }
192
+ const pairs = Object.entries(byName).sort((left, right) => right[0].length - left[0].length);
193
+ pairCache.set(chatId, pairs);
194
+ return pairs;
195
+ };
196
+ const renderMentions = (content, chatId) => {
197
+ let mentioned = false;
198
+ let output = String(content).replace(/@(ou_[A-Za-z0-9]+|on_[A-Za-z0-9]+)/g, (_whole, id) => {
199
+ mentioned = true;
200
+ return `<at id="${id}"></at>`;
201
+ });
202
+ if (chatId && output.includes("@")) {
203
+ const pairs = memberNameIdPairs(chatId);
204
+ let rendered = "";
205
+ for (let index = 0; index < output.length;) {
206
+ if (output[index] === "@") {
207
+ const rest = output.slice(index + 1);
208
+ const hit = pairs.find(([name]) => rest.startsWith(name) && !/^[\p{L}\p{N}_]/u.test(rest.slice(name.length)));
209
+ if (hit) {
210
+ mentioned = true;
211
+ rendered += `<at id="${hit[1]}"></at>`;
212
+ index += hit[0].length + 1;
213
+ continue;
214
+ }
215
+ }
216
+ rendered += output[index];
217
+ index += 1;
218
+ }
219
+ output = rendered;
220
+ }
221
+ const ids = chatId ? chatUserIdSet(chatId) : null;
222
+ if (ids?.size) {
223
+ output = output.replace(/@([A-Za-z0-9]{2,})/g, (whole, token) => {
224
+ if (!ids.has(token))
225
+ return whole;
226
+ mentioned = true;
227
+ return `<at id="${token}"></at>`;
228
+ });
229
+ }
230
+ return { content: output, mentioned };
231
+ };
232
+ const larkSend = (chatId, content, idempotencyKey) => {
233
+ const rendered = renderMentions(content, chatId);
234
+ const args = ["im", "+messages-send", "--chat-id", chatId, rendered.mentioned || looksLikeMarkdown(rendered.content) ? "--markdown" : "--text", rendered.content, "--json"];
235
+ if (idempotencyKey)
236
+ args.push("--idempotency-key", String(idempotencyKey));
237
+ if (dryRun)
238
+ args.push("--dry-run");
239
+ return larkCli(args);
240
+ };
241
+ const larkReply = (messageId, content, idempotencyKey, chatId) => {
242
+ const rendered = renderMentions(content, chatId);
243
+ const args = ["im", "+messages-reply", "--message-id", messageId, "--reply-in-thread", rendered.mentioned || looksLikeMarkdown(rendered.content) ? "--markdown" : "--text", rendered.content, "--json"];
244
+ if (idempotencyKey)
245
+ args.push("--idempotency-key", String(idempotencyKey));
246
+ if (dryRun)
247
+ args.push("--dry-run");
248
+ return larkCli(args);
249
+ };
250
+ const larkSendMedia = (chatId, reply, attachment, idempotencyKey) => {
251
+ const flag = isImageMime(attachment.mimeType) ? "--image" : "--file";
252
+ const replyTo = reply?.in_topic && reply.reply_to ? reply.reply_to : null;
253
+ const args = replyTo
254
+ ? ["im", "+messages-reply", "--message-id", replyTo, "--reply-in-thread", flag, attachment.file, "--json"]
255
+ : ["im", "+messages-send", "--chat-id", chatId, flag, attachment.file, "--json"];
256
+ if (idempotencyKey)
257
+ args.push("--idempotency-key", idempotencyKey);
258
+ if (dryRun)
259
+ args.push("--dry-run");
260
+ return larkCli(args, attachmentDir);
261
+ };
262
+ const replyContextFor = (target) => {
263
+ try {
264
+ return JSON.parse(fs.readFileSync(paths.replyctx, "utf8"))[target] || null;
265
+ }
266
+ catch {
267
+ return null;
268
+ }
269
+ };
270
+ const outbound = createOutboundTransport({
271
+ attachmentDir, attachmentIndexFile, resolveChatId, replyContextFor,
272
+ sendText: larkSend, replyText: larkReply, sendMedia: larkSendMedia,
273
+ appendConversation, botDisplayName: () => botIdentity()?.name, agentName, dryRun, log,
274
+ });
275
+ const liveGrantLink = () => {
276
+ try {
277
+ const file = env.LARKIN_GRANT_URL_FILE || "/tmp/larkin-grant-url.txt";
278
+ if (!fs.existsSync(file))
279
+ return null;
280
+ const alive = spawnSync("pgrep", ["-f", "grant-scopes.mjs"], { encoding: "utf8" });
281
+ if (!alive.stdout.trim())
282
+ return null;
283
+ const url = fs.readFileSync(file, "utf8").trim();
284
+ return /^https?:\/\//.test(url) ? url : null;
285
+ }
286
+ catch {
287
+ return null;
288
+ }
289
+ };
290
+ const feishuError = (result) => {
291
+ return formatFeishuError(result?.error, liveGrantLink());
292
+ };
293
+ const query = (requestPath, name) => {
294
+ try {
295
+ return new URL(`http://x${requestPath.startsWith("/") ? requestPath : `/${requestPath}`}`).searchParams.get(name);
296
+ }
297
+ catch {
298
+ return null;
299
+ }
300
+ };
301
+ const channelContext = (target) => {
302
+ const value = String(target || "");
303
+ const base = value.split(":").slice(0, 2).join(":");
304
+ return {
305
+ chat_id: resolveChatId(value),
306
+ channel_type: value.startsWith("dm:") ? "dm" : "channel",
307
+ channel_name: base.replace(/^#/, "").replace(/^dm:@/, ""),
308
+ };
309
+ };
310
+ const chatMetaCache = {};
311
+ const chatMeta = (chatId) => {
312
+ if (!chatId)
313
+ return null;
314
+ if (Object.hasOwn(chatMetaCache, chatId))
315
+ return chatMetaCache[chatId];
316
+ const result = larkJsonOut(["im", "chats", "get", "--chat-id", chatId, "--json"]);
317
+ const meta = result?.ok && result.data
318
+ ? { name: result.data.name || "", chat_mode: result.data.chat_mode || "", chat_type: result.data.chat_type || "" }
319
+ : null;
320
+ chatMetaCache[chatId] = meta;
321
+ return meta;
322
+ };
323
+ const topicShort = (target) => {
324
+ const body = String(target || "").replace(/^#/, "").replace(/^dm:@/, "");
325
+ const index = body.indexOf(":");
326
+ return index >= 0 ? body.slice(index + 1, index + 9) : "";
327
+ };
328
+ const topicDescriptor = (thread) => {
329
+ const result = larkJsonOut(["im", "+threads-messages-list", "--thread", thread, "--order", "asc", "--page-size", "1", "--json"]);
330
+ const first = result?.ok && result.data ? (result.data.messages || result.data.items || [])[0] : undefined;
331
+ if (!first)
332
+ return null;
333
+ let content = typeof first.content === "string" ? first.content : JSON.stringify(first.content || "");
334
+ try {
335
+ const parsed = JSON.parse(content);
336
+ content = typeof parsed.text === "string" ? parsed.text : (typeof parsed.content === "string" ? parsed.content : content);
337
+ }
338
+ catch { /* plain text */ }
339
+ const normalized = content.replace(/\[([^\]]+)\]\((https?:[^)]+)\)/g, "$1").replace(/https?:\/\/\S+/g, "").replace(/\s+/g, " ").trim();
340
+ return normalized ? normalized.slice(0, 48) : null;
341
+ };
342
+ const channelLabel = (target) => {
343
+ const chatId = resolveChatId(target);
344
+ const meta = chatMeta(chatId);
345
+ let label = meta?.name || target.replace(/^#/, "").replace(/^dm:@/, "");
346
+ const short = topicShort(target);
347
+ const context = replyContextFor(target);
348
+ if (short) {
349
+ const thread = context && (context.thread_id || context.reply_to);
350
+ const descriptor = thread ? topicDescriptor(thread) : null;
351
+ if (descriptor)
352
+ label += ` › ${descriptor}`;
353
+ }
354
+ return { label, chatId, meta, ctx: context, topicShort: short };
355
+ };
356
+ let appInfoCache;
357
+ const feishuAppInfo = () => {
358
+ if (appInfoCache !== undefined)
359
+ return appInfoCache;
360
+ appInfoCache = null;
361
+ const result = larkJsonOut(["api", "GET", `/open-apis/application/v6/applications/${selectedAgent.feishuAppId}`, "--params", '{"lang":"zh_cn"}']);
362
+ const app = result?.ok && result.data?.app;
363
+ if (app)
364
+ appInfoCache = { name: app.app_name || null, description: app.description || null, avatarUrl: app.avatar_url || null };
365
+ return appInfoCache;
366
+ };
367
+ let botNameCache;
368
+ const botDisplayName = () => {
369
+ if (botNameCache !== undefined)
370
+ return botNameCache;
371
+ botNameCache = null;
372
+ const identity = botIdentity();
373
+ if (identity?.name)
374
+ return (botNameCache = identity.name);
375
+ const knownChat = Object.values(loadMap()).find((value) => value.startsWith("oc_"));
376
+ if (knownChat) {
377
+ const bots = memberData(knownChat)?.bots || [];
378
+ const mine = bots.find((bot) => bot.app_id === selectedAgent.feishuAppId || (identity?.open_id && bot.member_id === identity.open_id)) || (bots.length === 1 ? bots[0] : null);
379
+ if (mine?.name)
380
+ botNameCache = mine.name;
381
+ }
382
+ return botNameCache;
383
+ };
384
+ const resolveMember = (chatId, raw) => {
385
+ const handle = String(raw || "").replace(/^@/, "").trim();
386
+ if (!handle)
387
+ return null;
388
+ if (/^ou_[A-Za-z0-9]+$/.test(handle))
389
+ return { ids: [handle], type: "open_id" };
390
+ if (/^on_[A-Za-z0-9]+$/.test(handle))
391
+ return { ids: [handle], type: "union_id" };
392
+ if (/^(cli_|app_)[A-Za-z0-9]+$/.test(handle))
393
+ return { ids: [handle], type: "app_id" };
394
+ const data = memberData(chatId);
395
+ const user = data?.users?.find((candidate) => candidate.name === handle && candidate.member_id);
396
+ if (user?.member_id)
397
+ return { ids: [user.member_id], type: "open_id" };
398
+ const bot = data?.bots?.find((candidate) => candidate.name === handle);
399
+ if (bot?.app_id)
400
+ return { ids: [bot.app_id], type: "app_id" };
401
+ if (bot?.member_id)
402
+ return { ids: [bot.member_id], type: "open_id" };
403
+ return /^[A-Za-z0-9._-]{2,}$/.test(handle) ? { ids: [handle], type: "user_id" } : null;
404
+ };
405
+ const projectMessage = (message, channelType, channelName, names) => {
406
+ const senderId = String(message.sender?.id || "");
407
+ const isBot = Boolean(message.sender && (message.sender.sender_type === "app" || message.sender.id_type === "app_id"));
408
+ const self = Boolean(isBot && senderId && senderId === selectedAgent.feishuAppId);
409
+ return projectFeishuHistoryEnvelope({
410
+ message, channelType, channelName, names,
411
+ selectedAppId: selectedAgent.feishuAppId,
412
+ botIdentity: parseFeishuText(message) !== null || self ? botIdentity() : null,
413
+ senderDescription: self ? null : signatureDescription(senderSignature(senderId)),
414
+ });
415
+ };
416
+ const channelTransport = createChannelTransport({
417
+ dryRun, selectedAgent, agentId, agentName, agentRuntime, agentModel, agentEffort, serverId, unknownCreatedAt,
418
+ query, resolveChatId, resolveMemberTo: resolveMember, channelContext, channelLabel, chatMeta,
419
+ invalidateChatMeta: (chatId) => { delete chatMetaCache[chatId]; },
420
+ rememberChannel: (target, chatId, meta) => {
421
+ try {
422
+ const map = loadMap();
423
+ map[target] = chatId;
424
+ stateStore.writeJson("map", map);
425
+ chatMetaCache[chatId] = {
426
+ name: typeof meta.name === "string" ? meta.name : "",
427
+ chat_mode: typeof meta.chat_mode === "string" ? meta.chat_mode : "",
428
+ chat_type: typeof meta.chat_type === "string" ? meta.chat_type : "",
429
+ };
430
+ }
431
+ catch { /* preserve best-effort map update */ }
432
+ },
433
+ mappedChannels: loadMap, larkJsonOut, feishuError, botDisplayName, feishuAppInfo, log,
434
+ });
435
+ const reminderRoutes = createReminderRoutes({ stateFile: paths.reminders, agentId, query, log });
436
+ try {
437
+ fs.appendFileSync(transportLogFile, `${new Date().toISOString()} INIT profile=${selectedAgent.feishuProfile} larkCfgDir=${selectedAgent.larkConfigDir} inbox=${paths.inbox}\n`);
438
+ }
439
+ catch { /* best effort */ }
440
+ return {
441
+ stateStore, dryRun, agentId, agentName, serverId, transportLogFile,
442
+ outbound, channelTransport, reminderRoutes,
443
+ loadMap, larkJsonOut, feishuError, query, channelContext, chatMeta, topicShort,
444
+ memberNames, projectMessage, projectInboxEvents, sortVisibleHistory, logRequest, log,
445
+ };
446
+ }
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ /** Pure outer-shell helpers used by the Feishu Agent transport. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.resolveMappedChatId = resolveMappedChatId;
5
+ exports.safeConversationExcerpt = safeConversationExcerpt;
6
+ exports.looksLikeMarkdown = looksLikeMarkdown;
7
+ exports.isImageMime = isImageMime;
8
+ /**
9
+ * Resolve a larkin target through the canonical Agent-local Feishu map.
10
+ * The base-target fallback intentionally preserves the transport's existing
11
+ * split semantics; protocol parsing does not belong in this shell helper.
12
+ */
13
+ function resolveMappedChatId(map, target) {
14
+ if (target && map[String(target)])
15
+ return map[String(target)];
16
+ if (target) {
17
+ const base = String(target).split(":").slice(0, 2).join(":");
18
+ if (map[base])
19
+ return map[base];
20
+ }
21
+ return "";
22
+ }
23
+ /** Redact common credential shapes and bound locally persisted conversation text. */
24
+ function safeConversationExcerpt(value, max = 360) {
25
+ let excerpt = String(value || "").replace(/\s+/g, " ").trim();
26
+ excerpt = excerpt
27
+ .replace(/\b(sk-[A-Za-z0-9_-]{12,})\b/g, "[已隐藏凭证]")
28
+ .replace(/\b(token|secret|password|passwd)\s*[:=]\s*[^\s,;]+/gi, "$1=[已隐藏]");
29
+ return excerpt.length > max ? `${excerpt.slice(0, max - 1)}…` : excerpt;
30
+ }
31
+ /** Decide whether Feishu should receive rich Markdown instead of a plain text bubble. */
32
+ function looksLikeMarkdown(value) {
33
+ const text = String(value);
34
+ return /(^|\n)#{1,6}\s/.test(text)
35
+ || /\*\*[^*\n]+\*\*/.test(text)
36
+ || /(^|\n)\s*([-*+]|\d+\.)\s+\S/.test(text)
37
+ || /```/.test(text)
38
+ || /`[^`\n]+`/.test(text)
39
+ || /(^|\n)>\s/.test(text)
40
+ || /\[[^\]\n]+\]\(https?:[^)\s]+\)/.test(text)
41
+ || /(^|\n)\|.+\|\s*\n\s*\|[\s:|-]+\|/.test(text);
42
+ }
43
+ function isImageMime(value) {
44
+ return /^image\//.test(String(value || ""));
45
+ }
@@ -0,0 +1,39 @@
1
+ /** Pure outer-shell helpers used by the Feishu Agent transport. */
2
+ /**
3
+ * Resolve a larkin target through the canonical Agent-local Feishu map.
4
+ * The base-target fallback intentionally preserves the transport's existing
5
+ * split semantics; protocol parsing does not belong in this shell helper.
6
+ */
7
+ export function resolveMappedChatId(map, target) {
8
+ if (target && map[String(target)])
9
+ return map[String(target)];
10
+ if (target) {
11
+ const base = String(target).split(":").slice(0, 2).join(":");
12
+ if (map[base])
13
+ return map[base];
14
+ }
15
+ return "";
16
+ }
17
+ /** Redact common credential shapes and bound locally persisted conversation text. */
18
+ export function safeConversationExcerpt(value, max = 360) {
19
+ let excerpt = String(value || "").replace(/\s+/g, " ").trim();
20
+ excerpt = excerpt
21
+ .replace(/\b(sk-[A-Za-z0-9_-]{12,})\b/g, "[已隐藏凭证]")
22
+ .replace(/\b(token|secret|password|passwd)\s*[:=]\s*[^\s,;]+/gi, "$1=[已隐藏]");
23
+ return excerpt.length > max ? `${excerpt.slice(0, max - 1)}…` : excerpt;
24
+ }
25
+ /** Decide whether Feishu should receive rich Markdown instead of a plain text bubble. */
26
+ export function looksLikeMarkdown(value) {
27
+ const text = String(value);
28
+ return /(^|\n)#{1,6}\s/.test(text)
29
+ || /\*\*[^*\n]+\*\*/.test(text)
30
+ || /(^|\n)\s*([-*+]|\d+\.)\s+\S/.test(text)
31
+ || /```/.test(text)
32
+ || /`[^`\n]+`/.test(text)
33
+ || /(^|\n)>\s/.test(text)
34
+ || /\[[^\]\n]+\]\(https?:[^)\s]+\)/.test(text)
35
+ || /(^|\n)\|.+\|\s*\n\s*\|[\s:|-]+\|/.test(text);
36
+ }
37
+ export function isImageMime(value) {
38
+ return /^image\//.test(String(value || ""));
39
+ }