@modelzen/feishu-codex-bridge 0.1.6 → 0.1.8
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 +493 -39
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -538,7 +538,7 @@ async function spawnExecProvider(pc, ref) {
|
|
|
538
538
|
const timeoutMs = pc.noOutputTimeoutMs ?? DEFAULT_EXEC_TIMEOUT_MS;
|
|
539
539
|
const maxOutput = pc.maxOutputBytes ?? DEFAULT_EXEC_MAX_OUTPUT;
|
|
540
540
|
const providerName = ref.provider ?? DEFAULT_PROVIDER;
|
|
541
|
-
return new Promise((
|
|
541
|
+
return new Promise((resolve6, reject) => {
|
|
542
542
|
const env = {};
|
|
543
543
|
if (pc.passEnv) for (const k of pc.passEnv) {
|
|
544
544
|
const v = process.env[k];
|
|
@@ -583,7 +583,7 @@ async function spawnExecProvider(pc, ref) {
|
|
|
583
583
|
try {
|
|
584
584
|
const parsed = JSON.parse(stdout);
|
|
585
585
|
const value = parsed.values?.[ref.id];
|
|
586
|
-
if (typeof value === "string") return
|
|
586
|
+
if (typeof value === "string") return resolve6(value);
|
|
587
587
|
const err = parsed.errors?.[ref.id]?.message;
|
|
588
588
|
reject(new Error(`exec provider did not return secret for ${ref.id}${err ? `: ${err}` : ""}`));
|
|
589
589
|
} catch (err) {
|
|
@@ -680,6 +680,30 @@ var COMMENT_SCOPES = [
|
|
|
680
680
|
"wiki:wiki:readonly"
|
|
681
681
|
];
|
|
682
682
|
var GRANT_SCOPES = [...REQUIRED_SCOPES, ...COMMENT_SCOPES];
|
|
683
|
+
var SCOPE_LABELS = {
|
|
684
|
+
"im:message.group_at_msg:readonly": "\u63A5\u6536\u7FA4\u91CC @\u673A\u5668\u4EBA \u7684\u6D88\u606F",
|
|
685
|
+
"im:message.group_msg": "\u63A5\u6536\u7FA4\u5185\u6240\u6709\u6D88\u606F\uFF08\u514D@\uFF09",
|
|
686
|
+
"im:message.p2p_msg:readonly": "\u63A5\u6536\u79C1\u804A\u6D88\u606F\uFF08\u7BA1\u7406\u53F0\uFF09",
|
|
687
|
+
"im:message:send_as_bot": "\u53D1\u9001\u6D88\u606F / \u5361\u7247",
|
|
688
|
+
"im:message.pins:write_only": "\u7F6E\u9876\u6D88\u606F\u5230\u7FA4 Pin",
|
|
689
|
+
"im:message.reactions:write_only": "\u6D88\u606F\u8868\u60C5\u56DE\u590D\uFF08\u8FD0\u884C\u72B6\u6001\uFF09",
|
|
690
|
+
"im:resource": "\u56FE\u7247 / \u6587\u4EF6\u4E0A\u4F20\u4E0E\u4E0B\u8F7D",
|
|
691
|
+
"im:chat:create": "\u521B\u5EFA\u9879\u76EE\u7FA4",
|
|
692
|
+
"im:chat:update": "\u8F6C\u79FB\u7FA4\u4E3B\uFF08\u89E3\u7ED1\u65F6\uFF09",
|
|
693
|
+
"im:chat.managers:write_only": "\u8BBE\u7F6E\u7FA4\u7BA1\u7406\u5458",
|
|
694
|
+
"im:chat.announcement:read": "\u8BFB\u53D6\u7FA4\u516C\u544A",
|
|
695
|
+
"im:chat.announcement:write_only": "\u7F16\u8F91\u7FA4\u516C\u544A",
|
|
696
|
+
"im:chat.top_notice:write_only": "\u7F6E\u9876\u7FA4\u516C\u544A\u6A2A\u5E45",
|
|
697
|
+
"im:chat.tabs:write_only": "\u6DFB\u52A0\u7FA4\u6807\u7B7E\u9875",
|
|
698
|
+
"cardkit:card:write": "\u4EA4\u4E92\u6309\u94AE\u5361\u7247",
|
|
699
|
+
"docs:document.comment:read": "\u8BFB\u53D6\u6587\u6863\u8BC4\u8BBA",
|
|
700
|
+
"docs:document.comment:create": "\u53D1\u8868\u6587\u6863\u8BC4\u8BBA\u56DE\u590D",
|
|
701
|
+
"wiki:wiki:readonly": "\u8BFB\u53D6\u77E5\u8BC6\u5E93\u8282\u70B9"
|
|
702
|
+
};
|
|
703
|
+
function labelScope(scope) {
|
|
704
|
+
const label = SCOPE_LABELS[scope];
|
|
705
|
+
return label ? `${label}\uFF08${scope}\uFF09` : scope;
|
|
706
|
+
}
|
|
683
707
|
var HOSTS = {
|
|
684
708
|
feishu: "open.feishu.cn",
|
|
685
709
|
lark: "open.larksuite.com"
|
|
@@ -1112,7 +1136,7 @@ var AsyncQueue = class {
|
|
|
1112
1136
|
continue;
|
|
1113
1137
|
}
|
|
1114
1138
|
if (this.closed) return;
|
|
1115
|
-
const next = await new Promise((
|
|
1139
|
+
const next = await new Promise((resolve6) => this.waiters.push(resolve6));
|
|
1116
1140
|
if (next.done) return;
|
|
1117
1141
|
yield next.value;
|
|
1118
1142
|
}
|
|
@@ -1163,8 +1187,8 @@ var AppServerClient = class {
|
|
|
1163
1187
|
const id = ++this.nextId;
|
|
1164
1188
|
const payload = `${JSON.stringify({ jsonrpc: "2.0", id, method, params: params ?? {} })}
|
|
1165
1189
|
`;
|
|
1166
|
-
return new Promise((
|
|
1167
|
-
this.pending.set(id, { resolve:
|
|
1190
|
+
return new Promise((resolve6, reject) => {
|
|
1191
|
+
this.pending.set(id, { resolve: resolve6, reject });
|
|
1168
1192
|
this.child.stdin.write(payload, (err) => {
|
|
1169
1193
|
if (err) {
|
|
1170
1194
|
this.pending.delete(id);
|
|
@@ -1188,14 +1212,14 @@ var AppServerClient = class {
|
|
|
1188
1212
|
const child = this.child;
|
|
1189
1213
|
if (!child || child.exitCode !== null) return;
|
|
1190
1214
|
child.kill("SIGTERM");
|
|
1191
|
-
await new Promise((
|
|
1215
|
+
await new Promise((resolve6) => {
|
|
1192
1216
|
const t = setTimeout(() => {
|
|
1193
1217
|
if (child.exitCode === null) child.kill("SIGKILL");
|
|
1194
|
-
|
|
1218
|
+
resolve6();
|
|
1195
1219
|
}, graceMs);
|
|
1196
1220
|
child.once("exit", () => {
|
|
1197
1221
|
clearTimeout(t);
|
|
1198
|
-
|
|
1222
|
+
resolve6();
|
|
1199
1223
|
});
|
|
1200
1224
|
});
|
|
1201
1225
|
}
|
|
@@ -1312,14 +1336,28 @@ function mapItemComplete(item) {
|
|
|
1312
1336
|
// src/agent/codex-appserver/backend.ts
|
|
1313
1337
|
var APPROVAL_POLICY = "never";
|
|
1314
1338
|
var SANDBOX = "danger-full-access";
|
|
1339
|
+
var BRIDGE_DEVELOPER_INSTRUCTIONS = [
|
|
1340
|
+
"\u4F60\u73B0\u5728\u901A\u8FC7\u300C\u98DE\u4E66\u6865\u300D\u4E0E\u7528\u6237\u5BF9\u8BDD\uFF1A\u4F60\u7684\u56DE\u590D\u4F1A\u88AB\u6E32\u67D3\u6210\u98DE\u4E66\u6D88\u606F\u3002\u8BF7\u9075\u5B88\u4E24\u6761\u8F93\u51FA\u7EA6\u5B9A\u3002",
|
|
1341
|
+
"",
|
|
1342
|
+
"1) \u56FE\u7247\uFF1A\u8981\u914D\u56FE\u65F6\uFF0C\u7528\u6807\u51C6 Markdown \u56FE\u7247\u8BED\u6CD5  \u5F15\u7528\u4E00\u4E2A\u3010\u771F\u5B9E\u5B58\u5728\u3011\u7684\u56FE\u7247\uFF0C",
|
|
1343
|
+
"\u98DE\u4E66\u6865\u4F1A\u81EA\u52A8\u4E0A\u4F20\u5E76\u5728\u98DE\u4E66\u91CC\u6E32\u67D3\u3002\u8DEF\u5F84\u53EF\u4EE5\u662F\u76F8\u5BF9\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u7684\u76F8\u5BF9\u8DEF\u5F84\u3001\u5DE5\u4F5C\u76EE\u5F55\u5185\u7684\u7EDD\u5BF9\u8DEF\u5F84\uFF0C",
|
|
1344
|
+
"\u6216\u4E00\u4E2A http(s) \u56FE\u7247 URL\u3002\u7EDD\u4E0D\u8981\u7F16\u9020\u4E0D\u5B58\u5728\u7684\u56FE\u7247\u5360\u4F4D\uFF08\u4F8B\u5982\u5199 ![\u7BA1\u7406\u53F0\u622A\u56FE] \u5374\u6CA1\u6709\u5BF9\u5E94\u6587\u4EF6\uFF09\u2014\u2014",
|
|
1345
|
+
"\u6CA1\u6709\u771F\u5B9E\u56FE\u7247\u5C31\u4E0D\u8981\u5199\u56FE\u7247\u8BED\u6CD5\u3002",
|
|
1346
|
+
"",
|
|
1347
|
+
"2) \u5361\u7247\uFF1A\u4EC5\u5F53\u7528\u6237\u660E\u786E\u8981\u6C42\u300C\u7528\u5361\u7247\u56DE\u590D / \u505A\u6210\u98DE\u4E66\u5361\u7247 / \u5361\u7247\u5F62\u5F0F\u5C55\u793A / changelog \u5361\u7247\u300D\u4E4B\u7C7B\u65F6\uFF0C",
|
|
1348
|
+
"\u628A\u8981\u5C55\u793A\u7684\u5185\u5BB9\u5305\u8FDB\u4E00\u4E2A ```feishu-card \u4EE3\u7801\u5757\uFF0C\u5757\u5185\u7528 Markdown \u4E66\u5199\uFF1A",
|
|
1349
|
+
"\u9996\u884C\u7528 `# \u6807\u9898` \u4F5C\u4E3A\u5361\u7247\u6807\u9898\u680F\uFF1B\u7528 `---` \u4F5C\u5206\u9694\u7EBF\uFF1B\u7528 `> \u6587\u5B57` \u4F5C\u7070\u8272\u6CE8\u811A\uFF1B",
|
|
1350
|
+
"`**\u7C97\u4F53**`\u3001\u5217\u8868\u3001\u94FE\u63A5\u7167\u5E38\u4F7F\u7528\uFF1B\u914D\u56FE\u540C\u6837\u7528 \u3002",
|
|
1351
|
+
"\u4E0D\u8981\u624B\u5199\u98DE\u4E66\u5361\u7247\u7684 JSON\u3002\u666E\u901A\u95EE\u7B54\u6B63\u5E38\u56DE\u590D\u5373\u53EF\uFF0C\u53EA\u6709\u7528\u6237\u8981\u5361\u7247\u65F6\u624D\u7528 ```feishu-card \u4EE3\u7801\u5757\u3002"
|
|
1352
|
+
].join("\n");
|
|
1315
1353
|
var READ_HISTORY_TIMEOUT_MS = 2e4;
|
|
1316
1354
|
function withDeadline(p, ms, label) {
|
|
1317
|
-
return new Promise((
|
|
1355
|
+
return new Promise((resolve6, reject) => {
|
|
1318
1356
|
const t = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
1319
1357
|
p.then(
|
|
1320
1358
|
(v) => {
|
|
1321
1359
|
clearTimeout(t);
|
|
1322
|
-
|
|
1360
|
+
resolve6(v);
|
|
1323
1361
|
},
|
|
1324
1362
|
(e) => {
|
|
1325
1363
|
clearTimeout(t);
|
|
@@ -1359,11 +1397,11 @@ var CodexThread = class {
|
|
|
1359
1397
|
if (self.model) params.model = self.model;
|
|
1360
1398
|
if (self.effort) params.effort = self.effort;
|
|
1361
1399
|
let startError;
|
|
1362
|
-
const startFailed = new Promise((
|
|
1400
|
+
const startFailed = new Promise((resolve6) => {
|
|
1363
1401
|
self.client.request("turn/start", params).then(void 0, (err) => {
|
|
1364
1402
|
startError = err instanceof Error ? err : new Error(String(err));
|
|
1365
1403
|
log.fail("agent", startError, { phase: "turn/start" });
|
|
1366
|
-
|
|
1404
|
+
resolve6("start-failed");
|
|
1367
1405
|
});
|
|
1368
1406
|
});
|
|
1369
1407
|
const stream2 = self.client.stream()[Symbol.asyncIterator]();
|
|
@@ -1487,6 +1525,7 @@ var CodexAppServerBackend = class {
|
|
|
1487
1525
|
cwd: opts.cwd,
|
|
1488
1526
|
approvalPolicy: APPROVAL_POLICY,
|
|
1489
1527
|
sandbox: SANDBOX,
|
|
1528
|
+
developerInstructions: BRIDGE_DEVELOPER_INSTRUCTIONS,
|
|
1490
1529
|
...opts.model ? { model: opts.model } : {}
|
|
1491
1530
|
});
|
|
1492
1531
|
return new CodexThread(client, res.thread.id, opts.model, opts.effort);
|
|
@@ -1498,6 +1537,7 @@ var CodexAppServerBackend = class {
|
|
|
1498
1537
|
cwd: opts.cwd,
|
|
1499
1538
|
approvalPolicy: APPROVAL_POLICY,
|
|
1500
1539
|
sandbox: SANDBOX,
|
|
1540
|
+
developerInstructions: BRIDGE_DEVELOPER_INSTRUCTIONS,
|
|
1501
1541
|
...opts.model ? { model: opts.model } : {}
|
|
1502
1542
|
});
|
|
1503
1543
|
return new CodexThread(client, res.thread.id, opts.model, opts.effort);
|
|
@@ -1928,6 +1968,15 @@ function card(elements, opts = {}) {
|
|
|
1928
1968
|
function md(content) {
|
|
1929
1969
|
return { tag: "markdown", content };
|
|
1930
1970
|
}
|
|
1971
|
+
function image(imgKey, alt = "") {
|
|
1972
|
+
return {
|
|
1973
|
+
tag: "img",
|
|
1974
|
+
img_key: imgKey,
|
|
1975
|
+
alt: { tag: "plain_text", content: alt },
|
|
1976
|
+
mode: "fit_horizontal",
|
|
1977
|
+
preview: true
|
|
1978
|
+
};
|
|
1979
|
+
}
|
|
1931
1980
|
function note(content) {
|
|
1932
1981
|
return { tag: "div", text: { tag: "lark_md", content, text_size: "notation", text_color: "grey" } };
|
|
1933
1982
|
}
|
|
@@ -2337,6 +2386,92 @@ function truncateTail(s, n) {
|
|
|
2337
2386
|
return t.length > n ? `\u2026${t.slice(t.length - n)}` : t;
|
|
2338
2387
|
}
|
|
2339
2388
|
|
|
2389
|
+
// src/card/markdown-render.ts
|
|
2390
|
+
var NO_IMAGES = /* @__PURE__ */ new Map();
|
|
2391
|
+
var IMG_RE = /!\[([^\]]*)\]\(\s*(<[^>]+>|[^)\s]+)(?:\s+(?:"[^"]*"|'[^']*'))?\s*\)/g;
|
|
2392
|
+
var FENCE_RE = /```feishu-card[^\n]*\n([\s\S]*?)```/g;
|
|
2393
|
+
function cleanSrc(raw) {
|
|
2394
|
+
let s = raw.trim();
|
|
2395
|
+
if (s.startsWith("<") && s.endsWith(">")) s = s.slice(1, -1).trim();
|
|
2396
|
+
return s;
|
|
2397
|
+
}
|
|
2398
|
+
function extractCardFences(text) {
|
|
2399
|
+
const fences = [];
|
|
2400
|
+
const re = new RegExp(FENCE_RE.source, "g");
|
|
2401
|
+
const stripped = text.replace(re, (_full, inner) => {
|
|
2402
|
+
fences.push(inner.trim());
|
|
2403
|
+
return "";
|
|
2404
|
+
});
|
|
2405
|
+
return { fences, stripped };
|
|
2406
|
+
}
|
|
2407
|
+
function renderRichText(text, images = NO_IMAGES) {
|
|
2408
|
+
const body = extractCardFences(text).stripped;
|
|
2409
|
+
if (!body.includes("![")) {
|
|
2410
|
+
const t = body.trim();
|
|
2411
|
+
return t ? [md(t)] : [];
|
|
2412
|
+
}
|
|
2413
|
+
const els = [];
|
|
2414
|
+
let buf = "";
|
|
2415
|
+
const flush = () => {
|
|
2416
|
+
const t = buf.trim();
|
|
2417
|
+
if (t) els.push(md(t));
|
|
2418
|
+
buf = "";
|
|
2419
|
+
};
|
|
2420
|
+
const re = new RegExp(IMG_RE.source, "g");
|
|
2421
|
+
let last = 0;
|
|
2422
|
+
let m;
|
|
2423
|
+
while ((m = re.exec(body)) !== null) {
|
|
2424
|
+
buf += body.slice(last, m.index);
|
|
2425
|
+
const alt = m[1] ?? "";
|
|
2426
|
+
const src = cleanSrc(m[2] ?? "");
|
|
2427
|
+
const key = images.get(src);
|
|
2428
|
+
if (key) {
|
|
2429
|
+
flush();
|
|
2430
|
+
els.push(image(key, alt));
|
|
2431
|
+
} else {
|
|
2432
|
+
buf += m[0];
|
|
2433
|
+
}
|
|
2434
|
+
last = m.index + m[0].length;
|
|
2435
|
+
}
|
|
2436
|
+
buf += body.slice(last);
|
|
2437
|
+
flush();
|
|
2438
|
+
return els;
|
|
2439
|
+
}
|
|
2440
|
+
function buildCleanCard(fenceMarkdown, images = NO_IMAGES, template = "blue") {
|
|
2441
|
+
const lines = fenceMarkdown.split("\n");
|
|
2442
|
+
let start = 0;
|
|
2443
|
+
while (start < lines.length && lines[start]?.trim() === "") start++;
|
|
2444
|
+
const headingMatch = lines[start]?.match(/^#{1,6}\s+(.+?)\s*$/);
|
|
2445
|
+
const title = headingMatch ? headingMatch[1] : "";
|
|
2446
|
+
if (headingMatch) start++;
|
|
2447
|
+
const bodyMarkdown = lines.slice(start).join("\n").trim();
|
|
2448
|
+
const elements = renderCleanBody(bodyMarkdown, images);
|
|
2449
|
+
const body = elements.length > 0 ? elements : [md(title || "\xAD")];
|
|
2450
|
+
return card(body, {
|
|
2451
|
+
...title ? { header: { title, template } } : {},
|
|
2452
|
+
summary: title || "\u5361\u7247"
|
|
2453
|
+
});
|
|
2454
|
+
}
|
|
2455
|
+
function renderCleanBody(bodyMarkdown, images) {
|
|
2456
|
+
const out = [];
|
|
2457
|
+
for (const raw of bodyMarkdown.split(/\n{2,}/)) {
|
|
2458
|
+
const block = raw.trim();
|
|
2459
|
+
if (!block) continue;
|
|
2460
|
+
if (/^(-{3,}|\*{3,}|_{3,})$/.test(block)) {
|
|
2461
|
+
out.push(hr());
|
|
2462
|
+
continue;
|
|
2463
|
+
}
|
|
2464
|
+
const blockLines = block.split("\n");
|
|
2465
|
+
if (blockLines.every((l) => l.trim() === "" || /^\s*>\s?/.test(l))) {
|
|
2466
|
+
const noteText = blockLines.map((l) => l.replace(/^\s*>\s?/, "")).join("\n").trim();
|
|
2467
|
+
if (noteText) out.push(note(noteText));
|
|
2468
|
+
continue;
|
|
2469
|
+
}
|
|
2470
|
+
out.push(...renderRichText(block, images));
|
|
2471
|
+
}
|
|
2472
|
+
return out;
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2340
2475
|
// src/card/tool-render.ts
|
|
2341
2476
|
var HEADER_TITLE_MAX = 80;
|
|
2342
2477
|
var OUTPUT_MAX = 1200;
|
|
@@ -2415,7 +2550,7 @@ function renderTerminal(state, rc) {
|
|
|
2415
2550
|
})
|
|
2416
2551
|
);
|
|
2417
2552
|
}
|
|
2418
|
-
if (answer) elements.push(
|
|
2553
|
+
if (answer) elements.push(...renderRichText(answer, rc.images));
|
|
2419
2554
|
if (state.terminal === "interrupted") {
|
|
2420
2555
|
elements.push(noteMd("_\u23F9 \u5DF2\u88AB\u4E2D\u65AD_"));
|
|
2421
2556
|
} else if (state.terminal === "idle_timeout") {
|
|
@@ -2624,6 +2759,142 @@ var RunCardStream = class {
|
|
|
2624
2759
|
}
|
|
2625
2760
|
};
|
|
2626
2761
|
|
|
2762
|
+
// src/card/outbound-images.ts
|
|
2763
|
+
import { readFile as readFile5, stat as stat2 } from "fs/promises";
|
|
2764
|
+
import { extname, isAbsolute, resolve as resolve2, sep } from "path";
|
|
2765
|
+
var MAX_IMAGES = 9;
|
|
2766
|
+
var MAX_BYTES = 10 * 1024 * 1024;
|
|
2767
|
+
var DOWNLOAD_TIMEOUT_MS = 1e4;
|
|
2768
|
+
var ALLOWED_EXT = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "webp", "gif", "tif", "tiff", "bmp", "ico"]);
|
|
2769
|
+
var cache = /* @__PURE__ */ new Map();
|
|
2770
|
+
var IMG_RE2 = /!\[([^\]]*)\]\(\s*(<[^>]+>|[^)\s]+)(?:\s+(?:"[^"]*"|'[^']*'))?\s*\)/g;
|
|
2771
|
+
function cleanSrc2(raw) {
|
|
2772
|
+
let s = raw.trim();
|
|
2773
|
+
if (s.startsWith("<") && s.endsWith(">")) s = s.slice(1, -1).trim();
|
|
2774
|
+
return s;
|
|
2775
|
+
}
|
|
2776
|
+
function imageSources(text) {
|
|
2777
|
+
const out = [];
|
|
2778
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2779
|
+
const re = new RegExp(IMG_RE2.source, "g");
|
|
2780
|
+
let m;
|
|
2781
|
+
while ((m = re.exec(text)) !== null) {
|
|
2782
|
+
const src = cleanSrc2(m[2] ?? "");
|
|
2783
|
+
if (src && !seen.has(src)) {
|
|
2784
|
+
seen.add(src);
|
|
2785
|
+
out.push(src);
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
return out;
|
|
2789
|
+
}
|
|
2790
|
+
async function uploadOutboundImages(channel, sources, cwd) {
|
|
2791
|
+
const picked = sources.slice(0, MAX_IMAGES);
|
|
2792
|
+
if (sources.length > picked.length) {
|
|
2793
|
+
log.warn("outbound", "image-cap", { skipped: sources.length - picked.length });
|
|
2794
|
+
}
|
|
2795
|
+
const results = await Promise.all(
|
|
2796
|
+
picked.map(async (src) => {
|
|
2797
|
+
try {
|
|
2798
|
+
return [src, await resolveAndUpload(channel, src, cwd)];
|
|
2799
|
+
} catch (err) {
|
|
2800
|
+
log.warn("outbound", "image-failed", { src: src.slice(0, 80), err: String(err) });
|
|
2801
|
+
return [src, void 0];
|
|
2802
|
+
}
|
|
2803
|
+
})
|
|
2804
|
+
);
|
|
2805
|
+
const out = /* @__PURE__ */ new Map();
|
|
2806
|
+
for (const [src, key] of results) if (key) out.set(src, key);
|
|
2807
|
+
if (out.size > 0) log.info("outbound", "images", { want: sources.length, uploaded: out.size });
|
|
2808
|
+
return out;
|
|
2809
|
+
}
|
|
2810
|
+
async function resolveAndUpload(channel, src, cwd) {
|
|
2811
|
+
const { buffer, cacheKey } = await loadSource(src, cwd);
|
|
2812
|
+
if (!buffer) return void 0;
|
|
2813
|
+
const hit = cache.get(cacheKey);
|
|
2814
|
+
if (hit) return hit;
|
|
2815
|
+
const key = await uploadBuffer(channel, buffer);
|
|
2816
|
+
if (key) cache.set(cacheKey, key);
|
|
2817
|
+
return key;
|
|
2818
|
+
}
|
|
2819
|
+
async function loadSource(src, cwd) {
|
|
2820
|
+
if (/^https?:\/\//i.test(src)) return loadRemote(src);
|
|
2821
|
+
return loadLocal(src, cwd);
|
|
2822
|
+
}
|
|
2823
|
+
async function loadLocal(src, cwd) {
|
|
2824
|
+
const cwdAbs = resolve2(cwd);
|
|
2825
|
+
const abs = isAbsolute(src) ? resolve2(src) : resolve2(cwdAbs, src);
|
|
2826
|
+
if (abs !== cwdAbs && !abs.startsWith(cwdAbs + sep)) {
|
|
2827
|
+
log.warn("outbound", "image-outside-cwd", { src: src.slice(0, 80) });
|
|
2828
|
+
return { cacheKey: `local:${abs}` };
|
|
2829
|
+
}
|
|
2830
|
+
const ext = extname(abs).slice(1).toLowerCase();
|
|
2831
|
+
if (!ALLOWED_EXT.has(ext)) {
|
|
2832
|
+
log.warn("outbound", "image-ext", { ext, src: src.slice(0, 80) });
|
|
2833
|
+
return { cacheKey: `local:${abs}` };
|
|
2834
|
+
}
|
|
2835
|
+
let size;
|
|
2836
|
+
let mtimeMs;
|
|
2837
|
+
try {
|
|
2838
|
+
const st = await stat2(abs);
|
|
2839
|
+
if (!st.isFile()) throw new Error("not a file");
|
|
2840
|
+
size = st.size;
|
|
2841
|
+
mtimeMs = st.mtimeMs;
|
|
2842
|
+
} catch {
|
|
2843
|
+
log.warn("outbound", "image-missing", { src: src.slice(0, 80) });
|
|
2844
|
+
return { cacheKey: `local:${abs}` };
|
|
2845
|
+
}
|
|
2846
|
+
if (size === 0 || size > MAX_BYTES) {
|
|
2847
|
+
log.warn("outbound", "image-size", { size, src: src.slice(0, 80) });
|
|
2848
|
+
return { cacheKey: `local:${abs}:${size}` };
|
|
2849
|
+
}
|
|
2850
|
+
const buffer = await readFile5(abs);
|
|
2851
|
+
return { buffer, cacheKey: `local:${abs}:${mtimeMs}:${size}` };
|
|
2852
|
+
}
|
|
2853
|
+
async function loadRemote(url) {
|
|
2854
|
+
const cacheKey = `url:${url}`;
|
|
2855
|
+
const ctrl = new AbortController();
|
|
2856
|
+
const timer = setTimeout(() => ctrl.abort(), DOWNLOAD_TIMEOUT_MS);
|
|
2857
|
+
try {
|
|
2858
|
+
const res = await fetch(url, { signal: ctrl.signal, redirect: "follow" });
|
|
2859
|
+
if (!res.ok) {
|
|
2860
|
+
log.warn("outbound", "image-http", { url: url.slice(0, 80), status: res.status });
|
|
2861
|
+
return { cacheKey };
|
|
2862
|
+
}
|
|
2863
|
+
const ct = (res.headers.get("content-type") ?? "").split(";")[0]?.trim().toLowerCase();
|
|
2864
|
+
if (ct && !ct.startsWith("image/")) {
|
|
2865
|
+
log.warn("outbound", "image-ctype", { ct, url: url.slice(0, 80) });
|
|
2866
|
+
return { cacheKey };
|
|
2867
|
+
}
|
|
2868
|
+
const declared = Number(res.headers.get("content-length") ?? 0);
|
|
2869
|
+
if (declared > MAX_BYTES) {
|
|
2870
|
+
log.warn("outbound", "image-size", { declared, url: url.slice(0, 80) });
|
|
2871
|
+
return { cacheKey };
|
|
2872
|
+
}
|
|
2873
|
+
const ab = await res.arrayBuffer();
|
|
2874
|
+
if (ab.byteLength === 0 || ab.byteLength > MAX_BYTES) {
|
|
2875
|
+
log.warn("outbound", "image-size", { size: ab.byteLength, url: url.slice(0, 80) });
|
|
2876
|
+
return { cacheKey };
|
|
2877
|
+
}
|
|
2878
|
+
return { buffer: Buffer.from(ab), cacheKey };
|
|
2879
|
+
} catch (err) {
|
|
2880
|
+
log.warn("outbound", "image-fetch", { url: url.slice(0, 80), err: String(err) });
|
|
2881
|
+
return { cacheKey };
|
|
2882
|
+
} finally {
|
|
2883
|
+
clearTimeout(timer);
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
async function uploadBuffer(channel, buffer) {
|
|
2887
|
+
const res = await channel.rawClient.im.v1.image.create({
|
|
2888
|
+
data: { image_type: "message", image: buffer }
|
|
2889
|
+
});
|
|
2890
|
+
const key = res?.image_key ?? res?.data?.image_key;
|
|
2891
|
+
if (!key) {
|
|
2892
|
+
log.warn("outbound", "image-no-key", { res: JSON.stringify(res).slice(0, 120) });
|
|
2893
|
+
return void 0;
|
|
2894
|
+
}
|
|
2895
|
+
return key;
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2627
2898
|
// src/card/dm-cards.ts
|
|
2628
2899
|
function openChatUrl(chatId) {
|
|
2629
2900
|
return `https://applink.feishu.cn/client/chat/open?openChatId=${encodeURIComponent(chatId)}`;
|
|
@@ -2780,8 +3051,9 @@ function scopeDiagnosis(i) {
|
|
|
2780
3051
|
return [md("- \u98DE\u4E66\u6743\u9650\uFF1A\u2705 \u5FC5\u9700\u6743\u9650\u5DF2\u5168\u90E8\u5F00\u901A")];
|
|
2781
3052
|
}
|
|
2782
3053
|
return [
|
|
2783
|
-
md(`- \u98DE\u4E66\u6743\u9650\uFF1A\u274C \u7F3A ${i.missingScopes.length} \u9879 \u2014\u2014 \u5F00\u901A\u524D\u76F8\u5173\u529F\u80FD\uFF08\u6536\u53D1\u6D88\u606F / \u5361\u7247 / \u5EFA\u7FA4\u7B49\uFF09\u4E0D\u53EF\u7528`),
|
|
2784
|
-
note(`\u5F85\u5F00\u901A\uFF1A
|
|
3054
|
+
md(`- \u98DE\u4E66\u6743\u9650\uFF1A\u274C \u7F3A ${i.missingScopes.length} \u9879 \u2014\u2014 \u5F00\u901A\u524D\u76F8\u5173\u529F\u80FD\uFF08\u6536\u53D1\u6D88\u606F / \u5361\u7247 / \u56FE\u7247 / \u5EFA\u7FA4\u7B49\uFF09\u4E0D\u53EF\u7528`),
|
|
3055
|
+
note(`\u5F85\u5F00\u901A\uFF1A
|
|
3056
|
+
${i.missingScopes.map((s) => `\xB7 ${labelScope(s)}`).join("\n")}`),
|
|
2785
3057
|
actions([linkButton("\u{1F511} \u4E00\u952E\u53BB\u5F00\u901A\u8FD9\u4E9B\u6743\u9650", i.scopeGrantUrl)])
|
|
2786
3058
|
];
|
|
2787
3059
|
}
|
|
@@ -2977,7 +3249,7 @@ function buildGroupSettingsCard(project) {
|
|
|
2977
3249
|
// src/service/update.ts
|
|
2978
3250
|
import { execFile, spawn as spawn5 } from "child_process";
|
|
2979
3251
|
import { existsSync as existsSync5, readFileSync as readFileSync2 } from "fs";
|
|
2980
|
-
import { dirname as dirname6, join as join8, resolve as
|
|
3252
|
+
import { dirname as dirname6, join as join8, resolve as resolve4 } from "path";
|
|
2981
3253
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2982
3254
|
import { promisify } from "util";
|
|
2983
3255
|
|
|
@@ -2986,7 +3258,7 @@ import { spawn as spawn4, spawnSync } from "child_process";
|
|
|
2986
3258
|
import { existsSync as existsSync4 } from "fs";
|
|
2987
3259
|
import { appendFile, mkdir as mkdir4, rm as rm2, writeFile as writeFile4 } from "fs/promises";
|
|
2988
3260
|
import { homedir as homedir3, userInfo as userInfo2 } from "os";
|
|
2989
|
-
import { dirname as dirname5, join as join7, resolve as
|
|
3261
|
+
import { dirname as dirname5, join as join7, resolve as resolve3 } from "path";
|
|
2990
3262
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2991
3263
|
var LAUNCHD_LABEL = "ai.feishu-codex-bridge.bot";
|
|
2992
3264
|
function launchAgentPlistPath() {
|
|
@@ -3000,7 +3272,7 @@ function serviceStderrPath() {
|
|
|
3000
3272
|
}
|
|
3001
3273
|
function resolveCliBinPath() {
|
|
3002
3274
|
const distDir = dirname5(fileURLToPath2(import.meta.url));
|
|
3003
|
-
return
|
|
3275
|
+
return resolve3(distDir, "..", "bin", "feishu-codex-bridge.mjs");
|
|
3004
3276
|
}
|
|
3005
3277
|
function escapeXml(value) {
|
|
3006
3278
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -3166,7 +3438,7 @@ function getServiceAdapter() {
|
|
|
3166
3438
|
var execFileP = promisify(execFile);
|
|
3167
3439
|
var NPM = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
3168
3440
|
function pkgRoot() {
|
|
3169
|
-
return
|
|
3441
|
+
return resolve4(dirname6(fileURLToPath3(import.meta.url)), "..");
|
|
3170
3442
|
}
|
|
3171
3443
|
function pkgJson() {
|
|
3172
3444
|
try {
|
|
@@ -3232,12 +3504,12 @@ async function restartDaemon() {
|
|
|
3232
3504
|
}
|
|
3233
3505
|
|
|
3234
3506
|
// src/project/registry.ts
|
|
3235
|
-
import { mkdir as mkdir5, readFile as
|
|
3507
|
+
import { mkdir as mkdir5, readFile as readFile6, rename as rename4, writeFile as writeFile5 } from "fs/promises";
|
|
3236
3508
|
import { dirname as dirname7 } from "path";
|
|
3237
3509
|
var FILE_VERSION2 = 1;
|
|
3238
3510
|
async function read() {
|
|
3239
3511
|
try {
|
|
3240
|
-
const text = await
|
|
3512
|
+
const text = await readFile6(paths.projectsFile, "utf8");
|
|
3241
3513
|
const parsed = JSON.parse(text);
|
|
3242
3514
|
return Array.isArray(parsed.projects) ? parsed.projects : [];
|
|
3243
3515
|
} catch (err) {
|
|
@@ -3292,7 +3564,7 @@ async function removeProject(name) {
|
|
|
3292
3564
|
// src/project/lifecycle.ts
|
|
3293
3565
|
import { mkdir as mkdir6 } from "fs/promises";
|
|
3294
3566
|
import { existsSync as existsSync6 } from "fs";
|
|
3295
|
-
import { isAbsolute, join as join9, resolve as
|
|
3567
|
+
import { isAbsolute as isAbsolute2, join as join9, resolve as resolve5 } from "path";
|
|
3296
3568
|
|
|
3297
3569
|
// src/project/git-info.ts
|
|
3298
3570
|
import { execFile as execFile2 } from "child_process";
|
|
@@ -3430,7 +3702,7 @@ async function createProject(channel, input2) {
|
|
|
3430
3702
|
let cwd;
|
|
3431
3703
|
let blank;
|
|
3432
3704
|
if (input2.existingPath) {
|
|
3433
|
-
cwd =
|
|
3705
|
+
cwd = isAbsolute2(input2.existingPath) ? input2.existingPath : resolve5(input2.existingPath);
|
|
3434
3706
|
if (!existsSync6(cwd)) throw new Error(`\u6587\u4EF6\u5939\u4E0D\u5B58\u5728\uFF1A${cwd}`);
|
|
3435
3707
|
blank = false;
|
|
3436
3708
|
} else {
|
|
@@ -3468,12 +3740,12 @@ async function transferOwnership(channel, chatId, toOpenId) {
|
|
|
3468
3740
|
}
|
|
3469
3741
|
|
|
3470
3742
|
// src/bot/session-store.ts
|
|
3471
|
-
import { mkdir as mkdir7, readFile as
|
|
3743
|
+
import { mkdir as mkdir7, readFile as readFile7, rename as rename5, writeFile as writeFile6 } from "fs/promises";
|
|
3472
3744
|
import { dirname as dirname8 } from "path";
|
|
3473
3745
|
var FILE_VERSION3 = 1;
|
|
3474
3746
|
async function read2() {
|
|
3475
3747
|
try {
|
|
3476
|
-
const text = await
|
|
3748
|
+
const text = await readFile7(paths.sessionsFile, "utf8");
|
|
3477
3749
|
const parsed = JSON.parse(text);
|
|
3478
3750
|
return Array.isArray(parsed.sessions) ? parsed.sessions : [];
|
|
3479
3751
|
} catch (err) {
|
|
@@ -3528,6 +3800,156 @@ async function handleDmConsole(channel, cfg, msg) {
|
|
|
3528
3800
|
});
|
|
3529
3801
|
}
|
|
3530
3802
|
|
|
3803
|
+
// src/bot/media.ts
|
|
3804
|
+
import { mkdir as mkdir8, readdir as readdir2, rm as rm3, stat as stat3 } from "fs/promises";
|
|
3805
|
+
import { join as join10 } from "path";
|
|
3806
|
+
var MAX_IMAGES2 = 9;
|
|
3807
|
+
var MEDIA_TTL_MS = 60 * 6e4;
|
|
3808
|
+
var EXT_BY_CONTENT_TYPE = {
|
|
3809
|
+
"image/png": "png",
|
|
3810
|
+
"image/jpeg": "jpg",
|
|
3811
|
+
"image/jpg": "jpg",
|
|
3812
|
+
"image/gif": "gif",
|
|
3813
|
+
"image/webp": "webp",
|
|
3814
|
+
"image/bmp": "bmp",
|
|
3815
|
+
"image/heic": "heic",
|
|
3816
|
+
"image/heif": "heif",
|
|
3817
|
+
"image/tiff": "tiff"
|
|
3818
|
+
};
|
|
3819
|
+
function messageHasImages(msg) {
|
|
3820
|
+
if ((msg.resources ?? []).some((r) => r.type === "image")) return true;
|
|
3821
|
+
return msg.rawContentType === "merge_forward";
|
|
3822
|
+
}
|
|
3823
|
+
async function collectInboundImages(channel, msg) {
|
|
3824
|
+
let refs;
|
|
3825
|
+
try {
|
|
3826
|
+
refs = await gatherRefs(channel, msg);
|
|
3827
|
+
} catch (err) {
|
|
3828
|
+
log.warn("intake", "image-gather-failed", { err: String(err) });
|
|
3829
|
+
return [];
|
|
3830
|
+
}
|
|
3831
|
+
if (refs.length === 0) return [];
|
|
3832
|
+
await pruneOldMedia();
|
|
3833
|
+
try {
|
|
3834
|
+
await mkdir8(paths.mediaDir, { recursive: true });
|
|
3835
|
+
} catch {
|
|
3836
|
+
}
|
|
3837
|
+
const out = [];
|
|
3838
|
+
let index = 0;
|
|
3839
|
+
for (const ref of refs.slice(0, MAX_IMAGES2)) {
|
|
3840
|
+
const path = await downloadOne(channel, ref, index++);
|
|
3841
|
+
if (path) out.push(path);
|
|
3842
|
+
}
|
|
3843
|
+
log.info("intake", "images", { found: refs.length, downloaded: out.length });
|
|
3844
|
+
return out;
|
|
3845
|
+
}
|
|
3846
|
+
async function gatherRefs(channel, msg) {
|
|
3847
|
+
const refs = [];
|
|
3848
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3849
|
+
const add = (messageId, fileKey) => {
|
|
3850
|
+
if (!fileKey || seen.has(fileKey)) return;
|
|
3851
|
+
seen.add(fileKey);
|
|
3852
|
+
refs.push({ messageId, fileKey });
|
|
3853
|
+
};
|
|
3854
|
+
for (const r of msg.resources ?? []) {
|
|
3855
|
+
if (r.type === "image") add(msg.messageId, r.fileKey);
|
|
3856
|
+
}
|
|
3857
|
+
if (msg.rawContentType === "merge_forward") {
|
|
3858
|
+
const items = await fetchSubMessages(channel, msg.messageId);
|
|
3859
|
+
for (const sub of items) {
|
|
3860
|
+
if (!sub.message_id || sub.message_id === msg.messageId) continue;
|
|
3861
|
+
for (const key of imageKeysFromContent(sub.msg_type, sub.body?.content)) {
|
|
3862
|
+
add(sub.message_id, key);
|
|
3863
|
+
}
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3866
|
+
return refs;
|
|
3867
|
+
}
|
|
3868
|
+
async function fetchSubMessages(channel, messageId) {
|
|
3869
|
+
try {
|
|
3870
|
+
const res = await channel.rawClient.im.v1.message.get({ path: { message_id: messageId } });
|
|
3871
|
+
return res.data?.items ?? [];
|
|
3872
|
+
} catch (err) {
|
|
3873
|
+
log.warn("intake", "submessages-failed", { messageId, err: String(err) });
|
|
3874
|
+
return [];
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
function imageKeysFromContent(msgType, content) {
|
|
3878
|
+
if (!content) return [];
|
|
3879
|
+
let parsed;
|
|
3880
|
+
try {
|
|
3881
|
+
parsed = JSON.parse(content);
|
|
3882
|
+
} catch {
|
|
3883
|
+
return [];
|
|
3884
|
+
}
|
|
3885
|
+
if (msgType === "image") {
|
|
3886
|
+
const key = parsed?.image_key;
|
|
3887
|
+
return key ? [key] : [];
|
|
3888
|
+
}
|
|
3889
|
+
const keys = [];
|
|
3890
|
+
walkForImageKeys(parsed, keys);
|
|
3891
|
+
return keys;
|
|
3892
|
+
}
|
|
3893
|
+
function walkForImageKeys(node, out) {
|
|
3894
|
+
if (!node || typeof node !== "object") return;
|
|
3895
|
+
if (Array.isArray(node)) {
|
|
3896
|
+
for (const child of node) walkForImageKeys(child, out);
|
|
3897
|
+
return;
|
|
3898
|
+
}
|
|
3899
|
+
const obj = node;
|
|
3900
|
+
if (obj.tag === "img" && typeof obj.image_key === "string") out.push(obj.image_key);
|
|
3901
|
+
for (const k of Object.keys(obj)) walkForImageKeys(obj[k], out);
|
|
3902
|
+
}
|
|
3903
|
+
async function downloadOne(channel, ref, index) {
|
|
3904
|
+
try {
|
|
3905
|
+
const res = await channel.rawClient.im.v1.messageResource.get({
|
|
3906
|
+
path: { message_id: ref.messageId, file_key: ref.fileKey },
|
|
3907
|
+
params: { type: "image" }
|
|
3908
|
+
});
|
|
3909
|
+
const ext = extFromHeaders(res.headers);
|
|
3910
|
+
const file = join10(paths.mediaDir, `${safeName(ref.fileKey)}-${index}.${ext}`);
|
|
3911
|
+
await res.writeFile(file);
|
|
3912
|
+
return file;
|
|
3913
|
+
} catch (err) {
|
|
3914
|
+
log.warn("intake", "image-download-failed", { fileKey: ref.fileKey.slice(0, 24), err: String(err) });
|
|
3915
|
+
return void 0;
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
function extFromHeaders(headers) {
|
|
3919
|
+
const ct = readHeader(headers, "content-type");
|
|
3920
|
+
if (ct) {
|
|
3921
|
+
const base = ct.split(";")[0]?.trim().toLowerCase();
|
|
3922
|
+
if (base && EXT_BY_CONTENT_TYPE[base]) return EXT_BY_CONTENT_TYPE[base];
|
|
3923
|
+
}
|
|
3924
|
+
return "png";
|
|
3925
|
+
}
|
|
3926
|
+
function readHeader(headers, name) {
|
|
3927
|
+
if (!headers || typeof headers !== "object") return void 0;
|
|
3928
|
+
const h = headers;
|
|
3929
|
+
const raw = typeof h.get === "function" ? h.get(name) : h[name] ?? h[name.toLowerCase()];
|
|
3930
|
+
return typeof raw === "string" ? raw : Array.isArray(raw) ? String(raw[0]) : void 0;
|
|
3931
|
+
}
|
|
3932
|
+
function safeName(fileKey) {
|
|
3933
|
+
return fileKey.replace(/[^a-zA-Z0-9_-]/g, "").slice(-40) || "img";
|
|
3934
|
+
}
|
|
3935
|
+
async function pruneOldMedia() {
|
|
3936
|
+
let entries;
|
|
3937
|
+
try {
|
|
3938
|
+
entries = await readdir2(paths.mediaDir);
|
|
3939
|
+
} catch {
|
|
3940
|
+
return;
|
|
3941
|
+
}
|
|
3942
|
+
const cutoff = Date.now() - MEDIA_TTL_MS;
|
|
3943
|
+
for (const name of entries) {
|
|
3944
|
+
const file = join10(paths.mediaDir, name);
|
|
3945
|
+
try {
|
|
3946
|
+
const st = await stat3(file);
|
|
3947
|
+
if (st.mtimeMs < cutoff) await rm3(file, { force: true });
|
|
3948
|
+
} catch {
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
}
|
|
3952
|
+
|
|
3531
3953
|
// src/bot/comments.ts
|
|
3532
3954
|
var SUPPORTED_FILE_TYPES = /* @__PURE__ */ new Set(["doc", "docx", "sheet", "file"]);
|
|
3533
3955
|
var REPLY_MAX_CHARS = 2e3;
|
|
@@ -3922,19 +4344,34 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
3922
4344
|
async function handleTurn(msg, text, sessionKey, flat, project) {
|
|
3923
4345
|
const existing = active.get(sessionKey);
|
|
3924
4346
|
if (existing) {
|
|
3925
|
-
|
|
3926
|
-
|
|
4347
|
+
const images = messageHasImages(msg) ? await collectInboundImages(channel, msg) : void 0;
|
|
4348
|
+
const cur = active.get(sessionKey);
|
|
4349
|
+
if (!cur) {
|
|
4350
|
+
startReservedRun(msg, text, sessionKey, flat, project, images);
|
|
4351
|
+
return;
|
|
4352
|
+
}
|
|
4353
|
+
if (getPendingPolicy(cfg) === "steer" && cur.run && cur.thread) {
|
|
4354
|
+
const tid = cur.run.turnId();
|
|
3927
4355
|
if (tid) {
|
|
3928
4356
|
try {
|
|
3929
|
-
await
|
|
3930
|
-
log.info("intake", "steer", { tid });
|
|
4357
|
+
await cur.thread.steer({ text, images }, tid);
|
|
4358
|
+
log.info("intake", "steer", { tid, images: images?.length ?? 0 });
|
|
3931
4359
|
return;
|
|
3932
4360
|
} catch (err) {
|
|
3933
4361
|
log.warn("intake", "steer-failed", { err: String(err) });
|
|
3934
4362
|
}
|
|
3935
4363
|
}
|
|
3936
4364
|
}
|
|
3937
|
-
|
|
4365
|
+
cur.queue.push({ text, images });
|
|
4366
|
+
log.info("intake", "queued", { depth: cur.queue.length });
|
|
4367
|
+
return;
|
|
4368
|
+
}
|
|
4369
|
+
startReservedRun(msg, text, sessionKey, flat, project);
|
|
4370
|
+
}
|
|
4371
|
+
function startReservedRun(msg, text, sessionKey, flat, project, preloadedImages) {
|
|
4372
|
+
const existing = active.get(sessionKey);
|
|
4373
|
+
if (existing) {
|
|
4374
|
+
existing.queue.push({ text, images: preloadedImages });
|
|
3938
4375
|
log.info("intake", "queued", { depth: existing.queue.length });
|
|
3939
4376
|
return;
|
|
3940
4377
|
}
|
|
@@ -3943,6 +4380,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
3943
4380
|
void withTrace({ chatId: msg.chatId, msgId: msg.messageId }, async () => {
|
|
3944
4381
|
const reaction = runReaction(msg.messageId, !sema.hasFree());
|
|
3945
4382
|
try {
|
|
4383
|
+
const images = preloadedImages ?? (messageHasImages(msg) ? await collectInboundImages(channel, msg) : void 0);
|
|
3946
4384
|
let thread = await resolveThread(sessionKey, msg.chatId);
|
|
3947
4385
|
if (!thread) {
|
|
3948
4386
|
const cwd = project?.cwd ?? fallbackCwd;
|
|
@@ -3967,6 +4405,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
3967
4405
|
flat,
|
|
3968
4406
|
thread,
|
|
3969
4407
|
firstText: text,
|
|
4408
|
+
images,
|
|
3970
4409
|
knownThreadId: sessionKey,
|
|
3971
4410
|
requesterOpenId: msg.senderId
|
|
3972
4411
|
},
|
|
@@ -4019,7 +4458,8 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4019
4458
|
return;
|
|
4020
4459
|
}
|
|
4021
4460
|
const firstText = text || "\u4F60\u597D\uFF0C\u6211\u4EEC\u5F00\u59CB\u5427\u3002";
|
|
4022
|
-
|
|
4461
|
+
const images = messageHasImages(msg) ? await collectInboundImages(channel, msg) : void 0;
|
|
4462
|
+
log.info("card", "start", { project: project?.name ?? "(unregistered)", model, effort, images: images?.length ?? 0 });
|
|
4023
4463
|
await launchRun(
|
|
4024
4464
|
{
|
|
4025
4465
|
chatId: msg.chatId,
|
|
@@ -4027,6 +4467,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4027
4467
|
replyInThread: true,
|
|
4028
4468
|
thread,
|
|
4029
4469
|
firstText,
|
|
4470
|
+
images,
|
|
4030
4471
|
model,
|
|
4031
4472
|
effort,
|
|
4032
4473
|
cwd,
|
|
@@ -4432,14 +4873,14 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
4432
4873
|
};
|
|
4433
4874
|
let curCardKey;
|
|
4434
4875
|
try {
|
|
4435
|
-
let
|
|
4876
|
+
let turnInput = { text: opts.firstText, images: opts.images };
|
|
4436
4877
|
let replyTo = opts.replyTo;
|
|
4437
4878
|
let replyInThread = opts.flat ? false : opts.replyInThread ?? Boolean(opts.knownThreadId);
|
|
4438
4879
|
for (; ; ) {
|
|
4439
4880
|
const rec = topicThreadId ? await getSession(topicThreadId) : void 0;
|
|
4440
4881
|
const turnModel = rec?.model ?? opts.model;
|
|
4441
4882
|
const turnEffort = rec?.effort ?? opts.effort;
|
|
4442
|
-
const run = opts.thread.runStreamed(
|
|
4883
|
+
const run = opts.thread.runStreamed(turnInput, { model: turnModel, effort: turnEffort });
|
|
4443
4884
|
state.run = run;
|
|
4444
4885
|
const render = new RunRender();
|
|
4445
4886
|
render.showTools = getShowToolCalls(cfg);
|
|
@@ -4517,16 +4958,29 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
4517
4958
|
const finalMsgId = cardMsgId;
|
|
4518
4959
|
await adoptThreadId(finalMsgId);
|
|
4519
4960
|
rc.cardKey = finalMsgId;
|
|
4961
|
+
const answerText = finalMessageText(rc.rs);
|
|
4962
|
+
const { fences } = extractCardFences(answerText);
|
|
4963
|
+
const imgSources = imageSources(answerText);
|
|
4964
|
+
if (imgSources.length > 0) {
|
|
4965
|
+
rc.images = await uploadOutboundImages(channel, imgSources, opts.cwd ?? fallbackCwd);
|
|
4966
|
+
}
|
|
4520
4967
|
await stream2.updateCard(channel, buildRunCard(rc));
|
|
4521
4968
|
runsByCard.delete(cardMsgId);
|
|
4522
4969
|
promoteCard(finalMsgId, rc);
|
|
4970
|
+
for (const fence of fences) {
|
|
4971
|
+
try {
|
|
4972
|
+
await sendManagedCard(channel, opts.chatId, buildCleanCard(fence, rc.images), finalMsgId, !opts.flat);
|
|
4973
|
+
} catch (err) {
|
|
4974
|
+
log.fail("card", err, { phase: "clean-card" });
|
|
4975
|
+
}
|
|
4976
|
+
}
|
|
4523
4977
|
if (topicThreadId) await patchSession(topicThreadId, { updatedAt: Date.now() });
|
|
4524
4978
|
replyTo = finalMsgId;
|
|
4525
4979
|
replyInThread = !opts.flat;
|
|
4526
4980
|
log.info("card", "final", { terminal: render.terminal() });
|
|
4527
4981
|
if (killed) break;
|
|
4528
4982
|
if (state.queue.length === 0) break;
|
|
4529
|
-
|
|
4983
|
+
turnInput = state.queue.shift();
|
|
4530
4984
|
}
|
|
4531
4985
|
} catch (err) {
|
|
4532
4986
|
log.fail("intake", err);
|
|
@@ -4890,7 +5344,7 @@ async function runUpdate(opts = {}) {
|
|
|
4890
5344
|
}
|
|
4891
5345
|
|
|
4892
5346
|
// src/cli/commands/bot.ts
|
|
4893
|
-
import { rm as
|
|
5347
|
+
import { rm as rm4 } from "fs/promises";
|
|
4894
5348
|
async function runBotInit(name) {
|
|
4895
5349
|
if (!ensureCodex()) {
|
|
4896
5350
|
process.exitCode = 1;
|
|
@@ -4944,7 +5398,7 @@ async function runBotRm(name) {
|
|
|
4944
5398
|
}
|
|
4945
5399
|
const after = await removeBot(bot2.appId);
|
|
4946
5400
|
await removeSecret(secretKeyForApp(bot2.appId));
|
|
4947
|
-
await
|
|
5401
|
+
await rm4(botDir(bot2.appId), { recursive: true, force: true });
|
|
4948
5402
|
console.log(`\u2713 \u5DF2\u79FB\u9664\u673A\u5668\u4EBA\u300C${bot2.name}\u300D(${bot2.appId})\uFF1A\u6CE8\u518C\u8868 + \u5BC6\u94A5 + \u72B6\u6001\u76EE\u5F55(projects/sessions)\u3002`);
|
|
4949
5403
|
if (after.bots.length === 0) {
|
|
4950
5404
|
console.log(" \u5DF2\u65E0\u4EFB\u4F55\u673A\u5668\u4EBA\uFF0C`bot init` \u91CD\u65B0\u521B\u5EFA\u3002");
|
|
@@ -5002,15 +5456,15 @@ async function secretsRemove(id) {
|
|
|
5002
5456
|
console.log(ok ? `\u2713 \u5DF2\u5220\u9664: ${id}` : `\u672A\u627E\u5230: ${id}`);
|
|
5003
5457
|
}
|
|
5004
5458
|
function readStdin() {
|
|
5005
|
-
return new Promise((
|
|
5459
|
+
return new Promise((resolve6) => {
|
|
5006
5460
|
let data = "";
|
|
5007
5461
|
if (process.stdin.isTTY) {
|
|
5008
|
-
|
|
5462
|
+
resolve6("");
|
|
5009
5463
|
return;
|
|
5010
5464
|
}
|
|
5011
5465
|
process.stdin.setEncoding("utf8");
|
|
5012
5466
|
process.stdin.on("data", (c) => data += c);
|
|
5013
|
-
process.stdin.on("end", () =>
|
|
5467
|
+
process.stdin.on("end", () => resolve6(data));
|
|
5014
5468
|
});
|
|
5015
5469
|
}
|
|
5016
5470
|
|