@modelzen/feishu-codex-bridge 0.3.5 → 0.3.7-test.0

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 (2) hide show
  1. package/dist/cli.js +251 -52
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1111,7 +1111,7 @@ async function registerNewBot(desiredName) {
1111
1111
  await addBot({ name, appId: app.id, tenant: app.tenant, botName: v.botName, createdAt: Date.now() });
1112
1112
  console.log(`\u2713 \u5DF2\u521B\u5EFA\u673A\u5668\u4EBA\u300C${name}\u300D bot: ${v.botName ?? "-"} appId: ${app.id}`);
1113
1113
  log.info("onboard", "bot-created", { name, appId: app.id, bot: v.botName ?? null });
1114
- showScopeGrant(cfg, v.missingScopes);
1114
+ noticeMissingScopes(cfg, v.missingScopes);
1115
1115
  const secret = await resolveAppSecret(cfg);
1116
1116
  return { cfg, secret, missingScopes: v.missingScopes };
1117
1117
  }
@@ -1125,28 +1125,12 @@ async function validateAndReport(cfg) {
1125
1125
  }
1126
1126
  console.log(`\u2713 \u51ED\u636E\u6821\u9A8C\u901A\u8FC7 bot: ${v.botName ?? "-"} appId: ${cfg.accounts.app.id}`);
1127
1127
  log.info("onboard", "credentials-ok", { appId: cfg.accounts.app.id, bot: v.botName ?? null });
1128
- showScopeGrant(cfg, v.missingScopes);
1128
+ noticeMissingScopes(cfg, v.missingScopes);
1129
1129
  return { secret, missingScopes: v.missingScopes };
1130
1130
  }
1131
1131
  async function confirmReadyForDaemon(result) {
1132
1132
  if (!process.stdin.isTTY) return true;
1133
1133
  const { app } = result.cfg.accounts;
1134
- let missing = result.missingScopes;
1135
- while (missing && missing.length > 0) {
1136
- const url = buildScopeGrantUrl(app.id, app.tenant);
1137
- console.log(`
1138
- \u23F3 \u8FD8\u5DEE ${missing.length} \u9879\u6743\u9650\u672A\u5F00\u901A\uFF0C\u540E\u53F0\u670D\u52A1\u6682\u4E0D\u5B89\u88C5\u3002`);
1139
- console.log(` \u5F00\u901A\u9875\uFF1A${url}`);
1140
- await promptEnter(" \u5728\u6D4F\u89C8\u5668\u52FE\u9009\u5168\u90E8\u6743\u9650\u5E76\u786E\u8BA4\u540E\uFF0C\u6309 Enter \u91CD\u65B0\u68C0\u6D4B\uFF08Ctrl+C \u53D6\u6D88\uFF09\u2026 ");
1141
- const v = await validateAppCredentials(app.id, result.secret, app.tenant);
1142
- if (!v.ok) {
1143
- console.error(`\u2717 \u51ED\u636E\u6821\u9A8C\u5931\u8D25\uFF1A${v.reason}`);
1144
- return false;
1145
- }
1146
- missing = v.missingScopes;
1147
- if (missing && missing.length > 0) console.log(` \u4ECD\u7F3A\uFF1A${missing.join(" ")}`);
1148
- }
1149
- console.log("\u2713 \u6743\u9650\u5DF2\u5F00\u901A\u3002");
1150
1134
  const eventUrl = buildEventConfigUrl(app.id, app.tenant);
1151
1135
  const opened = openUrl(eventUrl);
1152
1136
  console.log("\n\u6700\u540E\u8FD9\u51E0\u6B65\u98DE\u4E66\u6CA1\u6709 API/\u6DF1\u94FE\u53EF\u4EE3\u529E\uFF08\u8FDE\u67E5\u8BE2\u8BA2\u9605\u72B6\u6001\u7684\u63A5\u53E3\u90FD\u6CA1\u6709\uFF09\uFF0C\u9700\u4F60\u624B\u52A8\u70B9\uFF1A\n");
@@ -1178,27 +1162,26 @@ async function promptEnter(message) {
1178
1162
  rl.close();
1179
1163
  }
1180
1164
  }
