chattercatcher 0.1.26 → 0.1.28

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/dist/cli.js CHANGED
@@ -3,12 +3,12 @@
3
3
  // src/cli.ts
4
4
  import { input, password, select, confirm, number } from "@inquirer/prompts";
5
5
  import { Command } from "commander";
6
- import fs14 from "fs/promises";
6
+ import fs15 from "fs/promises";
7
7
 
8
8
  // package.json
9
9
  var package_default = {
10
10
  name: "chattercatcher",
11
- version: "0.1.26",
11
+ version: "0.1.28",
12
12
  description: "\u672C\u5730\u4F18\u5148\u7684\u98DE\u4E66/Lark \u5BB6\u5EAD\u7FA4\u77E5\u8BC6\u5E93\u673A\u5668\u4EBA",
13
13
  type: "module",
14
14
  main: "dist/index.js",
@@ -548,12 +548,17 @@ function migrateDatabase(database) {
548
548
  next_run_at TEXT NOT NULL,
549
549
  last_error TEXT,
550
550
  created_at TEXT NOT NULL,
551
- updated_at TEXT NOT NULL
551
+ updated_at TEXT NOT NULL,
552
+ image_file_name TEXT
552
553
  );
553
554
 
554
555
  CREATE INDEX IF NOT EXISTS cron_jobs_chat_status_idx ON cron_jobs(chat_id, status, updated_at);
555
556
  CREATE INDEX IF NOT EXISTS cron_jobs_due_idx ON cron_jobs(status, next_run_at);
556
557
  `);
558
+ const cronJobColumns = database.prepare("PRAGMA table_info(cron_jobs)").all();
559
+ if (!cronJobColumns.some((column) => column.name === "image_file_name")) {
560
+ database.prepare("ALTER TABLE cron_jobs ADD COLUMN image_file_name TEXT").run();
561
+ }
557
562
  }
558
563
 
559
564
  // src/doctor/checks.ts
@@ -966,6 +971,7 @@ function getGatewayStatus(config, secrets) {
966
971
  }
967
972
 
968
973
  // src/llm/openai-compatible.ts
974
+ var OPENAI_EMBEDDING_BATCH_SIZE = 64;
969
975
  function normalizeBaseUrl(baseUrl) {
970
976
  return baseUrl.replace(/\/+$/, "");
971
977
  }
@@ -997,12 +1003,66 @@ function toOpenAITool(tool) {
997
1003
  }
998
1004
  };
999
1005
  }
1006
+ function parseToolCallArguments(value) {
1007
+ try {
1008
+ return JSON.parse(value);
1009
+ } catch {
1010
+ return {};
1011
+ }
1012
+ }
1013
+ function decodeDsmlValue(value, isString) {
1014
+ const trimmed = value.trim();
1015
+ if (isString) {
1016
+ return trimmed;
1017
+ }
1018
+ if (trimmed === "true") return true;
1019
+ if (trimmed === "false") return false;
1020
+ if (trimmed === "null") return null;
1021
+ const numberValue = Number(trimmed);
1022
+ if (trimmed && Number.isFinite(numberValue)) {
1023
+ return numberValue;
1024
+ }
1025
+ return trimmed;
1026
+ }
1027
+ function parseDsmlToolCalls(content) {
1028
+ if (!content?.includes("DSML")) {
1029
+ return [];
1030
+ }
1031
+ const toolCalls = [];
1032
+ const invokePattern = /<||DSML||invoke\s+name="([^"]+)"\s*>([\s\S]*?)<\/||DSML||invoke>/g;
1033
+ const parameterPattern = /<||DSML||parameter\s+name="([^"]+)"\s+string="(true|false)"\s*>([\s\S]*?)<\/||DSML||parameter>/g;
1034
+ for (const invoke of content.matchAll(invokePattern)) {
1035
+ const name = invoke[1];
1036
+ if (!name) {
1037
+ continue;
1038
+ }
1039
+ const input2 = {};
1040
+ const body = invoke[2] ?? "";
1041
+ for (const parameter of body.matchAll(parameterPattern)) {
1042
+ const parameterName = parameter[1];
1043
+ if (!parameterName) {
1044
+ continue;
1045
+ }
1046
+ input2[parameterName] = decodeDsmlValue(parameter[3] ?? "", parameter[2] === "true");
1047
+ }
1048
+ toolCalls.push({
1049
+ id: `dsml_${toolCalls.length + 1}`,
1050
+ name,
1051
+ input: input2
1052
+ });
1053
+ }
1054
+ return toolCalls;
1055
+ }
1000
1056
  function parseToolCalls(message) {
1001
- return message?.tool_calls?.map((toolCall) => ({
1057
+ const standardToolCalls = message?.tool_calls?.map((toolCall) => ({
1002
1058
  id: toolCall.id,
1003
1059
  name: toolCall.function.name,
1004
- input: JSON.parse(toolCall.function.arguments)
1060
+ input: parseToolCallArguments(toolCall.function.arguments)
1005
1061
  })) ?? [];
1062
+ return standardToolCalls.length > 0 ? standardToolCalls : parseDsmlToolCalls(message?.content);
1063
+ }
1064
+ function isDsmlToolCallContent(content) {
1065
+ return parseDsmlToolCalls(content).length > 0;
1006
1066
  }
1007
1067
  var OpenAICompatibleChatModel = class {
1008
1068
  constructor(options) {
@@ -1061,9 +1121,10 @@ var OpenAICompatibleChatModel = class {
1061
1121
  }
1062
1122
  const data2 = await response.json();
1063
1123
  const message = data2.choices?.[0]?.message;
1124
+ const toolCalls = parseToolCalls(message);
1064
1125
  return {
1065
- content: message?.content ?? "",
1066
- toolCalls: parseToolCalls(message),
1126
+ content: toolCalls.length > 0 && isDsmlToolCallContent(message?.content) ? "" : message?.content ?? "",
1127
+ toolCalls,
1067
1128
  reasoningContent: message?.reasoning_content ?? void 0
1068
1129
  };
1069
1130
  }
@@ -1081,6 +1142,13 @@ var OpenAICompatibleEmbeddingModel = class {
1081
1142
  if (!this.options.baseUrl || !this.options.apiKey || !this.options.model) {
1082
1143
  throw new Error("Embedding \u914D\u7F6E\u4E0D\u5B8C\u6574\u3002\u8BF7\u8FD0\u884C chattercatcher setup \u6216 chattercatcher settings\u3002");
1083
1144
  }
1145
+ const vectors = [];
1146
+ for (let index2 = 0; index2 < texts.length; index2 += OPENAI_EMBEDDING_BATCH_SIZE) {
1147
+ vectors.push(...await this.fetchEmbeddingBatch(texts.slice(index2, index2 + OPENAI_EMBEDDING_BATCH_SIZE)));
1148
+ }
1149
+ return vectors;
1150
+ }
1151
+ async fetchEmbeddingBatch(texts) {
1084
1152
  const response = await fetch(`${normalizeBaseUrl(this.options.baseUrl)}/embeddings`, {
1085
1153
  method: "POST",
1086
1154
  headers: {
@@ -1304,6 +1372,9 @@ var MessageRepository = class {
1304
1372
  throw new Error("\u539F\u59CB\u56FE\u7247\u6D88\u606F\u4E0D\u5B58\u5728\u3002");
1305
1373
  }
1306
1374
  const derivedPlatformMessageId = `${source.platformMessageId}:image-summary:${input2.imageKey}`;
1375
+ const imageFileName = input2.imageFileName?.trim();
1376
+ const summaryText = imageFileName ? `[\u56FE\u7247\u8F6C\u8FF0] \u6587\u4EF6\u540D\uFF1A${imageFileName}
1377
+ ${input2.summary.trim()}` : `[\u56FE\u7247\u8F6C\u8FF0] ${input2.summary.trim()}`;
1307
1378
  return this.ingest({
1308
1379
  platform: source.platform,
1309
1380
  platformChatId: source.platformChatId,
@@ -1312,12 +1383,13 @@ var MessageRepository = class {
1312
1383
  senderId: source.senderId,
1313
1384
  senderName: source.senderName,
1314
1385
  messageType: "image_summary",
1315
- text: `[\u56FE\u7247\u8F6C\u8FF0] ${input2.summary.trim()}`,
1386
+ text: summaryText,
1316
1387
  sentAt: source.sentAt,
1317
1388
  rawPayload: {
1318
1389
  derivedFromMessageId: input2.sourceMessageId,
1319
1390
  sourceAttachmentKind: "image",
1320
1391
  sourceResourceKey: input2.imageKey,
1392
+ ...imageFileName ? { imageFileName } : {},
1321
1393
  multimodalModel: input2.multimodalModel,
1322
1394
  isMeaningful: true,
1323
1395
  ...input2.reason?.trim() ? { reason: input2.reason.trim() } : {},
@@ -2618,7 +2690,7 @@ async function summarizeEpisodeWindow(window, model, now) {
2618
2690
  const summary = await model.complete([
2619
2691
  {
2620
2692
  role: "system",
2621
- content: "\u4F60\u662F ChatterCatcher \u7684\u4F1A\u8BDD\u8BB0\u5FC6\u6574\u7406\u6A21\u5757\u3002\u4F60\u7684\u4EFB\u52A1\u662F\u628A\u788E\u7247\u5316\u95F2\u804A\u6574\u7406\u6210\u53EF\u68C0\u7D22\u4E8B\u5B9E\uFF0C\u8865\u5168\u77ED\u6D88\u606F\u3001\u4EE3\u8BCD\u3001\u7F29\u5199\u4E0E\u4E0A\u4E0B\u6587\u4E4B\u95F4\u7684\u5173\u7CFB\u3002\u53EA\u603B\u7ED3\u660E\u786E\u4E8B\u5B9E\uFF0C\u4E0D\u8981\u7F16\u9020\u3002\u4FDD\u7559\u91CD\u8981\u6570\u5B57\u3001\u65E5\u671F\u3001\u94FE\u63A5\u548C\u4EE3\u7801\uFF1B\u5982\u679C\u5185\u5BB9\u50CF\u5BC6\u7801\u3001API key\u3001token \u6216\u5BC6\u94A5\uFF0C\u53EA\u63CF\u8FF0\u5176\u4E0A\u4E0B\u6587\u5173\u7CFB\uFF0C\u4E0D\u8981\u5728\u6458\u8981\u4E2D\u590D\u5199\u539F\u6587\u3002\u6D88\u606F\u91CC\u7684\u201C\u4ECA\u5929\u201D\u201C\u660E\u5929\u201D\u201C\u6628\u665A\u201D\u201C\u4E0B\u5468\u4E09\u201D\u7B49\u76F8\u5BF9\u65F6\u95F4\u8868\u8FF0\uFF0C\u8BF7\u57FA\u4E8E\u6BCF\u6761\u6D88\u606F\u524D\u7684\u53D1\u9001\u65F6\u95F4\u6233\u63A8\u5BFC\u4E3A\u5177\u4F53\u65E5\u671F\u5199\u5165\u6458\u8981\u3002\u4F8B\u5982 [2026-05-05T20:00:00.000Z] \u5988\u5988\u8BF4\u201C\u660E\u5929\u8981\u7528\u4E1D\u4E1D\u9732\u201D\uFF0C\u6458\u8981\u5E94\u5199\u4E3A\u201C2026-05-06 \u8981\u7528\u4E1D\u4E1D\u9732\u201D\u3002"
2693
+ content: "\u4F60\u662F ChatterCatcher \u7684\u4F1A\u8BDD\u8BB0\u5FC6\u6574\u7406\u6A21\u5757\u3002\u4F60\u7684\u4EFB\u52A1\u662F\u628A\u788E\u7247\u5316\u95F2\u804A\u6574\u7406\u6210\u53EF\u68C0\u7D22\u4E8B\u5B9E\uFF0C\u8865\u5168\u77ED\u6D88\u606F\u3001\u4EE3\u8BCD\u3001\u7F29\u5199\u4E0E\u4E0A\u4E0B\u6587\u4E4B\u95F4\u7684\u5173\u7CFB\u3002\u53EA\u603B\u7ED3\u660E\u786E\u4E8B\u5B9E\uFF0C\u4E0D\u8981\u7F16\u9020\u3002\u4FDD\u7559\u91CD\u8981\u6570\u5B57\u3001\u65E5\u671F\u3001\u94FE\u63A5\u3001\u6587\u4EF6\u540D\u548C\u4EE3\u7801\uFF1B\u5982\u679C\u56FE\u7247\u8F6C\u8FF0\u91CC\u51FA\u73B0\u6587\u4EF6\u540D\uFF0C\u5FC5\u987B\u5728\u6458\u8981\u4E2D\u539F\u6837\u4FDD\u7559\u8BE5\u6587\u4EF6\u540D\uFF0C\u65B9\u4FBF\u4E4B\u540E\u6309\u6587\u4EF6\u540D\u627E\u56DE\u56FE\u7247\u3002\u5982\u679C\u5185\u5BB9\u50CF\u5BC6\u7801\u3001API key\u3001token \u6216\u5BC6\u94A5\uFF0C\u53EA\u63CF\u8FF0\u5176\u4E0A\u4E0B\u6587\u5173\u7CFB\uFF0C\u4E0D\u8981\u5728\u6458\u8981\u4E2D\u590D\u5199\u539F\u6587\u3002\u6D88\u606F\u91CC\u7684\u201C\u4ECA\u5929\u201D\u201C\u660E\u5929\u201D\u201C\u6628\u665A\u201D\u201C\u4E0B\u5468\u4E09\u201D\u7B49\u76F8\u5BF9\u65F6\u95F4\u8868\u8FF0\uFF0C\u8BF7\u57FA\u4E8E\u6BCF\u6761\u6D88\u606F\u524D\u7684\u53D1\u9001\u65F6\u95F4\u6233\u63A8\u5BFC\u4E3A\u5177\u4F53\u65E5\u671F\u5199\u5165\u6458\u8981\u3002\u4F8B\u5982 [2026-05-05T20:00:00.000Z] \u5988\u5988\u8BF4\u201C\u660E\u5929\u8981\u7528\u4E1D\u4E1D\u9732\u201D\uFF0C\u6458\u8981\u5E94\u5199\u4E3A\u201C2026-05-06 \u8981\u7528\u4E1D\u4E1D\u9732\u201D\u3002"
2622
2694
  },
2623
2695
  {
2624
2696
  role: "user",
@@ -2723,6 +2795,7 @@ async function ensureFeishuBotOpenId(config, secrets, options = {}) {
2723
2795
 
2724
2796
  // src/feishu/gateway.ts
2725
2797
  import * as lark2 from "@larksuiteoapi/node-sdk";
2798
+ import path12 from "path";
2726
2799
 
2727
2800
  // src/cron/jobs.ts
2728
2801
  import crypto4 from "crypto";
@@ -2827,6 +2900,7 @@ var CronJobRepository = class {
2827
2900
  create(input2) {
2828
2901
  const schedule = input2.schedule.trim();
2829
2902
  const prompt = input2.prompt.trim();
2903
+ const imageFileName = input2.imageFileName?.trim();
2830
2904
  if (!isValidCronSchedule(schedule)) {
2831
2905
  throw new Error("cron \u8868\u8FBE\u5F0F\u65E0\u6548\u3002");
2832
2906
  }
@@ -2844,6 +2918,7 @@ var CronJobRepository = class {
2844
2918
  createdByOpenId: input2.createdByOpenId,
2845
2919
  schedule,
2846
2920
  prompt,
2921
+ ...imageFileName ? { imageFileName } : {},
2847
2922
  status: "active",
2848
2923
  nextRunAt: nextRunAt.toISOString(),
2849
2924
  createdAt: now.toISOString(),
@@ -2852,15 +2927,18 @@ var CronJobRepository = class {
2852
2927
  this.database.prepare(
2853
2928
  `
2854
2929
  INSERT INTO cron_jobs (
2855
- id, chat_id, created_by_open_id, schedule, prompt, status,
2930
+ id, chat_id, created_by_open_id, schedule, prompt, image_file_name, status,
2856
2931
  last_run_at, next_run_at, last_error, created_at, updated_at
2857
2932
  )
2858
2933
  VALUES (
2859
- @id, @chatId, @createdByOpenId, @schedule, @prompt, @status,
2934
+ @id, @chatId, @createdByOpenId, @schedule, @prompt, @imageFileName, @status,
2860
2935
  NULL, @nextRunAt, NULL, @createdAt, @updatedAt
2861
2936
  )
2862
2937
  `
2863
- ).run(record);
2938
+ ).run({
2939
+ ...record,
2940
+ imageFileName: record.imageFileName ?? null
2941
+ });
2864
2942
  return record;
2865
2943
  }
2866
2944
  get(id) {
@@ -2885,6 +2963,7 @@ var CronJobRepository = class {
2885
2963
  created_by_open_id AS createdByOpenId,
2886
2964
  schedule,
2887
2965
  prompt,
2966
+ image_file_name AS imageFileName,
2888
2967
  status,
2889
2968
  last_run_at AS lastRunAt,
2890
2969
  next_run_at AS nextRunAt,
@@ -2903,6 +2982,7 @@ var CronJobRepository = class {
2903
2982
  createdByOpenId: row.createdByOpenId ?? void 0,
2904
2983
  schedule: row.schedule,
2905
2984
  prompt: row.prompt,
2985
+ imageFileName: row.imageFileName ?? void 0,
2906
2986
  status: row.status,
2907
2987
  lastRunAt: row.lastRunAt ?? void 0,
2908
2988
  nextRunAt: row.nextRunAt,
@@ -2976,6 +3056,7 @@ var CronJobRepository = class {
2976
3056
  created_by_open_id AS createdByOpenId,
2977
3057
  schedule,
2978
3058
  prompt,
3059
+ image_file_name AS imageFileName,
2979
3060
  status,
2980
3061
  last_run_at AS lastRunAt,
2981
3062
  next_run_at AS nextRunAt,
@@ -2994,6 +3075,7 @@ var CronJobRepository = class {
2994
3075
  createdByOpenId: row.createdByOpenId ?? void 0,
2995
3076
  schedule: row.schedule,
2996
3077
  prompt: row.prompt,
3078
+ imageFileName: row.imageFileName ?? void 0,
2997
3079
  status: row.status,
2998
3080
  lastRunAt: row.lastRunAt ?? void 0,
2999
3081
  nextRunAt: row.nextRunAt,
@@ -3098,6 +3180,12 @@ function createCronJobScheduler(options) {
3098
3180
  try {
3099
3181
  const text = await options.generateMessage(job, startedAt);
3100
3182
  await options.sendTextToChat(job.chatId, text);
3183
+ if (job.imageFileName) {
3184
+ if (!options.sendImageToChat) {
3185
+ throw new Error("\u5F53\u524D\u5B9A\u65F6\u4EFB\u52A1\u8FD0\u884C\u73AF\u5883\u4E0D\u652F\u6301\u53D1\u9001\u56FE\u7247\u3002");
3186
+ }
3187
+ await options.sendImageToChat(job.chatId, job.imageFileName);
3188
+ }
3101
3189
  options.repository.markSuccess(job.id, startedAt);
3102
3190
  } catch (error) {
3103
3191
  const message = error instanceof Error ? error.message : String(error);
@@ -3238,12 +3326,20 @@ function parseExactNumber2(field, min, max) {
3238
3326
  }
3239
3327
 
3240
3328
  // src/rag/indexer.ts
3329
+ var EMBEDDING_INDEX_BATCH_SIZE = 64;
3241
3330
  async function indexMessageChunks(input2) {
3242
3331
  const chunks = input2.messageIds ? input2.messages.listMessageChunksByMessageIds(input2.messageIds, input2.limit ?? 1e4) : input2.messages.listAllMessageChunks(input2.limit ?? 1e4);
3243
3332
  if (chunks.length === 0) {
3244
3333
  return { chunks: 0, vectors: 0 };
3245
3334
  }
3246
- const vectors = await input2.embedding.embedBatch(chunks.map((chunk) => chunk.text));
3335
+ const vectors = [];
3336
+ for (let index2 = 0; index2 < chunks.length; index2 += EMBEDDING_INDEX_BATCH_SIZE) {
3337
+ vectors.push(
3338
+ ...await input2.embedding.embedBatch(
3339
+ chunks.slice(index2, index2 + EMBEDDING_INDEX_BATCH_SIZE).map((chunk) => chunk.text)
3340
+ )
3341
+ );
3342
+ }
3247
3343
  const records = [];
3248
3344
  for (const [index2, chunk] of chunks.entries()) {
3249
3345
  const vector = vectors[index2];
@@ -3514,6 +3610,7 @@ var ImageMultimodalTaskRepository = class {
3514
3610
  };
3515
3611
 
3516
3612
  // src/multimodal/worker.ts
3613
+ import path11 from "path";
3517
3614
  var ImageMultimodalWorker = class {
3518
3615
  constructor(options) {
3519
3616
  this.options = options;
@@ -3540,9 +3637,11 @@ var ImageMultimodalWorker = class {
3540
3637
  throw error;
3541
3638
  }
3542
3639
  try {
3640
+ const imageFileName = path11.basename(running.storedPath);
3543
3641
  const described = await this.options.model.describeImage({
3544
3642
  imagePath: running.storedPath,
3545
- mimeType: running.mimeType
3643
+ mimeType: running.mimeType,
3644
+ context: `\u56FE\u7247\u6587\u4EF6\u540D\uFF1A${imageFileName}`
3546
3645
  });
3547
3646
  if (!described.isMeaningful) {
3548
3647
  this.options.tasks.markSkipped(running.id, described.reason || "\u591A\u6A21\u6001\u6A21\u578B\u5224\u5B9A\u56FE\u7247\u65E0\u610F\u4E49\u3002");
@@ -3552,6 +3651,7 @@ var ImageMultimodalWorker = class {
3552
3651
  const derivedMessageId = this.options.messages.createImageSummaryMessage({
3553
3652
  sourceMessageId: running.sourceMessageId,
3554
3653
  imageKey: running.imageKey,
3654
+ imageFileName,
3555
3655
  summary: described.summary,
3556
3656
  reason: described.reason,
3557
3657
  multimodalModel: this.options.multimodalModelName,
@@ -3585,6 +3685,17 @@ function readString(input2, key) {
3585
3685
  }
3586
3686
  return value.trim();
3587
3687
  }
3688
+ function readOptionalString(input2, key) {
3689
+ const value = typeof input2 === "object" && input2 !== null && key in input2 ? input2[key] : void 0;
3690
+ if (value === void 0 || value === null) {
3691
+ return void 0;
3692
+ }
3693
+ if (typeof value !== "string") {
3694
+ throw new Error(`${key} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u3002`);
3695
+ }
3696
+ const trimmed = value.trim();
3697
+ return trimmed || void 0;
3698
+ }
3588
3699
  function createCronJobTools(input2) {
3589
3700
  return [
3590
3701
  {
@@ -3600,6 +3711,10 @@ function createCronJobTools(input2) {
3600
3711
  prompt: {
3601
3712
  type: "string",
3602
3713
  description: "Prompt used later to generate the scheduled message."
3714
+ },
3715
+ imageFileName: {
3716
+ type: "string",
3717
+ description: "Optional image filename already stored from the current chat, for example om_xxx-image.jpg."
3603
3718
  }
3604
3719
  },
3605
3720
  required: ["schedule", "prompt"],
@@ -3610,7 +3725,8 @@ function createCronJobTools(input2) {
3610
3725
  chatId: input2.chatId,
3611
3726
  createdByOpenId: input2.createdByOpenId,
3612
3727
  schedule: readString(rawInput, "schedule"),
3613
- prompt: readString(rawInput, "prompt")
3728
+ prompt: readString(rawInput, "prompt"),
3729
+ imageFileName: readOptionalString(rawInput, "imageFileName")
3614
3730
  });
3615
3731
  return JSON.stringify({ ok: true, job });
3616
3732
  }
@@ -3773,11 +3889,14 @@ function stripMentions(text, mentions) {
3773
3889
  }
3774
3890
  return result.replace(/@/g, " ").replace(/\s+/g, " ").trim();
3775
3891
  }
3776
- var FEISHU_TOOL_SYSTEM_PROMPT = `\u4F60\u662F\u98DE\u4E66\u7FA4\u804A\u52A9\u624B\u3002\u4F60\u53EF\u4EE5\u5148\u641C\u7D22\u672C\u5730\u77E5\u8BC6\u6765\u56DE\u7B54\u95EE\u9898\uFF1B\u5F53\u7528\u6237\u660E\u786E\u8981\u6C42\u521B\u5EFA\u3001\u67E5\u770B\u6216\u5220\u9664\u7FA4\u6D88\u606F\u5B9A\u65F6\u4EFB\u52A1\u65F6\uFF0C\u4E5F\u53EF\u4EE5\u8C03\u7528\u5B9A\u65F6\u4EFB\u52A1\u5DE5\u5177\u3002\u5B9A\u65F6\u4EFB\u52A1\u5DE5\u5177\u53EA\u7BA1\u7406\u5F53\u524D\u7FA4\u804A\uFF0C\u4E0D\u80FD\u8DE8\u7FA4\u64CD\u4F5C\u3002\u82E5\u7528\u6237\u7528\u81EA\u7136\u8BED\u8A00\u63CF\u8FF0\u65F6\u95F4\uFF0C\u4F60\u9700\u8981\u5148\u5C06\u5176\u8F6C\u6362\u4E3A\u4E94\u5B57\u6BB5 cron \u8868\u8FBE\u5F0F\uFF08\u5206 \u65F6 \u65E5 \u6708 \u5468\uFF09\uFF0C\u518D\u8C03\u7528\u5DE5\u5177\u3002\u5F53\u524D\u65F6\u95F4\u4F1A\u63D0\u4F9B\u7ED9\u4F60\u3002\u68C0\u7D22\u8BC1\u636E\u4E2D\u7684\u65F6\u95F4\u6233\u662F\u6D88\u606F\u88AB\u53D1\u9001\u65F6\u7684\u771F\u5B9E\u65F6\u95F4\u3002\u56DE\u7B54\u65F6\u82E5\u6D89\u53CA\u76F8\u5BF9\u65F6\u95F4\u8868\u8FF0\uFF08\u5982\u6D88\u606F\u4E2D\u8BF4\u201D\u660E\u5929\u201D\u201D\u4ECA\u665A\u201D\uFF09\uFF0C\u5FC5\u987B\u57FA\u4E8E\u8BC1\u636E\u4E2D\u6BCF\u6761\u6D88\u606F\u7684\u65F6\u95F4\u6233\u63A8\u5BFC\u4E3A\u5177\u4F53\u65E5\u671F\uFF0C\u4E0D\u8981\u7167\u642C\u539F\u6587\u7684\u76F8\u5BF9\u8868\u8FF0\u3002\u5BF9\u4E8E\u4E00\u822C\u95EE\u7B54\uFF0C\u5148\u6309\u9700\u8C03\u7528\u641C\u7D22\u5DE5\u5177\uFF0C\u518D\u57FA\u4E8E\u5DE5\u5177\u8FD4\u56DE\u7684\u8BC1\u636E\u76F4\u63A5\u7ED9\u51FA\u6700\u7EC8\u7B54\u6848\uFF1B\u82E5\u5F15\u7528\u4E86\u68C0\u7D22\u7ED3\u679C\uFF0C\u8981\u5728\u7B54\u6848\u91CC\u76F4\u63A5\u5199\u51FA\u5F15\u7528\u5185\u5BB9\u3002\u4E0D\u8981\u58F0\u79F0\u5B8C\u6210\u4E86\u672A\u5B9E\u9645\u8C03\u7528\u7684\u64CD\u4F5C\u3002\u91CD\u8981\uFF1A\u4F60\u7684\u56DE\u7B54\u5FC5\u987B\u662F\u9762\u5411\u7FA4\u6210\u5458\u7684\u81EA\u7136\u8BED\u8A00\uFF0C\u7EDD\u5BF9\u4E0D\u80FD\u8F93\u51FA JSON\u3001\u5DE5\u5177\u8C03\u7528\u7EC6\u8282\u6216\u539F\u59CB\u7684\u641C\u7D22\u7ED3\u679C\u683C\u5F0F\u3002\u7528\u6237\u53EA\u5E94\u770B\u5230\u4F60\u6574\u5408\u540E\u7684\u6700\u7EC8\u7B54\u6848\u3002`;
3892
+ var FEISHU_TOOL_SYSTEM_PROMPT = `\u4F60\u662F\u98DE\u4E66\u7FA4\u804A\u52A9\u624B\u3002\u4F60\u53EF\u4EE5\u5148\u641C\u7D22\u672C\u5730\u77E5\u8BC6\u6765\u56DE\u7B54\u95EE\u9898\uFF1B\u5F53\u7528\u6237\u660E\u786E\u8981\u6C42\u521B\u5EFA\u3001\u67E5\u770B\u6216\u5220\u9664\u7FA4\u6D88\u606F\u5B9A\u65F6\u4EFB\u52A1\u65F6\uFF0C\u4E5F\u53EF\u4EE5\u8C03\u7528\u5B9A\u65F6\u4EFB\u52A1\u5DE5\u5177\u3002\u5B9A\u65F6\u4EFB\u52A1\u5DE5\u5177\u53EA\u7BA1\u7406\u5F53\u524D\u7FA4\u804A\uFF0C\u4E0D\u80FD\u8DE8\u7FA4\u64CD\u4F5C\u3002\u82E5\u7528\u6237\u8981\u6C42\u5B9A\u65F6\u4EFB\u52A1\u53D1\u9001\u56FE\u7247\uFF0C\u53EA\u80FD\u4F7F\u7528\u5F53\u524D\u7FA4\u804A\u91CC\u5DF2\u7ECF\u4E0B\u8F7D\u5165\u5E93\u7684\u56FE\u7247\u6587\u4EF6\u540D\uFF0C\u5E76\u5728\u521B\u5EFA\u5B9A\u65F6\u4EFB\u52A1\u65F6\u628A\u6587\u4EF6\u540D\u586B\u5165 imageFileName\uFF1B\u4E0D\u8981\u7F16\u9020\u672C\u5730\u8DEF\u5F84\u3002\u82E5\u7528\u6237\u7528\u81EA\u7136\u8BED\u8A00\u63CF\u8FF0\u65F6\u95F4\uFF0C\u4F60\u9700\u8981\u5148\u5C06\u5176\u8F6C\u6362\u4E3A\u4E94\u5B57\u6BB5 cron \u8868\u8FBE\u5F0F\uFF08\u5206 \u65F6 \u65E5 \u6708 \u5468\uFF09\uFF0C\u518D\u8C03\u7528\u5DE5\u5177\u3002\u5F53\u524D\u65F6\u95F4\u4F1A\u63D0\u4F9B\u7ED9\u4F60\u3002\u68C0\u7D22\u8BC1\u636E\u4E2D\u7684\u65F6\u95F4\u6233\u662F\u6D88\u606F\u88AB\u53D1\u9001\u65F6\u7684\u771F\u5B9E\u65F6\u95F4\u3002\u56DE\u7B54\u65F6\u82E5\u6D89\u53CA\u76F8\u5BF9\u65F6\u95F4\u8868\u8FF0\uFF08\u5982\u6D88\u606F\u4E2D\u8BF4\u201D\u660E\u5929\u201D\u201D\u4ECA\u665A\u201D\uFF09\uFF0C\u5FC5\u987B\u57FA\u4E8E\u8BC1\u636E\u4E2D\u6BCF\u6761\u6D88\u606F\u7684\u65F6\u95F4\u6233\u63A8\u5BFC\u4E3A\u5177\u4F53\u65E5\u671F\uFF0C\u4E0D\u8981\u7167\u642C\u539F\u6587\u7684\u76F8\u5BF9\u8868\u8FF0\u3002\u5BF9\u4E8E\u4E00\u822C\u95EE\u7B54\uFF0C\u5148\u6309\u9700\u8C03\u7528\u641C\u7D22\u5DE5\u5177\uFF0C\u518D\u57FA\u4E8E\u5DE5\u5177\u8FD4\u56DE\u7684\u8BC1\u636E\u76F4\u63A5\u7ED9\u51FA\u6700\u7EC8\u7B54\u6848\uFF1B\u82E5\u5F15\u7528\u4E86\u68C0\u7D22\u7ED3\u679C\uFF0C\u8981\u5728\u7B54\u6848\u91CC\u76F4\u63A5\u5199\u51FA\u5F15\u7528\u5185\u5BB9\u3002\u4E0D\u8981\u58F0\u79F0\u5B8C\u6210\u4E86\u672A\u5B9E\u9645\u8C03\u7528\u7684\u64CD\u4F5C\u3002\u91CD\u8981\uFF1A\u4F60\u7684\u56DE\u7B54\u5FC5\u987B\u662F\u9762\u5411\u7FA4\u6210\u5458\u7684\u81EA\u7136\u8BED\u8A00\uFF0C\u7EDD\u5BF9\u4E0D\u80FD\u8F93\u51FA JSON\u3001\u5DE5\u5177\u8C03\u7528\u7EC6\u8282\u6216\u539F\u59CB\u7684\u641C\u7D22\u7ED3\u679C\u683C\u5F0F\u3002\u7528\u6237\u53EA\u5E94\u770B\u5230\u4F60\u6574\u5408\u540E\u7684\u6700\u7EC8\u7B54\u6848\u3002`;
3777
3893
  var DEFAULT_MAX_MODEL_TURNS = 4;
3778
3894
  var DEFAULT_MAX_TOOL_CALLS = 8;
3779
3895
  var FEISHU_TOOL_LOOP_FALLBACK = "\u5B9A\u65F6\u4EFB\u52A1\u64CD\u4F5C\u5DF2\u63D0\u4EA4\uFF0C\u4F46\u6A21\u578B\u6CA1\u6709\u751F\u6210\u6700\u7EC8\u56DE\u590D\u3002";
3780
3896
  var FEISHU_TOOL_LOOP_LIMIT_REACHED = "\u5DE5\u5177\u8C03\u7528\u6B21\u6570\u5DF2\u8FBE\u5230\u4E0A\u9650\uFF0C\u8BF7\u7F29\u5C0F\u8BF7\u6C42\u540E\u91CD\u8BD5\u3002";
3897
+ function containsRawToolCallMarkup(content) {
3898
+ return /<||DSML||tool_calls>|<||DSML||invoke\s+name=|<tool_call>|<tool_calls>/i.test(content);
3899
+ }
3781
3900
  function toToolResultContent(value) {
3782
3901
  if (typeof value === "string") return value;
3783
3902
  return JSON.stringify(value);
@@ -3820,6 +3939,7 @@ async function runFeishuToolLoop(input2) {
3820
3939
  let toolCallsUsed = 0;
3821
3940
  for (let turn = 0; turn < maxModelTurns; turn += 1) {
3822
3941
  const assistantResult = await input2.model.completeWithTools(messages, input2.tools);
3942
+ const hasRawToolCallMarkup = containsRawToolCallMarkup(assistantResult.content);
3823
3943
  messages.push({
3824
3944
  role: "assistant",
3825
3945
  content: assistantResult.content,
@@ -3827,6 +3947,9 @@ async function runFeishuToolLoop(input2) {
3827
3947
  reasoningContent: assistantResult.reasoningContent
3828
3948
  });
3829
3949
  if (assistantResult.toolCalls.length === 0) {
3950
+ if (hasRawToolCallMarkup) {
3951
+ break;
3952
+ }
3830
3953
  return assistantResult.content || FEISHU_TOOL_LOOP_FALLBACK;
3831
3954
  }
3832
3955
  for (const toolCall of assistantResult.toolCalls) {
@@ -3939,7 +4062,7 @@ var FeishuQuestionHandler = class {
3939
4062
  }
3940
4063
  if (this.options.sender.addReactionToMessage) {
3941
4064
  try {
3942
- await this.options.sender.addReactionToMessage(messageId, this.options.thinkingEmojiType ?? "keyboard");
4065
+ await this.options.sender.addReactionToMessage(messageId, this.options.thinkingEmojiType ?? "OK");
3943
4066
  return;
3944
4067
  } catch (error) {
3945
4068
  console.log(`\u98DE\u4E66\u63D0\u95EE\u8868\u60C5\u53CD\u9988\u5931\u8D25\uFF0C\u6539\u7528\u6587\u5B57\u53CD\u9988\uFF1A${error instanceof Error ? error.message : String(error)}`);
@@ -4012,9 +4135,22 @@ var FeishuQuestionHandler = class {
4012
4135
 
4013
4136
  // src/feishu/sender.ts
4014
4137
  import * as lark from "@larksuiteoapi/node-sdk";
4138
+ import fs9 from "fs/promises";
4015
4139
  function mapDomain(domain) {
4016
4140
  return domain === "lark" ? lark.Domain.Lark : lark.Domain.Feishu;
4017
4141
  }
4142
+ function extractImageKey(response) {
4143
+ const data2 = response && typeof response === "object" ? response : {};
4144
+ const direct = data2.image_key;
4145
+ if (typeof direct === "string" && direct.trim()) {
4146
+ return direct.trim();
4147
+ }
4148
+ const nested = data2.data && typeof data2.data === "object" ? data2.data.image_key : void 0;
4149
+ if (typeof nested === "string" && nested.trim()) {
4150
+ return nested.trim();
4151
+ }
4152
+ throw new Error("\u98DE\u4E66\u56FE\u7247\u4E0A\u4F20\u54CD\u5E94\u7F3A\u5C11 image_key\u3002");
4153
+ }
4018
4154
  var FeishuMessageSender = class _FeishuMessageSender {
4019
4155
  constructor(client) {
4020
4156
  this.client = client;
@@ -4051,6 +4187,39 @@ var FeishuMessageSender = class _FeishuMessageSender {
4051
4187
  throw new Error("\u5F53\u524D\u98DE\u4E66 SDK \u4E0D\u652F\u6301\u6D88\u606F\u53D1\u9001\u63A5\u53E3\u3002");
4052
4188
  }
4053
4189
  }
4190
+ async sendImageToChat(chatId, imagePath) {
4191
+ const imageCreate = this.client.im.v1?.image?.create;
4192
+ if (!imageCreate) {
4193
+ throw new Error("\u5F53\u524D\u98DE\u4E66 SDK \u4E0D\u652F\u6301\u56FE\u7247\u4E0A\u4F20\u63A5\u53E3\u3002");
4194
+ }
4195
+ const image = await fs9.readFile(imagePath);
4196
+ const uploaded = await imageCreate({
4197
+ data: {
4198
+ image_type: "message",
4199
+ image
4200
+ }
4201
+ });
4202
+ const imageKey = extractImageKey(uploaded);
4203
+ const payload = {
4204
+ data: {
4205
+ receive_id: chatId,
4206
+ msg_type: "image",
4207
+ content: JSON.stringify({ image_key: imageKey })
4208
+ },
4209
+ params: {
4210
+ receive_id_type: "chat_id"
4211
+ }
4212
+ };
4213
+ if (this.client.im.v1?.message.create) {
4214
+ await this.client.im.v1.message.create(payload);
4215
+ return;
4216
+ }
4217
+ if (this.client.im.message?.create) {
4218
+ await this.client.im.message.create(payload);
4219
+ return;
4220
+ }
4221
+ throw new Error("\u5F53\u524D\u98DE\u4E66 SDK \u4E0D\u652F\u6301\u6D88\u606F\u53D1\u9001\u63A5\u53E3\u3002");
4222
+ }
4054
4223
  async replyTextToMessage(messageId, text) {
4055
4224
  const payload = {
4056
4225
  path: {
@@ -4191,6 +4360,13 @@ function createFeishuEventDispatcher(options) {
4191
4360
  }
4192
4361
  });
4193
4362
  }
4363
+ function resolveFeishuImagePath(config, imageFileName) {
4364
+ const fileName = path12.basename(imageFileName.trim());
4365
+ if (!fileName || fileName !== imageFileName.trim()) {
4366
+ throw new Error("\u56FE\u7247\u6587\u4EF6\u540D\u65E0\u6548\u3002");
4367
+ }
4368
+ return path12.join(resolveHomePath(config.storage.dataDir), "files", "feishu", fileName);
4369
+ }
4194
4370
  function createFeishuGateway(options) {
4195
4371
  assertFeishuConfig(options.config, options.secrets);
4196
4372
  const wsClient = options.wsClientFactory?.({
@@ -4236,6 +4412,10 @@ function createFeishuGateway(options) {
4236
4412
  const cronJobScheduler = options.cronJobScheduler ?? (options.cronJobProcessor ? createCronJobScheduler({
4237
4413
  repository: new CronJobRepository(options.cronJobProcessor.database),
4238
4414
  sendTextToChat: (chatId, text) => options.cronJobProcessor.sender.sendTextToChat(chatId, text),
4415
+ sendImageToChat: options.cronJobProcessor.sender.sendImageToChat ? (chatId, imageFileName) => options.cronJobProcessor.sender.sendImageToChat(
4416
+ chatId,
4417
+ resolveFeishuImagePath(options.config, imageFileName)
4418
+ ) : void 0,
4239
4419
  generateMessage: async (job, now) => {
4240
4420
  const { tools, close } = await createAgenticRagSearchTools({
4241
4421
  config: options.config,
@@ -4273,8 +4453,8 @@ function createFeishuGateway(options) {
4273
4453
 
4274
4454
  // src/feishu/resource-downloader.ts
4275
4455
  import * as lark3 from "@larksuiteoapi/node-sdk";
4276
- import fs9 from "fs/promises";
4277
- import path11 from "path";
4456
+ import fs10 from "fs/promises";
4457
+ import path13 from "path";
4278
4458
  var RESOURCE_TYPE_BY_KIND = {
4279
4459
  file: "file",
4280
4460
  image: "image",
@@ -4312,10 +4492,10 @@ var FeishuResourceDownloader = class _FeishuResourceDownloader {
4312
4492
  }
4313
4493
  async download(input2) {
4314
4494
  const resourceType = RESOURCE_TYPE_BY_KIND[input2.attachment.kind];
4315
- const targetDir = path11.join(this.dataDir, "files", "feishu");
4316
- await fs9.mkdir(targetDir, { recursive: true });
4495
+ const targetDir = path13.join(this.dataDir, "files", "feishu");
4496
+ await fs10.mkdir(targetDir, { recursive: true });
4317
4497
  const fileName = buildStoredFileName(input2);
4318
- const storedPath = path11.join(targetDir, fileName);
4498
+ const storedPath = path13.join(targetDir, fileName);
4319
4499
  const payload = {
4320
4500
  params: { type: resourceType },
4321
4501
  path: { message_id: input2.messageId, file_key: input2.attachment.fileKey }
@@ -4338,29 +4518,29 @@ var FeishuResourceDownloader = class _FeishuResourceDownloader {
4338
4518
 
4339
4519
  // src/files/ingest.ts
4340
4520
  import crypto7 from "crypto";
4341
- import fs11 from "fs/promises";
4342
- import path13 from "path";
4521
+ import fs12 from "fs/promises";
4522
+ import path15 from "path";
4343
4523
 
4344
4524
  // src/files/parser.ts
4345
- import fs10 from "fs/promises";
4346
- import path12 from "path";
4525
+ import fs11 from "fs/promises";
4526
+ import path14 from "path";
4347
4527
  import mammoth from "mammoth";
4348
4528
  import { PDFParse } from "pdf-parse";
4349
4529
  var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([".txt", ".md", ".markdown", ".json", ".csv", ".tsv", ".log"]);
4350
4530
  var DOCX_EXTENSIONS = /* @__PURE__ */ new Set([".docx"]);
4351
4531
  var PDF_EXTENSIONS = /* @__PURE__ */ new Set([".pdf"]);
4352
4532
  function isSupportedParseFile(filePath) {
4353
- const extension = path12.extname(filePath).toLowerCase();
4533
+ const extension = path14.extname(filePath).toLowerCase();
4354
4534
  return TEXT_EXTENSIONS.has(extension) || DOCX_EXTENSIONS.has(extension) || PDF_EXTENSIONS.has(extension);
4355
4535
  }
4356
4536
  function describeSupportedParseTypes() {
4357
4537
  return "txt\u3001md\u3001json\u3001csv\u3001tsv\u3001log\u3001docx\u3001pdf";
4358
4538
  }
4359
4539
  async function parseFileToText(filePath) {
4360
- const extension = path12.extname(filePath).toLowerCase();
4540
+ const extension = path14.extname(filePath).toLowerCase();
4361
4541
  if (TEXT_EXTENSIONS.has(extension)) {
4362
4542
  return {
4363
- text: await fs10.readFile(filePath, "utf8"),
4543
+ text: await fs11.readFile(filePath, "utf8"),
4364
4544
  parser: "text",
4365
4545
  warnings: []
4366
4546
  };
@@ -4374,7 +4554,7 @@ async function parseFileToText(filePath) {
4374
4554
  };
4375
4555
  }
4376
4556
  if (PDF_EXTENSIONS.has(extension)) {
4377
- const buffer = await fs10.readFile(filePath);
4557
+ const buffer = await fs11.readFile(filePath);
4378
4558
  const parser = new PDFParse({ data: buffer });
4379
4559
  try {
4380
4560
  const result = await parser.getText();
@@ -4396,7 +4576,7 @@ function isSupportedTextFile(filePath) {
4396
4576
  }
4397
4577
  function ensureSupportedTextFile(filePath) {
4398
4578
  if (!isSupportedTextFile(filePath)) {
4399
- const extension = path13.extname(filePath).toLowerCase();
4579
+ const extension = path15.extname(filePath).toLowerCase();
4400
4580
  throw new Error(`\u6682\u4E0D\u652F\u6301\u8BE5\u6587\u4EF6\u7C7B\u578B\uFF1A${extension || "\u65E0\u6269\u5C55\u540D"}\u3002\u5F53\u524D\u652F\u6301 ${describeSupportedParseTypes()}\u3002`);
4401
4581
  }
4402
4582
  }
@@ -4405,12 +4585,12 @@ function stableStoredName(sourcePath, fileName) {
4405
4585
  return `${digest}-${fileName}`;
4406
4586
  }
4407
4587
  async function ingestLocalFile(input2) {
4408
- const sourcePath = path13.resolve(input2.filePath);
4409
- const fileName = path13.basename(sourcePath);
4588
+ const sourcePath = path15.resolve(input2.filePath);
4589
+ const fileName = path15.basename(sourcePath);
4410
4590
  const jobId = input2.jobs?.start({ sourcePath, fileName });
4411
4591
  try {
4412
4592
  ensureSupportedTextFile(sourcePath);
4413
- const stat = await fs11.stat(sourcePath);
4593
+ const stat = await fs12.stat(sourcePath);
4414
4594
  if (!stat.isFile()) {
4415
4595
  throw new Error(`\u4E0D\u662F\u6587\u4EF6\uFF1A${sourcePath}`);
4416
4596
  }
@@ -4419,10 +4599,10 @@ async function ingestLocalFile(input2) {
4419
4599
  if (!text) {
4420
4600
  throw new Error(`\u6587\u4EF6\u6CA1\u6709\u53EF\u7D22\u5F15\u6587\u672C\uFF1A${sourcePath}`);
4421
4601
  }
4422
- const fileDir = path13.join(resolveHomePath(input2.config.storage.dataDir), "files");
4423
- await fs11.mkdir(fileDir, { recursive: true });
4424
- const storedPath = path13.join(fileDir, stableStoredName(sourcePath, fileName));
4425
- await fs11.copyFile(sourcePath, storedPath);
4602
+ const fileDir = path15.join(resolveHomePath(input2.config.storage.dataDir), "files");
4603
+ await fs12.mkdir(fileDir, { recursive: true });
4604
+ const storedPath = path15.join(fileDir, stableStoredName(sourcePath, fileName));
4605
+ await fs12.copyFile(sourcePath, storedPath);
4426
4606
  const messageId = input2.messages.ingest({
4427
4607
  platform: "local-file",
4428
4608
  platformChatId: "local-files",
@@ -4727,8 +4907,8 @@ var GatewayIngestor = class {
4727
4907
 
4728
4908
  // src/gateway/detached.ts
4729
4909
  import { spawn } from "child_process";
4730
- import fs12 from "fs";
4731
- import path14 from "path";
4910
+ import fs13 from "fs";
4911
+ import path16 from "path";
4732
4912
  var START_FAILURE_GRACE_MS = 250;
4733
4913
  function buildGatewayForegroundSpawnCommand(argv = process.argv) {
4734
4914
  const [command = process.execPath, ...rawArgs] = argv;
@@ -4785,7 +4965,7 @@ async function startDetachedGateway(input2) {
4785
4965
  ...status.pid ? { pid: status.pid } : {}
4786
4966
  };
4787
4967
  }
4788
- fs12.mkdirSync(path14.dirname(logFile), { recursive: true });
4968
+ fs13.mkdirSync(path16.dirname(logFile), { recursive: true });
4789
4969
  let out;
4790
4970
  let err;
4791
4971
  let stdioClosed = false;
@@ -4795,15 +4975,15 @@ async function startDetachedGateway(input2) {
4795
4975
  }
4796
4976
  stdioClosed = true;
4797
4977
  if (typeof out === "number") {
4798
- fs12.closeSync(out);
4978
+ fs13.closeSync(out);
4799
4979
  }
4800
4980
  if (typeof err === "number") {
4801
- fs12.closeSync(err);
4981
+ fs13.closeSync(err);
4802
4982
  }
4803
4983
  };
4804
4984
  try {
4805
- out = fs12.openSync(logFile, "a");
4806
- err = fs12.openSync(logFile, "a");
4985
+ out = fs13.openSync(logFile, "a");
4986
+ err = fs13.openSync(logFile, "a");
4807
4987
  const foreground = buildGatewayForegroundSpawnCommand(input2.argv);
4808
4988
  const child = spawn(foreground.command, foreground.args, {
4809
4989
  detached: true,
@@ -4834,7 +5014,7 @@ async function startDetachedGateway(input2) {
4834
5014
  }
4835
5015
 
4836
5016
  // src/multimodal/openai-compatible.ts
4837
- import fs13 from "fs/promises";
5017
+ import fs14 from "fs/promises";
4838
5018
  function normalizeBaseUrl2(baseUrl) {
4839
5019
  return baseUrl.replace(/\/+$/, "");
4840
5020
  }
@@ -4844,6 +5024,7 @@ function buildPrompt(context) {
4844
5024
  "\u8BF7\u7406\u89E3\u8FD9\u5F20\u56FE\u7247\uFF0C\u5224\u65AD\u5B83\u662F\u5426\u5305\u542B\u503C\u5F97\u8FDB\u5165\u77E5\u8BC6\u5E93\u548C\u4F1A\u8BDD\u8BB0\u5FC6\u7684\u6709\u610F\u4E49\u4FE1\u606F\u3002",
4845
5025
  '\u8BF7\u53EA\u8F93\u51FA JSON\uFF0C\u683C\u5F0F\u4E3A {"summary": string, "isMeaningful": boolean, "reason": string}\u3002',
4846
5026
  "summary \u4F7F\u7528\u7B80\u6D01\u4E2D\u6587\u8F6C\u8FF0\u56FE\u7247\u4E2D\u7684\u5173\u952E\u4FE1\u606F\uFF1B\u65E0\u610F\u4E49\u56FE\u7247\u4E5F\u8981\u7ED9\u51FA\u7B80\u77ED summary\u3002",
5027
+ "\u5982\u679C\u4E0A\u4E0B\u6587\u63D0\u4F9B\u4E86\u56FE\u7247\u6587\u4EF6\u540D\uFF0Csummary \u5FC5\u987B\u539F\u6837\u5305\u542B\u8BE5\u6587\u4EF6\u540D\uFF0C\u4FBF\u4E8E\u4E4B\u540E\u6309\u6587\u4EF6\u540D\u68C0\u7D22\u548C\u53D1\u9001\u56FE\u7247\u3002",
4847
5028
  contextText ? `\u4E0A\u4E0B\u6587\uFF1A${contextText}` : void 0
4848
5029
  ].filter(Boolean).join("\n");
4849
5030
  }
@@ -4881,7 +5062,7 @@ var OpenAICompatibleMultimodalModel = class {
4881
5062
  if (!this.options.baseUrl || !this.options.apiKey || !this.options.model) {
4882
5063
  throw new Error("\u591A\u6A21\u6001\u914D\u7F6E\u4E0D\u5B8C\u6574\u3002\u8BF7\u8FD0\u884C chattercatcher setup \u6216 chattercatcher settings\u3002");
4883
5064
  }
4884
- const image = await fs13.readFile(input2.imagePath);
5065
+ const image = await fs14.readFile(input2.imagePath);
4885
5066
  const response = await fetch(`${normalizeBaseUrl2(this.options.baseUrl)}/chat/completions`, {
4886
5067
  method: "POST",
4887
5068
  headers: {
@@ -6415,7 +6596,7 @@ dev.command("ingest-feishu-event").description("\u4ECE JSON \u6587\u4EF6\u6A21\u
6415
6596
  const config = await loadConfig();
6416
6597
  const database = openDatabase(config);
6417
6598
  try {
6418
- const raw = await fs14.readFile(options.file, "utf8");
6599
+ const raw = await fs15.readFile(options.file, "utf8");
6419
6600
  const payload = JSON.parse(raw);
6420
6601
  const result = new GatewayIngestor(database).ingestFeishuEvent(payload);
6421
6602
  if (!result.accepted) {