@modelzen/feishu-codex-bridge 0.1.0 → 0.1.1

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 (3) hide show
  1. package/README.md +20 -21
  2. package/dist/cli.js +69 -7
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -32,33 +32,17 @@
32
32
 
33
33
  ---
34
34
 
35
- ## ⚠️ 安全须知(务必先读)
36
-
37
- 本机器人调用 Codex 时固定使用 **`approvalPolicy: "never"` + `sandbox: "danger-full-access"`** —— 即 **无任何人工审批、对磁盘完全访问**。这意味着:
38
-
39
- > **任何能在项目群里给机器人发消息的人,都能在你这台机器上、以你的身份、在该项目目录里执行任意命令(读写文件、联网、运行脚本)。**
40
-
41
- 因此:
42
-
43
- - 只把**你信任的人**拉进项目群;
44
- - 在**你自己掌控的机器/账号**上运行,最好是隔离的开发机或容器;
45
- - 绑定的项目目录里不要放你不愿被读写的敏感数据;
46
- - 它不是多租户托管服务,是给你(和你信任的小团队)自用的桥。
47
-
48
- ---
49
-
50
35
  ## 📦 前置条件
51
36
 
52
37
  | 依赖 | 说明 | 获取方式 |
53
38
  |------|------|----------|
54
39
  | **Node.js ≥ 20** | 运行时 | <https://nodejs.org> 或 `nvm install 20` |
55
- | **Codex CLI** | 后端,bridge 会 spawn `codex app-server` | `npm i -g @openai/codex`,或安装 Codex.app,或用环境变量 `CODEX_BIN` 指向已有二进制 |
56
- | **Codex 已登录** | app-server 需要 `~/.codex/auth.json` | 运行 `codex login` |
57
- | **飞书 / Lark 账号** | 且该租户允许「扫码创建应用」(个人/开发者租户一般可以;部分企业租户由管理员限制) | 首次 `run` 时扫码即可创建 |
58
- | **lark-cli**(可选,但用「文档评论回复」**强烈建议装**) | 仅「文档评论回复」用到:要回答「总结本文 / 这段写得对吗」这类**需要读文档正文**的问题时,Codex 靠 `lark-cli` 去读文档(回复本身由桥用 SDK 以机器人身份发,不依赖它)。不装也能跑,但机器人只能凭评论里给到的上下文作答,读不到正文。 | 安装并 `lark-cli auth login` 登录(与本机 lark-* 技能同款的那个 lark-cli),确保 Codex 能在 PATH 上直接调用 |
40
+ | **Codex CLI** | 后端,bridge 会 spawn `codex app-server` | `npm i -g @openai/codex`,或装 Codex.app,或用 `CODEX_BIN` 指向已有二进制 |
41
+ | **Codex 已登录** | app-server 需要 `~/.codex/auth.json` | `codex login` |
42
+ | **飞书 / Lark 账号** | 租户需允许「扫码创建应用」(个人/开发者租户一般可以) | 首次 `run` 时扫码创建 |
43
+ | **lark-cli**(可选) | 仅「文档评论回复」需读文档正文时用到;不装也能跑,只是读不到正文 | `lark-cli auth login`,确保在 PATH |
59
44
 
60
- > 机器人**收发消息、回卡片、发评论回复**全部走 `@larksuiteoapi/node-sdk` 长连接,核心功能**不依赖** `lark-cli`。`lark-cli` 只是「文档评论回复」里让 Codex **读文档正文**的途径——见上表。
61
- > ⚠️ `lark-cli` 以**你的用户身份**登录,所以 Codex 只应用它来**读**;prompt 已明确禁止 Codex 用它发评论(否则评论会署成你本人,而不是机器人)。
45
+ > 收发消息、回卡片、发评论回复均走 `@larksuiteoapi/node-sdk` 长连接,**不依赖** `lark-cli`。⚠️ `lark-cli` 以**你的身份**登录,仅供 Codex **读**文档;prompt 已禁止用它发评论(否则评论会署你本人)。
62
46
 
63
47
  ---
64
48
 