1181
- function showScopeGrant(cfg, missingScopes) {
1182
- if (missingScopes && missingScopes.length > 0) {
1183
- const url = buildScopeGrantUrl(cfg.accounts.app.id, cfg.accounts.app.tenant);
1184
- const rule = "\u2500".repeat(64);
1185
- const opened = openUrl(url);
1186
- console.log(`
1187
- ${rule}`);
1188
- console.log(`\u26A0\uFE0F \u8FD8\u5DEE ${missingScopes.length} \u9879\u6743\u9650\u672A\u5F00\u901A \u2014\u2014 \u4E0D\u5F00\u901A\u5219\u6536\u4E0D\u5230\u6D88\u606F\u3001\u53D1\u4E0D\u51FA\u5361\u7247`);
1189
- console.log(" \u98DE\u4E66\u6CA1\u6709\u300C\u626B\u7801\u5373\u6388\u6743\u300D\u7684\u63A5\u53E3\uFF0C\u53EA\u80FD\u5728\u6D4F\u89C8\u5668\u5F00\u901A\uFF08\u5373\u65F6\u751F\u6548\uFF0C\u65E0\u9700\u91CD\u542F\uFF09\uFF1A");
1190
- console.log(
1191
- opened ? "\n \u{1F310} \u5DF2\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668\u6388\u6743\u9875\u3002\u82E5\u6CA1\u5F39\u51FA\uFF0C\u624B\u52A8\u590D\u5236\u4E0B\u9762\u94FE\u63A5\u6253\u5F00\uFF1A" : "\n \u{1F310} \u5728\u6D4F\u89C8\u5668\u6253\u5F00\u4E0B\u9762\u94FE\u63A5\uFF0C\u52FE\u9009\u5168\u90E8\u6743\u9650 \u2192 \u786E\u8BA4\uFF1A"
1192
- );
1193
- console.log(`
1194
- \u{1F449} ${url}
1195
- `);
1196
- console.log(` \uFF08\u672C\u6B21\u7F3A\u5931\uFF1A${missingScopes.join(" ")}\uFF09`);
1197
- console.log(`${rule}
1198
- `);
1199
- } else if (missingScopes === void 0) {
1165
+ function noticeMissingScopes(cfg, missingScopes) {
1166
+ if (missingScopes === void 0) {
1200
1167
  log.info("onboard", "scope-check-skipped", { reason: "scope list unavailable" });
1168
+ return;
1201
1169
  }
1170
+ if (missingScopes.length === 0) return;
1171
+ const url = buildScopeGrantUrl(cfg.accounts.app.id, cfg.accounts.app.tenant);
1172
+ const opened = openUrl(url);
1173
+ const rule = "-".repeat(64);
1174
+ console.log(`
1175
+ ${rule}`);
1176
+ console.log(`\u26A0\uFE0F \u7F3A ${missingScopes.length} \u9879\u6743\u9650\uFF08\u4E0D\u5F71\u54CD\u542F\u52A8\uFF0C\u4F46\u8FD9\u4E9B\u529F\u80FD\u5F00\u901A\u524D\u7528\u4E0D\u4E86\uFF09\uFF1A`);
1177
+ for (const s of missingScopes) console.log(` \xB7 ${labelScope(s)}`);
1178
+ console.log(
1179
+ opened ? "\u{1F310} \u5DF2\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668\u6388\u6743\u9875\uFF08\u5373\u65F6\u751F\u6548\u3001\u65E0\u9700\u91CD\u542F\uFF09\uFF1A" : " \u53BB\u8FD9\u91CC\u7533\u8BF7\uFF08\u52FE\u9009 \u2192 \u786E\u8BA4\uFF0C\u5373\u65F6\u751F\u6548\u3001\u65E0\u9700\u91CD\u542F\uFF09\uFF1A"
1180
+ );
1181
+ console.log(` \u{1F449} ${url}`);
1182
+ console.log(" \u4E0D\u60F3\u73B0\u5728\u5F04\u4E5F\u884C\uFF1A\u4E4B\u540E\u79C1\u804A\u673A\u5668\u4EBA \u2192\u300C\u{1FA7A} \u8BCA\u65AD\u300D\u53EF\u968F\u65F6\u518D\u7533\u8BF7\u3002");
1183
+ console.log(`${rule}
1184
+ `);
1202
1185
  }
1203
1186
 
1204
1187
  // src/bot/bridge.ts
@@ -5606,6 +5589,199 @@ ${lines}
5606
5589
  ]`;
5607
5590
  }
5608
5591
 
5592
+ // src/bot/context-weave.ts
5593
+ var QUOTE_MAX = 800;
5594
+ var LINE_MAX = 280;
5595
+ var THREAD_WEAVE_MAX = 20;
5596
+ var THREAD_PAGE_SIZE = 50;
5597
+ async function fetchQuotedMessage(channel, messageId) {
5598
+ try {
5599
+ const res = await channel.rawClient.im.v1.message.get({ path: { message_id: messageId } });
5600
+ const items = res.data?.items ?? [];
5601
+ const item = items[0];
5602
+ if (!item || item.deleted) return void 0;
5603
+ const cm = toContextMessage(item);
5604
+ return cm.text.trim() ? cm : void 0;
5605
+ } catch (err) {
5606
+ log.warn("intake", "quote-fetch-failed", { messageId, err: String(err) });
5607
+ return void 0;
5608
+ }
5609
+ }
5610
+ async function fetchThreadContext(channel, threadId, opts = {}) {
5611
+ const limit = opts.limit ?? THREAD_WEAVE_MAX;
5612
+ const since = opts.sinceTime ?? 0;
5613
+ try {
5614
+ const res = await channel.rawClient.im.v1.message.list({
5615
+ params: {
5616
+ container_id_type: "thread",
5617
+ container_id: threadId,
5618
+ sort_type: "ByCreateTimeDesc",
5619
+ page_size: THREAD_PAGE_SIZE
5620
+ }
5621
+ });
5622
+ const items = res.data?.items ?? [];
5623
+ const picked = items.filter((it) => !it.deleted).map(toContextMessage).filter(
5624
+ (m) => m.fromUser && // drop the bot's own replies, other apps, system notices
5625
+ m.messageId !== opts.excludeMessageId && // drop the triggering @ message
5626
+ (since === 0 || m.createTime > since) && // delta only for existing sessions
5627
+ m.text.trim().length > 0
5628
+ );
5629
+ picked.sort((a, b) => a.createTime - b.createTime);
5630
+ const out = picked.slice(-limit);
5631
+ if (picked.length > out.length) {
5632
+ log.info("intake", "thread-context-truncated", { threadId, kept: out.length, total: picked.length });
5633
+ }
5634
+ return out;
5635
+ } catch (err) {
5636
+ log.warn("intake", "thread-context-failed", { threadId, err: String(err) });
5637
+ return [];
5638
+ }
5639
+ }
5640
+ function toContextMessage(item) {
5641
+ const id = item.sender?.id ?? "";
5642
+ const name = item.sender?.sender_name || (id ? `\u7528\u6237${id.slice(-4)}` : "\u67D0\u4EBA");
5643
+ return {
5644
+ messageId: item.message_id ?? "",
5645
+ senderName: name,
5646
+ text: extractMessageText(item.msg_type, item.body?.content, item.mentions),
5647
+ fromUser: item.sender?.sender_type === "user",
5648
+ createTime: Number(item.create_time) || 0
5649
+ };
5650
+ }
5651
+ function extractMessageText(msgType, content, mentions) {
5652
+ if (!content) return placeholderFor(msgType);
5653
+ let parsed;
5654
+ try {
5655
+ parsed = JSON.parse(content);
5656
+ } catch {
5657
+ return placeholderFor(msgType);
5658
+ }
5659
+ switch (msgType) {
5660
+ case "text":
5661
+ return replaceMentions(parsed?.text ?? "", mentions);
5662
+ case "post":
5663
+ return replaceMentions(extractPostText(parsed), mentions);
5664
+ case "image":
5665
+ return "[\u56FE\u7247]";
5666
+ case "audio":
5667
+ return "[\u8BED\u97F3]";
5668
+ case "media":
5669
+ return "[\u89C6\u9891]";
5670
+ case "file": {
5671
+ const name = parsed?.file_name;
5672
+ return name ? `[\u6587\u4EF6\uFF1A${name}]` : "[\u6587\u4EF6]";
5673
+ }
5674
+ case "sticker":
5675
+ return "[\u8868\u60C5]";
5676
+ case "interactive":
5677
+ return "[\u5361\u7247\u6D88\u606F]";
5678
+ case "share_chat":
5679
+ return "[\u5206\u4EAB\u7FA4\u540D\u7247]";
5680
+ case "share_user":
5681
+ return "[\u5206\u4EAB\u4E2A\u4EBA\u540D\u7247]";
5682
+ case "merge_forward":
5683
+ case "forward":
5684
+ return "[\u5408\u5E76\u8F6C\u53D1\u6D88\u606F]";
5685
+ default:
5686
+ return placeholderFor(msgType);
5687
+ }
5688
+ }
5689
+ function placeholderFor(msgType) {
5690
+ return msgType ? `[${msgType} \u6D88\u606F]` : "[\u6D88\u606F]";
5691
+ }
5692
+ function extractPostText(parsed) {
5693
+ if (!parsed || typeof parsed !== "object") return "";
5694
+ const obj = parsed;
5695
+ let title = obj.title;
5696
+ let blocks = obj.content;
5697
+ if (!Array.isArray(blocks)) {
5698
+ for (const v of Object.values(obj)) {
5699
+ if (v && typeof v === "object" && Array.isArray(v.content)) {
5700
+ title = v.title;
5701
+ blocks = v.content;
5702
+ break;
5703
+ }
5704
+ }
5705
+ }
5706
+ const parts = [];
5707
+ if (typeof title === "string" && title.trim()) parts.push(title.trim());
5708
+ if (Array.isArray(blocks)) {
5709
+ for (const line of blocks) {
5710
+ if (!Array.isArray(line)) continue;
5711
+ const lineText = line.map(nodeToText).join("");
5712
+ if (lineText) parts.push(lineText);
5713
+ }
5714
+ }
5715
+ return parts.join("\n");
5716
+ }
5717
+ function nodeToText(node) {
5718
+ if (!node || typeof node !== "object") return "";
5719
+ const n = node;
5720
+ switch (n.tag) {
5721
+ case "text":
5722
+ return typeof n.text === "string" ? n.text : "";
5723
+ case "a":
5724
+ return typeof n.text === "string" ? n.text : typeof n.href === "string" ? n.href : "";
5725
+ case "at": {
5726
+ const name = typeof n.user_name === "string" ? n.user_name : "";
5727
+ return name ? `@${name}` : "@\u67D0\u4EBA";
5728
+ }
5729
+ case "img":
5730
+ return "[\u56FE\u7247]";
5731
+ case "media":
5732
+ return "[\u89C6\u9891]";
5733
+ case "emotion":
5734
+ return "[\u8868\u60C5]";
5735
+ default:
5736
+ return typeof n.text === "string" ? n.text : "";
5737
+ }
5738
+ }
5739
+ function replaceMentions(text, mentions) {
5740
+ if (!text || !mentions?.length) return text;
5741
+ let out = text;
5742
+ for (const m of mentions) {
5743
+ if (!m.key) continue;
5744
+ out = out.split(m.key).join(m.name ? `@${m.name}` : "@\u67D0\u4EBA");
5745
+ }
5746
+ return out;
5747
+ }
5748
+ function sanitizeContext(s, maxLen, oneLine2) {
5749
+ if (!s) return "";
5750
+ let out = s.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "").replace(/\r\n?/g, "\n");
5751
+ out = oneLine2 ? out.replace(/\s+/g, " ") : out.replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n");
5752
+ out = out.trim();
5753
+ return out.length > maxLen ? `${out.slice(0, maxLen)}\u2026` : out;
5754
+ }
5755
+ function weaveQuote(text, quoted) {
5756
+ if (!quoted) return text;
5757
+ const who = sanitizeContext(quoted.senderName, 40, true) || "\u67D0\u4EBA";
5758
+ const body = sanitizeContext(quoted.text, QUOTE_MAX, true);
5759
+ if (!body) return text;
5760
+ const block = `[\u7528\u6237\u5F15\u7528\u4E86\u4E00\u6761\u6D88\u606F\uFF08\u6765\u81EA ${who}\uFF09\uFF1A
5761
+ ${body}
5762
+ ]`;
5763
+ const base = text.trim();
5764
+ return base ? `${block}
5765
+
5766
+ ${base}` : block;
5767
+ }
5768
+ function weaveThreadHistory(text, msgs) {
5769
+ if (msgs.length === 0) return text;
5770
+ const lines = msgs.map((m) => {
5771
+ const who = sanitizeContext(m.senderName, 40, true) || "\u67D0\u4EBA";
5772
+ const body = sanitizeContext(m.text, LINE_MAX, true);
5773
+ return body ? `${who}\uFF1A${body}` : "";
5774
+ }).filter((l) => l.length > 0);
5775
+ if (lines.length === 0) return text;
5776
+ const block = `[\u8BDD\u9898\u4E2D\u5728\u6B64\u4E4B\u524D\u5DF2\u6709\u7684\u6D88\u606F\uFF08\u6309\u65F6\u95F4\u5148\u540E\u6392\u5217\uFF0C\u4F9B\u4F60\u7406\u89E3\u4E0A\u4E0B\u6587\uFF09\uFF1A
5777
+ ${lines.join("\n")}
5778
+ ]`;
5779
+ const base = text.trim();
5780
+ return base ? `${block}
5781
+
5782
+ ${base}` : block;
5783
+ }
5784
+
5609
5785
  // src/bot/comments.ts
5610
5786
  var SUPPORTED_FILE_TYPES = /* @__PURE__ */ new Set(["doc", "docx", "sheet", "file"]);
5611
5787
  var REPLY_MAX_CHARS = 2e3;
@@ -6091,20 +6267,26 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
6091
6267
  const perm = turnPerm(project, senderId);
6092
6268
  return { sessionKey: perm.roleSuffix ? `${baseKey}#${perm.roleSuffix}` : baseKey, ...perm };
6093
6269
  }
