sessix-server 0.3.0 → 0.3.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/dist/index.js +208 -58
  2. package/dist/server.js +208 -58
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -326,14 +326,14 @@ var import_node_os = require("os");
326
326
  var import_node_child_process = require("child_process");
327
327
  var isWindows = process.platform === "win32";
328
328
  function killProcessCrossPlatform(proc, timeoutMs = 3e3) {
329
- return new Promise((resolve) => {
329
+ return new Promise((resolve2) => {
330
330
  if (proc.exitCode !== null || proc.signalCode !== null) {
331
- resolve();
331
+ resolve2();
332
332
  return;
333
333
  }
334
334
  const onExit = () => {
335
335
  clearTimeout(timer);
336
- resolve();
336
+ resolve2();
337
337
  };
338
338
  proc.once("exit", onExit);
339
339
  if (isWindows) {
@@ -349,7 +349,7 @@ function killProcessCrossPlatform(proc, timeoutMs = 3e3) {
349
349
  proc.kill("SIGKILL");
350
350
  }
351
351
  }
352
- resolve();
352
+ resolve2();
353
353
  }, timeoutMs);
354
354
  });
355
355
  }
@@ -781,7 +781,7 @@ var ProcessProvider = class {
781
781
  const prompt = `You are an AI coding assistant. Based on the following Claude Code conversation context, suggest the most valuable next instruction for the user (give the instruction directly, no explanation, no quotes):
782
782
 
783
783
  ${context}`;
784
- return new Promise((resolve, reject) => {
784
+ return new Promise((resolve2, reject) => {
785
785
  const env = { ...process.env };
786
786
  delete env.CLAUDECODE;
787
787
  const proc = (0, import_child_process.spawn)(CLAUDE_PATH, ["-p", prompt, "--output-format", "text"], {
@@ -796,7 +796,7 @@ ${context}`;
796
796
  });
797
797
  proc.once("exit", (code) => {
798
798
  if (code === 0) {
799
- resolve(output.trim());
799
+ resolve2(output.trim());
800
800
  } else {
801
801
  reject(new Error(`generateSuggestion process exit code: ${code}`));
802
802
  }
@@ -823,10 +823,10 @@ ${context}`;
823
823
  tool_use_id: toolUseId,
824
824
  content: answer
825
825
  });
826
- await new Promise((resolve, reject) => {
826
+ await new Promise((resolve2, reject) => {
827
827
  entry.process.stdin.write(toolResult + "\n", (err) => {
828
828
  if (err) reject(err);
829
- else resolve();
829
+ else resolve2();
830
830
  });
831
831
  });
832
832
  console.log(`[ProcessProvider] Session ${sessionId}: AskUserQuestion answered (toolUseId=${toolUseId})`);
@@ -861,6 +861,9 @@ ${context}`;
861
861
  var import_child_process2 = require("child_process");
862
862
  var import_readline2 = require("readline");
863
863
  var import_events2 = require("events");
864
+ var import_fs = require("fs");
865
+ var import_path = require("path");
866
+ var import_os = require("os");
864
867
  var import_uuid2 = require("uuid");
865
868
 
866
869
  // src/utils/codexPath.ts
@@ -891,10 +894,31 @@ function findCodexPath() {
891
894
  }
892
895
  return "codex";
893
896
  }
897
+ function resolveCodexJsEntry(codexPath) {
898
+ try {
899
+ let realPath;
900
+ try {
901
+ realPath = (0, import_node_fs2.readlinkSync)(codexPath);
902
+ if (!realPath.startsWith("/")) {
903
+ realPath = (0, import_node_path2.resolve)((0, import_node_path2.dirname)(codexPath), realPath);
904
+ }
905
+ } catch {
906
+ realPath = codexPath;
907
+ }
908
+ const head = (0, import_node_fs2.readFileSync)(realPath, { encoding: "utf-8", flag: "r" }).slice(0, 100);
909
+ if (head.startsWith("#!/usr/bin/env node") || head.startsWith("#!/usr/bin/node")) {
910
+ return realPath;
911
+ }
912
+ } catch {
913
+ }
914
+ return void 0;
915
+ }
894
916
  var CODEX_PATH = findCodexPath();
917
+ var CODEX_JS_ENTRY = resolveCodexJsEntry(CODEX_PATH);
895
918
  async function getCodexVersion() {
896
919
  try {
897
- const output = (0, import_node_child_process3.execSync)(`"${CODEX_PATH}" --version`, {
920
+ const cmd = CODEX_JS_ENTRY ? `"${process.execPath}" "${CODEX_JS_ENTRY}" --version` : `"${CODEX_PATH}" --version`;
921
+ const output = (0, import_node_child_process3.execSync)(cmd, {
898
922
  encoding: "utf-8",
899
923
  timeout: 5e3
900
924
  }).trim();
@@ -904,6 +928,14 @@ async function getCodexVersion() {
904
928
  }
905
929
  }
906
930
  function isCodexAvailable() {
931
+ if (CODEX_JS_ENTRY) {
932
+ try {
933
+ (0, import_node_fs2.accessSync)(CODEX_JS_ENTRY, import_node_fs2.constants.R_OK);
934
+ return true;
935
+ } catch {
936
+ return false;
937
+ }
938
+ }
907
939
  try {
908
940
  (0, import_node_fs2.accessSync)(CODEX_PATH, import_node_fs2.constants.X_OK);
909
941
  return true;
@@ -918,11 +950,18 @@ function isCodexAvailable() {
918
950
  }
919
951
 
920
952
  // src/providers/CodexProvider.ts
953
+ var SESSIX_DIR = (0, import_path.join)((0, import_os.homedir)(), ".sessix");
954
+ var CODEX_SESSIONS_FILE = (0, import_path.join)(SESSIX_DIR, "codex-sessions.json");
921
955
  var CodexProvider = class {
922
956
  activeSessions = /* @__PURE__ */ new Map();
923
957
  emitter = new import_events2.EventEmitter();
958
+ /** 持久化的会话元数据(sessionId → metadata) */
959
+ persistedSessions = /* @__PURE__ */ new Map();
924
960
  /** 自增 ID 计数器,用于生成 ClaudeStreamEvent 中的 message/block ID */
925
961
  idCounter = 0;
962
+ constructor() {
963
+ this.loadPersistedSessions();
964
+ }
926
965
  async startSession(opts) {
927
966
  const { projectPath, message, sessionId: existingSessionId } = opts;
928
967
  const sessionId = existingSessionId ?? (0, import_uuid2.v4)();
@@ -941,12 +980,22 @@ var CodexProvider = class {
941
980
  agentType: "codex"
942
981
  };
943
982
  const resume = opts.resume ?? !!existingSessionId;
944
- const proc = this.spawnCodexProcess(projectPath, message, void 0);
983
+ let resumeThreadId;
984
+ if (resume && existingSessionId) {
985
+ const persisted = this.persistedSessions.get(existingSessionId);
986
+ if (persisted?.threadId) {
987
+ resumeThreadId = persisted.threadId;
988
+ console.log(`[CodexProvider] Resuming session ${sessionId} with threadId: ${resumeThreadId}`);
989
+ } else {
990
+ console.warn(`[CodexProvider] Session ${sessionId} resume requested but no persisted threadId found, creating new session`);
991
+ }
992
+ }
993
+ const proc = this.spawnCodexProcess(projectPath, message, resumeThreadId, opts.model);
945
994
  session.pid = proc.pid;
946
995
  this.activeSessions.set(sessionId, {
947
996
  session,
948
997
  process: proc,
949
- threadId: void 0,
998
+ threadId: resumeThreadId,
950
999
  turnStartTime: Date.now()
951
1000
  });
952
1001
  const initEvent = {
@@ -978,22 +1027,45 @@ var CodexProvider = class {
978
1027
  this.activeSessions.delete(sessionId);
979
1028
  }
980
1029
  async sendMessage(sessionId, message) {
981
- const entry = this.activeSessions.get(sessionId);
1030
+ let entry = this.activeSessions.get(sessionId);
982
1031
  if (!entry) {
983
- throw new Error(`Session ${sessionId} not found or already ended`);
1032
+ const persisted = this.persistedSessions.get(sessionId);
1033
+ if (!persisted?.threadId) {
1034
+ throw new Error(`Session ${sessionId} not found or already ended`);
1035
+ }
1036
+ const projectId = persisted.projectPath.split("/").filter(Boolean).pop() ?? "unknown";
1037
+ const session = {
1038
+ id: sessionId,
1039
+ projectId,
1040
+ projectPath: persisted.projectPath,
1041
+ status: "running",
1042
+ createdAt: persisted.createdAt,
1043
+ lastActiveAt: Date.now(),
1044
+ summary: persisted.summary,
1045
+ agentType: "codex"
1046
+ };
1047
+ const placeholderProc = (0, import_child_process2.spawn)("true", [], { stdio: "ignore" });
1048
+ entry = {
1049
+ session,
1050
+ process: placeholderProc,
1051
+ threadId: persisted.threadId,
1052
+ turnStartTime: Date.now()
1053
+ };
1054
+ this.activeSessions.set(sessionId, entry);
984
1055
  }
985
- if (entry.process.exitCode === null && entry.process.signalCode === null) {
1056
+ const procAlive = entry.process.exitCode === null && entry.process.signalCode === null;
1057
+ if (procAlive) {
986
1058
  try {
987
1059
  entry.process.stdin?.end();
988
1060
  } catch {
989
1061
  }
990
1062
  await killProcessCrossPlatform(entry.process);
991
1063
  }
992
- const threadId = entry.threadId;
1064
+ const threadId = entry.threadId ?? this.persistedSessions.get(sessionId)?.threadId;
993
1065
  if (!threadId) {
994
- throw new Error(`Session ${sessionId} has no thread ID, cannot resume`);
1066
+ console.warn(`[CodexProvider] Session ${sessionId} has no thread ID, falling back to new thread`);
995
1067
  }
996
- const proc = this.spawnCodexProcess(entry.session.projectPath, message, threadId);
1068
+ const proc = this.spawnCodexProcess(entry.session.projectPath, message, threadId ?? void 0);
997
1069
  entry.session.status = "running";
998
1070
  entry.session.lastActiveAt = Date.now();
999
1071
  entry.session.pid = proc.pid;
@@ -1040,16 +1112,29 @@ var CodexProvider = class {
1040
1112
  * @param message 用户消息
1041
1113
  * @param resumeThreadId 如果提供,则使用 `codex exec resume` 恢复会话
1042
1114
  */
1043
- spawnCodexProcess(projectPath, message, resumeThreadId) {
1115
+ spawnCodexProcess(projectPath, message, resumeThreadId, model) {
1044
1116
  const args = ["exec", "--json", "--full-auto"];
1117
+ if (model) {
1118
+ args.push("-m", model);
1119
+ }
1120
+ args.push("-C", projectPath);
1045
1121
  if (resumeThreadId) {
1046
1122
  args.push("resume", resumeThreadId);
1047
- } else {
1048
- args.push("-C", projectPath);
1049
1123
  }
1050
1124
  args.push(message);
1051
1125
  const env = { ...process.env };
1052
- const proc = (0, import_child_process2.spawn)(CODEX_PATH, args, {
1126
+ let cmd;
1127
+ let spawnArgs;
1128
+ if (CODEX_JS_ENTRY) {
1129
+ cmd = process.execPath;
1130
+ spawnArgs = [CODEX_JS_ENTRY, ...args];
1131
+ console.log(`[CodexProvider] Spawning via node: ${cmd} ${CODEX_JS_ENTRY} ${args.join(" ")}`);
1132
+ } else {
1133
+ cmd = CODEX_PATH;
1134
+ spawnArgs = args;
1135
+ console.log(`[CodexProvider] Spawning: ${CODEX_PATH} ${args.join(" ")}`);
1136
+ }
1137
+ const proc = (0, import_child_process2.spawn)(cmd, spawnArgs, {
1053
1138
  cwd: projectPath,
1054
1139
  env,
1055
1140
  stdio: ["pipe", "pipe", "pipe"]
@@ -1071,6 +1156,7 @@ var CodexProvider = class {
1071
1156
  rl.on("line", (line) => {
1072
1157
  const trimmed = line.trim();
1073
1158
  if (!trimmed) return;
1159
+ console.log(`[CodexProvider] Session ${sessionId} stdout: ${trimmed.substring(0, 200)}`);
1074
1160
  let event;
1075
1161
  try {
1076
1162
  event = JSON.parse(trimmed);
@@ -1092,6 +1178,14 @@ var CodexProvider = class {
1092
1178
  case "thread.started": {
1093
1179
  if (event.thread_id) {
1094
1180
  entry.threadId = event.thread_id;
1181
+ this.persistSession(sessionId, {
1182
+ threadId: event.thread_id,
1183
+ projectPath: entry.session.projectPath,
1184
+ summary: entry.session.summary,
1185
+ createdAt: entry.session.createdAt,
1186
+ lastActiveAt: Date.now()
1187
+ });
1188
+ console.log(`[CodexProvider] Session ${sessionId} threadId persisted: ${event.thread_id}`);
1095
1189
  }
1096
1190
  break;
1097
1191
  }
@@ -1255,6 +1349,7 @@ var CodexProvider = class {
1255
1349
  const entry = this.activeSessions.get(sessionId);
1256
1350
  if (!entry) return;
1257
1351
  if (entry.process !== proc) return;
1352
+ console.log(`[CodexProvider] Session ${sessionId} process exited: code=${code} signal=${signal} threadId=${entry.threadId ?? "NONE"}`);
1258
1353
  if (entry.rl) {
1259
1354
  entry.rl.close();
1260
1355
  entry.rl = void 0;
@@ -1295,6 +1390,54 @@ var CodexProvider = class {
1295
1390
  getEventName(sessionId) {
1296
1391
  return `claude:${sessionId}`;
1297
1392
  }
1393
+ // ============================================
1394
+ // 持久化方法
1395
+ // ============================================
1396
+ /**
1397
+ * 从磁盘加载持久化的 Codex 会话元数据
1398
+ */
1399
+ loadPersistedSessions() {
1400
+ try {
1401
+ if (!(0, import_fs.existsSync)(CODEX_SESSIONS_FILE)) return;
1402
+ const data = JSON.parse((0, import_fs.readFileSync)(CODEX_SESSIONS_FILE, "utf-8"));
1403
+ for (const [sessionId, meta] of Object.entries(data)) {
1404
+ this.persistedSessions.set(sessionId, meta);
1405
+ }
1406
+ console.log(`[CodexProvider] Loaded ${this.persistedSessions.size} persisted sessions`);
1407
+ } catch (err) {
1408
+ console.warn("[CodexProvider] Failed to load persisted sessions:", err);
1409
+ }
1410
+ }
1411
+ /**
1412
+ * 持久化单个会话的元数据到磁盘
1413
+ */
1414
+ persistSession(sessionId, meta) {
1415
+ this.persistedSessions.set(sessionId, meta);
1416
+ this.flushPersistedSessions();
1417
+ }
1418
+ /**
1419
+ * 将所有持久化数据写入磁盘
1420
+ */
1421
+ flushPersistedSessions() {
1422
+ try {
1423
+ if (!(0, import_fs.existsSync)(SESSIX_DIR)) {
1424
+ (0, import_fs.mkdirSync)(SESSIX_DIR, { recursive: true });
1425
+ }
1426
+ const data = {};
1427
+ for (const [sessionId, meta] of this.persistedSessions) {
1428
+ data[sessionId] = meta;
1429
+ }
1430
+ (0, import_fs.writeFileSync)(CODEX_SESSIONS_FILE, JSON.stringify(data, null, 2), "utf-8");
1431
+ } catch (err) {
1432
+ console.error("[CodexProvider] Failed to persist sessions:", err);
1433
+ }
1434
+ }
1435
+ /**
1436
+ * 检查某个 sessionId 是否为已知的 Codex 会话(供 SessionManager 查询 agentType)
1437
+ */
1438
+ isKnownSession(sessionId) {
1439
+ return this.activeSessions.has(sessionId) || this.persistedSessions.has(sessionId);
1440
+ }
1298
1441
  };
1299
1442
 
1300
1443
  // src/providers/ProviderFactory.ts
@@ -1387,8 +1530,15 @@ var SessionManager = class {
1387
1530
  }
1388
1531
  getProviderForSession(sessionId) {
1389
1532
  if (!this.providerFactory) return this.provider;
1390
- const agentType = this.sessionAgentType.get(sessionId) ?? "claude-code";
1391
- return this.providerFactory.getProvider(agentType);
1533
+ let agentType = this.sessionAgentType.get(sessionId);
1534
+ if (!agentType) {
1535
+ const codexProvider = this.providerFactory.getProvider("codex");
1536
+ if (codexProvider instanceof CodexProvider && codexProvider.isKnownSession(sessionId)) {
1537
+ agentType = "codex";
1538
+ this.sessionAgentType.set(sessionId, agentType);
1539
+ }
1540
+ }
1541
+ return this.providerFactory.getProvider(agentType ?? "claude-code");
1392
1542
  }
1393
1543
  // ============================================
1394
1544
  // 公开 API
@@ -1767,8 +1917,8 @@ var SessionManager = class {
1767
1917
  };
1768
1918
  this.updateSessionStatus(sessionId, "waiting_question");
1769
1919
  this.emit({ type: "question_request", request });
1770
- const answerPromise = new Promise((resolve) => {
1771
- this.pendingQuestions.set(requestId, { sessionId, toolUseId, question, options, createdAt: request.createdAt, resolve });
1920
+ const answerPromise = new Promise((resolve2) => {
1921
+ this.pendingQuestions.set(requestId, { sessionId, toolUseId, question, options, createdAt: request.createdAt, resolve: resolve2 });
1772
1922
  });
1773
1923
  answerPromise.then(async (answer) => {
1774
1924
  try {
@@ -2010,11 +2160,11 @@ var WsBridge = class _WsBridge {
2010
2160
  * 使用此方法代替 new WsBridge(),确保 EADDRINUSE 等错误能被调用方的 try-catch 捕获。
2011
2161
  */
2012
2162
  static async create(options) {
2013
- return new Promise((resolve, reject) => {
2163
+ return new Promise((resolve2, reject) => {
2014
2164
  const bridge = new _WsBridge(options);
2015
2165
  bridge.wss.once("listening", () => {
2016
2166
  bridge.wss.on("error", (err) => console.error(`[WsBridge] ${t("ws.serverError")}:`, err));
2017
- resolve(bridge);
2167
+ resolve2(bridge);
2018
2168
  });
2019
2169
  bridge.wss.once("error", reject);
2020
2170
  });
@@ -2077,7 +2227,7 @@ var WsBridge = class _WsBridge {
2077
2227
  }
2078
2228
  /** 优雅关闭 WebSocket 服务 */
2079
2229
  close() {
2080
- return new Promise((resolve, reject) => {
2230
+ return new Promise((resolve2, reject) => {
2081
2231
  if (this.heartbeatTimer) {
2082
2232
  clearInterval(this.heartbeatTimer);
2083
2233
  this.heartbeatTimer = null;
@@ -2090,7 +2240,7 @@ var WsBridge = class _WsBridge {
2090
2240
  reject(err);
2091
2241
  } else {
2092
2242
  console.log("[WsBridge] WebSocket server closed");
2093
- resolve();
2243
+ resolve2();
2094
2244
  }
2095
2245
  });
2096
2246
  });
@@ -2230,11 +2380,11 @@ var ApprovalProxy = class _ApprovalProxy {
2230
2380
  * 异步工厂方法:等待端口监听成功后 resolve,端口占用等错误时 reject。
2231
2381
  */
2232
2382
  static async create(options) {
2233
- return new Promise((resolve, reject) => {
2383
+ return new Promise((resolve2, reject) => {
2234
2384
  const proxy = new _ApprovalProxy(options);
2235
2385
  proxy.server.once("listening", () => {
2236
2386
  proxy.server.on("error", (err) => console.error(`[ApprovalProxy] ${t("approval.serverError")}:`, err));
2237
- resolve(proxy);
2387
+ resolve2(proxy);
2238
2388
  });
2239
2389
  proxy.server.once("error", reject);
2240
2390
  });
@@ -2391,7 +2541,7 @@ var ApprovalProxy = class _ApprovalProxy {
2391
2541
  }
2392
2542
  /** 优雅关闭 HTTP 服务 */
2393
2543
  close() {
2394
- return new Promise((resolve, reject) => {
2544
+ return new Promise((resolve2, reject) => {
2395
2545
  const pendingEntries = Array.from(this.pendingApprovals.entries());
2396
2546
  for (const [, pending] of pendingEntries) {
2397
2547
  clearTimeout(pending.timer);
@@ -2403,7 +2553,7 @@ var ApprovalProxy = class _ApprovalProxy {
2403
2553
  reject(err);
2404
2554
  } else {
2405
2555
  console.log(`[ApprovalProxy] ${t("approval.httpClosed")}`);
2406
- resolve();
2556
+ resolve2();
2407
2557
  }
2408
2558
  });
2409
2559
  });
@@ -2474,13 +2624,13 @@ var ApprovalProxy = class _ApprovalProxy {
2474
2624
  return;
2475
2625
  }
2476
2626
  this.notifyApprovalRequest(approvalRequest);
2477
- const decision = await new Promise((resolve) => {
2627
+ const decision = await new Promise((resolve2) => {
2478
2628
  const timer = setTimeout(() => {
2479
2629
  console.log(`[ApprovalProxy] ${t("approval.timeout", { id: requestId })}`);
2480
2630
  this.pendingApprovals.delete(requestId);
2481
- resolve({ decision: "allow" });
2631
+ resolve2({ decision: "allow" });
2482
2632
  }, 325e3);
2483
- this.pendingApprovals.set(requestId, { resolve, timer, request: approvalRequest });
2633
+ this.pendingApprovals.set(requestId, { resolve: resolve2, timer, request: approvalRequest });
2484
2634
  });
2485
2635
  this.sendJson(res, 200, decision);
2486
2636
  } catch (err) {
@@ -2541,7 +2691,7 @@ var ApprovalProxy = class _ApprovalProxy {
2541
2691
  /** 手动解析请求的 JSON body(限制最大 1MB 防止滥用) */
2542
2692
  parseJsonBody(req) {
2543
2693
  const MAX_BODY_SIZE = 1024 * 1024;
2544
- return new Promise((resolve, reject) => {
2694
+ return new Promise((resolve2, reject) => {
2545
2695
  const chunks = [];
2546
2696
  let totalSize = 0;
2547
2697
  let destroyed = false;
@@ -2559,7 +2709,7 @@ var ApprovalProxy = class _ApprovalProxy {
2559
2709
  try {
2560
2710
  const raw = Buffer.concat(chunks).toString("utf-8");
2561
2711
  const parsed = JSON.parse(raw);
2562
- resolve(parsed);
2712
+ resolve2(parsed);
2563
2713
  } catch {
2564
2714
  reject(new Error(t("approval.invalidJson")));
2565
2715
  }
@@ -3226,12 +3376,12 @@ var DesktopNotificationChannel = class {
3226
3376
  const body = payload.body.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
3227
3377
  const sound = payload.sound ?? "Ping";
3228
3378
  const script = `display notification "${body}" with title "${title}" sound name "${sound}"`;
3229
- return new Promise((resolve) => {
3379
+ return new Promise((resolve2) => {
3230
3380
  (0, import_node_child_process6.execFile)("osascript", ["-e", script], (err) => {
3231
3381
  if (err) {
3232
3382
  console.warn("[DesktopNotificationChannel] Send notification failed:", err.message);
3233
3383
  }
3234
- resolve();
3384
+ resolve2();
3235
3385
  });
3236
3386
  });
3237
3387
  }
@@ -3431,7 +3581,7 @@ var ActivityPushChannel = class {
3431
3581
  const topic = "com.kachun.sessix.push-type.liveactivity";
3432
3582
  const jwt = this.getJWT();
3433
3583
  const payloadStr = JSON.stringify(payload);
3434
- return new Promise((resolve, reject) => {
3584
+ return new Promise((resolve2, reject) => {
3435
3585
  let client;
3436
3586
  try {
3437
3587
  client = this.getHttp2Client();
@@ -3459,7 +3609,7 @@ var ActivityPushChannel = class {
3459
3609
  });
3460
3610
  req.on("end", () => {
3461
3611
  if (statusCode === 200) {
3462
- resolve();
3612
+ resolve2();
3463
3613
  } else {
3464
3614
  if (statusCode === 0) {
3465
3615
  this.http2Client?.destroy();
@@ -3502,11 +3652,11 @@ var ActivityPushChannel = class {
3502
3652
  // src/session/ProjectReader.ts
3503
3653
  var import_promises3 = require("fs/promises");
3504
3654
  var import_readline3 = require("readline");
3505
- var import_path = require("path");
3506
- var import_os = require("os");
3507
- var CLAUDE_PROJECTS_DIR = (0, import_path.join)((0, import_os.homedir)(), ".claude", "projects");
3655
+ var import_path2 = require("path");
3656
+ var import_os2 = require("os");
3657
+ var CLAUDE_PROJECTS_DIR = (0, import_path2.join)((0, import_os2.homedir)(), ".claude", "projects");
3508
3658
  function getSessionFilePath(projectPath, sessionId) {
3509
- return (0, import_path.join)(CLAUDE_PROJECTS_DIR, encodeDirName(projectPath), `${sessionId}.jsonl`);
3659
+ return (0, import_path2.join)(CLAUDE_PROJECTS_DIR, encodeDirName(projectPath), `${sessionId}.jsonl`);
3510
3660
  }
3511
3661
  async function getProjects() {
3512
3662
  try {
@@ -3523,7 +3673,7 @@ async function getProjects() {
3523
3673
  const encodedPath = entry.name;
3524
3674
  const decodedPath = decodeDirName(encodedPath);
3525
3675
  const name = decodedPath.split("/").filter(Boolean).pop() ?? encodedPath;
3526
- const projectDir = (0, import_path.join)(CLAUDE_PROJECTS_DIR, encodedPath);
3676
+ const projectDir = (0, import_path2.join)(CLAUDE_PROJECTS_DIR, encodedPath);
3527
3677
  const { count: sessionCount, latestMtime } = await countJsonlFilesWithMtime(projectDir);
3528
3678
  projects.push({
3529
3679
  id: encodedPath,
@@ -3545,7 +3695,7 @@ async function getProjects() {
3545
3695
  async function getHistoricalSessions(projectPath) {
3546
3696
  try {
3547
3697
  const encodedPath = encodeDirName(projectPath);
3548
- const projectDir = (0, import_path.join)(CLAUDE_PROJECTS_DIR, encodedPath);
3698
+ const projectDir = (0, import_path2.join)(CLAUDE_PROJECTS_DIR, encodedPath);
3549
3699
  const dirExists = await directoryExists(projectDir);
3550
3700
  if (!dirExists) {
3551
3701
  return { ok: true, value: [] };
@@ -3556,7 +3706,7 @@ async function getHistoricalSessions(projectPath) {
3556
3706
  await Promise.all(
3557
3707
  jsonlFiles.map(async (entry) => {
3558
3708
  const sessionId = entry.name.slice(0, -6);
3559
- const filePath = (0, import_path.join)(projectDir, entry.name);
3709
+ const filePath = (0, import_path2.join)(projectDir, entry.name);
3560
3710
  try {
3561
3711
  const contentTs = await extractLastTimestamp(filePath);
3562
3712
  if (contentTs) {
@@ -3575,13 +3725,13 @@ async function getHistoricalSessions(projectPath) {
3575
3725
  );
3576
3726
  for (const entry of uuidDirs) {
3577
3727
  try {
3578
- const fileStat = await (0, import_promises3.stat)((0, import_path.join)(projectDir, entry.name));
3728
+ const fileStat = await (0, import_promises3.stat)((0, import_path2.join)(projectDir, entry.name));
3579
3729
  mtimeMap.set(entry.name, fileStat.mtimeMs);
3580
3730
  } catch {
3581
3731
  mtimeMap.set(entry.name, 0);
3582
3732
  }
3583
3733
  }
3584
- const indexPath = (0, import_path.join)(projectDir, "sessions-index.json");
3734
+ const indexPath = (0, import_path2.join)(projectDir, "sessions-index.json");
3585
3735
  const sessionMap = /* @__PURE__ */ new Map();
3586
3736
  try {
3587
3737
  const indexContent = await (0, import_promises3.readFile)(indexPath, "utf-8");
@@ -3599,7 +3749,7 @@ async function getHistoricalSessions(projectPath) {
3599
3749
  }
3600
3750
  await Promise.all(
3601
3751
  Array.from(sessionMap.values()).filter((s) => (s.messageCount ?? 0) > 0 && !s.summary && !s.firstPrompt).map(async (s) => {
3602
- const filePath = (0, import_path.join)(projectDir, `${s.sessionId}.jsonl`);
3752
+ const filePath = (0, import_path2.join)(projectDir, `${s.sessionId}.jsonl`);
3603
3753
  const firstPrompt = await extractFirstPrompt(filePath).catch(() => void 0);
3604
3754
  if (firstPrompt) s.firstPrompt = firstPrompt;
3605
3755
  })
@@ -3613,7 +3763,7 @@ async function getHistoricalSessions(projectPath) {
3613
3763
  if (uuidDirSet.has(sessionId)) {
3614
3764
  sessionMap.set(sessionId, { sessionId, lastModified: mtime, messageCount: -1 });
3615
3765
  } else {
3616
- const filePath = (0, import_path.join)(projectDir, `${sessionId}.jsonl`);
3766
+ const filePath = (0, import_path2.join)(projectDir, `${sessionId}.jsonl`);
3617
3767
  const firstPrompt = await extractFirstPrompt(filePath).catch(() => void 0);
3618
3768
  sessionMap.set(sessionId, { sessionId, lastModified: mtime, firstPrompt });
3619
3769
  }
@@ -3637,7 +3787,7 @@ async function getHistoricalSessions(projectPath) {
3637
3787
  async function getSessionHistory(projectPath, sessionId) {
3638
3788
  try {
3639
3789
  const encodedPath = encodeDirName(projectPath);
3640
- const filePath = (0, import_path.join)(CLAUDE_PROJECTS_DIR, encodedPath, `${sessionId}.jsonl`);
3790
+ const filePath = (0, import_path2.join)(CLAUDE_PROJECTS_DIR, encodedPath, `${sessionId}.jsonl`);
3641
3791
  const raw = await (0, import_promises3.readFile)(filePath, "utf-8").catch((err) => {
3642
3792
  if (err.code === "ENOENT") return null;
3643
3793
  throw err;
@@ -3818,15 +3968,15 @@ async function countJsonlFilesWithMtime(dirPath) {
3818
3968
  await Promise.all([
3819
3969
  ...jsonlEntries.map(async (entry) => {
3820
3970
  try {
3821
- const contentTs = await extractLastTimestamp((0, import_path.join)(dirPath, entry.name));
3822
- const ts = contentTs ?? (await (0, import_promises3.stat)((0, import_path.join)(dirPath, entry.name))).mtimeMs;
3971
+ const contentTs = await extractLastTimestamp((0, import_path2.join)(dirPath, entry.name));
3972
+ const ts = contentTs ?? (await (0, import_promises3.stat)((0, import_path2.join)(dirPath, entry.name))).mtimeMs;
3823
3973
  if (ts > latestMtime) latestMtime = ts;
3824
3974
  } catch {
3825
3975
  }
3826
3976
  }),
3827
3977
  ...uuidDirs.map(async (entry) => {
3828
3978
  try {
3829
- const fileStat = await (0, import_promises3.stat)((0, import_path.join)(dirPath, entry.name));
3979
+ const fileStat = await (0, import_promises3.stat)((0, import_path2.join)(dirPath, entry.name));
3830
3980
  if (fileStat.mtimeMs > latestMtime) latestMtime = fileStat.mtimeMs;
3831
3981
  } catch {
3832
3982
  }
@@ -4132,7 +4282,7 @@ async function killPortProcess(port) {
4132
4282
  await execAsync(`kill -9 ${pids.join(" ")}`);
4133
4283
  }
4134
4284
  }
4135
- await new Promise((resolve) => setTimeout(resolve, 600));
4285
+ await new Promise((resolve2) => setTimeout(resolve2, 600));
4136
4286
  } catch {
4137
4287
  }
4138
4288
  }
package/dist/server.js CHANGED
@@ -331,14 +331,14 @@ var import_node_os = require("os");
331
331
  var import_node_child_process = require("child_process");
332
332
  var isWindows = process.platform === "win32";
333
333
  function killProcessCrossPlatform(proc, timeoutMs = 3e3) {
334
- return new Promise((resolve) => {
334
+ return new Promise((resolve2) => {
335
335
  if (proc.exitCode !== null || proc.signalCode !== null) {
336
- resolve();
336
+ resolve2();
337
337
  return;
338
338
  }
339
339
  const onExit = () => {
340
340
  clearTimeout(timer);
341
- resolve();
341
+ resolve2();
342
342
  };
343
343
  proc.once("exit", onExit);
344
344
  if (isWindows) {
@@ -354,7 +354,7 @@ function killProcessCrossPlatform(proc, timeoutMs = 3e3) {
354
354
  proc.kill("SIGKILL");
355
355
  }
356
356
  }
357
- resolve();
357
+ resolve2();
358
358
  }, timeoutMs);
359
359
  });
360
360
  }
@@ -786,7 +786,7 @@ var ProcessProvider = class {
786
786
  const prompt = `You are an AI coding assistant. Based on the following Claude Code conversation context, suggest the most valuable next instruction for the user (give the instruction directly, no explanation, no quotes):
787
787
 
788
788
  ${context}`;
789
- return new Promise((resolve, reject) => {
789
+ return new Promise((resolve2, reject) => {
790
790
  const env = { ...process.env };
791
791
  delete env.CLAUDECODE;
792
792
  const proc = (0, import_child_process.spawn)(CLAUDE_PATH, ["-p", prompt, "--output-format", "text"], {
@@ -801,7 +801,7 @@ ${context}`;
801
801
  });
802
802
  proc.once("exit", (code) => {
803
803
  if (code === 0) {
804
- resolve(output.trim());
804
+ resolve2(output.trim());
805
805
  } else {
806
806
  reject(new Error(`generateSuggestion process exit code: ${code}`));
807
807
  }
@@ -828,10 +828,10 @@ ${context}`;
828
828
  tool_use_id: toolUseId,
829
829
  content: answer
830
830
  });
831
- await new Promise((resolve, reject) => {
831
+ await new Promise((resolve2, reject) => {
832
832
  entry.process.stdin.write(toolResult + "\n", (err) => {
833
833
  if (err) reject(err);
834
- else resolve();
834
+ else resolve2();
835
835
  });
836
836
  });
837
837
  console.log(`[ProcessProvider] Session ${sessionId}: AskUserQuestion answered (toolUseId=${toolUseId})`);
@@ -866,6 +866,9 @@ ${context}`;
866
866
  var import_child_process2 = require("child_process");
867
867
  var import_readline2 = require("readline");
868
868
  var import_events2 = require("events");
869
+ var import_fs = require("fs");
870
+ var import_path = require("path");
871
+ var import_os = require("os");
869
872
  var import_uuid2 = require("uuid");
870
873
 
871
874
  // src/utils/codexPath.ts
@@ -896,10 +899,31 @@ function findCodexPath() {
896
899
  }
897
900
  return "codex";
898
901
  }
902
+ function resolveCodexJsEntry(codexPath) {
903
+ try {
904
+ let realPath;
905
+ try {
906
+ realPath = (0, import_node_fs2.readlinkSync)(codexPath);
907
+ if (!realPath.startsWith("/")) {
908
+ realPath = (0, import_node_path2.resolve)((0, import_node_path2.dirname)(codexPath), realPath);
909
+ }
910
+ } catch {
911
+ realPath = codexPath;
912
+ }
913
+ const head = (0, import_node_fs2.readFileSync)(realPath, { encoding: "utf-8", flag: "r" }).slice(0, 100);
914
+ if (head.startsWith("#!/usr/bin/env node") || head.startsWith("#!/usr/bin/node")) {
915
+ return realPath;
916
+ }
917
+ } catch {
918
+ }
919
+ return void 0;
920
+ }
899
921
  var CODEX_PATH = findCodexPath();
922
+ var CODEX_JS_ENTRY = resolveCodexJsEntry(CODEX_PATH);
900
923
  async function getCodexVersion() {
901
924
  try {
902
- const output = (0, import_node_child_process3.execSync)(`"${CODEX_PATH}" --version`, {
925
+ const cmd = CODEX_JS_ENTRY ? `"${process.execPath}" "${CODEX_JS_ENTRY}" --version` : `"${CODEX_PATH}" --version`;
926
+ const output = (0, import_node_child_process3.execSync)(cmd, {
903
927
  encoding: "utf-8",
904
928
  timeout: 5e3
905
929
  }).trim();
@@ -909,6 +933,14 @@ async function getCodexVersion() {
909
933
  }
910
934
  }
911
935
  function isCodexAvailable() {
936
+ if (CODEX_JS_ENTRY) {
937
+ try {
938
+ (0, import_node_fs2.accessSync)(CODEX_JS_ENTRY, import_node_fs2.constants.R_OK);
939
+ return true;
940
+ } catch {
941
+ return false;
942
+ }
943
+ }
912
944
  try {
913
945
  (0, import_node_fs2.accessSync)(CODEX_PATH, import_node_fs2.constants.X_OK);
914
946
  return true;
@@ -923,11 +955,18 @@ function isCodexAvailable() {
923
955
  }
924
956
 
925
957
  // src/providers/CodexProvider.ts
958
+ var SESSIX_DIR = (0, import_path.join)((0, import_os.homedir)(), ".sessix");
959
+ var CODEX_SESSIONS_FILE = (0, import_path.join)(SESSIX_DIR, "codex-sessions.json");
926
960
  var CodexProvider = class {
927
961
  activeSessions = /* @__PURE__ */ new Map();
928
962
  emitter = new import_events2.EventEmitter();
963
+ /** 持久化的会话元数据(sessionId → metadata) */
964
+ persistedSessions = /* @__PURE__ */ new Map();
929
965
  /** 自增 ID 计数器,用于生成 ClaudeStreamEvent 中的 message/block ID */
930
966
  idCounter = 0;
967
+ constructor() {
968
+ this.loadPersistedSessions();
969
+ }
931
970
  async startSession(opts) {
932
971
  const { projectPath, message, sessionId: existingSessionId } = opts;
933
972
  const sessionId = existingSessionId ?? (0, import_uuid2.v4)();
@@ -946,12 +985,22 @@ var CodexProvider = class {
946
985
  agentType: "codex"
947
986
  };
948
987
  const resume = opts.resume ?? !!existingSessionId;
949
- const proc = this.spawnCodexProcess(projectPath, message, void 0);
988
+ let resumeThreadId;
989
+ if (resume && existingSessionId) {
990
+ const persisted = this.persistedSessions.get(existingSessionId);
991
+ if (persisted?.threadId) {
992
+ resumeThreadId = persisted.threadId;
993
+ console.log(`[CodexProvider] Resuming session ${sessionId} with threadId: ${resumeThreadId}`);
994
+ } else {
995
+ console.warn(`[CodexProvider] Session ${sessionId} resume requested but no persisted threadId found, creating new session`);
996
+ }
997
+ }
998
+ const proc = this.spawnCodexProcess(projectPath, message, resumeThreadId, opts.model);
950
999
  session.pid = proc.pid;
951
1000
  this.activeSessions.set(sessionId, {
952
1001
  session,
953
1002
  process: proc,
954
- threadId: void 0,
1003
+ threadId: resumeThreadId,
955
1004
  turnStartTime: Date.now()
956
1005
  });
957
1006
  const initEvent = {
@@ -983,22 +1032,45 @@ var CodexProvider = class {
983
1032
  this.activeSessions.delete(sessionId);
984
1033
  }
985
1034
  async sendMessage(sessionId, message) {
986
- const entry = this.activeSessions.get(sessionId);
1035
+ let entry = this.activeSessions.get(sessionId);
987
1036
  if (!entry) {
988
- throw new Error(`Session ${sessionId} not found or already ended`);
1037
+ const persisted = this.persistedSessions.get(sessionId);
1038
+ if (!persisted?.threadId) {
1039
+ throw new Error(`Session ${sessionId} not found or already ended`);
1040
+ }
1041
+ const projectId = persisted.projectPath.split("/").filter(Boolean).pop() ?? "unknown";
1042
+ const session = {
1043
+ id: sessionId,
1044
+ projectId,
1045
+ projectPath: persisted.projectPath,
1046
+ status: "running",
1047
+ createdAt: persisted.createdAt,
1048
+ lastActiveAt: Date.now(),
1049
+ summary: persisted.summary,
1050
+ agentType: "codex"
1051
+ };
1052
+ const placeholderProc = (0, import_child_process2.spawn)("true", [], { stdio: "ignore" });
1053
+ entry = {
1054
+ session,
1055
+ process: placeholderProc,
1056
+ threadId: persisted.threadId,
1057
+ turnStartTime: Date.now()
1058
+ };
1059
+ this.activeSessions.set(sessionId, entry);
989
1060
  }
990
- if (entry.process.exitCode === null && entry.process.signalCode === null) {
1061
+ const procAlive = entry.process.exitCode === null && entry.process.signalCode === null;
1062
+ if (procAlive) {
991
1063
  try {
992
1064
  entry.process.stdin?.end();
993
1065
  } catch {
994
1066
  }
995
1067
  await killProcessCrossPlatform(entry.process);
996
1068
  }
997
- const threadId = entry.threadId;
1069
+ const threadId = entry.threadId ?? this.persistedSessions.get(sessionId)?.threadId;
998
1070
  if (!threadId) {
999
- throw new Error(`Session ${sessionId} has no thread ID, cannot resume`);
1071
+ console.warn(`[CodexProvider] Session ${sessionId} has no thread ID, falling back to new thread`);
1000
1072
  }
1001
- const proc = this.spawnCodexProcess(entry.session.projectPath, message, threadId);
1073
+ const proc = this.spawnCodexProcess(entry.session.projectPath, message, threadId ?? void 0);
1002
1074
  entry.session.status = "running";
1003
1075
  entry.session.lastActiveAt = Date.now();
1004
1076
  entry.session.pid = proc.pid;
@@ -1045,16 +1117,29 @@ var CodexProvider = class {
1045
1117
  * @param message 用户消息
1046
1118
  * @param resumeThreadId 如果提供,则使用 `codex exec resume` 恢复会话
1047
1119
  */
1048
- spawnCodexProcess(projectPath, message, resumeThreadId) {
1120
+ spawnCodexProcess(projectPath, message, resumeThreadId, model) {
1049
1121
  const args = ["exec", "--json", "--full-auto"];
1122
+ if (model) {
1123
+ args.push("-m", model);
1124
+ }
1125
+ args.push("-C", projectPath);
1050
1126
  if (resumeThreadId) {
1051
1127
  args.push("resume", resumeThreadId);
1052
- } else {
1053
- args.push("-C", projectPath);
1054
1128
  }
1055
1129
  args.push(message);
1056
1130
  const env = { ...process.env };
1057
- const proc = (0, import_child_process2.spawn)(CODEX_PATH, args, {
1131
+ let cmd;
1132
+ let spawnArgs;
1133
+ if (CODEX_JS_ENTRY) {
1134
+ cmd = process.execPath;
1135
+ spawnArgs = [CODEX_JS_ENTRY, ...args];
1136
+ console.log(`[CodexProvider] Spawning via node: ${cmd} ${CODEX_JS_ENTRY} ${args.join(" ")}`);
1137
+ } else {
1138
+ cmd = CODEX_PATH;
1139
+ spawnArgs = args;
1140
+ console.log(`[CodexProvider] Spawning: ${CODEX_PATH} ${args.join(" ")}`);
1141
+ }
1142
+ const proc = (0, import_child_process2.spawn)(cmd, spawnArgs, {
1058
1143
  cwd: projectPath,
1059
1144
  env,
1060
1145
  stdio: ["pipe", "pipe", "pipe"]
@@ -1076,6 +1161,7 @@ var CodexProvider = class {
1076
1161
  rl.on("line", (line) => {
1077
1162
  const trimmed = line.trim();
1078
1163
  if (!trimmed) return;
1164
+ console.log(`[CodexProvider] Session ${sessionId} stdout: ${trimmed.substring(0, 200)}`);
1079
1165
  let event;
1080
1166
  try {
1081
1167
  event = JSON.parse(trimmed);
@@ -1097,6 +1183,14 @@ var CodexProvider = class {
1097
1183
  case "thread.started": {
1098
1184
  if (event.thread_id) {
1099
1185
  entry.threadId = event.thread_id;
1186
+ this.persistSession(sessionId, {
1187
+ threadId: event.thread_id,
1188
+ projectPath: entry.session.projectPath,
1189
+ summary: entry.session.summary,
1190
+ createdAt: entry.session.createdAt,
1191
+ lastActiveAt: Date.now()
1192
+ });
1193
+ console.log(`[CodexProvider] Session ${sessionId} threadId persisted: ${event.thread_id}`);
1100
1194
  }
1101
1195
  break;
1102
1196
  }
@@ -1260,6 +1354,7 @@ var CodexProvider = class {
1260
1354
  const entry = this.activeSessions.get(sessionId);
1261
1355
  if (!entry) return;
1262
1356
  if (entry.process !== proc) return;
1357
+ console.log(`[CodexProvider] Session ${sessionId} process exited: code=${code} signal=${signal} threadId=${entry.threadId ?? "NONE"}`);
1263
1358
  if (entry.rl) {
1264
1359
  entry.rl.close();
1265
1360
  entry.rl = void 0;
@@ -1300,6 +1395,54 @@ var CodexProvider = class {
1300
1395
  getEventName(sessionId) {
1301
1396
  return `claude:${sessionId}`;
1302
1397
  }
1398
+ // ============================================
1399
+ // 持久化方法
1400
+ // ============================================
1401
+ /**
1402
+ * 从磁盘加载持久化的 Codex 会话元数据
1403
+ */
1404
+ loadPersistedSessions() {
1405
+ try {
1406
+ if (!(0, import_fs.existsSync)(CODEX_SESSIONS_FILE)) return;
1407
+ const data = JSON.parse((0, import_fs.readFileSync)(CODEX_SESSIONS_FILE, "utf-8"));
1408
+ for (const [sessionId, meta] of Object.entries(data)) {
1409
+ this.persistedSessions.set(sessionId, meta);
1410
+ }
1411
+ console.log(`[CodexProvider] Loaded ${this.persistedSessions.size} persisted sessions`);
1412
+ } catch (err) {
1413
+ console.warn("[CodexProvider] Failed to load persisted sessions:", err);
1414
+ }
1415
+ }
1416
+ /**
1417
+ * 持久化单个会话的元数据到磁盘
1418
+ */
1419
+ persistSession(sessionId, meta) {
1420
+ this.persistedSessions.set(sessionId, meta);
1421
+ this.flushPersistedSessions();
1422
+ }
1423
+ /**
1424
+ * 将所有持久化数据写入磁盘
1425
+ */
1426
+ flushPersistedSessions() {
1427
+ try {
1428
+ if (!(0, import_fs.existsSync)(SESSIX_DIR)) {
1429
+ (0, import_fs.mkdirSync)(SESSIX_DIR, { recursive: true });
1430
+ }
1431
+ const data = {};
1432
+ for (const [sessionId, meta] of this.persistedSessions) {
1433
+ data[sessionId] = meta;
1434
+ }
1435
+ (0, import_fs.writeFileSync)(CODEX_SESSIONS_FILE, JSON.stringify(data, null, 2), "utf-8");
1436
+ } catch (err) {
1437
+ console.error("[CodexProvider] Failed to persist sessions:", err);
1438
+ }
1439
+ }
1440
+ /**
1441
+ * 检查某个 sessionId 是否为已知的 Codex 会话(供 SessionManager 查询 agentType)
1442
+ */
1443
+ isKnownSession(sessionId) {
1444
+ return this.activeSessions.has(sessionId) || this.persistedSessions.has(sessionId);
1445
+ }
1303
1446
  };
1304
1447
 
1305
1448
  // src/providers/ProviderFactory.ts
@@ -1392,8 +1535,15 @@ var SessionManager = class {
1392
1535
  }
1393
1536
  getProviderForSession(sessionId) {
1394
1537
  if (!this.providerFactory) return this.provider;
1395
- const agentType = this.sessionAgentType.get(sessionId) ?? "claude-code";
1396
- return this.providerFactory.getProvider(agentType);
1538
+ let agentType = this.sessionAgentType.get(sessionId);
1539
+ if (!agentType) {
1540
+ const codexProvider = this.providerFactory.getProvider("codex");
1541
+ if (codexProvider instanceof CodexProvider && codexProvider.isKnownSession(sessionId)) {
1542
+ agentType = "codex";
1543
+ this.sessionAgentType.set(sessionId, agentType);
1544
+ }
1545
+ }
1546
+ return this.providerFactory.getProvider(agentType ?? "claude-code");
1397
1547
  }
1398
1548
  // ============================================
1399
1549
  // 公开 API
@@ -1772,8 +1922,8 @@ var SessionManager = class {
1772
1922
  };
1773
1923
  this.updateSessionStatus(sessionId, "waiting_question");
1774
1924
  this.emit({ type: "question_request", request });
1775
- const answerPromise = new Promise((resolve) => {
1776
- this.pendingQuestions.set(requestId, { sessionId, toolUseId, question, options, createdAt: request.createdAt, resolve });
1925
+ const answerPromise = new Promise((resolve2) => {
1926
+ this.pendingQuestions.set(requestId, { sessionId, toolUseId, question, options, createdAt: request.createdAt, resolve: resolve2 });
1777
1927
  });
1778
1928
  answerPromise.then(async (answer) => {
1779
1929
  try {
@@ -2015,11 +2165,11 @@ var WsBridge = class _WsBridge {
2015
2165
  * 使用此方法代替 new WsBridge(),确保 EADDRINUSE 等错误能被调用方的 try-catch 捕获。
2016
2166
  */
2017
2167
  static async create(options) {
2018
- return new Promise((resolve, reject) => {
2168
+ return new Promise((resolve2, reject) => {
2019
2169
  const bridge = new _WsBridge(options);
2020
2170
  bridge.wss.once("listening", () => {
2021
2171
  bridge.wss.on("error", (err) => console.error(`[WsBridge] ${t("ws.serverError")}:`, err));
2022
- resolve(bridge);
2172
+ resolve2(bridge);
2023
2173
  });
2024
2174
  bridge.wss.once("error", reject);
2025
2175
  });
@@ -2082,7 +2232,7 @@ var WsBridge = class _WsBridge {
2082
2232
  }
2083
2233
  /** 优雅关闭 WebSocket 服务 */
2084
2234
  close() {
2085
- return new Promise((resolve, reject) => {
2235
+ return new Promise((resolve2, reject) => {
2086
2236
  if (this.heartbeatTimer) {
2087
2237
  clearInterval(this.heartbeatTimer);
2088
2238
  this.heartbeatTimer = null;
@@ -2095,7 +2245,7 @@ var WsBridge = class _WsBridge {
2095
2245
  reject(err);
2096
2246
  } else {
2097
2247
  console.log("[WsBridge] WebSocket server closed");
2098
- resolve();
2248
+ resolve2();
2099
2249
  }
2100
2250
  });
2101
2251
  });
@@ -2235,11 +2385,11 @@ var ApprovalProxy = class _ApprovalProxy {
2235
2385
  * 异步工厂方法:等待端口监听成功后 resolve,端口占用等错误时 reject。
2236
2386
  */
2237
2387
  static async create(options) {
2238
- return new Promise((resolve, reject) => {
2388
+ return new Promise((resolve2, reject) => {
2239
2389
  const proxy = new _ApprovalProxy(options);
2240
2390
  proxy.server.once("listening", () => {
2241
2391
  proxy.server.on("error", (err) => console.error(`[ApprovalProxy] ${t("approval.serverError")}:`, err));
2242
- resolve(proxy);
2392
+ resolve2(proxy);
2243
2393
  });
2244
2394
  proxy.server.once("error", reject);
2245
2395
  });
@@ -2396,7 +2546,7 @@ var ApprovalProxy = class _ApprovalProxy {
2396
2546
  }
2397
2547
  /** 优雅关闭 HTTP 服务 */
2398
2548
  close() {
2399
- return new Promise((resolve, reject) => {
2549
+ return new Promise((resolve2, reject) => {
2400
2550
  const pendingEntries = Array.from(this.pendingApprovals.entries());
2401
2551
  for (const [, pending] of pendingEntries) {
2402
2552
  clearTimeout(pending.timer);
@@ -2408,7 +2558,7 @@ var ApprovalProxy = class _ApprovalProxy {
2408
2558
  reject(err);
2409
2559
  } else {
2410
2560
  console.log(`[ApprovalProxy] ${t("approval.httpClosed")}`);
2411
- resolve();
2561
+ resolve2();
2412
2562
  }
2413
2563
  });
2414
2564
  });
@@ -2479,13 +2629,13 @@ var ApprovalProxy = class _ApprovalProxy {
2479
2629
  return;
2480
2630
  }
2481
2631
  this.notifyApprovalRequest(approvalRequest);
2482
- const decision = await new Promise((resolve) => {
2632
+ const decision = await new Promise((resolve2) => {
2483
2633
  const timer = setTimeout(() => {
2484
2634
  console.log(`[ApprovalProxy] ${t("approval.timeout", { id: requestId })}`);
2485
2635
  this.pendingApprovals.delete(requestId);
2486
- resolve({ decision: "allow" });
2636
+ resolve2({ decision: "allow" });
2487
2637
  }, 325e3);
2488
- this.pendingApprovals.set(requestId, { resolve, timer, request: approvalRequest });
2638
+ this.pendingApprovals.set(requestId, { resolve: resolve2, timer, request: approvalRequest });
2489
2639
  });
2490
2640
  this.sendJson(res, 200, decision);
2491
2641
  } catch (err) {
@@ -2546,7 +2696,7 @@ var ApprovalProxy = class _ApprovalProxy {
2546
2696
  /** 手动解析请求的 JSON body(限制最大 1MB 防止滥用) */
2547
2697
  parseJsonBody(req) {
2548
2698
  const MAX_BODY_SIZE = 1024 * 1024;
2549
- return new Promise((resolve, reject) => {
2699
+ return new Promise((resolve2, reject) => {
2550
2700
  const chunks = [];
2551
2701
  let totalSize = 0;
2552
2702
  let destroyed = false;
@@ -2564,7 +2714,7 @@ var ApprovalProxy = class _ApprovalProxy {
2564
2714
  try {
2565
2715
  const raw = Buffer.concat(chunks).toString("utf-8");
2566
2716
  const parsed = JSON.parse(raw);
2567
- resolve(parsed);
2717
+ resolve2(parsed);
2568
2718
  } catch {
2569
2719
  reject(new Error(t("approval.invalidJson")));
2570
2720
  }
@@ -3231,12 +3381,12 @@ var DesktopNotificationChannel = class {
3231
3381
  const body = payload.body.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
3232
3382
  const sound = payload.sound ?? "Ping";
3233
3383
  const script = `display notification "${body}" with title "${title}" sound name "${sound}"`;
3234
- return new Promise((resolve) => {
3384
+ return new Promise((resolve2) => {
3235
3385
  (0, import_node_child_process6.execFile)("osascript", ["-e", script], (err) => {
3236
3386
  if (err) {
3237
3387
  console.warn("[DesktopNotificationChannel] Send notification failed:", err.message);
3238
3388
  }
3239
- resolve();
3389
+ resolve2();
3240
3390
  });
3241
3391
  });
3242
3392
  }
@@ -3436,7 +3586,7 @@ var ActivityPushChannel = class {
3436
3586
  const topic = "com.kachun.sessix.push-type.liveactivity";
3437
3587
  const jwt = this.getJWT();
3438
3588
  const payloadStr = JSON.stringify(payload);
3439
- return new Promise((resolve, reject) => {
3589
+ return new Promise((resolve2, reject) => {
3440
3590
  let client;
3441
3591
  try {
3442
3592
  client = this.getHttp2Client();
@@ -3464,7 +3614,7 @@ var ActivityPushChannel = class {
3464
3614
  });
3465
3615
  req.on("end", () => {
3466
3616
  if (statusCode === 200) {
3467
- resolve();
3617
+ resolve2();
3468
3618
  } else {
3469
3619
  if (statusCode === 0) {
3470
3620
  this.http2Client?.destroy();
@@ -3507,11 +3657,11 @@ var ActivityPushChannel = class {
3507
3657
  // src/session/ProjectReader.ts
3508
3658
  var import_promises3 = require("fs/promises");
3509
3659
  var import_readline3 = require("readline");
3510
- var import_path = require("path");
3511
- var import_os = require("os");
3512
- var CLAUDE_PROJECTS_DIR = (0, import_path.join)((0, import_os.homedir)(), ".claude", "projects");
3660
+ var import_path2 = require("path");
3661
+ var import_os2 = require("os");
3662
+ var CLAUDE_PROJECTS_DIR = (0, import_path2.join)((0, import_os2.homedir)(), ".claude", "projects");
3513
3663
  function getSessionFilePath(projectPath, sessionId) {
3514
- return (0, import_path.join)(CLAUDE_PROJECTS_DIR, encodeDirName(projectPath), `${sessionId}.jsonl`);
3664
+ return (0, import_path2.join)(CLAUDE_PROJECTS_DIR, encodeDirName(projectPath), `${sessionId}.jsonl`);
3515
3665
  }
3516
3666
  async function getProjects() {
3517
3667
  try {
@@ -3528,7 +3678,7 @@ async function getProjects() {
3528
3678
  const encodedPath = entry.name;
3529
3679
  const decodedPath = decodeDirName(encodedPath);
3530
3680
  const name = decodedPath.split("/").filter(Boolean).pop() ?? encodedPath;
3531
- const projectDir = (0, import_path.join)(CLAUDE_PROJECTS_DIR, encodedPath);
3681
+ const projectDir = (0, import_path2.join)(CLAUDE_PROJECTS_DIR, encodedPath);
3532
3682
  const { count: sessionCount, latestMtime } = await countJsonlFilesWithMtime(projectDir);
3533
3683
  projects.push({
3534
3684
  id: encodedPath,
@@ -3550,7 +3700,7 @@ async function getProjects() {
3550
3700
  async function getHistoricalSessions(projectPath) {
3551
3701
  try {
3552
3702
  const encodedPath = encodeDirName(projectPath);
3553
- const projectDir = (0, import_path.join)(CLAUDE_PROJECTS_DIR, encodedPath);
3703
+ const projectDir = (0, import_path2.join)(CLAUDE_PROJECTS_DIR, encodedPath);
3554
3704
  const dirExists = await directoryExists(projectDir);
3555
3705
  if (!dirExists) {
3556
3706
  return { ok: true, value: [] };
@@ -3561,7 +3711,7 @@ async function getHistoricalSessions(projectPath) {
3561
3711
  await Promise.all(
3562
3712
  jsonlFiles.map(async (entry) => {
3563
3713
  const sessionId = entry.name.slice(0, -6);
3564
- const filePath = (0, import_path.join)(projectDir, entry.name);
3714
+ const filePath = (0, import_path2.join)(projectDir, entry.name);
3565
3715
  try {
3566
3716
  const contentTs = await extractLastTimestamp(filePath);
3567
3717
  if (contentTs) {
@@ -3580,13 +3730,13 @@ async function getHistoricalSessions(projectPath) {
3580
3730
  );
3581
3731
  for (const entry of uuidDirs) {
3582
3732
  try {
3583
- const fileStat = await (0, import_promises3.stat)((0, import_path.join)(projectDir, entry.name));
3733
+ const fileStat = await (0, import_promises3.stat)((0, import_path2.join)(projectDir, entry.name));
3584
3734
  mtimeMap.set(entry.name, fileStat.mtimeMs);
3585
3735
  } catch {
3586
3736
  mtimeMap.set(entry.name, 0);
3587
3737
  }
3588
3738
  }
3589
- const indexPath = (0, import_path.join)(projectDir, "sessions-index.json");
3739
+ const indexPath = (0, import_path2.join)(projectDir, "sessions-index.json");
3590
3740
  const sessionMap = /* @__PURE__ */ new Map();
3591
3741
  try {
3592
3742
  const indexContent = await (0, import_promises3.readFile)(indexPath, "utf-8");
@@ -3604,7 +3754,7 @@ async function getHistoricalSessions(projectPath) {
3604
3754
  }
3605
3755
  await Promise.all(
3606
3756
  Array.from(sessionMap.values()).filter((s) => (s.messageCount ?? 0) > 0 && !s.summary && !s.firstPrompt).map(async (s) => {
3607
- const filePath = (0, import_path.join)(projectDir, `${s.sessionId}.jsonl`);
3757
+ const filePath = (0, import_path2.join)(projectDir, `${s.sessionId}.jsonl`);
3608
3758
  const firstPrompt = await extractFirstPrompt(filePath).catch(() => void 0);
3609
3759
  if (firstPrompt) s.firstPrompt = firstPrompt;
3610
3760
  })
@@ -3618,7 +3768,7 @@ async function getHistoricalSessions(projectPath) {
3618
3768
  if (uuidDirSet.has(sessionId)) {
3619
3769
  sessionMap.set(sessionId, { sessionId, lastModified: mtime, messageCount: -1 });
3620
3770
  } else {
3621
- const filePath = (0, import_path.join)(projectDir, `${sessionId}.jsonl`);
3771
+ const filePath = (0, import_path2.join)(projectDir, `${sessionId}.jsonl`);
3622
3772
  const firstPrompt = await extractFirstPrompt(filePath).catch(() => void 0);
3623
3773
  sessionMap.set(sessionId, { sessionId, lastModified: mtime, firstPrompt });
3624
3774
  }
@@ -3642,7 +3792,7 @@ async function getHistoricalSessions(projectPath) {
3642
3792
  async function getSessionHistory(projectPath, sessionId) {
3643
3793
  try {
3644
3794
  const encodedPath = encodeDirName(projectPath);
3645
- const filePath = (0, import_path.join)(CLAUDE_PROJECTS_DIR, encodedPath, `${sessionId}.jsonl`);
3795
+ const filePath = (0, import_path2.join)(CLAUDE_PROJECTS_DIR, encodedPath, `${sessionId}.jsonl`);
3646
3796
  const raw = await (0, import_promises3.readFile)(filePath, "utf-8").catch((err) => {
3647
3797
  if (err.code === "ENOENT") return null;
3648
3798
  throw err;
@@ -3823,15 +3973,15 @@ async function countJsonlFilesWithMtime(dirPath) {
3823
3973
  await Promise.all([
3824
3974
  ...jsonlEntries.map(async (entry) => {
3825
3975
  try {
3826
- const contentTs = await extractLastTimestamp((0, import_path.join)(dirPath, entry.name));
3827
- const ts = contentTs ?? (await (0, import_promises3.stat)((0, import_path.join)(dirPath, entry.name))).mtimeMs;
3976
+ const contentTs = await extractLastTimestamp((0, import_path2.join)(dirPath, entry.name));
3977
+ const ts = contentTs ?? (await (0, import_promises3.stat)((0, import_path2.join)(dirPath, entry.name))).mtimeMs;
3828
3978
  if (ts > latestMtime) latestMtime = ts;
3829
3979
  } catch {
3830
3980
  }
3831
3981
  }),
3832
3982
  ...uuidDirs.map(async (entry) => {
3833
3983
  try {
3834
- const fileStat = await (0, import_promises3.stat)((0, import_path.join)(dirPath, entry.name));
3984
+ const fileStat = await (0, import_promises3.stat)((0, import_path2.join)(dirPath, entry.name));
3835
3985
  if (fileStat.mtimeMs > latestMtime) latestMtime = fileStat.mtimeMs;
3836
3986
  } catch {
3837
3987
  }
@@ -4137,7 +4287,7 @@ async function killPortProcess(port) {
4137
4287
  await execAsync(`kill -9 ${pids.join(" ")}`);
4138
4288
  }
4139
4289
  }
4140
- await new Promise((resolve) => setTimeout(resolve, 600));
4290
+ await new Promise((resolve2) => setTimeout(resolve2, 600));
4141
4291
  } catch {
4142
4292
  }
4143
4293
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sessix-server",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "bin": {
5
5
  "sessix-server": "./dist/index.js"
6
6
  },