@@ -239,6 +223,21 @@ src/
239
223
 
240
224
  ---
241
225
 
226
+ ## ⚠️ 安全须知
227
+
228
+ 本机器人调用 Codex 时固定使用 **`approvalPolicy: "never"` + `sandbox: "danger-full-access"`** —— 即 **无任何人工审批、对磁盘完全访问**。这意味着:
229
+
230
+ > **任何能在项目群里给机器人发消息的人,都能在你这台机器上、以你的身份、在该项目目录里执行任意命令(读写文件、联网、运行脚本)。**
231
+
232
+ 因此:
233
+
234
+ - 只把**你信任的人**拉进项目群;
235
+ - 在**你自己掌控的机器/账号**上运行,最好是隔离的开发机或容器;
236
+ - 绑定的项目目录里不要放你不愿被读写的敏感数据;
237
+ - 它不是多租户托管服务,是给你(和你信任的小团队)自用的桥。
238
+
239
+ ---
240
+
242
241
  ## ❓ 故障排查
243
242
 
244
243
  | 现象 | 排查 |
package/dist/cli.js CHANGED
@@ -2358,9 +2358,14 @@ var RC = {
2358
2358
  };
2359
2359
  var REASONING_MAX = 1500;
2360
2360
  var COLLAPSE_TOOL_THRESHOLD = 3;