6094
- async function ingestFiles(msg, text) {
6095
- if (!messageHasFiles(msg)) return text;
6096
- const files = await collectInboundFiles(channel, msg);
6097
- const woven = weaveFileManifest(text, files);
6098
- if (!woven.trim()) {
6099
- return "\u7528\u6237\u53D1\u6765\u4E00\u4E2A\u9644\u4EF6\uFF0C\u4F46\u6865\u6CA1\u80FD\u4E0B\u8F7D\u5B83\uFF08\u53EF\u80FD\u8D85\u8FC7 50MB \u4E0A\u9650\u6216\u88AB\u98DE\u4E66\u62D2\u7EDD\uFF09\u3002\u8BF7\u544A\u8BC9\u7528\u6237\u9644\u4EF6\u6CA1\u8BFB\u5230\uFF0C\u53EF\u4EE5\u91CD\u53D1\uFF0C\u6216\u6539\u4E3A\u7C98\u8D34\u6587\u672C / \u53D1\u56FE\u7247\u3002";
6270
+ async function ingestContext(msg, text) {
6271
+ let body = text;
6272
+ if (messageHasFiles(msg)) {
6273
+ const files = await collectInboundFiles(channel, msg);
6274
+ body = weaveFileManifest(text, files);
6275
+ if (!body.trim()) {
6276
+ body = "\u7528\u6237\u53D1\u6765\u4E00\u4E2A\u9644\u4EF6\uFF0C\u4F46\u6865\u6CA1\u80FD\u4E0B\u8F7D\u5B83\uFF08\u53EF\u80FD\u8D85\u8FC7 50MB \u4E0A\u9650\u6216\u88AB\u98DE\u4E66\u62D2\u7EDD\uFF09\u3002\u8BF7\u544A\u8BC9\u7528\u6237\u9644\u4EF6\u6CA1\u8BFB\u5230\uFF0C\u53EF\u4EE5\u91CD\u53D1\uFF0C\u6216\u6539\u4E3A\u7C98\u8D34\u6587\u672C / \u53D1\u56FE\u7247\u3002";
6277
+ }
6278
+ }
6279
+ if (msg.replyToMessageId) {
6280
+ const quoted = await fetchQuotedMessage(channel, msg.replyToMessageId);
6281
+ body = weaveQuote(body, quoted);
6100
6282
  }
6101
- return woven;
6283
+ return body;
6102
6284
  }
6103
6285
  async function handleTurn(msg, text, sessionKey, flat, project, perm) {
6104
6286
  const existing = active.get(sessionKey);
6105
6287
  if (existing) {
6106
6288
  const images = messageHasImages(msg) ? await collectInboundImages(channel, msg) : void 0;
6107
- const woven = await ingestFiles(msg, text);
6289
+ const woven = await ingestContext(msg, text);
6108
6290
  const cur = active.get(sessionKey);
6109
6291
  if (!cur) {
6110
6292
  startReservedRun(msg, woven, sessionKey, flat, project, perm, images, true, text);
@@ -6141,8 +6323,15 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
6141
6323
  const reaction = runReaction(msg.messageId, !sema.hasFree());
6142
6324
  try {
6143
6325
  const images = preloadedImages ?? (messageHasImages(msg) ? await collectInboundImages(channel, msg) : void 0);
6144
- const firstText = preIngested ? text : await ingestFiles(msg, text);
6145
- let thread = await resolveThread(sessionKey, msg.chatId, { mode: perm.mode, network: perm.network });
6326
+ let firstText = preIngested ? text : await ingestContext(msg, text);
6327
+ const { thread: resolved, recreated } = await resolveThread(sessionKey, msg.chatId, {
6328
+ mode: perm.mode,
6329
+ network: perm.network
6330
+ });
6331
+ let thread = resolved;
6332
+ const neverSeen = !thread;
6333
+ const codexEmpty = neverSeen || recreated;
6334
+ const prior = neverSeen ? void 0 : await getSession(sessionKey);
6146
6335
  if (!thread) {
6147
6336
  const cwd = project?.cwd ?? fallbackCwd;
6148
6337
  thread = await backend.startThread({ cwd, mode: perm.mode, network: perm.network });
@@ -6156,10 +6345,19 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
6156
6345
  // `summaryText` (handleTurn's original) so the session label isn't
6157
6346
  // manifest boilerplate + a temp path.
6158
6347
  summary: stripFileTokens(summaryText2 ?? text).slice(0, 80),
6348
+ lastSeenAt: msg.createTime,
6159
6349
  createdAt: Date.now(),
6160
6350
  updatedAt: Date.now()
6161
6351
  });
6162
6352
  }
6353
+ if (msg.threadId && (codexEmpty || prior?.lastSeenAt !== void 0)) {
6354
+ const history = await fetchThreadContext(channel, msg.threadId, {
6355
+ sinceTime: codexEmpty ? 0 : prior?.lastSeenAt ?? 0,
6356
+ excludeMessageId: msg.messageId
6357
+ });
6358
+ firstText = weaveThreadHistory(firstText, history);
6359
+ }
6360
+ if (!neverSeen) void patchSession(sessionKey, { lastSeenAt: msg.createTime }).catch(() => void 0);
6163
6361
  reserved.thread = thread;
6164
6362
  await launchRun(
6165
6363
  {
@@ -6185,9 +6383,9 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
6185
6383
  }
6186
6384
  async function resolveThread(threadId, chatId, perm) {
6187
6385
  const live = sessions.get(threadId);
6188
- if (live) return live;
6386
+ if (live) return { thread: live, recreated: false };
6189
6387
  const rec = await getSession(threadId);
6190
- if (!rec) return void 0;
6388
+ if (!rec) return { thread: void 0, recreated: false };
6191
6389
  try {
6192
6390
  const resumed = await backend.resumeThread({
6193
6391
  cwd: rec.cwd,
@@ -6198,7 +6396,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
6198
6396
  network: perm?.network
6199
6397
  });
6200
6398
  sessions.set(threadId, resumed);
6201
- return resumed;
6399
+ return { thread: resumed, recreated: false };
6202
6400
  } catch (err) {
6203
6401
  log.fail("agent", err, { phase: "resume-on-turn", threadId });
6204
6402
  const project = await getProjectByChatId(chatId);
@@ -6211,7 +6409,8 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
6211
6409
  network: perm?.network ?? project?.network
6212
6410
  });
6213
6411
  sessions.set(threadId, fresh);
6214
- return fresh;
6412
+ await patchSession(threadId, { codexThreadId: fresh.codexThreadId }).catch(() => void 0);
6413
+ return { thread: fresh, recreated: true };
6215
6414
  }
6216
6415
  }
6217
6416
  async function evictLiveSessionsForChat(chatId) {
@@ -6243,7 +6442,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
6243
6442
  return;
6244
6443
  }
6245
6444
  const images = messageHasImages(msg) ? await collectInboundImages(channel, msg) : void 0;
6246
- const firstText = await ingestFiles(msg, text) || "\u4F60\u597D\uFF0C\u6211\u4EEC\u5F00\u59CB\u5427\u3002";
6445
+ const firstText = await ingestContext(msg, text) || "\u4F60\u597D\uFF0C\u6211\u4EEC\u5F00\u59CB\u5427\u3002";
6247
6446
  log.info("card", "start", { project: project?.name ?? "(unregistered)", model, effort, images: images?.length ?? 0 });
6248
6447
  await launchRun(
6249
6448
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelzen/feishu-codex-bridge",
3
- "version": "0.3.5",
3
+ "version": "0.3.7-test.0",
4
4
  "description": "Bridge Feishu/Lark messenger with local Codex via app-server (project=group, thread=session)",
5
5
  "type": "module",
6
6
  "bin": {