@modelzen/feishu-codex-bridge 0.1.7 → 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 +317 -32
- 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) {
|
|
@@ -3529,9 +3801,9 @@ async function handleDmConsole(channel, cfg, msg) {
|
|
|
3529
3801
|
}
|
|
3530
3802
|
|
|
3531
3803
|
// src/bot/media.ts
|
|
3532
|
-
import { mkdir as mkdir8, readdir as readdir2, rm as rm3, stat as
|
|
3804
|
+
import { mkdir as mkdir8, readdir as readdir2, rm as rm3, stat as stat3 } from "fs/promises";
|
|
3533
3805
|
import { join as join10 } from "path";
|
|
3534
|
-
var
|
|
3806
|
+
var MAX_IMAGES2 = 9;
|
|
3535
3807
|
var MEDIA_TTL_MS = 60 * 6e4;
|
|
3536
3808
|
var EXT_BY_CONTENT_TYPE = {
|
|
3537
3809
|
"image/png": "png",
|
|
@@ -3564,7 +3836,7 @@ async function collectInboundImages(channel, msg) {
|
|
|
3564
3836
|
}
|
|
3565
3837
|
const out = [];
|
|
3566
3838
|
let index = 0;
|
|
3567
|
-
for (const ref of refs.slice(0,
|
|
3839
|
+
for (const ref of refs.slice(0, MAX_IMAGES2)) {
|
|
3568
3840
|
const path = await downloadOne(channel, ref, index++);
|
|
3569
3841
|
if (path) out.push(path);
|
|
3570
3842
|
}
|
|
@@ -3671,7 +3943,7 @@ async function pruneOldMedia() {
|
|
|
3671
3943
|
for (const name of entries) {
|
|
3672
3944
|
const file = join10(paths.mediaDir, name);
|
|
3673
3945
|
try {
|
|
3674
|
-
const st = await
|
|
3946
|
+
const st = await stat3(file);
|
|
3675
3947
|
if (st.mtimeMs < cutoff) await rm3(file, { force: true });
|
|
3676
3948
|
} catch {
|
|
3677
3949
|
}
|
|
@@ -4686,9 +4958,22 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
4686
4958
|
const finalMsgId = cardMsgId;
|
|
4687
4959
|
await adoptThreadId(finalMsgId);
|
|
4688
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
|
+
}
|
|
4689
4967
|
await stream2.updateCard(channel, buildRunCard(rc));
|
|
4690
4968
|
runsByCard.delete(cardMsgId);
|
|
4691
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
|
+
}
|
|
4692
4977
|
if (topicThreadId) await patchSession(topicThreadId, { updatedAt: Date.now() });
|
|
4693
4978
|
replyTo = finalMsgId;
|
|
4694
4979
|
replyInThread = !opts.flat;
|
|
@@ -5171,15 +5456,15 @@ async function secretsRemove(id) {
|
|
|
5171
5456
|
console.log(ok ? `\u2713 \u5DF2\u5220\u9664: ${id}` : `\u672A\u627E\u5230: ${id}`);
|
|
5172
5457
|
}
|
|
5173
5458
|
function readStdin() {
|
|
5174
|
-
return new Promise((
|
|
5459
|
+
return new Promise((resolve6) => {
|
|
5175
5460
|
let data = "";
|
|
5176
5461
|
if (process.stdin.isTTY) {
|
|
5177
|
-
|
|
5462
|
+
resolve6("");
|
|
5178
5463
|
return;
|
|
5179
5464
|
}
|
|
5180
5465
|
process.stdin.setEncoding("utf8");
|
|
5181
5466
|
process.stdin.on("data", (c) => data += c);
|
|
5182
|
-
process.stdin.on("end", () =>
|
|
5467
|
+
process.stdin.on("end", () => resolve6(data));
|
|
5183
5468
|
});
|
|
5184
5469
|
}
|
|
5185
5470
|
|