2361
+ var PROCESS_BODY_BUDGET = 22e3;
2361
2362
  function buildRunCard(rc) {
2362
2363
  const state = rc.rs;
2363
2364
  const running = state.terminal === "running";
2365
+ const elements = running ? renderRunning(state, rc) : renderTerminal(state, rc);
2366
+ return card(elements, { streaming: running, summary: summaryText(state) });
2367
+ }
2368
+ function renderRunning(state, rc) {
2364
2369
  const elements = [];
2365
2370
  const reasoning = reasoningContent(state);
2366
2371
  if (reasoning) elements.push(reasoningPanel(reasoning, state.reasoningActive));
@@ -2369,23 +2374,79 @@ function buildRunCard(rc) {
2369
2374
  if (group.kind === "text") {
2370
2375
  if (group.content.trim()) elements.push(md(group.content));
2371
2376
  } else {
2372
- elements.push(...renderToolGroup(group.tools, !running));
2377
+ elements.push(...renderToolGroup(group.tools, false));
2373
2378
  }
2374
2379
  }
2380
+ if (state.footer) elements.push(footerStatus(state.footer));
2381
+ if (rc.cardKey) elements.push(actions([button("\u23F9 \u7EC8\u6B62", { a: RC.stop, m: rc.cardKey }, "danger")]));
2382
+ return elements;
2383
+ }
2384
+ function renderTerminal(state, rc) {
2385
+ const elements = [];
2386
+ const answerIdx = lastTextIndex(state.blocks);
2387
+ const answer = answerIdx >= 0 ? state.blocks[answerIdx].content.trim() : "";
2388
+ const processBlocks = state.blocks.filter((_, i) => i !== answerIdx);
2389
+ const blocks = rc.showTools === false ? processBlocks.filter((b) => b.kind !== "tool") : processBlocks;
2390
+ const reasoning = reasoningContent(state);
2391
+ const processEls = buildProcessBody(reasoning, blocks);
2392
+ if (processEls.length > 0) {
2393
+ const toolCount = blocks.reduce((n, b) => b.kind === "tool" ? n + 1 : n, 0);
2394
+ elements.push(
2395
+ collapsiblePanelEl({
2396
+ title: processTitle(Boolean(reasoning), toolCount),
2397
+ expanded: false,
2398
+ border: "grey",
2399
+ elements: processEls
2400
+ })
2401
+ );
2402
+ }
2403
+ if (answer) elements.push(md(answer));
2375
2404
  if (state.terminal === "interrupted") {
2376
2405
  elements.push(noteMd("_\u23F9 \u5DF2\u88AB\u4E2D\u65AD_"));
2377
2406
  } else if (state.terminal === "idle_timeout") {
2378
2407
  elements.push(noteMd(`_\u23F1 ${state.idleTimeoutMinutes ?? 0} \u5206\u949F\u65E0\u54CD\u5E94\uFF0C\u5DF2\u81EA\u52A8\u7EC8\u6B62_`));
2379
2408
  } else if (state.terminal === "error" && state.errorMsg) {
2380
2409
  elements.push(noteMd(`\u26A0\uFE0F agent \u5931\u8D25\uFF1A${state.errorMsg}`));
2381
- } else if (state.terminal === "done" && elements.length === 0) {
2410
+ } else if (state.terminal === "done" && !answer) {
2382
2411
  elements.push(noteMd("_\uFF08\u672A\u8FD4\u56DE\u5185\u5BB9\uFF09_"));
2383
2412
  }
2384
- if (running) {
2385
- if (state.footer) elements.push(footerStatus(state.footer));
2386
- if (rc.cardKey) elements.push(actions([button("\u23F9 \u7EC8\u6B62", { a: RC.stop, m: rc.cardKey }, "danger")]));
2413
+ return elements;
2414
+ }
2415
+ function lastTextIndex(blocks) {
2416
+ for (let i = blocks.length - 1; i >= 0; i--) {
2417
+ const b = blocks[i];
2418
+ if (b && b.kind === "text" && b.content.trim()) return i;
2387
2419
  }
2388
- return card(elements, { streaming: running, summary: summaryText(state) });
2420
+ return -1;
2421
+ }
2422
+ function buildProcessBody(reasoning, blocks) {
2423
+ const rich = processElements(reasoning, blocks, false);
2424
+ if (estimateSize2(rich) <= PROCESS_BODY_BUDGET) return rich;
2425
+ return processElements(reasoning, blocks, true);
2426
+ }
2427
+ function processElements(reasoning, blocks, compactTools) {
2428
+ const out = [];
2429
+ if (reasoning) out.push(reasoningPanel(reasoning, false));
2430
+ for (const group of groupBlocks(blocks)) {
2431
+ if (group.kind === "text") {
2432
+ if (group.content.trim()) out.push(md(group.content));
2433
+ } else {
2434
+ out.push(...renderToolGroup(group.tools, true, compactTools));
2435
+ }
2436
+ }
2437
+ return out;
2438
+ }
2439
+ function processTitle(hasReasoning, toolCount) {
2440
+ const parts = [];
2441
+ if (hasReasoning) parts.push("\u{1F9E0} \u601D\u8003");
2442
+ if (toolCount > 0) parts.push(`\u{1F9F0} ${toolCount} \u4E2A\u5DE5\u5177\u8C03\u7528`);
2443
+ const detail = parts.length > 0 ? `\uFF1A${parts.join(" \xB7 ")}` : "";
2444
+ return `\u{1F5C2} **\u8FC7\u7A0B${detail}**\uFF08\u70B9\u51FB\u5C55\u5F00\uFF09`;
2445
+ }
2446
+ function estimateSize2(els) {
2447
+ let n = 0;
2448
+ for (const el of els) n += JSON.stringify(el).length;
2449
+ return n;
2389
2450
  }
2390
2451
  function buildRunCardPlain(rc) {
2391
2452
  return buildRunCard({ ...rc, cardKey: void 0 });
@@ -2405,8 +2466,9 @@ function* groupBlocks(blocks) {
2405
2466
  }
2406
2467
  if (toolBuf.length > 0) yield { kind: "tools", tools: toolBuf };
2407
2468
  }
2408
- function renderToolGroup(tools, finalized) {
2469
+ function renderToolGroup(tools, finalized, compact = false) {
2409
2470
  if (tools.length === 0) return [];
2471
+ if (compact) return [collapsedToolSummary(tools, true)];
2410
2472
  if (tools.length < COLLAPSE_TOOL_THRESHOLD) {
2411
2473
  return tools.map((t) => toolPanel(t, false));
2412
2474
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelzen/feishu-codex-bridge",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Bridge Feishu/Lark messenger with local Codex via app-server (project=group, thread=session)",
5
5
  "type": "module",
6
6
  "bin": {