sessix-server 0.2.9 → 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 +823 -125
  2. package/dist/server.js +790 -113
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -24,9 +24,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // src/index.ts
27
- var import_node_os7 = require("os");
28
- var import_node_fs3 = require("fs");
29
- var import_node_path6 = require("path");
27
+ var import_node_os8 = require("os");
28
+ var import_node_fs4 = require("fs");
29
+ var import_node_path7 = require("path");
30
+ var import_node_child_process9 = require("child_process");
30
31
 
31
32
  // src/i18n/locales/zh.ts
32
33
  var zh = {
@@ -51,6 +52,9 @@ var zh = {
51
52
  tokenRegenerated: "\u{1F511} Token \u5DF2\u91CD\u7F6E\uFF0C\u6240\u6709\u5BA2\u6237\u7AEF\u5DF2\u65AD\u5F00\uFF0C\u8BF7\u91CD\u65B0\u626B\u7801\u914D\u5BF9",
52
53
  tokenRegenerateFailed: "Token \u91CD\u7F6E\u5931\u8D25:",
53
54
  updateAvailable: "\u53D1\u73B0\u65B0\u7248\u672C v{{latest}}\uFF08\u5F53\u524D v{{current}}\uFF09\uFF0C\u8FD0\u884C\u4EE5\u4E0B\u547D\u4EE4\u66F4\u65B0\uFF1A",
55
+ autoUpdating: "\u53D1\u73B0\u65B0\u7248\u672C v{{latest}}\uFF08\u5F53\u524D v{{current}}\uFF09\uFF0C\u6B63\u5728\u81EA\u52A8\u66F4\u65B0...",
56
+ autoUpdateFailed: "\u81EA\u52A8\u66F4\u65B0\u5931\u8D25\uFF0C\u7EE7\u7EED\u4F7F\u7528\u5F53\u524D\u7248\u672C",
57
+ skipUpdateCheck: "\u8DF3\u8FC7\u7248\u672C\u68C0\u67E5\uFF08SESSIX_NO_UPDATE_CHECK=1\uFF09",
54
58
  receivedSignal: "\u6536\u5230 {{signal}}\uFF0C\u6B63\u5728\u4F18\u96C5\u5173\u95ED...",
55
59
  goodbye: "\u6240\u6709\u670D\u52A1\u5DF2\u5173\u95ED\uFF0C\u518D\u89C1\uFF01",
56
60
  shutdownError: "\u5173\u95ED\u8FC7\u7A0B\u51FA\u9519:",
@@ -161,6 +165,9 @@ var en = {
161
165
  tokenRegenerated: "\u{1F511} Token regenerated, all clients disconnected. Scan QR to re-pair",
162
166
  tokenRegenerateFailed: "Token regeneration failed:",
163
167
  updateAvailable: "New version v{{latest}} available (current v{{current}}). Update with:",
168
+ autoUpdating: "New version v{{latest}} available (current v{{current}}), auto-updating...",
169
+ autoUpdateFailed: "Auto-update failed, continuing with current version",
170
+ skipUpdateCheck: "Skipping update check (SESSIX_NO_UPDATE_CHECK=1)",
164
171
  receivedSignal: "Received {{signal}}, graceful shutdown...",
165
172
  goodbye: "All services closed, goodbye!",
166
173
  shutdownError: "Shutdown error:",
@@ -295,11 +302,11 @@ function t(key, params) {
295
302
  }
296
303
 
297
304
  // src/server.ts
298
- var import_uuid5 = require("uuid");
305
+ var import_uuid6 = require("uuid");
299
306
  var import_promises4 = require("fs/promises");
300
- var import_node_os6 = require("os");
301
- var import_node_path5 = require("path");
302
- var import_node_child_process6 = require("child_process");
307
+ var import_node_os7 = require("os");
308
+ var import_node_path6 = require("path");
309
+ var import_node_child_process8 = require("child_process");
303
310
  var import_node_util = require("util");
304
311
 
305
312
  // src/providers/ProcessProvider.ts
@@ -319,14 +326,14 @@ var import_node_os = require("os");
319
326
  var import_node_child_process = require("child_process");
320
327
  var isWindows = process.platform === "win32";
321
328
  function killProcessCrossPlatform(proc, timeoutMs = 3e3) {
322
- return new Promise((resolve) => {
329
+ return new Promise((resolve2) => {
323
330
  if (proc.exitCode !== null || proc.signalCode !== null) {
324
- resolve();
331
+ resolve2();
325
332
  return;
326
333
  }
327
334
  const onExit = () => {
328
335
  clearTimeout(timer);
329
- resolve();
336
+ resolve2();
330
337
  };
331
338
  proc.once("exit", onExit);
332
339
  if (isWindows) {
@@ -342,7 +349,7 @@ function killProcessCrossPlatform(proc, timeoutMs = 3e3) {
342
349
  proc.kill("SIGKILL");
343
350
  }
344
351
  }
345
- resolve();
352
+ resolve2();
346
353
  }, timeoutMs);
347
354
  });
348
355
  }
@@ -644,7 +651,9 @@ var ProcessProvider = class {
644
651
  const event = result.value;
645
652
  if (event.type === "assistant") {
646
653
  for (const block of event.message.content) {
647
- if (block.type === "tool_use" && block.name === "AskUserQuestion") {
654
+ if (block.type === "tool_use") {
655
+ const isQuestion = block.name === "AskUserQuestion" || block.name === "AskFollowupQuestion";
656
+ if (!isQuestion) continue;
648
657
  const input = block.input;
649
658
  const question = input.question ?? "";
650
659
  if (!question) continue;
@@ -656,6 +665,7 @@ var ProcessProvider = class {
656
665
  }
657
666
  if (sessionSet.has(prevKey)) continue;
658
667
  sessionSet.add(prevKey);
668
+ console.log(`[ProcessProvider] Session ${sessionId}: detected ${block.name} (toolUseId=${block.id})`);
659
669
  this.emitter.emit(this.getQuestionEventName(sessionId), {
660
670
  toolUseId: block.id,
661
671
  question,
@@ -771,7 +781,7 @@ var ProcessProvider = class {
771
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):
772
782
 
773
783
  ${context}`;
774
- return new Promise((resolve, reject) => {
784
+ return new Promise((resolve2, reject) => {
775
785
  const env = { ...process.env };
776
786
  delete env.CLAUDECODE;
777
787
  const proc = (0, import_child_process.spawn)(CLAUDE_PATH, ["-p", prompt, "--output-format", "text"], {
@@ -786,7 +796,7 @@ ${context}`;
786
796
  });
787
797
  proc.once("exit", (code) => {
788
798
  if (code === 0) {
789
- resolve(output.trim());
799
+ resolve2(output.trim());
790
800
  } else {
791
801
  reject(new Error(`generateSuggestion process exit code: ${code}`));
792
802
  }
@@ -813,10 +823,10 @@ ${context}`;
813
823
  tool_use_id: toolUseId,
814
824
  content: answer
815
825
  });
816
- await new Promise((resolve, reject) => {
826
+ await new Promise((resolve2, reject) => {
817
827
  entry.process.stdin.write(toolResult + "\n", (err) => {
818
828
  if (err) reject(err);
819
- else resolve();
829
+ else resolve2();
820
830
  });
821
831
  });
822
832
  console.log(`[ProcessProvider] Session ${sessionId}: AskUserQuestion answered (toolUseId=${toolUseId})`);
@@ -847,11 +857,643 @@ ${context}`;
847
857
  }
848
858
  };
849
859
 
850
- // src/session/SessionManager.ts
860
+ // src/providers/CodexProvider.ts
861
+ var import_child_process2 = require("child_process");
862
+ var import_readline2 = require("readline");
863
+ var import_events2 = require("events");
864
+ var import_fs = require("fs");
865
+ var import_path = require("path");
866
+ var import_os = require("os");
851
867
  var import_uuid2 = require("uuid");
868
+
869
+ // src/utils/codexPath.ts
870
+ var import_node_child_process3 = require("child_process");
871
+ var import_node_fs2 = require("fs");
872
+ var import_node_path2 = require("path");
873
+ var import_node_os3 = require("os");
874
+ function findCodexPath() {
875
+ try {
876
+ const cmd = isWindows ? "where codex" : "which codex";
877
+ return (0, import_node_child_process3.execSync)(cmd, { encoding: "utf-8" }).trim().split("\n")[0];
878
+ } catch {
879
+ }
880
+ const candidates = isWindows ? [
881
+ (0, import_node_path2.join)(process.env.LOCALAPPDATA ?? "", "Programs", "codex", "codex.exe"),
882
+ (0, import_node_path2.join)((0, import_node_os3.homedir)(), ".codex", "local", "codex.exe")
883
+ ] : [
884
+ "/opt/homebrew/bin/codex",
885
+ "/usr/local/bin/codex",
886
+ (0, import_node_path2.join)((0, import_node_os3.homedir)(), ".local", "bin", "codex")
887
+ ];
888
+ for (const candidate of candidates) {
889
+ try {
890
+ (0, import_node_fs2.accessSync)(candidate, import_node_fs2.constants.X_OK);
891
+ return candidate;
892
+ } catch {
893
+ }
894
+ }
895
+ return "codex";
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
+ }
916
+ var CODEX_PATH = findCodexPath();
917
+ var CODEX_JS_ENTRY = resolveCodexJsEntry(CODEX_PATH);
918
+ async function getCodexVersion() {
919
+ try {
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, {
922
+ encoding: "utf-8",
923
+ timeout: 5e3
924
+ }).trim();
925
+ return output.replace(/^codex-cli\s+/, "");
926
+ } catch {
927
+ return void 0;
928
+ }
929
+ }
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
+ }
939
+ try {
940
+ (0, import_node_fs2.accessSync)(CODEX_PATH, import_node_fs2.constants.X_OK);
941
+ return true;
942
+ } catch {
943
+ try {
944
+ (0, import_node_child_process3.execSync)(`"${CODEX_PATH}" --version`, { timeout: 5e3 });
945
+ return true;
946
+ } catch {
947
+ return false;
948
+ }
949
+ }
950
+ }
951
+
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");
955
+ var CodexProvider = class {
956
+ activeSessions = /* @__PURE__ */ new Map();
957
+ emitter = new import_events2.EventEmitter();
958
+ /** 持久化的会话元数据(sessionId → metadata) */
959
+ persistedSessions = /* @__PURE__ */ new Map();
960
+ /** 自增 ID 计数器,用于生成 ClaudeStreamEvent 中的 message/block ID */
961
+ idCounter = 0;
962
+ constructor() {
963
+ this.loadPersistedSessions();
964
+ }
965
+ async startSession(opts) {
966
+ const { projectPath, message, sessionId: existingSessionId } = opts;
967
+ const sessionId = existingSessionId ?? (0, import_uuid2.v4)();
968
+ if (this.activeSessions.has(sessionId)) {
969
+ await this.killSession(sessionId);
970
+ }
971
+ const projectId = projectPath.split("/").filter(Boolean).pop() ?? "unknown";
972
+ const session = {
973
+ id: sessionId,
974
+ projectId,
975
+ projectPath,
976
+ status: "running",
977
+ createdAt: Date.now(),
978
+ lastActiveAt: Date.now(),
979
+ summary: message.slice(0, 80),
980
+ agentType: "codex"
981
+ };
982
+ const resume = opts.resume ?? !!existingSessionId;
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);
994
+ session.pid = proc.pid;
995
+ this.activeSessions.set(sessionId, {
996
+ session,
997
+ process: proc,
998
+ threadId: resumeThreadId,
999
+ turnStartTime: Date.now()
1000
+ });
1001
+ const initEvent = {
1002
+ type: "system",
1003
+ subtype: "init",
1004
+ session_id: sessionId
1005
+ };
1006
+ this.emitter.emit(this.getEventName(sessionId), initEvent);
1007
+ proc.on("error", (err) => {
1008
+ console.error(`[CodexProvider] Session ${sessionId} process error:`, err.message);
1009
+ this.activeSessions.delete(sessionId);
1010
+ this.emitError(sessionId, `Process spawn failed: ${err.message}`);
1011
+ });
1012
+ this.attachStdoutListener(sessionId, proc);
1013
+ this.attachStderrListener(sessionId, proc);
1014
+ this.attachExitListener(sessionId, proc);
1015
+ return session;
1016
+ }
1017
+ async killSession(sessionId) {
1018
+ const entry = this.activeSessions.get(sessionId);
1019
+ if (!entry) return;
1020
+ if (entry.process.exitCode === null && entry.process.signalCode === null) {
1021
+ try {
1022
+ entry.process.stdin?.end();
1023
+ } catch {
1024
+ }
1025
+ await killProcessCrossPlatform(entry.process);
1026
+ }
1027
+ this.activeSessions.delete(sessionId);
1028
+ }
1029
+ async sendMessage(sessionId, message) {
1030
+ let entry = this.activeSessions.get(sessionId);
1031
+ if (!entry) {
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);
1055
+ }
1056
+ const procAlive = entry.process.exitCode === null && entry.process.signalCode === null;
1057
+ if (procAlive) {
1058
+ try {
1059
+ entry.process.stdin?.end();
1060
+ } catch {
1061
+ }
1062
+ await killProcessCrossPlatform(entry.process);
1063
+ }
1064
+ const threadId = entry.threadId ?? this.persistedSessions.get(sessionId)?.threadId;
1065
+ if (!threadId) {
1066
+ console.warn(`[CodexProvider] Session ${sessionId} has no thread ID, falling back to new thread`);
1067
+ }
1068
+ const proc = this.spawnCodexProcess(entry.session.projectPath, message, threadId ?? void 0);
1069
+ entry.session.status = "running";
1070
+ entry.session.lastActiveAt = Date.now();
1071
+ entry.session.pid = proc.pid;
1072
+ entry.process = proc;
1073
+ entry.turnStartTime = Date.now();
1074
+ proc.on("error", (err) => {
1075
+ console.error(`[CodexProvider] Session ${sessionId} sendMessage process error:`, err.message);
1076
+ this.activeSessions.delete(sessionId);
1077
+ this.emitError(sessionId, `Failed to send message: ${err.message}`);
1078
+ });
1079
+ this.attachStdoutListener(sessionId, proc);
1080
+ this.attachStderrListener(sessionId, proc);
1081
+ this.attachExitListener(sessionId, proc);
1082
+ }
1083
+ onEvent(sessionId, callback) {
1084
+ const eventName = this.getEventName(sessionId);
1085
+ this.emitter.on(eventName, callback);
1086
+ return () => {
1087
+ this.emitter.off(eventName, callback);
1088
+ };
1089
+ }
1090
+ getActiveSessions() {
1091
+ return Array.from(this.activeSessions.values()).map((e) => e.session);
1092
+ }
1093
+ async generateSuggestion(_context) {
1094
+ return "";
1095
+ }
1096
+ async answerQuestion(_sessionId, _toolUseId, _answer) {
1097
+ }
1098
+ onQuestion(_sessionId, _callback) {
1099
+ return () => {
1100
+ };
1101
+ }
1102
+ // ============================================
1103
+ // 私有方法
1104
+ // ============================================
1105
+ nextId() {
1106
+ return `codex_${++this.idCounter}`;
1107
+ }
1108
+ /**
1109
+ * 启动 codex CLI 进程
1110
+ *
1111
+ * @param projectPath 工作目录
1112
+ * @param message 用户消息
1113
+ * @param resumeThreadId 如果提供,则使用 `codex exec resume` 恢复会话
1114
+ */
1115
+ spawnCodexProcess(projectPath, message, resumeThreadId, model) {
1116
+ const args = ["exec", "--json", "--full-auto"];
1117
+ if (model) {
1118
+ args.push("-m", model);
1119
+ }
1120
+ args.push("-C", projectPath);
1121
+ if (resumeThreadId) {
1122
+ args.push("resume", resumeThreadId);
1123
+ }
1124
+ args.push(message);
1125
+ const env = { ...process.env };
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, {
1138
+ cwd: projectPath,
1139
+ env,
1140
+ stdio: ["pipe", "pipe", "pipe"]
1141
+ });
1142
+ try {
1143
+ proc.stdin?.end();
1144
+ } catch {
1145
+ }
1146
+ return proc;
1147
+ }
1148
+ /**
1149
+ * 挂载 stdout 监听器,逐行解析 Codex NDJSON 并转换为 ClaudeStreamEvent
1150
+ */
1151
+ attachStdoutListener(sessionId, proc) {
1152
+ if (!proc.stdout) return;
1153
+ const rl = (0, import_readline2.createInterface)({ input: proc.stdout, crlfDelay: Infinity });
1154
+ const entry = this.activeSessions.get(sessionId);
1155
+ if (entry) entry.rl = rl;
1156
+ rl.on("line", (line) => {
1157
+ const trimmed = line.trim();
1158
+ if (!trimmed) return;
1159
+ console.log(`[CodexProvider] Session ${sessionId} stdout: ${trimmed.substring(0, 200)}`);
1160
+ let event;
1161
+ try {
1162
+ event = JSON.parse(trimmed);
1163
+ } catch {
1164
+ console.warn(`[CodexProvider] Session ${sessionId}: failed to parse: ${trimmed.substring(0, 100)}`);
1165
+ return;
1166
+ }
1167
+ this.handleCodexEvent(sessionId, event);
1168
+ });
1169
+ }
1170
+ /**
1171
+ * 处理单个 Codex NDJSON 事件,转换并发射 ClaudeStreamEvent
1172
+ */
1173
+ handleCodexEvent(sessionId, event) {
1174
+ const entry = this.activeSessions.get(sessionId);
1175
+ if (!entry) return;
1176
+ entry.session.lastActiveAt = Date.now();
1177
+ switch (event.type) {
1178
+ case "thread.started": {
1179
+ if (event.thread_id) {
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}`);
1189
+ }
1190
+ break;
1191
+ }
1192
+ case "turn.started": {
1193
+ entry.session.status = "running";
1194
+ entry.turnStartTime = Date.now();
1195
+ break;
1196
+ }
1197
+ case "item.completed": {
1198
+ if (!event.item) break;
1199
+ this.handleItemCompleted(sessionId, event.item);
1200
+ break;
1201
+ }
1202
+ case "turn.completed": {
1203
+ entry.session.status = "idle";
1204
+ const duration = entry.turnStartTime ? Date.now() - entry.turnStartTime : 0;
1205
+ const resultEvent = {
1206
+ type: "result",
1207
+ subtype: "success",
1208
+ session_id: sessionId,
1209
+ is_error: false,
1210
+ result: "",
1211
+ duration_ms: duration,
1212
+ num_turns: 1,
1213
+ usage: event.usage
1214
+ };
1215
+ this.emitter.emit(this.getEventName(sessionId), resultEvent);
1216
+ break;
1217
+ }
1218
+ case "turn.failed": {
1219
+ entry.session.status = "error";
1220
+ const failEvent = {
1221
+ type: "result",
1222
+ subtype: "error",
1223
+ session_id: sessionId,
1224
+ is_error: true,
1225
+ result: event.error ?? "Turn failed",
1226
+ duration_ms: entry.turnStartTime ? Date.now() - entry.turnStartTime : 0,
1227
+ num_turns: 1
1228
+ };
1229
+ this.emitter.emit(this.getEventName(sessionId), failEvent);
1230
+ break;
1231
+ }
1232
+ }
1233
+ }
1234
+ /**
1235
+ * 处理 item.completed 事件,按 item.type 转换为对应的 ClaudeStreamEvent
1236
+ */
1237
+ handleItemCompleted(sessionId, item) {
1238
+ switch (item.type) {
1239
+ case "agent_message": {
1240
+ const msgEvent = {
1241
+ type: "assistant",
1242
+ session_id: sessionId,
1243
+ message: {
1244
+ id: item.id || this.nextId(),
1245
+ model: "codex",
1246
+ role: "assistant",
1247
+ content: [{ type: "text", text: item.text ?? "" }]
1248
+ }
1249
+ };
1250
+ this.emitter.emit(this.getEventName(sessionId), msgEvent);
1251
+ break;
1252
+ }
1253
+ case "command_execution": {
1254
+ const toolUseId = item.id || this.nextId();
1255
+ const toolEvent = {
1256
+ type: "assistant",
1257
+ session_id: sessionId,
1258
+ message: {
1259
+ id: this.nextId(),
1260
+ model: "codex",
1261
+ role: "assistant",
1262
+ content: [{
1263
+ type: "tool_use",
1264
+ id: toolUseId,
1265
+ name: "Bash",
1266
+ input: { command: item.command ?? "" }
1267
+ }]
1268
+ }
1269
+ };
1270
+ this.emitter.emit(this.getEventName(sessionId), toolEvent);
1271
+ const resultContent = item.aggregated_output ?? "";
1272
+ const isError = item.exit_code != null && item.exit_code !== 0;
1273
+ const toolResultEvent = {
1274
+ type: "user",
1275
+ session_id: sessionId,
1276
+ message: {
1277
+ role: "user",
1278
+ content: [{
1279
+ type: "tool_result",
1280
+ tool_use_id: toolUseId,
1281
+ content: resultContent,
1282
+ is_error: isError
1283
+ }]
1284
+ }
1285
+ };
1286
+ this.emitter.emit(this.getEventName(sessionId), toolResultEvent);
1287
+ break;
1288
+ }
1289
+ case "file_change": {
1290
+ const editToolUseId = item.id || this.nextId();
1291
+ const toolEvent = {
1292
+ type: "assistant",
1293
+ session_id: sessionId,
1294
+ message: {
1295
+ id: this.nextId(),
1296
+ model: "codex",
1297
+ role: "assistant",
1298
+ content: [{
1299
+ type: "tool_use",
1300
+ id: editToolUseId,
1301
+ name: "Edit",
1302
+ input: { file_path: item.file_path ?? "", diff: item.diff ?? "" }
1303
+ }]
1304
+ }
1305
+ };
1306
+ this.emitter.emit(this.getEventName(sessionId), toolEvent);
1307
+ const toolResultEvent = {
1308
+ type: "user",
1309
+ session_id: sessionId,
1310
+ message: {
1311
+ role: "user",
1312
+ content: [{
1313
+ type: "tool_result",
1314
+ tool_use_id: editToolUseId,
1315
+ content: `File edited: ${item.file_path ?? "unknown"}`
1316
+ }]
1317
+ }
1318
+ };
1319
+ this.emitter.emit(this.getEventName(sessionId), toolResultEvent);
1320
+ break;
1321
+ }
1322
+ case "reasoning": {
1323
+ const thinkEvent = {
1324
+ type: "assistant",
1325
+ session_id: sessionId,
1326
+ message: {
1327
+ id: item.id || this.nextId(),
1328
+ model: "codex",
1329
+ role: "assistant",
1330
+ content: [{ type: "thinking", thinking: item.text ?? "" }]
1331
+ }
1332
+ };
1333
+ this.emitter.emit(this.getEventName(sessionId), thinkEvent);
1334
+ break;
1335
+ }
1336
+ }
1337
+ }
1338
+ attachStderrListener(sessionId, proc) {
1339
+ if (!proc.stderr) return;
1340
+ proc.stderr.on("data", (data) => {
1341
+ const text = data.toString().trim();
1342
+ if (text) {
1343
+ console.error(`[CodexProvider] Session ${sessionId} stderr: ${text}`);
1344
+ }
1345
+ });
1346
+ }
1347
+ attachExitListener(sessionId, proc) {
1348
+ proc.once("exit", (code, signal) => {
1349
+ const entry = this.activeSessions.get(sessionId);
1350
+ if (!entry) return;
1351
+ if (entry.process !== proc) return;
1352
+ console.log(`[CodexProvider] Session ${sessionId} process exited: code=${code} signal=${signal} threadId=${entry.threadId ?? "NONE"}`);
1353
+ if (entry.rl) {
1354
+ entry.rl.close();
1355
+ entry.rl = void 0;
1356
+ }
1357
+ entry.session.pid = void 0;
1358
+ entry.session.lastActiveAt = Date.now();
1359
+ const alreadyHasResult = entry.session.status === "idle" || entry.session.status === "error";
1360
+ if (alreadyHasResult) return;
1361
+ const isNormal = isNormalExit(code, signal);
1362
+ entry.session.status = isNormal ? "idle" : "error";
1363
+ if (!isNormal) {
1364
+ console.error(`[CodexProvider] Session ${sessionId}: process exited abnormally code=${code} signal=${signal}`);
1365
+ }
1366
+ const syntheticResult = {
1367
+ type: "result",
1368
+ subtype: isNormal ? "success" : "error",
1369
+ session_id: sessionId,
1370
+ is_error: !isNormal,
1371
+ result: isNormal ? "" : `Process exited code=${code} signal=${signal}`,
1372
+ duration_ms: 0,
1373
+ num_turns: 0
1374
+ };
1375
+ this.emitter.emit(this.getEventName(sessionId), syntheticResult);
1376
+ });
1377
+ }
1378
+ emitError(sessionId, message) {
1379
+ const event = {
1380
+ type: "result",
1381
+ subtype: "error",
1382
+ result: message,
1383
+ session_id: sessionId,
1384
+ duration_ms: 0,
1385
+ is_error: true,
1386
+ num_turns: 0
1387
+ };
1388
+ this.emitter.emit(this.getEventName(sessionId), event);
1389
+ }
1390
+ getEventName(sessionId) {
1391
+ return `claude:${sessionId}`;
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
+ }
1441
+ };
1442
+
1443
+ // src/providers/ProviderFactory.ts
1444
+ var import_node_child_process4 = require("child_process");
1445
+ var ProviderFactory = class {
1446
+ providers = /* @__PURE__ */ new Map();
1447
+ getProvider(agentType) {
1448
+ const existing = this.providers.get(agentType);
1449
+ if (existing) return existing;
1450
+ let provider;
1451
+ switch (agentType) {
1452
+ case "codex":
1453
+ provider = new CodexProvider();
1454
+ break;
1455
+ case "claude-code":
1456
+ default:
1457
+ provider = new ProcessProvider();
1458
+ break;
1459
+ }
1460
+ this.providers.set(agentType, provider);
1461
+ return provider;
1462
+ }
1463
+ async detectAgents() {
1464
+ const agents = [];
1465
+ try {
1466
+ const claudePath = findClaudePath();
1467
+ const claudeVersion = (0, import_node_child_process4.execSync)(`"${claudePath}" --version`, {
1468
+ encoding: "utf-8",
1469
+ timeout: 5e3
1470
+ }).trim();
1471
+ agents.push({
1472
+ type: "claude-code",
1473
+ name: "Claude Code",
1474
+ available: true,
1475
+ version: claudeVersion
1476
+ });
1477
+ } catch {
1478
+ agents.push({ type: "claude-code", name: "Claude Code", available: false });
1479
+ }
1480
+ if (isCodexAvailable()) {
1481
+ const version = await getCodexVersion();
1482
+ agents.push({ type: "codex", name: "Codex", available: true, version });
1483
+ } else {
1484
+ agents.push({ type: "codex", name: "Codex", available: false });
1485
+ }
1486
+ return agents;
1487
+ }
1488
+ };
1489
+
1490
+ // src/session/SessionManager.ts
1491
+ var import_uuid3 = require("uuid");
852
1492
  var BUFFER_MAX = 5e3;
853
1493
  var SessionManager = class {
854
1494
  provider;
1495
+ providerFactory;
1496
+ sessionAgentType = /* @__PURE__ */ new Map();
855
1497
  /** 事件回调列表(事件会被转发到 WsBridge) */
856
1498
  eventCallbacks = [];
857
1499
  /** 每个会话的事件流取消订阅函数 */
@@ -877,8 +1519,26 @@ var SessionManager = class {
877
1519
  bufferTruncated = /* @__PURE__ */ new Set();
878
1520
  /** sessionId → projectPath 映射,用于截断时从 JSONL 补全历史 */
879
1521
  sessionProjectPaths = /* @__PURE__ */ new Map();
880
- constructor(provider) {
881
- this.provider = provider;
1522
+ constructor(providerOrFactory) {
1523
+ if (providerOrFactory instanceof ProviderFactory) {
1524
+ this.providerFactory = providerOrFactory;
1525
+ this.provider = providerOrFactory.getProvider("claude-code");
1526
+ } else {
1527
+ this.provider = providerOrFactory;
1528
+ this.providerFactory = null;
1529
+ }
1530
+ }
1531
+ getProviderForSession(sessionId) {
1532
+ if (!this.providerFactory) return this.provider;
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");
882
1542
  }
883
1543
  // ============================================
884
1544
  // 公开 API
@@ -889,8 +1549,9 @@ var SessionManager = class {
889
1549
  * 调用 provider.startSession(),订阅事件流,
890
1550
  * 将 ClaudeStreamEvent 包装为 ServerEvent 转发。
891
1551
  */
892
- async createSession(projectPath, message, resumeSessionId, newSessionId, model, permissionMode, effort, images) {
893
- const session = await this.provider.startSession({
1552
+ async createSession(projectPath, message, resumeSessionId, newSessionId, model, permissionMode, effort, images, agentType = "claude-code") {
1553
+ const provider = this.providerFactory ? this.providerFactory.getProvider(agentType) : this.provider;
1554
+ const session = await provider.startSession({
894
1555
  projectPath,
895
1556
  message,
896
1557
  sessionId: resumeSessionId ?? newSessionId,
@@ -900,6 +1561,7 @@ var SessionManager = class {
900
1561
  effort,
901
1562
  images
902
1563
  });
1564
+ this.sessionAgentType.set(session.id, agentType);
903
1565
  this.lastBroadcastStatus.set(session.id, session.status);
904
1566
  this.sessionProjectPaths.set(session.id, projectPath);
905
1567
  this.unsubscribeSession(session.id);
@@ -911,7 +1573,8 @@ var SessionManager = class {
911
1573
  * 发送消息到已有会话
912
1574
  */
913
1575
  async sendMessage(sessionId, message, permissionMode, images) {
914
- await this.provider.sendMessage(sessionId, message, permissionMode, images);
1576
+ const provider = this.getProviderForSession(sessionId);
1577
+ await provider.sendMessage(sessionId, message, permissionMode, images);
915
1578
  this.updateSessionStatus(sessionId, "running");
916
1579
  console.log(`[SessionManager] Message sent to session: ${sessionId}`);
917
1580
  }
@@ -931,7 +1594,9 @@ var SessionManager = class {
931
1594
  clearTimeout(pending.timer);
932
1595
  this.pendingAssistantEvents.delete(sessionId);
933
1596
  }
934
- await this.provider.killSession(sessionId);
1597
+ const provider = this.getProviderForSession(sessionId);
1598
+ await provider.killSession(sessionId);
1599
+ this.sessionAgentType.delete(sessionId);
935
1600
  console.log(`[SessionManager] Session killed: ${sessionId}`);
936
1601
  }
937
1602
  /**
@@ -1010,7 +1675,11 @@ var SessionManager = class {
1010
1675
  * 获取所有活跃会话(含服务器端统计)
1011
1676
  */
1012
1677
  getActiveSessions() {
1013
- return this.provider.getActiveSessions().map((session) => {
1678
+ const rawSessions = this.providerFactory ? [
1679
+ ...this.providerFactory.getProvider("claude-code").getActiveSessions(),
1680
+ ...this.providerFactory.getProvider("codex").getActiveSessions()
1681
+ ] : this.provider.getActiveSessions();
1682
+ return rawSessions.map((session) => {
1014
1683
  const stats = this.getSessionStats(session.id);
1015
1684
  return stats ? { ...session, stats } : session;
1016
1685
  });
@@ -1040,6 +1709,7 @@ var SessionManager = class {
1040
1709
  this.sessionEventBuffers.clear();
1041
1710
  this.bufferTruncated.clear();
1042
1711
  this.sessionProjectPaths.clear();
1712
+ this.sessionAgentType.clear();
1043
1713
  this.sessionStats.clear();
1044
1714
  for (const [, pending] of this.pendingAssistantEvents) {
1045
1715
  clearTimeout(pending.timer);
@@ -1057,10 +1727,11 @@ var SessionManager = class {
1057
1727
  * 订阅指定会话的事件流(包括 AskUserQuestion 问题事件)
1058
1728
  */
1059
1729
  subscribeToSession(sessionId) {
1060
- const unsubscribeEvent = this.provider.onEvent(sessionId, (event) => {
1730
+ const provider = this.getProviderForSession(sessionId);
1731
+ const unsubscribeEvent = provider.onEvent(sessionId, (event) => {
1061
1732
  this.handleClaudeEvent(sessionId, event);
1062
1733
  });
1063
- const unsubscribeQuestion = this.provider.onQuestion(
1734
+ const unsubscribeQuestion = provider.onQuestion(
1064
1735
  sessionId,
1065
1736
  ({ toolUseId, question, options }) => {
1066
1737
  this.handleAskUserQuestion(sessionId, toolUseId, question, options);
@@ -1235,7 +1906,7 @@ var SessionManager = class {
1235
1906
  console.log(`[SessionManager] Session ${sessionId}: AskUserQuestion updated (requestId=${existingRequestId})`);
1236
1907
  return;
1237
1908
  }
1238
- const requestId = (0, import_uuid2.v4)();
1909
+ const requestId = (0, import_uuid3.v4)();
1239
1910
  const request = {
1240
1911
  id: requestId,
1241
1912
  sessionId,
@@ -1246,12 +1917,13 @@ var SessionManager = class {
1246
1917
  };
1247
1918
  this.updateSessionStatus(sessionId, "waiting_question");
1248
1919
  this.emit({ type: "question_request", request });
1249
- const answerPromise = new Promise((resolve) => {
1250
- 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 });
1251
1922
  });
1252
1923
  answerPromise.then(async (answer) => {
1253
1924
  try {
1254
- await this.provider.answerQuestion(sessionId, toolUseId, answer);
1925
+ const provider = this.getProviderForSession(sessionId);
1926
+ await provider.answerQuestion(sessionId, toolUseId, answer);
1255
1927
  } catch (err) {
1256
1928
  console.error(`[SessionManager] answerQuestion failed (${sessionId}):`, err);
1257
1929
  }
@@ -1488,11 +2160,11 @@ var WsBridge = class _WsBridge {
1488
2160
  * 使用此方法代替 new WsBridge(),确保 EADDRINUSE 等错误能被调用方的 try-catch 捕获。
1489
2161
  */
1490
2162
  static async create(options) {
1491
- return new Promise((resolve, reject) => {
2163
+ return new Promise((resolve2, reject) => {
1492
2164
  const bridge = new _WsBridge(options);
1493
2165
  bridge.wss.once("listening", () => {
1494
2166
  bridge.wss.on("error", (err) => console.error(`[WsBridge] ${t("ws.serverError")}:`, err));
1495
- resolve(bridge);
2167
+ resolve2(bridge);
1496
2168
  });
1497
2169
  bridge.wss.once("error", reject);
1498
2170
  });
@@ -1555,7 +2227,7 @@ var WsBridge = class _WsBridge {
1555
2227
  }
1556
2228
  /** 优雅关闭 WebSocket 服务 */
1557
2229
  close() {
1558
- return new Promise((resolve, reject) => {
2230
+ return new Promise((resolve2, reject) => {
1559
2231
  if (this.heartbeatTimer) {
1560
2232
  clearInterval(this.heartbeatTimer);
1561
2233
  this.heartbeatTimer = null;
@@ -1568,7 +2240,7 @@ var WsBridge = class _WsBridge {
1568
2240
  reject(err);
1569
2241
  } else {
1570
2242
  console.log("[WsBridge] WebSocket server closed");
1571
- resolve();
2243
+ resolve2();
1572
2244
  }
1573
2245
  });
1574
2246
  });
@@ -1674,15 +2346,15 @@ var WsBridge = class _WsBridge {
1674
2346
 
1675
2347
  // src/approval/ApprovalProxy.ts
1676
2348
  var import_node_http = __toESM(require("http"));
1677
- var import_node_fs2 = __toESM(require("fs"));
1678
- var import_node_path2 = __toESM(require("path"));
1679
- var import_node_os3 = __toESM(require("os"));
1680
- var import_uuid3 = require("uuid");
2349
+ var import_node_fs3 = __toESM(require("fs"));
2350
+ var import_node_path3 = __toESM(require("path"));
2351
+ var import_node_os4 = __toESM(require("os"));
2352
+ var import_uuid4 = require("uuid");
1681
2353
  var ApprovalProxy = class _ApprovalProxy {
1682
2354
  server;
1683
2355
  token;
1684
2356
  port;
1685
- settingsPath = import_node_path2.default.join(import_node_os3.default.homedir(), ".claude", "settings.json");
2357
+ settingsPath = import_node_path3.default.join(import_node_os4.default.homedir(), ".claude", "settings.json");
1686
2358
  /** 待处理的审批请求:requestId -> { resolve, timer, request } */
1687
2359
  pendingApprovals = /* @__PURE__ */ new Map();
1688
2360
  /** 审批请求回调(通知外部推送到手机) */
@@ -1708,11 +2380,11 @@ var ApprovalProxy = class _ApprovalProxy {
1708
2380
  * 异步工厂方法:等待端口监听成功后 resolve,端口占用等错误时 reject。
1709
2381
  */
1710
2382
  static async create(options) {
1711
- return new Promise((resolve, reject) => {
2383
+ return new Promise((resolve2, reject) => {
1712
2384
  const proxy = new _ApprovalProxy(options);
1713
2385
  proxy.server.once("listening", () => {
1714
2386
  proxy.server.on("error", (err) => console.error(`[ApprovalProxy] ${t("approval.serverError")}:`, err));
1715
- resolve(proxy);
2387
+ resolve2(proxy);
1716
2388
  });
1717
2389
  proxy.server.once("error", reject);
1718
2390
  });
@@ -1787,7 +2459,7 @@ var ApprovalProxy = class _ApprovalProxy {
1787
2459
  isToolInClaudeSettings(toolName, projectPath) {
1788
2460
  const checkPath = (filepath) => {
1789
2461
  try {
1790
- const raw = import_node_fs2.default.readFileSync(filepath, "utf-8");
2462
+ const raw = import_node_fs3.default.readFileSync(filepath, "utf-8");
1791
2463
  const settings = JSON.parse(raw);
1792
2464
  const allow = settings?.permissions?.allow ?? [];
1793
2465
  return allow.some((entry) => {
@@ -1801,24 +2473,24 @@ var ApprovalProxy = class _ApprovalProxy {
1801
2473
  }
1802
2474
  };
1803
2475
  if (projectPath) {
1804
- const projectSettingsPath = import_node_path2.default.join(projectPath, ".claude", "settings.json");
2476
+ const projectSettingsPath = import_node_path3.default.join(projectPath, ".claude", "settings.json");
1805
2477
  if (checkPath(projectSettingsPath)) return true;
1806
2478
  }
1807
2479
  return checkPath(this.settingsPath);
1808
2480
  }
1809
2481
  /** 将工具写入 settings.json permissions.allow(项目级或全局) */
1810
2482
  addToClaudeSettings(projectPath, toolName) {
1811
- const targetPath = projectPath ? import_node_path2.default.join(projectPath, ".claude", "settings.json") : this.settingsPath;
2483
+ const targetPath = projectPath ? import_node_path3.default.join(projectPath, ".claude", "settings.json") : this.settingsPath;
1812
2484
  try {
1813
2485
  if (projectPath) {
1814
- const dir = import_node_path2.default.dirname(targetPath);
1815
- if (!import_node_fs2.default.existsSync(dir)) {
1816
- import_node_fs2.default.mkdirSync(dir, { recursive: true });
2486
+ const dir = import_node_path3.default.dirname(targetPath);
2487
+ if (!import_node_fs3.default.existsSync(dir)) {
2488
+ import_node_fs3.default.mkdirSync(dir, { recursive: true });
1817
2489
  }
1818
2490
  }
1819
2491
  let settings = {};
1820
2492
  try {
1821
- settings = JSON.parse(import_node_fs2.default.readFileSync(targetPath, "utf-8"));
2493
+ settings = JSON.parse(import_node_fs3.default.readFileSync(targetPath, "utf-8"));
1822
2494
  } catch {
1823
2495
  }
1824
2496
  if (!settings.permissions) {
@@ -1832,7 +2504,7 @@ var ApprovalProxy = class _ApprovalProxy {
1832
2504
  const entry = `${toolName}(*)`;
1833
2505
  if (!allow.includes(entry)) {
1834
2506
  allow.push(entry);
1835
- import_node_fs2.default.writeFileSync(targetPath, JSON.stringify(settings, null, 2), "utf-8");
2507
+ import_node_fs3.default.writeFileSync(targetPath, JSON.stringify(settings, null, 2), "utf-8");
1836
2508
  const label = projectPath ? `${projectPath}/.claude/settings.json` : "~/.claude/settings.json";
1837
2509
  console.log(`[ApprovalProxy] ${t("approval.alwaysAllowWritten", { entry, label })}`);
1838
2510
  }
@@ -1869,7 +2541,7 @@ var ApprovalProxy = class _ApprovalProxy {
1869
2541
  }
1870
2542
  /** 优雅关闭 HTTP 服务 */
1871
2543
  close() {
1872
- return new Promise((resolve, reject) => {
2544
+ return new Promise((resolve2, reject) => {
1873
2545
  const pendingEntries = Array.from(this.pendingApprovals.entries());
1874
2546
  for (const [, pending] of pendingEntries) {
1875
2547
  clearTimeout(pending.timer);
@@ -1881,7 +2553,7 @@ var ApprovalProxy = class _ApprovalProxy {
1881
2553
  reject(err);
1882
2554
  } else {
1883
2555
  console.log(`[ApprovalProxy] ${t("approval.httpClosed")}`);
1884
- resolve();
2556
+ resolve2();
1885
2557
  }
1886
2558
  });
1887
2559
  });
@@ -1927,7 +2599,7 @@ var ApprovalProxy = class _ApprovalProxy {
1927
2599
  try {
1928
2600
  const body = await this.parseJsonBody(req);
1929
2601
  const payload = body.payload ?? body;
1930
- const requestId = (0, import_uuid3.v4)();
2602
+ const requestId = (0, import_uuid4.v4)();
1931
2603
  const projectPath = String(body.projectPath ?? "unknown");
1932
2604
  const toolName = String(payload.tool_name ?? body.tool_name ?? "unknown");
1933
2605
  const toolInput = payload.tool_input ?? body.tool_input ?? {};
@@ -1952,13 +2624,13 @@ var ApprovalProxy = class _ApprovalProxy {
1952
2624
  return;
1953
2625
  }
1954
2626
  this.notifyApprovalRequest(approvalRequest);
1955
- const decision = await new Promise((resolve) => {
2627
+ const decision = await new Promise((resolve2) => {
1956
2628
  const timer = setTimeout(() => {
1957
2629
  console.log(`[ApprovalProxy] ${t("approval.timeout", { id: requestId })}`);
1958
2630
  this.pendingApprovals.delete(requestId);
1959
- resolve({ decision: "allow" });
2631
+ resolve2({ decision: "allow" });
1960
2632
  }, 325e3);
1961
- this.pendingApprovals.set(requestId, { resolve, timer, request: approvalRequest });
2633
+ this.pendingApprovals.set(requestId, { resolve: resolve2, timer, request: approvalRequest });
1962
2634
  });
1963
2635
  this.sendJson(res, 200, decision);
1964
2636
  } catch (err) {
@@ -2019,7 +2691,7 @@ var ApprovalProxy = class _ApprovalProxy {
2019
2691
  /** 手动解析请求的 JSON body(限制最大 1MB 防止滥用) */
2020
2692
  parseJsonBody(req) {
2021
2693
  const MAX_BODY_SIZE = 1024 * 1024;
2022
- return new Promise((resolve, reject) => {
2694
+ return new Promise((resolve2, reject) => {
2023
2695
  const chunks = [];
2024
2696
  let totalSize = 0;
2025
2697
  let destroyed = false;
@@ -2037,7 +2709,7 @@ var ApprovalProxy = class _ApprovalProxy {
2037
2709
  try {
2038
2710
  const raw = Buffer.concat(chunks).toString("utf-8");
2039
2711
  const parsed = JSON.parse(raw);
2040
- resolve(parsed);
2712
+ resolve2(parsed);
2041
2713
  } catch {
2042
2714
  reject(new Error(t("approval.invalidJson")));
2043
2715
  }
@@ -2059,8 +2731,8 @@ var ApprovalProxy = class _ApprovalProxy {
2059
2731
  };
2060
2732
 
2061
2733
  // src/mdns/MdnsService.ts
2062
- var import_node_child_process3 = require("child_process");
2063
- var import_node_os4 = require("os");
2734
+ var import_node_child_process5 = require("child_process");
2735
+ var import_node_os5 = require("os");
2064
2736
  function buildTxtArgs(txt) {
2065
2737
  return Object.entries(txt).map(([k, v]) => `${k}=${v}`);
2066
2738
  }
@@ -2078,7 +2750,7 @@ var MdnsService = class {
2078
2750
  this.httpPort = options.httpPort;
2079
2751
  this.version = options.version ?? "0.1.0";
2080
2752
  this.pairing = options.pairing ?? "closed";
2081
- this.useDnsSd = (0, import_node_os4.platform)() === "darwin";
2753
+ this.useDnsSd = (0, import_node_os5.platform)() === "darwin";
2082
2754
  }
2083
2755
  getTxt() {
2084
2756
  return {
@@ -2111,7 +2783,7 @@ var MdnsService = class {
2111
2783
  String(this.wsPort),
2112
2784
  ...buildTxtArgs(this.getTxt())
2113
2785
  ];
2114
- this.proc = (0, import_node_child_process3.spawn)("dns-sd", args, { stdio: "ignore" });
2786
+ this.proc = (0, import_node_child_process5.spawn)("dns-sd", args, { stdio: "ignore" });
2115
2787
  this.proc.on("error", (err) => {
2116
2788
  console.warn(`[MdnsService] dns-sd failed, falling back to bonjour-service: ${err.message}`);
2117
2789
  this.proc = null;
@@ -2132,7 +2804,7 @@ var MdnsService = class {
2132
2804
  return;
2133
2805
  }
2134
2806
  try {
2135
- const { default: Bonjour } = await import("bonjour-service");
2807
+ const { Bonjour } = await import("bonjour-service");
2136
2808
  const { networkInterfaces: networkInterfaces2 } = await import("os");
2137
2809
  const lanAddrs = getLanAddresses(networkInterfaces2);
2138
2810
  const opts = lanAddrs.length > 0 ? { interface: lanAddrs[0] } : {};
@@ -2223,12 +2895,12 @@ function getLanAddresses(networkInterfacesFn) {
2223
2895
 
2224
2896
  // src/hooks/HookInstaller.ts
2225
2897
  var import_promises2 = require("fs/promises");
2226
- var import_node_path3 = require("path");
2227
- var import_node_os5 = require("os");
2228
- var SESSIX_HOOKS_DIR = (0, import_node_path3.join)((0, import_node_os5.homedir)(), ".sessix", "hooks");
2229
- var HOOK_SCRIPT_PATH = (0, import_node_path3.join)(SESSIX_HOOKS_DIR, "approval-hook.js");
2230
- var PERMISSION_ACCEPT_PATH = (0, import_node_path3.join)(SESSIX_HOOKS_DIR, "permission-accept.js");
2231
- var CLAUDE_SETTINGS_PATH = (0, import_node_path3.join)((0, import_node_os5.homedir)(), ".claude", "settings.json");
2898
+ var import_node_path4 = require("path");
2899
+ var import_node_os6 = require("os");
2900
+ var SESSIX_HOOKS_DIR = (0, import_node_path4.join)((0, import_node_os6.homedir)(), ".sessix", "hooks");
2901
+ var HOOK_SCRIPT_PATH = (0, import_node_path4.join)(SESSIX_HOOKS_DIR, "approval-hook.js");
2902
+ var PERMISSION_ACCEPT_PATH = (0, import_node_path4.join)(SESSIX_HOOKS_DIR, "permission-accept.js");
2903
+ var CLAUDE_SETTINGS_PATH = (0, import_node_path4.join)((0, import_node_os6.homedir)(), ".claude", "settings.json");
2232
2904
  var HOOK_COMMAND = "node ~/.sessix/hooks/approval-hook.js";
2233
2905
  var PERMISSION_ACCEPT_COMMAND = "node ~/.sessix/hooks/permission-accept.js";
2234
2906
  var LEGACY_HOOK_COMMANDS = [
@@ -2402,7 +3074,7 @@ var HookInstaller = class {
2402
3074
  * 写入 Claude Code settings.json
2403
3075
  */
2404
3076
  async writeClaudeSettings(settings) {
2405
- await (0, import_promises2.mkdir)((0, import_node_path3.join)((0, import_node_os5.homedir)(), ".claude"), { recursive: true });
3077
+ await (0, import_promises2.mkdir)((0, import_node_path4.join)((0, import_node_os6.homedir)(), ".claude"), { recursive: true });
2406
3078
  await (0, import_promises2.writeFile)(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n", "utf-8");
2407
3079
  }
2408
3080
  /**
@@ -2429,7 +3101,7 @@ var HookInstaller = class {
2429
3101
  };
2430
3102
 
2431
3103
  // src/notification/NotificationService.ts
2432
- var import_node_path4 = require("path");
3104
+ var import_node_path5 = require("path");
2433
3105
  var NotificationService = class {
2434
3106
  constructor(sessionManager, expoChannel = null) {
2435
3107
  this.sessionManager = sessionManager;
@@ -2525,7 +3197,7 @@ var NotificationService = class {
2525
3197
  const dangerLevel = this.getDangerLevel(request.toolName);
2526
3198
  const isDangerous = dangerLevel === "danger" || dangerLevel === "write";
2527
3199
  const categoryId = isDangerous ? "APPROVAL_DANGEROUS" : "APPROVAL_NORMAL";
2528
- const projectName = (0, import_node_path4.basename)(
3200
+ const projectName = (0, import_node_path5.basename)(
2529
3201
  this.sessionManager.getActiveSessions().find((s) => s.id === request.sessionId)?.projectPath ?? ""
2530
3202
  );
2531
3203
  const pushTitle = isDangerous ? `\u26A0\uFE0F ${title}` : title;
@@ -2581,7 +3253,7 @@ var NotificationService = class {
2581
3253
  /** 从审批请求中提取操作目标的简短描述 */
2582
3254
  extractTarget(request) {
2583
3255
  const input = request.toolInput;
2584
- if (input.file_path) return (0, import_node_path4.basename)(String(input.file_path));
3256
+ if (input.file_path) return (0, import_node_path5.basename)(String(input.file_path));
2585
3257
  if (input.command) return String(input.command).slice(0, 40);
2586
3258
  return request.description.slice(0, 40);
2587
3259
  }
@@ -2684,7 +3356,7 @@ var NotificationService = class {
2684
3356
  getSessionTitle(sessionId) {
2685
3357
  const session = this.sessionManager.getActiveSessions().find((s) => s.id === sessionId);
2686
3358
  if (!session) return "Unknown";
2687
- return session.summary ?? (0, import_node_path4.basename)(session.projectPath);
3359
+ return session.summary ?? (0, import_node_path5.basename)(session.projectPath);
2688
3360
  }
2689
3361
  /** 获取会话的 YOLO 模式状态 */
2690
3362
  getYoloMode(sessionId) {
@@ -2693,7 +3365,7 @@ var NotificationService = class {
2693
3365
  };
2694
3366
 
2695
3367
  // src/notification/DesktopNotificationChannel.ts
2696
- var import_node_child_process4 = require("child_process");
3368
+ var import_node_child_process6 = require("child_process");
2697
3369
  var DesktopNotificationChannel = class {
2698
3370
  isAvailable() {
2699
3371
  return process.platform === "darwin";
@@ -2704,12 +3376,12 @@ var DesktopNotificationChannel = class {
2704
3376
  const body = payload.body.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
2705
3377
  const sound = payload.sound ?? "Ping";
2706
3378
  const script = `display notification "${body}" with title "${title}" sound name "${sound}"`;
2707
- return new Promise((resolve) => {
2708
- (0, import_node_child_process4.execFile)("osascript", ["-e", script], (err) => {
3379
+ return new Promise((resolve2) => {
3380
+ (0, import_node_child_process6.execFile)("osascript", ["-e", script], (err) => {
2709
3381
  if (err) {
2710
3382
  console.warn("[DesktopNotificationChannel] Send notification failed:", err.message);
2711
3383
  }
2712
- resolve();
3384
+ resolve2();
2713
3385
  });
2714
3386
  });
2715
3387
  }
@@ -2909,7 +3581,7 @@ var ActivityPushChannel = class {
2909
3581
  const topic = "com.kachun.sessix.push-type.liveactivity";
2910
3582
  const jwt = this.getJWT();
2911
3583
  const payloadStr = JSON.stringify(payload);
2912
- return new Promise((resolve, reject) => {
3584
+ return new Promise((resolve2, reject) => {
2913
3585
  let client;
2914
3586
  try {
2915
3587
  client = this.getHttp2Client();
@@ -2937,7 +3609,7 @@ var ActivityPushChannel = class {
2937
3609
  });
2938
3610
  req.on("end", () => {
2939
3611
  if (statusCode === 200) {
2940
- resolve();
3612
+ resolve2();
2941
3613
  } else {
2942
3614
  if (statusCode === 0) {
2943
3615
  this.http2Client?.destroy();
@@ -2979,12 +3651,12 @@ var ActivityPushChannel = class {
2979
3651
 
2980
3652
  // src/session/ProjectReader.ts
2981
3653
  var import_promises3 = require("fs/promises");
2982
- var import_readline2 = require("readline");
2983
- var import_path = require("path");
2984
- var import_os = require("os");
2985
- var CLAUDE_PROJECTS_DIR = (0, import_path.join)((0, import_os.homedir)(), ".claude", "projects");
3654
+ var import_readline3 = require("readline");
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");
2986
3658
  function getSessionFilePath(projectPath, sessionId) {
2987
- 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`);
2988
3660
  }
2989
3661
  async function getProjects() {
2990
3662
  try {
@@ -3001,7 +3673,7 @@ async function getProjects() {
3001
3673
  const encodedPath = entry.name;
3002
3674
  const decodedPath = decodeDirName(encodedPath);
3003
3675
  const name = decodedPath.split("/").filter(Boolean).pop() ?? encodedPath;
3004
- const projectDir = (0, import_path.join)(CLAUDE_PROJECTS_DIR, encodedPath);
3676
+ const projectDir = (0, import_path2.join)(CLAUDE_PROJECTS_DIR, encodedPath);
3005
3677
  const { count: sessionCount, latestMtime } = await countJsonlFilesWithMtime(projectDir);
3006
3678
  projects.push({
3007
3679
  id: encodedPath,
@@ -3023,7 +3695,7 @@ async function getProjects() {
3023
3695
  async function getHistoricalSessions(projectPath) {
3024
3696
  try {
3025
3697
  const encodedPath = encodeDirName(projectPath);
3026
- const projectDir = (0, import_path.join)(CLAUDE_PROJECTS_DIR, encodedPath);
3698
+ const projectDir = (0, import_path2.join)(CLAUDE_PROJECTS_DIR, encodedPath);
3027
3699
  const dirExists = await directoryExists(projectDir);
3028
3700
  if (!dirExists) {
3029
3701
  return { ok: true, value: [] };
@@ -3034,7 +3706,7 @@ async function getHistoricalSessions(projectPath) {
3034
3706
  await Promise.all(
3035
3707
  jsonlFiles.map(async (entry) => {
3036
3708
  const sessionId = entry.name.slice(0, -6);
3037
- const filePath = (0, import_path.join)(projectDir, entry.name);
3709
+ const filePath = (0, import_path2.join)(projectDir, entry.name);
3038
3710
  try {
3039
3711
  const contentTs = await extractLastTimestamp(filePath);
3040
3712
  if (contentTs) {
@@ -3053,13 +3725,13 @@ async function getHistoricalSessions(projectPath) {
3053
3725
  );
3054
3726
  for (const entry of uuidDirs) {
3055
3727
  try {
3056
- 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));
3057
3729
  mtimeMap.set(entry.name, fileStat.mtimeMs);
3058
3730
  } catch {
3059
3731
  mtimeMap.set(entry.name, 0);
3060
3732
  }
3061
3733
  }
3062
- const indexPath = (0, import_path.join)(projectDir, "sessions-index.json");
3734
+ const indexPath = (0, import_path2.join)(projectDir, "sessions-index.json");
3063
3735
  const sessionMap = /* @__PURE__ */ new Map();
3064
3736
  try {
3065
3737
  const indexContent = await (0, import_promises3.readFile)(indexPath, "utf-8");
@@ -3077,7 +3749,7 @@ async function getHistoricalSessions(projectPath) {
3077
3749
  }
3078
3750
  await Promise.all(
3079
3751
  Array.from(sessionMap.values()).filter((s) => (s.messageCount ?? 0) > 0 && !s.summary && !s.firstPrompt).map(async (s) => {
3080
- const filePath = (0, import_path.join)(projectDir, `${s.sessionId}.jsonl`);
3752
+ const filePath = (0, import_path2.join)(projectDir, `${s.sessionId}.jsonl`);
3081
3753
  const firstPrompt = await extractFirstPrompt(filePath).catch(() => void 0);
3082
3754
  if (firstPrompt) s.firstPrompt = firstPrompt;
3083
3755
  })
@@ -3091,7 +3763,7 @@ async function getHistoricalSessions(projectPath) {
3091
3763
  if (uuidDirSet.has(sessionId)) {
3092
3764
  sessionMap.set(sessionId, { sessionId, lastModified: mtime, messageCount: -1 });
3093
3765
  } else {
3094
- const filePath = (0, import_path.join)(projectDir, `${sessionId}.jsonl`);
3766
+ const filePath = (0, import_path2.join)(projectDir, `${sessionId}.jsonl`);
3095
3767
  const firstPrompt = await extractFirstPrompt(filePath).catch(() => void 0);
3096
3768
  sessionMap.set(sessionId, { sessionId, lastModified: mtime, firstPrompt });
3097
3769
  }
@@ -3115,7 +3787,7 @@ async function getHistoricalSessions(projectPath) {
3115
3787
  async function getSessionHistory(projectPath, sessionId) {
3116
3788
  try {
3117
3789
  const encodedPath = encodeDirName(projectPath);
3118
- 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`);
3119
3791
  const raw = await (0, import_promises3.readFile)(filePath, "utf-8").catch((err) => {
3120
3792
  if (err.code === "ENOENT") return null;
3121
3793
  throw err;
@@ -3228,7 +3900,7 @@ async function extractFirstPrompt(filePath) {
3228
3900
  let fileHandle;
3229
3901
  try {
3230
3902
  fileHandle = await (0, import_promises3.open)(filePath, "r");
3231
- const rl = (0, import_readline2.createInterface)({
3903
+ const rl = (0, import_readline3.createInterface)({
3232
3904
  input: fileHandle.createReadStream({ encoding: "utf-8" }),
3233
3905
  crlfDelay: Infinity
3234
3906
  });
@@ -3296,15 +3968,15 @@ async function countJsonlFilesWithMtime(dirPath) {
3296
3968
  await Promise.all([
3297
3969
  ...jsonlEntries.map(async (entry) => {
3298
3970
  try {
3299
- const contentTs = await extractLastTimestamp((0, import_path.join)(dirPath, entry.name));
3300
- 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;
3301
3973
  if (ts > latestMtime) latestMtime = ts;
3302
3974
  } catch {
3303
3975
  }
3304
3976
  }),
3305
3977
  ...uuidDirs.map(async (entry) => {
3306
3978
  try {
3307
- 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));
3308
3980
  if (fileStat.mtimeMs > latestMtime) latestMtime = fileStat.mtimeMs;
3309
3981
  } catch {
3310
3982
  }
@@ -3376,14 +4048,14 @@ var PairingManager = class {
3376
4048
  };
3377
4049
 
3378
4050
  // src/auth/AuthManager.ts
3379
- var import_child_process2 = require("child_process");
3380
4051
  var import_child_process3 = require("child_process");
4052
+ var import_child_process4 = require("child_process");
3381
4053
  var import_util = require("util");
3382
- var import_events2 = require("events");
3383
- var execFileAsync = (0, import_util.promisify)(import_child_process3.execFile);
4054
+ var import_events3 = require("events");
4055
+ var execFileAsync = (0, import_util.promisify)(import_child_process4.execFile);
3384
4056
  var CLAUDE_PATH2 = findClaudePath();
3385
4057
  var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
3386
- var AuthManager = class extends import_events2.EventEmitter {
4058
+ var AuthManager = class extends import_events3.EventEmitter {
3387
4059
  loginProcess = null;
3388
4060
  loginTimeout = null;
3389
4061
  urlSent = false;
@@ -3411,7 +4083,7 @@ var AuthManager = class extends import_events2.EventEmitter {
3411
4083
  }
3412
4084
  this.clearLoginTimeout();
3413
4085
  this.urlSent = false;
3414
- const proc = (0, import_child_process2.spawn)(CLAUDE_PATH2, ["auth", "login"], {
4086
+ const proc = (0, import_child_process3.spawn)(CLAUDE_PATH2, ["auth", "login"], {
3415
4087
  env: { ...process.env, BROWSER: "echo" },
3416
4088
  stdio: ["pipe", "pipe", "pipe"]
3417
4089
  });
@@ -3497,8 +4169,8 @@ var AuthManager = class extends import_events2.EventEmitter {
3497
4169
  var import_promises5 = require("fs/promises");
3498
4170
 
3499
4171
  // src/terminal/TerminalExecutor.ts
3500
- var import_node_child_process5 = require("child_process");
3501
- var import_uuid4 = require("uuid");
4172
+ var import_node_child_process7 = require("child_process");
4173
+ var import_uuid5 = require("uuid");
3502
4174
  var EXEC_TIMEOUT_MS = 5 * 60 * 1e3;
3503
4175
  var TerminalExecutor = class {
3504
4176
  processes = /* @__PURE__ */ new Map();
@@ -3520,10 +4192,10 @@ var TerminalExecutor = class {
3520
4192
  }
3521
4193
  }
3522
4194
  exec(sessionId, command, cwd) {
3523
- const execId = (0, import_uuid4.v4)();
4195
+ const execId = (0, import_uuid5.v4)();
3524
4196
  const shell = isWindows ? "powershell" : "bash";
3525
4197
  const args = isWindows ? ["-Command", command] : ["-c", command];
3526
- const proc = (0, import_node_child_process5.spawn)(shell, args, {
4198
+ const proc = (0, import_node_child_process7.spawn)(shell, args, {
3527
4199
  cwd,
3528
4200
  stdio: ["ignore", "pipe", "pipe"],
3529
4201
  env: { ...process.env }
@@ -3586,7 +4258,7 @@ var TerminalExecutor = class {
3586
4258
  // src/server.ts
3587
4259
  var WS_PORT = 3745;
3588
4260
  var HTTP_PORT = 3746;
3589
- var execAsync = (0, import_node_util.promisify)(import_node_child_process6.exec);
4261
+ var execAsync = (0, import_node_util.promisify)(import_node_child_process8.exec);
3590
4262
  async function killPortProcess(port) {
3591
4263
  try {
3592
4264
  if (isWindows) {
@@ -3610,7 +4282,7 @@ async function killPortProcess(port) {
3610
4282
  await execAsync(`kill -9 ${pids.join(" ")}`);
3611
4283
  }
3612
4284
  }
3613
- await new Promise((resolve) => setTimeout(resolve, 600));
4285
+ await new Promise((resolve2) => setTimeout(resolve2, 600));
3614
4286
  } catch {
3615
4287
  }
3616
4288
  }
@@ -3628,8 +4300,8 @@ async function createWithRetry(label, port, factory) {
3628
4300
  }
3629
4301
  }
3630
4302
  async function start(opts = {}) {
3631
- const configDir = (0, import_node_path5.join)((0, import_node_os6.homedir)(), ".sessix");
3632
- const tokenFile = (0, import_node_path5.join)(configDir, "token");
4303
+ const configDir = (0, import_node_path6.join)((0, import_node_os7.homedir)(), ".sessix");
4304
+ const tokenFile = (0, import_node_path6.join)(configDir, "token");
3633
4305
  let token;
3634
4306
  if (opts.token !== void 0) {
3635
4307
  token = opts.token;
@@ -3641,14 +4313,14 @@ async function start(opts = {}) {
3641
4313
  try {
3642
4314
  token = (await (0, import_promises4.readFile)(tokenFile, "utf8")).trim();
3643
4315
  } catch {
3644
- token = (0, import_uuid5.v4)();
4316
+ token = (0, import_uuid6.v4)();
3645
4317
  await (0, import_promises4.mkdir)(configDir, { recursive: true });
3646
4318
  await (0, import_promises4.writeFile)(tokenFile, token, "utf8");
3647
4319
  }
3648
4320
  }
3649
4321
  }
3650
- const provider = new ProcessProvider();
3651
- const sessionManager = new SessionManager(provider);
4322
+ const providerFactory = new ProviderFactory();
4323
+ const sessionManager = new SessionManager(providerFactory);
3652
4324
  const terminalExecutor = new TerminalExecutor();
3653
4325
  const wsBridge = await createWithRetry(
3654
4326
  "WsBridge",
@@ -3688,7 +4360,7 @@ async function start(opts = {}) {
3688
4360
  let mdnsService = null;
3689
4361
  const pairingManager = new PairingManager({
3690
4362
  token,
3691
- serverName: (0, import_node_os6.hostname)(),
4363
+ serverName: (0, import_node_os7.hostname)(),
3692
4364
  version: "0.2.0",
3693
4365
  onStateChange: (state) => mdnsService?.updatePairingState(state)
3694
4366
  });
@@ -3745,7 +4417,8 @@ async function start(opts = {}) {
3745
4417
  event.model,
3746
4418
  event.permissionMode,
3747
4419
  event.effort,
3748
- event.images
4420
+ event.images,
4421
+ event.agentType
3749
4422
  );
3750
4423
  wsBridge.broadcast({
3751
4424
  type: "session_list",
@@ -3921,7 +4594,7 @@ async function start(opts = {}) {
3921
4594
  return null;
3922
4595
  }).filter(Boolean).join("\n");
3923
4596
  }
3924
- const suggestion = await provider.generateSuggestion(context);
4597
+ const suggestion = await providerFactory.getProvider("claude-code").generateSuggestion(context);
3925
4598
  wsBridge.send(ws, {
3926
4599
  type: "prompt_suggestion",
3927
4600
  sessionId: event.sessionId,
@@ -3999,6 +4672,11 @@ async function start(opts = {}) {
3999
4672
  }
4000
4673
  break;
4001
4674
  }
4675
+ case "list_agents": {
4676
+ const agents = await providerFactory.detectAgents();
4677
+ wsBridge.send(ws, { type: "agent_list", agents });
4678
+ break;
4679
+ }
4002
4680
  default: {
4003
4681
  wsBridge.send(ws, {
4004
4682
  type: "error",
@@ -4162,7 +4840,7 @@ async function start(opts = {}) {
4162
4840
  openPairing: (duration) => pairingManager.open(duration),
4163
4841
  closePairing: () => pairingManager.close(),
4164
4842
  regenerateToken: async () => {
4165
- const newToken = (0, import_uuid5.v4)();
4843
+ const newToken = (0, import_uuid6.v4)();
4166
4844
  await (0, import_promises4.mkdir)(configDir, { recursive: true });
4167
4845
  await (0, import_promises4.writeFile)(tokenFile, newToken, "utf8");
4168
4846
  instance.token = newToken;
@@ -4181,7 +4859,7 @@ async function start(opts = {}) {
4181
4859
  var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
4182
4860
  function getPackageVersion() {
4183
4861
  try {
4184
- const pkg = JSON.parse((0, import_node_fs3.readFileSync)((0, import_node_path6.join)(__dirname, "..", "package.json"), "utf8"));
4862
+ const pkg = JSON.parse((0, import_node_fs4.readFileSync)((0, import_node_path7.join)(__dirname, "..", "package.json"), "utf8"));
4185
4863
  return pkg.version ?? "0.0.0";
4186
4864
  } catch {
4187
4865
  return "0.0.0";
@@ -4203,11 +4881,38 @@ async function fetchLatestVersion() {
4203
4881
  return null;
4204
4882
  }
4205
4883
  }
4884
+ function isNewer(latest, current) {
4885
+ const a = latest.split(".").map(Number);
4886
+ const b = current.split(".").map(Number);
4887
+ for (let i = 0; i < 3; i++) {
4888
+ if ((a[i] ?? 0) > (b[i] ?? 0)) return true;
4889
+ if ((a[i] ?? 0) < (b[i] ?? 0)) return false;
4890
+ }
4891
+ return false;
4892
+ }
4893
+ async function autoUpdateIfNeeded() {
4894
+ if (process.env.SESSIX_NO_UPDATE_CHECK === "1" || process.env.__SESSIX_UPDATED === "1") return;
4895
+ const latest = await fetchLatestVersion();
4896
+ if (!latest || !isNewer(latest, PKG_VERSION)) return;
4897
+ console.log(` \u{1F4E6} ${t("startup.autoUpdating", { current: PKG_VERSION, latest })}`);
4898
+ console.log();
4899
+ try {
4900
+ (0, import_node_child_process9.execFileSync)("npx", [`sessix-server@${latest}`], {
4901
+ stdio: "inherit",
4902
+ env: { ...process.env, __SESSIX_UPDATED: "1" }
4903
+ });
4904
+ process.exit(0);
4905
+ } catch {
4906
+ console.log(` \u26A0\uFE0F ${t("startup.autoUpdateFailed")}`);
4907
+ console.log();
4908
+ }
4909
+ }
4206
4910
  async function main() {
4207
4911
  console.log("=".repeat(50));
4208
4912
  console.log(`${t("startup.banner")} v${PKG_VERSION}`);
4209
4913
  console.log("=".repeat(50));
4210
4914
  console.log();
4915
+ await autoUpdateIfNeeded();
4211
4916
  const enableAutoConnect = process.env.SESSIX_AUTO_CONNECT !== "false";
4212
4917
  const server = await start({ enableAutoConnect });
4213
4918
  const localIp = getLocalIp();
@@ -4248,13 +4953,6 @@ async function main() {
4248
4953
  console.log(t("startup.pairingOpen"));
4249
4954
  console.log(t("startup.pressT"));
4250
4955
  console.log();
4251
- fetchLatestVersion().then((latest) => {
4252
- if (!latest || latest === PKG_VERSION) return;
4253
- console.log();
4254
- console.log(` \u{1F4E6} ${t("startup.updateAvailable", { current: PKG_VERSION, latest })}`);
4255
- console.log(` npx sessix-server@latest`);
4256
- console.log();
4257
- });
4258
4956
  const shutdown = async (signal) => {
4259
4957
  console.log(`
4260
4958
  [Main] ${t("startup.receivedSignal", { signal })}`);
@@ -4305,7 +5003,7 @@ ${t("startup.pairingReopened")}`);
4305
5003
  }
4306
5004
  }
4307
5005
  function getLocalIp() {
4308
- const interfaces = (0, import_node_os7.networkInterfaces)();
5006
+ const interfaces = (0, import_node_os8.networkInterfaces)();
4309
5007
  for (const iface of Object.values(interfaces)) {
4310
5008
  for (const addr of iface ?? []) {
4311
5009
  if (addr.family === "IPv4" && !addr.internal) {