@modelzen/feishu-codex-bridge 0.2.1-win → 0.2.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 (2) hide show
  1. package/dist/cli.js +261 -352
  2. package/package.json +1 -3
package/dist/cli.js CHANGED
@@ -15,6 +15,7 @@ function bridgeVersion() {
15
15
  }
16
16
 
17
17
  // src/cli/commands/doctor.ts
18
+ import { execFileSync as execFileSync2 } from "child_process";
18
19
  import { existsSync as existsSync3 } from "fs";
19
20
  import { homedir as homedir2 } from "os";
20
21
  import { join as join4 } from "path";
@@ -268,56 +269,27 @@ async function moveIfExists(src, dest) {
268
269
  }
269
270
 
270
271
  // src/agent/codex-appserver/locate.ts
272
+ import { execFileSync } from "child_process";
271
273
  import { existsSync as existsSync2 } from "fs";
272
- import { extname, join as join3 } from "path";
273
-
274
- // src/platform/spawn.ts
275
- import crossSpawn from "cross-spawn";
276
- function spawnProcess(command, args = [], options = {}) {
277
- return crossSpawn(command, [...args], options);
278
- }
279
- function spawnProcessSync(command, args = [], options = {}) {
280
- return crossSpawn.sync(command, [...args], options);
281
- }
282
- function mergeProcessEnv(base = process.env, overrides = {}) {
283
- const out = { ...base };
284
- for (const [key, value] of Object.entries(overrides)) {
285
- for (const existing of Object.keys(out)) {
286
- if (existing.toLowerCase() === key.toLowerCase()) delete out[existing];
287
- }
288
- if (value !== void 0) out[key] = value;
289
- }
290
- return out;
291
- }
292
-
293
- // src/agent/codex-appserver/locate.ts
294
- var IS_WIN = process.platform === "win32";
274
+ import { join as join3 } from "path";
295
275
  function resolveCodexBin() {
296
276
  const env = process.env.CODEX_BIN;
297
277
  if (env && existsSync2(env)) return env;
298
278
  const onPath = which("codex");
299
279
  if (onPath) return onPath;
300
- for (const cand of execCandidates(paths.codexCliBinDir, "codex")) {
301
- if (existsSync2(cand)) return cand;
302
- }
280
+ const priv = join3(paths.codexCliBinDir, "codex");
281
+ if (existsSync2(priv)) return priv;
303
282
  const appBundle = "/Applications/Codex.app/Contents/Resources/codex";
304
283
  if (process.platform === "darwin" && existsSync2(appBundle)) return appBundle;
305
284
  return null;
306
285
  }
