@openai-lite/codex-feishu 0.1.5 → 0.1.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openai-lite/codex-feishu",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Feishu bridge for Codex with dual-end synchronized conversations.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -5944,6 +5944,13 @@ export async function runDaemon() {
5944
5944
  signal: info.signal ?? null,
5945
5945
  });
5946
5946
  });
5947
+ app.on("error", async (err) => {
5948
+ await appendEvent(store, {
5949
+ source: "app_server",
5950
+ type: "error",
5951
+ error: err?.message ?? String(err),
5952
+ });
5953
+ });
5947
5954
 
5948
5955
  try {
5949
5956
  await app.ensureStarted();
@@ -1,7 +1,59 @@
1
1
  import { EventEmitter, once } from "node:events";
2
+ import fs from "node:fs";
2
3
  import { spawn } from "node:child_process";
3
4
  import readline from "node:readline";
4
5
 
6
+ function normalizeExecutable(raw) {
7
+ const text = String(raw ?? "").trim();
8
+ if (!text) {
9
+ return text;
10
+ }
11
+ const quoted =
12
+ (text.startsWith('"') && text.endsWith('"')) || (text.startsWith("'") && text.endsWith("'"));
13
+ return quoted ? text.slice(1, -1).trim() : text;
14
+ }
15
+
16
+ function quoteForCmd(arg) {
17
+ const text = String(arg ?? "");
18
+ if (text.length === 0) {
19
+ return '""';
20
+ }
21
+ if (!/[\s"&|<>^()]/.test(text)) {
22
+ return text;
23
+ }
24
+ return `"${text.replace(/"/g, '""')}"`;
25
+ }
26
+
27
+ function sanitizeSpawnCwd(cwd) {
28
+ if (typeof cwd !== "string" || !cwd.trim()) {
29
+ return process.cwd();
30
+ }
31
+ const normalized = cwd.trim();
32
+ if (process.platform === "win32") {
33
+ try {
34
+ if (!fs.existsSync(normalized)) {
35
+ return process.cwd();
36
+ }
37
+ } catch {
38
+ return process.cwd();
39
+ }
40
+ }
41
+ return normalized;
42
+ }
43
+
44
+ function spawnCodex(codexBin, args, options = {}) {
45
+ const bin = normalizeExecutable(codexBin);
46
+ const spawnOptions = {
47
+ ...options,
48
+ cwd: sanitizeSpawnCwd(options.cwd),
49
+ };
50
+ if (process.platform !== "win32") {
51
+ return spawn(bin, args, spawnOptions);
52
+ }
53
+ const command = [bin, ...args].map(quoteForCmd).join(" ");
54
+ return spawn("cmd.exe", ["/d", "/s", "/c", command], spawnOptions);
55
+ }
56
+
5
57
  function normalizeJsonRpcPayload(payload) {
6
58
  if (!payload || typeof payload !== "object") {
7
59
  return null;
@@ -77,7 +129,7 @@ async function probeSubcommand(codexBin, args, timeoutMs = 2_500) {
77
129
  let stdout = "";
78
130
  let stderr = "";
79
131
  let timer = null;
80
- const child = spawn(codexBin, args, {
132
+ const child = spawnCodex(codexBin, args, {
81
133
  stdio: ["ignore", "pipe", "pipe"],
82
134
  env: process.env,
83
135
  });
@@ -135,7 +187,11 @@ async function probeSubcommand(codexBin, args, timeoutMs = 2_500) {
135
187
  export class AppServerClient extends EventEmitter {
136
188
  constructor(options = {}) {
137
189
  super();
138
- this.codexBin = options.codexBin || process.env.CODEX_BIN || "codex";
190
+ this.codexBin = normalizeExecutable(
191
+ options.codexBin ||
192
+ process.env.CODEX_BIN ||
193
+ (process.platform === "win32" ? "codex.cmd" : "codex"),
194
+ );
139
195
  this.transport = options.transport || process.env.CODEX_FEISHU_TRANSPORT || "auto";
140
196
  this.protoCwd = options.protoCwd || process.env.CODEX_FEISHU_CWD || process.cwd();
141
197
  this.protoModel = options.protoModel || process.env.CODEX_FEISHU_MODEL || "gpt-5.3-codex";
@@ -236,7 +292,7 @@ export class AppServerClient extends EventEmitter {
236
292
  }
237
293
 
238
294
  async startLegacyAppServer() {
239
- const child = spawn(this.codexBin, ["app-server", "--listen", "stdio://"], {
295
+ const child = spawnCodex(this.codexBin, ["app-server", "--listen", "stdio://"], {
240
296
  stdio: ["pipe", "pipe", "pipe"],
241
297
  env: process.env,
242
298
  cwd: this.protoCwd,
@@ -309,7 +365,7 @@ export class AppServerClient extends EventEmitter {
309
365
  }
310
366
 
311
367
  async startProto() {
312
- const child = spawn(this.codexBin, ["proto"], {
368
+ const child = spawnCodex(this.codexBin, ["proto"], {
313
369
  stdio: ["pipe", "pipe", "pipe"],
314
370
  env: process.env,
315
371
  cwd: this.protoCwd,
@@ -56,6 +56,22 @@ async function isDaemonRpcResponsive(timeoutMs = 1000) {
56
56
  }
57
57
  }
58
58
 
59
+ async function readLogTail(logPath, maxLines = 60) {
60
+ try {
61
+ const text = await readTextIfExists(logPath);
62
+ if (!text) {
63
+ return "";
64
+ }
65
+ const lines = text
66
+ .split(/\r?\n/)
67
+ .filter((line) => line.trim().length > 0)
68
+ .slice(-maxLines);
69
+ return lines.join("\n");
70
+ } catch {
71
+ return "";
72
+ }
73
+ }
74
+
59
75
  async function stopByPid(pid, timeoutMs = 3000) {
60
76
  if (!isPidAlive(pid)) {
61
77
  return { action: "already_stopped", pid };
@@ -201,7 +217,7 @@ export async function restartDaemonDetached() {
201
217
  for (const cliEntry of candidateEntries) {
202
218
  attempts.push([process.execPath, [cliEntry, "daemon"]]);
203
219
  }
204
- attempts.push(["codex-feishu", ["daemon"]]);
220
+ attempts.push(["cmd.exe", ["/d", "/s", "/c", "codex-feishu daemon"]]);
205
221
  } else {
206
222
  attempts.push(["codex-feishu", ["daemon"]]);
207
223
  for (const cliEntry of candidateEntries) {
@@ -234,8 +250,10 @@ export async function restartDaemonDetached() {
234
250
  failedAttempts.push(`${cmd} ${args.join(" ")} => ${startResult.error}`);
235
251
  }
236
252
  if (!startResult.ok) {
253
+ const logTail = await readLogTail(logPath, 80);
237
254
  const details = failedAttempts.length > 0 ? `; attempts: ${failedAttempts.join(" | ")}` : "";
238
- throw new Error(`failed to start daemon in background: ${startResult.error}${details}`);
255
+ const logHint = logTail ? `; daemon.log tail:\n${logTail}` : "";
256
+ throw new Error(`failed to start daemon in background: ${startResult.error}${details}${logHint}`);
239
257
  }
240
258
 
241
259
  if (startResult.pid) {