307
- function execCandidates(dir, base) {
308
- const exact = join3(dir, base);
309
- if (!IS_WIN || extname(base)) return [exact];
310
- const exts = (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").map((e) => e.trim()).filter(Boolean);
311
- return [exact, ...exts.map((e) => join3(dir, base + e.toLowerCase()))];
312
- }
313
286
  function which(cmd) {
314
287
  try {
315
- const res = spawnProcessSync(IS_WIN ? "where" : "which", [cmd], {
288
+ const out = execFileSync(process.platform === "win32" ? "where" : "which", [cmd], {
316
289
  encoding: "utf8",
317
290
  stdio: ["ignore", "pipe", "ignore"]
318
291
  });
319
- if (res.status !== 0 || typeof res.stdout !== "string") return null;
320
- const first = res.stdout.split("\n").map((l) => l.trim()).find(Boolean);
292
+ const first = out.split("\n").map((l) => l.trim()).find(Boolean);
321
293
  return first && existsSync2(first) ? first : null;
322
294
  } catch {
323
295
  return null;
@@ -325,9 +297,7 @@ function which(cmd) {
325
297
  }
326
298
  function codexVersion(bin) {
327
299
  try {
328
- const res = spawnProcessSync(bin, ["--version"], { encoding: "utf8" });
329
- if (res.status !== 0 || typeof res.stdout !== "string") return null;
330
- return res.stdout.trim();
300
+ return execFileSync(bin, ["--version"], { encoding: "utf8" }).trim();
331
301
  } catch {
332
302
  return null;
333
303
  }
@@ -385,9 +355,7 @@ ${failed === 0 ? "\u5168\u90E8\u901A\u8FC7 \u2713" : `${failed} \u9879\u9700\u59
385
355
  }
386
356
  function tryExec(cmd, args) {
387
357
  try {
388
- const res = spawnProcessSync(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
389
- if (res.status !== 0 || typeof res.stdout !== "string") return null;
390
- return res.stdout.trim();
358
+ return execFileSync2(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
391
359
  } catch {
392
360
  return null;
393
361
  }
@@ -570,7 +538,7 @@ async function spawnExecProvider(pc, ref) {
570
538
  const timeoutMs = pc.noOutputTimeoutMs ?? DEFAULT_EXEC_TIMEOUT_MS;
571
539
  const maxOutput = pc.maxOutputBytes ?? DEFAULT_EXEC_MAX_OUTPUT;
572
540
  const providerName = ref.provider ?? DEFAULT_PROVIDER;
573
- return new Promise((resolve7, reject) => {
541
+ return new Promise((resolve6, reject) => {
574
542
  const env = {};
575
543
  if (pc.passEnv) for (const k of pc.passEnv) {
576
544
  const v = process.env[k];
@@ -615,7 +583,7 @@ async function spawnExecProvider(pc, ref) {
615
583
  try {
616
584
  const parsed = JSON.parse(stdout);
617
585
  const value = parsed.values?.[ref.id];
618
- if (typeof value === "string") return resolve7(value);
586
+ if (typeof value === "string") return resolve6(value);
619
587
  const err = parsed.errors?.[ref.id]?.message;
620
588
  reject(new Error(`exec provider did not return secret for ${ref.id}${err ? `: ${err}` : ""}`));
621
589
  } catch (err) {
@@ -1163,6 +1131,7 @@ ${rule}`);
1163
1131
  import { createLarkChannel, Domain } from "@larksuiteoapi/node-sdk";
1164
1132
 
1165
1133
  // src/agent/codex-appserver/app-server-client.ts
1134
+ import { spawn as spawn3 } from "child_process";
1166
1135
  var AsyncQueue = class {
1167
1136
  items = [];
1168
1137
  waiters = [];
@@ -1183,7 +1152,7 @@ var AsyncQueue = class {
1183
1152
  continue;
1184
1153
  }
1185
1154
  if (this.closed) return;
1186
- const next = await new Promise((resolve7) => this.waiters.push(resolve7));
1155
+ const next = await new Promise((resolve6) => this.waiters.push(resolve6));
1187
1156
  if (next.done) return;
1188
1157
  yield next.value;
1189
1158
  }
@@ -1205,9 +1174,9 @@ var AppServerClient = class {
1205
1174
  }
1206
1175
  /** spawn + initialize handshake. Throws if spawn/handshake fails. */
1207
1176
  async connect() {
1208
- const child = spawnProcess(this.opts.bin, ["app-server", "--listen", "stdio://"], {
1177
+ const child = spawn3(this.opts.bin, ["app-server", "--listen", "stdio://"], {
1209
1178
  cwd: this.opts.cwd,
1210
- env: mergeProcessEnv(process.env, { ...this.opts.env, FEISHU_CODEX_BRIDGE: "1" }),
1179
+ env: { ...process.env, ...this.opts.env, FEISHU_CODEX_BRIDGE: "1" },
1211
1180
  stdio: ["pipe", "pipe", "pipe"]
1212
1181
  });
1213
1182
  this.child = child;
@@ -1234,8 +1203,8 @@ var AppServerClient = class {
1234
1203
  const id = ++this.nextId;
1235
1204
  const payload = `${JSON.stringify({ jsonrpc: "2.0", id, method, params: params ?? {} })}
1236
1205
  `;
1237
- return new Promise((resolve7, reject) => {
1238
- this.pending.set(id, { resolve: resolve7, reject });
1206
+ return new Promise((resolve6, reject) => {
1207
+ this.pending.set(id, { resolve: resolve6, reject });
1239
1208
  this.child.stdin.write(payload, (err) => {
1240
1209
  if (err) {
1241
1210
  this.pending.delete(id);
@@ -1258,36 +1227,15 @@ var AppServerClient = class {
1258
1227
  this.closed = true;
1259
1228
  const child = this.child;
1260
1229
  if (!child || child.exitCode !== null) return;
1261
- if (process.platform === "win32" && child.pid) {
1262
- await new Promise((resolve7) => {
1263
- let settled = false;
1264
- const done = () => {
1265
- if (settled) return;
1266
- settled = true;
1267
- clearTimeout(t);
1268
- resolve7();
1269
- };
1270
- const t = setTimeout(done, graceMs);
1271
- child.once("exit", done);
1272
- spawnProcess("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore" }).on(
1273
- "error",
1274
- () => {
1275
- child.kill();
1276
- done();
1277
- }
1278
- );
1279
- });
1280
- return;
1281
- }
1282
1230
  child.kill("SIGTERM");
1283
- await new Promise((resolve7) => {
1231
+ await new Promise((resolve6) => {
1284
1232
  const t = setTimeout(() => {
1285
1233
  if (child.exitCode === null) child.kill("SIGKILL");
1286
- resolve7();
1234
+ resolve6();
1287
1235
  }, graceMs);
1288
1236
  child.once("exit", () => {
1289
1237
  clearTimeout(t);
1290
- resolve7();
1238
+ resolve6();
1291
1239
  });
1292
1240
  });
1293
1241
  }
@@ -1420,12 +1368,12 @@ var BRIDGE_DEVELOPER_INSTRUCTIONS = [
1420
1368
  ].join("\n");
1421
1369
  var READ_HISTORY_TIMEOUT_MS = 2e4;
1422
1370
  function withDeadline(p, ms, label) {
1423
- return new Promise((resolve7, reject) => {
1371
+ return new Promise((resolve6, reject) => {
1424
1372
  const t = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
1425
1373
  p.then(
1426
1374
  (v) => {
1427
1375
  clearTimeout(t);
1428
- resolve7(v);
1376
+ resolve6(v);
1429
1377
  },
1430
1378
  (e) => {
1431
1379
  clearTimeout(t);
@@ -1465,11 +1413,11 @@ var CodexThread = class {
1465
1413
  if (self.model) params.model = self.model;
1466
1414
  if (self.effort) params.effort = self.effort;
1467
1415
  let startError;
1468
- const startFailed = new Promise((resolve7) => {
1416
+ const startFailed = new Promise((resolve6) => {
1469
1417
  self.client.request("turn/start", params).then(void 0, (err) => {
1470
1418
  startError = err instanceof Error ? err : new Error(String(err));
1471
1419
  log.fail("agent", startError, { phase: "turn/start" });
1472
- resolve7("start-failed");
1420
+ resolve6("start-failed");
1473
1421
  });
1474
1422
  });
1475
1423
  const stream2 = self.client.stream()[Symbol.asyncIterator]();
@@ -2013,8 +1961,8 @@ function card(elements, opts = {}) {
2013
1961
  if (opts.streaming) {
2014
1962
  config.streaming_mode = true;
2015
1963
  config.streaming_config = {
2016
- print_frequency_ms: { default: 70 },
2017
- print_step: { default: 1 },
1964
+ print_frequency_ms: { default: 25 },
1965
+ print_step: { default: 6 },
2018
1966
  print_strategy: "fast"
2019
1967
  };
2020
1968
  }
@@ -2036,6 +1984,9 @@ function card(elements, opts = {}) {
2036
1984
  function md(content) {
2037
1985
  return { tag: "markdown", content };
2038
1986
  }
1987
+ function mdStream(content, elementId) {
1988
+ return { tag: "markdown", element_id: elementId, content };
1989
+ }
2039
1990
  function image(imgKey, alt = "") {
2040
1991
  return {
2041
1992
  tag: "img",
@@ -2585,6 +2536,7 @@ function escapeInline2(s) {
2585
2536
  var RC = {
2586
2537
  stop: "run.stop"
2587
2538
  };
2539
+ var ANSWER_EID = "answer";
2588
2540
  var REASONING_MAX = 1500;
2589
2541
  var COLLAPSE_TOOL_THRESHOLD = 3;
2590
2542
  var PROCESS_BODY_BUDGET = 22e3;
@@ -2598,14 +2550,19 @@ function renderRunning(state, rc) {
2598
2550
  const elements = [];
2599
2551
  const reasoning = reasoningContent(state);
2600
2552
  if (reasoning) elements.push(reasoningPanel(reasoning, state.reasoningActive));
2601
- const blocks = rc.showTools === false ? state.blocks.filter((b) => b.kind !== "tool") : state.blocks;
2602
- for (const group of groupBlocks(blocks)) {
2603
- if (group.kind === "text") {
2604
- if (group.content.trim()) elements.push(md(group.content));
2605
- } else {
2606
- elements.push(...renderToolGroup(group.tools, false));
2553
+ const showTools = rc.showTools !== false;
2554
+ const tools = [];
2555
+ const textParts = [];
2556
+ for (const b of state.blocks) {
2557
+ if (b.kind === "tool") {
2558
+ if (showTools) tools.push(b.tool);
2559
+ } else if (b.content.trim()) {
2560
+ textParts.push(b.content);
2607
2561
  }
2608
2562
  }
2563
+ if (tools.length > 0) elements.push(...renderToolGroup(tools, false));
2564
+ const answer = textParts.join("\n\n");
2565
+ if (answer) elements.push(mdStream(answer, ANSWER_EID));
2609
2566
  if (state.footer) elements.push(footerStatus(state.footer));
2610
2567
  if (rc.cardKey) elements.push(actions([button("\u23F9 \u7EC8\u6B62", { a: RC.stop, m: rc.cardKey }, "danger")]));
2611
2568
  return elements;
@@ -2752,16 +2709,105 @@ function truncate4(s, n) {
2752
2709
  }
2753
2710
 
2754
2711
  // src/card/run-card-stream.ts
2755
- var STREAM_THROTTLE_MS = 250;
2712
+ var STREAM_THROTTLE_MS = 150;
2756
2713
  var RunCardStream = class {
2757
2714
  cardId = "";
2758
2715
  _messageId = "";
2759
2716
  seq = 0;
2760
2717
  lastPush = 0;
2761
2718
  lastContent = "";
2719
+ // Per-turn push counters — surfaced via stats() for the stream.timing log.
2720
+ pushCount = 0;
2721
+ cardPushes = 0;
2722
+ // whole-card card.update (structure)
2723
+ elPushes = 0;
2724
+ // element cardElement.content (answer typewriter)
2725
+ totalRttMs = 0;
2726
+ maxRttMs = 0;
2727
+ // Coalesced streaming. The consume loop records the latest {card, answerEid} in
2728
+ // `pending`; a single pump() drains it — NON-BLOCKING, so consuming codex events
2729
+ // never stalls on a round-trip (awaiting each push serially drained the backlog
2730
+ // at one event per ~RTT → "已回完、飞书还在慢慢打字"). The pump pushes only the most
2731
+ // recent snapshot per round-trip: when only the answer text grew it streams that
2732
+ // via cardElement.content (typewriter), otherwise it whole-card updates.
2733
+ pending = null;
2734
+ pumpChannel = null;
2735
+ pumpPromise = null;
2736
+ // Baselines for the pump's route decision (structure unchanged + answer grew?).
2737
+ lastStructureSig = "";
2738
+ lastAnswerText = "";
2762
2739
  get messageId() {
2763
2740
  return this._messageId;
2764
2741
  }
2742
+ /** Push counts (whole-card vs element) + round-trip stats for this card. */
2743
+ stats() {
2744
+ return {
2745
+ pushCount: this.pushCount,
2746
+ cardPushes: this.cardPushes,
2747
+ elPushes: this.elPushes,
2748
+ totalRttMs: this.totalRttMs,
2749
+ maxRttMs: this.maxRttMs
2750
+ };
2751
+ }
2752
+ /**
2753
+ * Record the latest card and ensure the pump is running. Returns immediately;
2754
+ * calls that arrive while a push is in flight collapse into a single push of
2755
+ * the most recent card once that round-trip completes. Use this from the
2756
+ * event-consume loop instead of awaiting {@link streamCard} per event.
2757
+ */
2758
+ streamCoalesced(channel, fullCard, answerEid) {
2759
+ this.pending = { card: fullCard, answerEid };
2760
+ this.pumpChannel = channel;
2761
+ if (!this.pumpPromise) this.pumpPromise = this.pump();
2762
+ }
2763
+ /** Await any in-flight coalesced push so the final streaming frame lands (and
2764
+ * `seq` stays ordered) before the terminal update. Call after the loop ends. */
2765
+ async drain() {
2766
+ if (this.pumpPromise) await this.pumpPromise;
2767
+ }
2768
+ async pump() {
2769
+ try {
2770
+ while (this.pending && this.pumpChannel) {
2771
+ const { card: card2, answerEid } = this.pending;
2772
+ this.pending = null;
2773
+ const t0 = Date.now();
2774
+ const answer = answerEid ? answerContent(card2, answerEid) : null;
2775
+ const sig = structureSig(card2, answerEid);
2776
+ if (answerEid && answer !== null && sig === this.lastStructureSig && answer !== this.lastAnswerText && answer.startsWith(this.lastAnswerText)) {
2777
+ await this.streamElement(this.pumpChannel, answerEid, answer);
2778
+ this.lastAnswerText = answer;
2779
+ } else {
2780
+ await this.streamCard(this.pumpChannel, card2, true);
2781
+ this.lastStructureSig = sig;
2782
+ this.lastAnswerText = answer ?? "";
2783
+ }
2784
+ const gap = STREAM_THROTTLE_MS - (Date.now() - t0);
2785
+ if (this.pending && gap > 0) await new Promise((r) => setTimeout(r, gap));
2786
+ }
2787
+ } finally {
2788
+ this.pumpPromise = null;
2789
+ }
2790
+ }
2791
+ /** Element-level streaming push (cardkit.v1.cardElement.content): the answer
2792
+ * element's accumulated full text. Feishu diffs the prefix and types the delta
2793
+ * per the card's streaming_config. Needs streaming_mode on the card. */
2794
+ async streamElement(channel, elementId, content) {
2795
+ if (!this.cardId) return;
2796
+ const t0 = Date.now();
2797
+ try {
2798
+ await channel.rawClient.cardkit.v1.cardElement.content({
2799
+ path: { card_id: this.cardId, element_id: elementId },
2800
+ data: { content, sequence: ++this.seq, uuid: `e_${this.cardId}_${this.seq}` }
2801
+ });
2802
+ const rtt = Date.now() - t0;
2803
+ this.pushCount++;
2804
+ this.elPushes++;
2805
+ this.totalRttMs += rtt;
2806
+ if (rtt > this.maxRttMs) this.maxRttMs = rtt;
2807
+ } catch (err) {
2808
+ log.fail("card", err, { phase: "run-stream-el", cardId: this.cardId, seq: this.seq });
2809
+ }
2810
+ }
2765
2811
  /** Create the entity from the initial (running) card and send a message
2766
2812
  * referencing it by card_id. Returns the carrier message id. */
2767
2813
  async create(channel, chatId, initialCard, opts) {
@@ -2803,11 +2849,17 @@ var RunCardStream = class {
2803
2849
  if (!force && now - this.lastPush < STREAM_THROTTLE_MS) return;
2804
2850
  this.lastPush = now;
2805
2851
  this.lastContent = data;
2852
+ const t0 = Date.now();
2806
2853
  try {
2807
2854
  await channel.rawClient.cardkit.v1.card.update({
2808
2855
  path: { card_id: this.cardId },
2809
2856
  data: { card: { type: "card_json", data }, sequence: ++this.seq, uuid: `s_${this.cardId}_${this.seq}` }
2810
2857
  });
2858
+ const rtt = Date.now() - t0;
2859
+ this.pushCount++;
2860
+ this.cardPushes++;
2861
+ this.totalRttMs += rtt;
2862
+ if (rtt > this.maxRttMs) this.maxRttMs = rtt;
2811
2863
  } catch (err) {
2812
2864
  log.fail("card", err, { phase: "run-stream", cardId: this.cardId, seq: this.seq });
2813
2865
  }
@@ -2837,10 +2889,25 @@ var RunCardStream = class {
2837
2889
  }
2838
2890
  }
2839
2891
  };
2892
+ function answerContent(card2, eid) {
2893
+ const els = card2.body?.elements;
2894
+ if (!Array.isArray(els)) return null;
2895
+ for (const el of els) {
2896
+ if (el && el.element_id === eid) return typeof el.content === "string" ? el.content : "";
2897
+ }
2898
+ return null;
2899
+ }
2900
+ function structureSig(card2, eid) {
2901
+ const body = card2.body;
2902
+ const els = body?.elements;
2903
+ if (!eid || !Array.isArray(els)) return JSON.stringify(card2);
2904
+ const blanked = els.map((el) => el && el.element_id === eid ? { ...el, content: "" } : el);
2905
+ return JSON.stringify({ ...card2, body: { ...body, elements: blanked } });
2906
+ }
2840
2907
 
2841
2908
  // src/card/outbound-images.ts
2842
2909
  import { readFile as readFile5, stat as stat2 } from "fs/promises";
2843
- import { extname as extname2, isAbsolute, resolve as resolve2, sep } from "path";
2910
+ import { extname, isAbsolute, resolve as resolve2, sep } from "path";
2844
2911
  var MAX_IMAGES = 9;
2845
2912
  var MAX_BYTES = 10 * 1024 * 1024;
2846
2913
  var DOWNLOAD_TIMEOUT_MS = 1e4;
@@ -2906,7 +2973,7 @@ async function loadLocal(src, cwd) {
2906
2973
  log.warn("outbound", "image-outside-cwd", { src: src.slice(0, 80) });
2907
2974
  return { cacheKey: `local:${abs}` };
2908
2975
  }
2909
- const ext = extname2(abs).slice(1).toLowerCase();
2976
+ const ext = extname(abs).slice(1).toLowerCase();
2910
2977
  if (!ALLOWED_EXT.has(ext)) {
2911
2978
  log.warn("outbound", "image-ext", { ext, src: src.slice(0, 80) });
2912
2979
  return { cacheKey: `local:${abs}` };
@@ -3437,40 +3504,29 @@ function buildGroupSettingsCard(project) {
3437
3504
  }
3438
3505
 
3439
3506
  // src/service/update.ts
3440
- import { execFile, spawn as spawn4 } from "child_process";
3441
- import { existsSync as existsSync6, readFileSync as readFileSync2 } from "fs";
3442
- import { dirname as dirname8, join as join10, resolve as resolve5 } from "path";
3443
- import { fileURLToPath as fileURLToPath4 } from "url";
3507
+ import { execFile, spawn as spawn5 } from "child_process";
3508
+ import { existsSync as existsSync5, readFileSync as readFileSync2 } from "fs";
3509
+ import { dirname as dirname7, join as join8, resolve as resolve4 } from "path";
3510
+ import { fileURLToPath as fileURLToPath3 } from "url";
3444
3511
  import { promisify } from "util";
3445
3512
 
3446
3513
  // src/service/launchd.ts
3447
- import { spawn as spawn3, spawnSync } from "child_process";
3514
+ import { spawn as spawn4, spawnSync } from "child_process";
3448
3515
  import { existsSync as existsSync4 } from "fs";
3449
- import { mkdir as mkdir6, rm as rm2, writeFile as writeFile5 } from "fs/promises";
3516
+ import { appendFile, mkdir as mkdir5, rm as rm2, writeFile as writeFile5 } from "fs/promises";
3450
3517
  import { homedir as homedir3, userInfo as userInfo2 } from "os";
3451
- import { dirname as dirname6, join as join8, resolve as resolve3 } from "path";
3518
+ import { dirname as dirname6, join as join7, resolve as resolve3 } from "path";
3452
3519
  import { fileURLToPath as fileURLToPath2 } from "url";
3453
-
3454
- // src/service/common.ts
3455
- import { appendFile, mkdir as mkdir5 } from "fs/promises";
3456
- import { join as join7 } from "path";
3520
+ var LAUNCHD_LABEL = "ai.feishu-codex-bridge.bot";
3521
+ function launchAgentPlistPath() {
3522
+ return join7(homedir3(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
3523
+ }
3457
3524
  function serviceStdoutPath() {
3458
3525
  return join7(paths.appDir, "service.log");
3459
3526
  }
3460
3527
  function serviceStderrPath() {
3461
3528
  return join7(paths.appDir, "service.err.log");
3462
3529
  }
3463
- async function ensureLogFiles() {
3464
- await mkdir5(paths.appDir, { recursive: true });
3465
- await appendFile(serviceStdoutPath(), "");
3466
- await appendFile(serviceStderrPath(), "");
3467
- }
3468
-
3469
- // src/service/launchd.ts
3470
- var LAUNCHD_LABEL = "ai.feishu-codex-bridge.bot";
3471
- function launchAgentPlistPath() {
3472
- return join8(homedir3(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
3473
- }
3474
3530
  function resolveCliBinPath() {
3475
3531
  const distDir = dirname6(fileURLToPath2(import.meta.url));
3476
3532
  return resolve3(distDir, "..", "bin", "feishu-codex-bridge.mjs");
@@ -3513,7 +3569,7 @@ function buildPlist() {
3513
3569
  }
3514
3570
  async function installLaunchd() {
3515
3571
  const plistPath = launchAgentPlistPath();
3516
- await mkdir6(dirname6(plistPath), { recursive: true });
3572
+ await mkdir5(dirname6(plistPath), { recursive: true });
3517
3573
  await ensureLogFiles();
3518
3574
  await writeFile5(plistPath, buildPlist(), "utf8");
3519
3575
  if (isLoaded()) {
@@ -3551,10 +3607,9 @@ function statusLaunchd() {
3551
3607
  const raw = result.stdout || result.stderr;
3552
3608
  const parsed = parseLaunchdStatus(raw);
3553
3609
  return {
3554
- platformName: "launchd (macOS)",
3555
3610
  installed: existsSync4(launchAgentPlistPath()),
3556
- running: result.ok,
3557
- servicePath: launchAgentPlistPath(),
3611
+ loaded: result.ok,
3612
+ plistPath: launchAgentPlistPath(),
3558
3613
  stdoutPath: serviceStdoutPath(),
3559
3614
  stderrPath: serviceStderrPath(),
3560
3615
  pid: parsed.pid,
@@ -3566,7 +3621,7 @@ async function tailLaunchdLogs(follow) {
3566
3621
  await ensureLogFiles();
3567
3622
  const args = follow ? ["-f", serviceStdoutPath(), serviceStderrPath()] : ["-n", "100", serviceStdoutPath(), serviceStderrPath()];
3568
3623
  await new Promise((resolvePromise, reject) => {
3569
- const child = spawn3("tail", args, { stdio: "inherit" });
3624
+ const child = spawn4("tail", args, { stdio: "inherit" });
3570
3625
  child.on("error", reject);
3571
3626
  child.on("close", (code) => {
3572
3627
  if (code === 0 || follow && code === null) {
@@ -3597,6 +3652,11 @@ async function waitUntilUnloaded(timeoutMs = 5e3) {
3597
3652
  }
3598
3653
  throw new Error(`launchd service \u672A\u5728 ${timeoutMs}ms \u5185\u5378\u8F7D\u5B8C\u6210`);
3599
3654
  }
3655
+ async function ensureLogFiles() {
3656
+ await mkdir5(paths.appDir, { recursive: true });
3657
+ await appendFile(serviceStdoutPath(), "");
3658
+ await appendFile(serviceStderrPath(), "");
3659
+ }
3600
3660
  function userTarget() {
3601
3661
  return `gui/${userInfo2().uid}`;
3602
3662
  }
@@ -3617,218 +3677,29 @@ function launchctlError(command, result) {
3617
3677
  return new Error(`${command} \u5931\u8D25\uFF08exit ${result.status ?? "unknown"}\uFF09${output ? `\uFF1A${output}` : ""}`);
3618
3678
  }
3619
3679
 
3620
- // src/service/schtasks.ts
3621
- import { spawnSync as spawnSync2 } from "child_process";
3622
- import { createReadStream, existsSync as existsSync5, statSync } from "fs";
3623
- import { mkdir as mkdir7, readFile as readFile7, rm as rm3, writeFile as writeFile6 } from "fs/promises";
3624
- import { dirname as dirname7, join as join9, resolve as resolve4 } from "path";
3625
- import { fileURLToPath as fileURLToPath3 } from "url";
3626
- var WINDOWS_TASK_NAME = "feishu-codex-bridge";
3627
- function launcherCmdPath() {
3628
- return join9(paths.appDir, "service-launcher.cmd");
3629
- }
3630
- function resolveCliBinPath2() {
3631
- const distDir = dirname7(fileURLToPath3(import.meta.url));
3632
- return resolve4(distDir, "..", "bin", "feishu-codex-bridge.mjs");
3633
- }
3634
- function buildLauncherCmd() {
3635
- const nodePath = process.execPath;
3636
- const cliBinPath = resolveCliBinPath2();
3637
- const pathEnv = process.env.PATH ?? "";
3638
- return [
3639
- "@echo off",
3640
- `set "PATH=${pathEnv}"`,
3641
- `"${nodePath}" "${cliBinPath}" run >> "${serviceStdoutPath()}" 2>> "${serviceStderrPath()}"`,
3642
- ""
3643
- ].join("\r\n");
3644
- }
3645
- function runSchtasks(args) {
3646
- const r = spawnSync2("schtasks", args, { encoding: "utf8" });
3647
- return {
3648
- ok: r.status === 0,
3649
- status: r.status,
3650
- stdout: r.stdout ?? "",
3651
- stderr: r.stderr ?? ""
3652
- };
3653
- }
3654
- function schtasksError(command, r) {
3655
- const out = [r.stderr.trim(), r.stdout.trim()].filter(Boolean).join("\n");
3656
- return new Error(`${command} \u5931\u8D25\uFF08exit ${r.status ?? "unknown"}\uFF09${out ? `\uFF1A${out}` : ""}`);
3657
- }
3658
- async function writeLauncherCmd() {
3659
- const cmdPath = launcherCmdPath();
3660
- await mkdir7(dirname7(cmdPath), { recursive: true });
3661
- await ensureLogFiles();
3662
- await writeFile6(cmdPath, buildLauncherCmd(), "utf8");
3663
- }
3664
- async function installSchtask() {
3665
- await writeLauncherCmd();
3666
- const create = runSchtasks([
3667
- "/Create",
3668
- "/F",
3669
- "/SC",
3670
- "ONLOGON",
3671
- "/RL",
3672
- "LIMITED",
3673
- "/TN",
3674
- WINDOWS_TASK_NAME,
3675
- "/TR",
3676
- `"${launcherCmdPath()}"`
3677
- ]);
3678
- if (!create.ok) throw schtasksError("schtasks /Create", create);
3679
- const run = runSchtasks(["/Run", "/TN", WINDOWS_TASK_NAME]);
3680
- if (!run.ok) throw schtasksError("schtasks /Run", run);
3681
- return statusSchtask();
3682
- }
3683
- async function uninstallSchtask() {
3684
- runSchtasks(["/End", "/TN", WINDOWS_TASK_NAME]);
3685
- const del = runSchtasks(["/Delete", "/F", "/TN", WINDOWS_TASK_NAME]);
3686
- if (!del.ok && isTaskRegistered()) throw schtasksError("schtasks /Delete", del);
3687
- if (existsSync5(launcherCmdPath())) await rm3(launcherCmdPath(), { force: true });
3688
- }
3689
- async function restartSchtask() {
3690
- if (!isTaskRegistered()) {
3691
- throw new Error(`\u8BA1\u5212\u4EFB\u52A1\u672A\u5B89\u88C5\uFF1A${WINDOWS_TASK_NAME}\uFF08\u5148\u8FD0\u884C \`feishu-codex-bridge start\`\uFF09`);
3692
- }
3693
- runSchtasks(["/End", "/TN", WINDOWS_TASK_NAME]);
3694
- await waitUntilStopped();
3695
- const run = runSchtasks(["/Run", "/TN", WINDOWS_TASK_NAME]);
3696
- if (!run.ok) throw schtasksError("schtasks /Run", run);
3697
- return statusSchtask();
3698
- }
3699
- function statusSchtask() {
3700
- const installed = isTaskRegistered();
3701
- const raw = installed ? describeTask() : "";
3702
- return {
3703
- platformName: "Task Scheduler (Windows)",
3704
- installed,
3705
- running: installed && /Status:\s+Running/i.test(raw),
3706
- servicePath: WINDOWS_TASK_NAME,
3707
- stdoutPath: serviceStdoutPath(),
3708
- stderrPath: serviceStderrPath(),
3709
- // `Process ID:` only appears in verbose output while the task is running.
3710
- pid: raw.match(/Process ID:\s*(\d+)/i)?.[1],
3711
- // `Last Result: 0` ⇒ last run succeeded. Surface it as the exit code.
3712
- lastExit: raw.match(/Last Result:\s*(-?\d+)/i)?.[1],
3713
- raw
3714
- };
3715
- }
3716
- function isTaskRegistered() {
3717
- const r = spawnSync2("schtasks", ["/Query", "/TN", WINDOWS_TASK_NAME], {
3718
- stdio: ["ignore", "ignore", "ignore"]
3719
- });
3720
- return r.status === 0;
3721
- }
3722
- function schtaskRunning() {
3723
- if (!isTaskRegistered()) return false;
3724
- return /Status:\s+Running/i.test(describeTask());
3725
- }
3726
- function describeTask() {
3727
- const r = runSchtasks(["/Query", "/V", "/FO", "LIST", "/TN", WINDOWS_TASK_NAME]);
3728
- return r.stdout || r.stderr || "";
3729
- }
3730
- async function waitUntilStopped(timeoutMs = 5e3) {
3731
- const deadline = Date.now() + timeoutMs;
3732
- while (Date.now() < deadline) {
3733
- if (!schtaskRunning()) return true;
3734
- await new Promise((r) => setTimeout(r, 200));
3735
- }
3736
- return false;
3737
- }
3738
- async function tailSchtaskLogs(follow) {
3739
- await ensureLogFiles();
3740
- const files = [serviceStdoutPath(), serviceStderrPath()];
3741
- for (const f of files) {
3742
- const tail = await lastLines(f, 100);
3743
- if (tail) process.stdout.write(`
3744
- ===== ${f} =====
3745
- ${tail}
3746
- `);
3747
- }
3748
- if (!follow) return;
3749
- const offsets = new Map(files.map((f) => [f, fileSize(f)]));
3750
- await new Promise((resolvePromise) => {
3751
- const onSigint = () => {
3752
- clearInterval(timer);
3753
- process.off("SIGINT", onSigint);
3754
- resolvePromise();
3755
- };
3756
- process.on("SIGINT", onSigint);
3757
- const timer = setInterval(() => {
3758
- for (const f of files) {
3759
- const size = fileSize(f);
3760
- const from = offsets.get(f) ?? 0;
3761
- if (size > from) {
3762
- offsets.set(f, size);
3763
- createReadStream(f, { start: from, end: size - 1, encoding: "utf8" }).pipe(process.stdout, {
3764
- end: false
3765
- });
3766
- } else if (size < from) {
3767
- offsets.set(f, size);
3768
- }
3769
- }
3770
- }, 700);
3771
- });
3772
- }
3773
- function fileSize(file) {
3774
- try {
3775
- return statSync(file).size;
3776
- } catch {
3777
- return 0;
3778
- }
3779
- }
3780
- async function lastLines(file, n) {
3781
- try {
3782
- const text = await readFile7(file, "utf8");
3783
- const lines = text.split("\n");
3784
- return lines.slice(-n - 1).join("\n").trimEnd();
3785
- } catch {
3786
- return "";
3787
- }
3788
- }
3789
-
3790
3680
  // src/service/adapter.ts
3791
3681
  function getServiceAdapter() {
3792
- if (process.platform === "darwin") {
3793
- return {
3794
- install: installLaunchd,
3795
- uninstall: uninstallLaunchd,
3796
- status: async () => statusLaunchd(),
3797
- restart: restartLaunchd,
3798
- logs: tailLaunchdLogs
3799
- };
3800
- }
3801
- if (process.platform === "win32") {
3802
- return {
3803
- install: installSchtask,
3804
- uninstall: uninstallSchtask,
3805
- status: async () => statusSchtask(),
3806
- restart: restartSchtask,
3807
- logs: tailSchtaskLogs
3808
- };
3682
+ if (process.platform !== "darwin") {
3683
+ throw new Error("service\uFF1A\u5F53\u524D\u5E73\u53F0\u6682\u4E0D\u652F\u6301\uFF0C\u540E\u7EED\u4F1A\u652F\u6301 Windows/systemd\u3002");
3809
3684
  }
3810
- throw new Error(
3811
- "service\uFF1A\u5F53\u524D\u5E73\u53F0\u6682\u4E0D\u652F\u6301\u540E\u53F0\u670D\u52A1\uFF08\u4EC5 macOS launchd / Windows \u8BA1\u5212\u4EFB\u52A1\uFF09\u3002\u8BF7\u7528 `feishu-codex-bridge run` \u524D\u53F0\u8FD0\u884C" + (process.platform === "linux" ? "\uFF1BLinux systemd \u652F\u6301\u540E\u7EED\u63D0\u4F9B\u3002" : "\u3002")
3812
- );
3813
- }
3814
- function isServiceRunning() {
3815
- try {
3816
- if (process.platform === "darwin") return isLoaded();
3817
- if (process.platform === "win32") return schtaskRunning();
3818
- } catch {
3819
- }
3820
- return false;
3685
+ return {
3686
+ install: installLaunchd,
3687
+ uninstall: uninstallLaunchd,
3688
+ status: async () => statusLaunchd(),
3689
+ restart: restartLaunchd,
3690
+ logs: tailLaunchdLogs
3691
+ };
3821
3692
  }
3822
3693
 
3823
3694
  // src/service/update.ts
3824
3695
  var execFileP = promisify(execFile);
3825
3696
  var NPM = process.platform === "win32" ? "npm.cmd" : "npm";
3826
3697
  function pkgRoot() {
3827
- return resolve5(dirname8(fileURLToPath4(import.meta.url)), "..");
3698
+ return resolve4(dirname7(fileURLToPath3(import.meta.url)), "..");
3828
3699
  }
3829
3700
  function pkgJson() {
3830
3701
  try {
3831
- return JSON.parse(readFileSync2(join10(pkgRoot(), "package.json"), "utf8"));
3702
+ return JSON.parse(readFileSync2(join8(pkgRoot(), "package.json"), "utf8"));
3832
3703
  } catch {
3833
3704
  return {};
3834
3705
  }
@@ -3840,7 +3711,7 @@ function packageName() {
3840
3711
  return pkgJson().name ?? "@modelzen/feishu-codex-bridge";
3841
3712
  }
3842
3713
  function isDevSource() {
3843
- return existsSync6(join10(pkgRoot(), ".git"));
3714
+ return existsSync5(join8(pkgRoot(), ".git"));
3844
3715
  }
3845
3716
  function isNewer(a, b) {
3846
3717
  const pa = a.split(".").map((n) => Number.parseInt(n, 10) || 0);
@@ -3863,7 +3734,7 @@ async function latestVersion() {
3863
3734
  async function installLatest(opts = {}) {
3864
3735
  const target = `${packageName()}@latest`;
3865
3736
  return await new Promise((resolveP) => {
3866
- const child = spawn4(NPM, ["install", "-g", target], {
3737
+ const child = spawn5(NPM, ["install", "-g", target], {
3867
3738
  stdio: opts.inherit ? ["ignore", "inherit", "inherit"] : ["ignore", "pipe", "pipe"]
3868
3739
  });
3869
3740
  let out = "";
@@ -3879,16 +3750,20 @@ async function installLatest(opts = {}) {
3879
3750
  });
3880
3751
  }
3881
3752
  function daemonRunning() {
3882
- return isServiceRunning();
3753
+ try {
3754
+ return statusLaunchd().loaded;
3755
+ } catch {
3756
+ return false;
3757
+ }
3883
3758
  }
3884
3759
  async function restartDaemon() {
3885
3760
  await getServiceAdapter().restart();
3886
3761
  }
3887
3762
 
3888
3763
  // src/project/lifecycle.ts
3889
- import { mkdir as mkdir8 } from "fs/promises";
3890
- import { existsSync as existsSync7 } from "fs";
3891
- import { isAbsolute as isAbsolute2, join as join11, resolve as resolve6 } from "path";
3764
+ import { mkdir as mkdir6 } from "fs/promises";
3765
+ import { existsSync as existsSync6 } from "fs";
3766
+ import { isAbsolute as isAbsolute2, join as join9, resolve as resolve5 } from "path";
3892
3767
 
3893
3768
  // src/project/git-info.ts
3894
3769
  import { execFile as execFile2 } from "child_process";
@@ -4023,12 +3898,12 @@ async function onboardGroup(channel, project) {
4023
3898
  // src/project/lifecycle.ts
4024
3899
  async function resolveCwd(name, existingPath) {
4025
3900
  if (existingPath) {
4026
- const cwd2 = isAbsolute2(existingPath) ? existingPath : resolve6(existingPath);
4027
- if (!existsSync7(cwd2)) throw new Error(`\u6587\u4EF6\u5939\u4E0D\u5B58\u5728\uFF1A${cwd2}`);
3901
+ const cwd2 = isAbsolute2(existingPath) ? existingPath : resolve5(existingPath);
3902
+ if (!existsSync6(cwd2)) throw new Error(`\u6587\u4EF6\u5939\u4E0D\u5B58\u5728\uFF1A${cwd2}`);
4028
3903
  return { cwd: cwd2, blank: false };
4029
3904
  }
4030
- const cwd = join11(paths.projectsRootDir, name);
4031
- await mkdir8(cwd, { recursive: true });
3905
+ const cwd = join9(paths.projectsRootDir, name);
3906
+ await mkdir6(cwd, { recursive: true });
4032
3907
  return { cwd, blank: true };
4033
3908
  }
4034
3909
  async function createProject(channel, input2) {
@@ -4095,12 +3970,12 @@ async function leaveChat(channel, chatId) {
4095
3970
  }
4096
3971
 
4097
3972
  // src/bot/session-store.ts
4098
- import { mkdir as mkdir9, readFile as readFile8, rename as rename5, writeFile as writeFile7 } from "fs/promises";
4099
- import { dirname as dirname9 } from "path";
3973
+ import { mkdir as mkdir7, readFile as readFile7, rename as rename5, writeFile as writeFile6 } from "fs/promises";
3974
+ import { dirname as dirname8 } from "path";
4100
3975
  var FILE_VERSION3 = 1;
4101
3976
  async function read2() {
4102
3977
  try {
4103
- const text = await readFile8(paths.sessionsFile, "utf8");
3978
+ const text = await readFile7(paths.sessionsFile, "utf8");
4104
3979
  const parsed = JSON.parse(text);
4105
3980
  return Array.isArray(parsed.sessions) ? parsed.sessions : [];
4106
3981
  } catch (err) {
@@ -4109,10 +3984,10 @@ async function read2() {
4109
3984
  }
4110
3985
  }
4111
3986
  async function write2(sessions) {
4112
- await mkdir9(dirname9(paths.sessionsFile), { recursive: true });
3987
+ await mkdir7(dirname8(paths.sessionsFile), { recursive: true });
4113
3988
  const tmp = `${paths.sessionsFile}.tmp-${process.pid}`;
4114
3989
  const body = { version: FILE_VERSION3, sessions };
4115
- await writeFile7(tmp, `${JSON.stringify(body, null, 2)}
3990
+ await writeFile6(tmp, `${JSON.stringify(body, null, 2)}
4116
3991
  `, "utf8");
4117
3992
  await rename5(tmp, paths.sessionsFile);
4118
3993
  }
@@ -4156,8 +4031,8 @@ async function handleDmConsole(channel, cfg, msg) {
4156
4031
  }
4157
4032
 
4158
4033
  // src/bot/media.ts
4159
- import { mkdir as mkdir10, readdir as readdir2, rm as rm4, stat as stat3 } from "fs/promises";
4160
- import { join as join12 } from "path";
4034
+ import { mkdir as mkdir8, readdir as readdir2, rm as rm3, stat as stat3 } from "fs/promises";
4035
+ import { join as join10 } from "path";
4161
4036
  var MAX_IMAGES2 = 9;
4162
4037
  var MEDIA_TTL_MS = 60 * 6e4;
4163
4038
  var EXT_BY_CONTENT_TYPE = {
@@ -4186,7 +4061,7 @@ async function collectInboundImages(channel, msg) {
4186
4061
  if (refs.length === 0) return [];
4187
4062
  await pruneOldMedia();
4188
4063
  try {
4189
- await mkdir10(paths.mediaDir, { recursive: true });
4064
+ await mkdir8(paths.mediaDir, { recursive: true });
4190
4065
  } catch {
4191
4066
  }
4192
4067
  const out = [];
@@ -4262,7 +4137,7 @@ async function downloadOne(channel, ref, index) {
4262
4137
  params: { type: "image" }
4263
4138
  });
4264
4139
  const ext = extFromHeaders(res.headers);
4265
- const file = join12(paths.mediaDir, `${safeName(ref.fileKey)}-${index}.${ext}`);
4140
+ const file = join10(paths.mediaDir, `${safeName(ref.fileKey)}-${index}.${ext}`);
4266
4141
  await res.writeFile(file);
4267
4142
  return file;
4268
4143
  } catch (err) {
@@ -4296,10 +4171,10 @@ async function pruneOldMedia() {
4296
4171
  }
4297
4172
  const cutoff = Date.now() - MEDIA_TTL_MS;
4298
4173
  for (const name of entries) {
4299
- const file = join12(paths.mediaDir, name);
4174
+ const file = join10(paths.mediaDir, name);
4300
4175
  try {
4301
4176
  const st = await stat3(file);
4302
- if (st.mtimeMs < cutoff) await rm4(file, { force: true });
4177
+ if (st.mtimeMs < cutoff) await rm3(file, { force: true });
4303
4178
  } catch {
4304
4179
  }
4305
4180
  }
@@ -5348,11 +5223,29 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
5348
5223
  },
5349
5224
  stopSignal
5350
5225
  );
5226
+ const tStart = Date.now();
5227
+ let firstEvAt = 0;
5228
+ let firstTextAt = 0;
5229
+ let lastEvAt = tStart;
5230
+ let evCount = 0;
5231
+ let textChars = 0;
5351
5232
  for await (const ev of guarded) {
5233
+ const tEv = Date.now();
5234
+ if (!firstEvAt) firstEvAt = tEv;
5235
+ const et = ev.type;
5236
+ if (et === "text_delta") {
5237
+ if (!firstTextAt) firstTextAt = tEv;
5238
+ const d = ev.delta;
5239
+ if (typeof d === "string") textChars += d.length;
5240
+ }
5241
+ lastEvAt = tEv;
5242
+ evCount++;
5352
5243
  render.apply(ev);
5353
5244
  rc.rs = render.snapshot();
5354
- await stream2.streamCard(channel, buildRunCard(rc));
5245
+ stream2.streamCoalesced(channel, buildRunCard(rc), ANSWER_EID);
5355
5246
  }
5247
+ const doneAt = Date.now();
5248
+ await stream2.drain();
5356
5249
  state.interrupt = void 0;
5357
5250
  const killed = interrupted || timedOut;
5358
5251
  if (timedOut) render.timeout(Math.max(1, Math.round(idleMs / 6e4)));
@@ -5373,6 +5266,25 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
5373
5266
  rc.images = await uploadOutboundImages(channel, imgSources, opts.cwd ?? fallbackCwd);
5374
5267
  }
5375
5268
  await stream2.updateCard(channel, buildRunCard(rc));
5269
+ {
5270
+ const terminalAt = Date.now();
5271
+ const st = stream2.stats();
5272
+ log.info("stream", "timing", {
5273
+ firstEv: firstEvAt ? firstEvAt - tStart : -1,
5274
+ firstText: firstTextAt ? firstTextAt - tStart : -1,
5275
+ lastEv: lastEvAt - tStart,
5276
+ done: doneAt - tStart,
5277
+ terminal: terminalAt - tStart,
5278
+ doneToTerminal: terminalAt - doneAt,
5279
+ events: evCount,
5280
+ textChars,
5281
+ pushes: st.pushCount,
5282
+ cardPushes: st.cardPushes,
5283
+ elPushes: st.elPushes,
5284
+ rttAvg: st.pushCount ? Math.round(st.totalRttMs / st.pushCount) : 0,
5285
+ rttMax: st.maxRttMs
5286
+ });
5287
+ }
5376
5288
  runsByCard.delete(cardMsgId);
5377
5289
  promoteCard(finalMsgId, rc);
5378
5290
  for (const fence of fences) {
@@ -5634,7 +5546,7 @@ async function startBridge(opts) {
5634
5546
 
5635
5547
  // src/core/single-instance.ts
5636
5548
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, unlinkSync, writeFileSync } from "fs";
5637
- import { dirname as dirname10 } from "path";
5549
+ import { dirname as dirname9 } from "path";
5638
5550
  var BridgeAlreadyRunningError = class extends Error {
5639
5551
  constructor(pid) {
5640
5552
  super(
@@ -5663,7 +5575,7 @@ function acquireSingleInstanceLock(appId) {
5663
5575
  } catch (err) {
5664
5576
  if (err instanceof BridgeAlreadyRunningError) throw err;
5665
5577
  }
5666
- mkdirSync2(dirname10(file), { recursive: true });
5578
+ mkdirSync2(dirname9(file), { recursive: true });
5667
5579
  const record = { pid: process.pid, appId, startedAt: Date.now() };
5668
5580
  writeFileSync(file, `${JSON.stringify(record)}
5669
5581
  `, "utf8");
@@ -5732,14 +5644,12 @@ async function runStart() {
5732
5644
  return;
5733
5645
  }
5734
5646
  const status = await getServiceAdapter().install();
5735
- console.log(
5736
- process.platform === "win32" ? "\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u5B89\u88C5\u5E76\u542F\u52A8\uFF08\u767B\u5F55\u81EA\u542F\uFF1B\u6CE8\u610F\uFF1AWindows \u8BA1\u5212\u4EFB\u52A1\u65E0\u5D29\u6E83\u81EA\u52A8\u62C9\u8D77\uFF09\u3002" : "\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u5B89\u88C5\u5E76\u542F\u52A8\uFF08\u5F00\u673A\u81EA\u542F\u3001\u5D29\u6E83\u81EA\u52A8\u62C9\u8D77\uFF09\u3002"
5737
- );
5647
+ console.log("\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u5B89\u88C5\u5E76\u542F\u52A8\uFF08\u5F00\u673A\u81EA\u542F\u3001\u5D29\u6E83\u81EA\u52A8\u62C9\u8D77\uFF09\u3002");
5738
5648
  printStatus(status);
5739
5649
  }
5740
5650
  async function runStop() {
5741
5651
  await getServiceAdapter().uninstall();
5742
- console.log("\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u505C\u6B62\uFF0C\u5E76\u5DF2\u5173\u95ED\u81EA\u542F\uFF08\u5DF2\u79FB\u9664\u670D\u52A1\u5B9A\u4E49\uFF09\u3002");
5652
+ console.log("\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u505C\u6B62\uFF0C\u5E76\u5DF2\u5173\u95ED\u5F00\u673A\u81EA\u542F\uFF08\u5DF2\u79FB\u9664 launchd plist\uFF09\u3002");
5743
5653
  }
5744
5654
  async function runRestart() {
5745
5655
  const status = await getServiceAdapter().restart();
@@ -5753,18 +5663,17 @@ async function runLogs(follow) {
5753
5663
  await getServiceAdapter().logs(follow);
5754
5664
  }
5755
5665
  function printStatus(status) {
5756
- console.log(`service: ${status.platformName}`);
5757
- console.log(`path: ${status.servicePath}`);
5666
+ console.log(`plist: ${status.plistPath}`);
5758
5667
  console.log(`installed: ${status.installed ? "yes" : "no"}`);
5759
- console.log(`running: ${status.running ? "yes" : "no"}`);
5668
+ console.log(`loaded: ${status.loaded ? "yes" : "no"}`);
5760
5669
  console.log(`pid: ${status.pid ?? "-"}`);
5761
5670
  console.log(`last exit: ${status.lastExit ?? "-"}`);
5762
5671
  console.log(`stdout: ${status.stdoutPath}`);
5763
5672
  console.log(`stderr: ${status.stderrPath}`);
5764
5673
  if (!status.installed) {
5765
5674
  console.log("\u63D0\u793A\uFF1A\u540E\u53F0\u670D\u52A1\u5C1A\u672A\u5B89\u88C5\uFF0C\u8FD0\u884C `feishu-codex-bridge start`\u3002");
5766
- } else if (!status.running) {
5767
- console.log("\u63D0\u793A\uFF1A\u670D\u52A1\u5DF2\u6CE8\u518C\u4F46\u5F53\u524D\u672A\u8FD0\u884C\uFF08\u8BD5\u8BD5 `restart`\uFF09\u3002");
5675
+ } else if (!status.loaded) {
5676
+ console.log("\u63D0\u793A\uFF1Aplist \u5DF2\u5B58\u5728\uFF0C\u4F46 launchd \u5F53\u524D\u672A\u52A0\u8F7D\uFF08\u8BD5\u8BD5 `restart`\uFF09\u3002");
5768
5677
  }
5769
5678
  }
5770
5679
 
@@ -5817,7 +5726,7 @@ async function runUpdate(opts = {}) {
5817
5726
  }
5818
5727
 
5819
5728
  // src/cli/commands/bot.ts
5820
- import { rm as rm5 } from "fs/promises";
5729
+ import { rm as rm4 } from "fs/promises";
5821
5730
  async function runBotInit(name) {
5822
5731
  if (!ensureCodex()) {
5823
5732
  process.exitCode = 1;
@@ -5872,7 +5781,7 @@ async function runBotRm(name) {
5872
5781
  }
5873
5782
  const after = await removeBot(bot2.appId);
5874
5783
  await removeSecret(secretKeyForApp(bot2.appId));
5875
- await rm5(botDir(bot2.appId), { recursive: true, force: true });
5784
+ await rm4(botDir(bot2.appId), { recursive: true, force: true });
5876
5785
  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`);
5877
5786
  if (after.bots.length === 0) {
5878
5787
  console.log(" \u5DF2\u65E0\u4EFB\u4F55\u673A\u5668\u4EBA\uFF0C`bot init` \u91CD\u65B0\u521B\u5EFA\u3002");
@@ -5930,15 +5839,15 @@ async function secretsRemove(id) {
5930
5839
  console.log(ok ? `\u2713 \u5DF2\u5220\u9664: ${id}` : `\u672A\u627E\u5230: ${id}`);
5931
5840
  }
5932
5841
  function readStdin() {
5933
- return new Promise((resolve7) => {
5842
+ return new Promise((resolve6) => {
5934
5843
  let data = "";
5935
5844
  if (process.stdin.isTTY) {
5936
- resolve7("");
5845
+ resolve6("");
5937
5846
  return;
5938
5847
  }
5939
5848
  process.stdin.setEncoding("utf8");
5940
5849
  process.stdin.on("data", (c) => data += c);
5941
- process.stdin.on("end", () => resolve7(data));
5850
+ process.stdin.on("end", () => resolve6(data));
5942
5851
  });
5943
5852
  }
5944
5853
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelzen/feishu-codex-bridge",
3
- "version": "0.2.1-win",
3
+ "version": "0.2.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": {
@@ -30,11 +30,9 @@
30
30
  "dependencies": {
31
31
  "@larksuiteoapi/node-sdk": "^1.65.0",
32
32
  "commander": "^12.1.0",
33
- "cross-spawn": "^7.0.6",
34
33
  "qrcode-terminal": "^0.12.0"
35
34
  },
36
35
  "devDependencies": {
37
- "@types/cross-spawn": "^6.0.6",
38
36
  "@types/node": "^22.10.0",
39
37
  "@types/qrcode-terminal": "^0.12.2",
40
38
  "tsup": "^8.3.5",