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/server.js CHANGED
@@ -57,6 +57,9 @@ var zh = {
57
57
  tokenRegenerated: "\u{1F511} Token \u5DF2\u91CD\u7F6E\uFF0C\u6240\u6709\u5BA2\u6237\u7AEF\u5DF2\u65AD\u5F00\uFF0C\u8BF7\u91CD\u65B0\u626B\u7801\u914D\u5BF9",
58
58
  tokenRegenerateFailed: "Token \u91CD\u7F6E\u5931\u8D25:",
59
59
  updateAvailable: "\u53D1\u73B0\u65B0\u7248\u672C v{{latest}}\uFF08\u5F53\u524D v{{current}}\uFF09\uFF0C\u8FD0\u884C\u4EE5\u4E0B\u547D\u4EE4\u66F4\u65B0\uFF1A",
60
+ autoUpdating: "\u53D1\u73B0\u65B0\u7248\u672C v{{latest}}\uFF08\u5F53\u524D v{{current}}\uFF09\uFF0C\u6B63\u5728\u81EA\u52A8\u66F4\u65B0...",
61
+ autoUpdateFailed: "\u81EA\u52A8\u66F4\u65B0\u5931\u8D25\uFF0C\u7EE7\u7EED\u4F7F\u7528\u5F53\u524D\u7248\u672C",
62
+ skipUpdateCheck: "\u8DF3\u8FC7\u7248\u672C\u68C0\u67E5\uFF08SESSIX_NO_UPDATE_CHECK=1\uFF09",
60
63
  receivedSignal: "\u6536\u5230 {{signal}}\uFF0C\u6B63\u5728\u4F18\u96C5\u5173\u95ED...",
61
64
  goodbye: "\u6240\u6709\u670D\u52A1\u5DF2\u5173\u95ED\uFF0C\u518D\u89C1\uFF01",
62
65
  shutdownError: "\u5173\u95ED\u8FC7\u7A0B\u51FA\u9519:",
@@ -167,6 +170,9 @@ var en = {
167
170
  tokenRegenerated: "\u{1F511} Token regenerated, all clients disconnected. Scan QR to re-pair",
168
171
  tokenRegenerateFailed: "Token regeneration failed:",
169
172
  updateAvailable: "New version v{{latest}} available (current v{{current}}). Update with:",
173
+ autoUpdating: "New version v{{latest}} available (current v{{current}}), auto-updating...",
174
+ autoUpdateFailed: "Auto-update failed, continuing with current version",
175
+ skipUpdateCheck: "Skipping update check (SESSIX_NO_UPDATE_CHECK=1)",
170
176
  receivedSignal: "Received {{signal}}, graceful shutdown...",
171
177
  goodbye: "All services closed, goodbye!",
172
178
  shutdownError: "Shutdown error:",
@@ -301,11 +307,11 @@ function t(key, params) {
301
307
  }
302
308
 
303
309
  // src/server.ts
304
- var import_uuid5 = require("uuid");
310
+ var import_uuid6 = require("uuid");
305
311
  var import_promises4 = require("fs/promises");
306
- var import_node_os6 = require("os");
307
- var import_node_path5 = require("path");
308
- var import_node_child_process6 = require("child_process");
312
+ var import_node_os7 = require("os");
313
+ var import_node_path6 = require("path");
314
+ var import_node_child_process8 = require("child_process");
309
315
  var import_node_util = require("util");
310
316
 
311
317
  // src/providers/ProcessProvider.ts
@@ -325,14 +331,14 @@ var import_node_os = require("os");
325
331
  var import_node_child_process = require("child_process");
326
332
  var isWindows = process.platform === "win32";
327
333
  function killProcessCrossPlatform(proc, timeoutMs = 3e3) {
328
- return new Promise((resolve) => {
334
+ return new Promise((resolve2) => {
329
335
  if (proc.exitCode !== null || proc.signalCode !== null) {
330
- resolve();
336
+ resolve2();
331
337
  return;
332
338
  }
333
339
  const onExit = () => {
334
340
  clearTimeout(timer);
335
- resolve();
341
+ resolve2();
336
342
  };
337
343
  proc.once("exit", onExit);
338
344
  if (isWindows) {
@@ -348,7 +354,7 @@ function killProcessCrossPlatform(proc, timeoutMs = 3e3) {
348
354
  proc.kill("SIGKILL");
349
355
  }
350
356
  }
351
- resolve();
357
+ resolve2();
352
358
  }, timeoutMs);
353
359
  });
354
360
  }
@@ -650,7 +656,9 @@ var ProcessProvider = class {
650
656
  const event = result.value;
651
657
  if (event.type === "assistant") {
652
658
  for (const block of event.message.content) {
653
- if (block.type === "tool_use" && block.name === "AskUserQuestion") {
659
+ if (block.type === "tool_use") {
660
+ const isQuestion = block.name === "AskUserQuestion" || block.name === "AskFollowupQuestion";
661
+ if (!isQuestion) continue;
654
662
  const input = block.input;
655
663
  const question = input.question ?? "";
656
664
  if (!question) continue;
@@ -662,6 +670,7 @@ var ProcessProvider = class {
662
670
  }
663
671
  if (sessionSet.has(prevKey)) continue;
664
672
  sessionSet.add(prevKey);
673
+ console.log(`[ProcessProvider] Session ${sessionId}: detected ${block.name} (toolUseId=${block.id})`);
665
674
  this.emitter.emit(this.getQuestionEventName(sessionId), {
666
675
  toolUseId: block.id,
667
676
  question,
@@ -777,7 +786,7 @@ var ProcessProvider = class {
777
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):
778
787
 
779
788
  ${context}`;
780
- return new Promise((resolve, reject) => {
789
+ return new Promise((resolve2, reject) => {
781
790
  const env = { ...process.env };
782
791
  delete env.CLAUDECODE;
783
792
  const proc = (0, import_child_process.spawn)(CLAUDE_PATH, ["-p", prompt, "--output-format", "text"], {
@@ -792,7 +801,7 @@ ${context}`;
792
801
  });
793
802
  proc.once("exit", (code) => {
794
803
  if (code === 0) {
795
- resolve(output.trim());
804
+ resolve2(output.trim());
796
805
  } else {
797
806
  reject(new Error(`generateSuggestion process exit code: ${code}`));
798
807
  }
@@ -819,10 +828,10 @@ ${context}`;
819
828
  tool_use_id: toolUseId,
820
829
  content: answer
821
830
  });
822
- await new Promise((resolve, reject) => {
831
+ await new Promise((resolve2, reject) => {
823
832
  entry.process.stdin.write(toolResult + "\n", (err) => {
824
833
  if (err) reject(err);
825
- else resolve();
834
+ else resolve2();
826
835
  });
827
836
  });
828
837
  console.log(`[ProcessProvider] Session ${sessionId}: AskUserQuestion answered (toolUseId=${toolUseId})`);
@@ -853,11 +862,643 @@ ${context}`;
853
862
  }
854
863
  };
855
864
 
856
- // src/session/SessionManager.ts
865
+ // src/providers/CodexProvider.ts
866
+ var import_child_process2 = require("child_process");
867
+ var import_readline2 = require("readline");
868
+ var import_events2 = require("events");
869
+ var import_fs = require("fs");
870
+ var import_path = require("path");
871
+ var import_os = require("os");
857
872
  var import_uuid2 = require("uuid");
873
+
874
+ // src/utils/codexPath.ts
875
+ var import_node_child_process3 = require("child_process");
876
+ var import_node_fs2 = require("fs");
877
+ var import_node_path2 = require("path");
878
+ var import_node_os3 = require("os");
879
+ function findCodexPath() {
880
+ try {
881
+ const cmd = isWindows ? "where codex" : "which codex";
882
+ return (0, import_node_child_process3.execSync)(cmd, { encoding: "utf-8" }).trim().split("\n")[0];
883
+ } catch {
884
+ }
885
+ const candidates = isWindows ? [
886
+ (0, import_node_path2.join)(process.env.LOCALAPPDATA ?? "", "Programs", "codex", "codex.exe"),
887
+ (0, import_node_path2.join)((0, import_node_os3.homedir)(), ".codex", "local", "codex.exe")
888
+ ] : [
889
+ "/opt/homebrew/bin/codex",
890
+ "/usr/local/bin/codex",
891
+ (0, import_node_path2.join)((0, import_node_os3.homedir)(), ".local", "bin", "codex")
892
+ ];
893
+ for (const candidate of candidates) {
894
+ try {
895
+ (0, import_node_fs2.accessSync)(candidate, import_node_fs2.constants.X_OK);
896
+ return candidate;
897
+ } catch {
898
+ }
899
+ }
900
+ return "codex";
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
+ }
921
+ var CODEX_PATH = findCodexPath();
922
+ var CODEX_JS_ENTRY = resolveCodexJsEntry(CODEX_PATH);
923
+ async function getCodexVersion() {
924
+ try {
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, {
927
+ encoding: "utf-8",
928
+ timeout: 5e3
929
+ }).trim();
930
+ return output.replace(/^codex-cli\s+/, "");
931
+ } catch {
932
+ return void 0;
933
+ }
934
+ }
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
+ }
944
+ try {
945
+ (0, import_node_fs2.accessSync)(CODEX_PATH, import_node_fs2.constants.X_OK);
946
+ return true;
947
+ } catch {
948
+ try {
949
+ (0, import_node_child_process3.execSync)(`"${CODEX_PATH}" --version`, { timeout: 5e3 });
950
+ return true;
951
+ } catch {
952
+ return false;
953
+ }
954
+ }
955
+ }
956
+
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");
960
+ var CodexProvider = class {
961
+ activeSessions = /* @__PURE__ */ new Map();
962
+ emitter = new import_events2.EventEmitter();
963
+ /** 持久化的会话元数据(sessionId → metadata) */
964
+ persistedSessions = /* @__PURE__ */ new Map();
965
+ /** 自增 ID 计数器,用于生成 ClaudeStreamEvent 中的 message/block ID */
966
+ idCounter = 0;
967
+ constructor() {
968
+ this.loadPersistedSessions();
969
+ }
970
+ async startSession(opts) {
971
+ const { projectPath, message, sessionId: existingSessionId } = opts;
972
+ const sessionId = existingSessionId ?? (0, import_uuid2.v4)();
973
+ if (this.activeSessions.has(sessionId)) {
974
+ await this.killSession(sessionId);
975
+ }
976
+ const projectId = projectPath.split("/").filter(Boolean).pop() ?? "unknown";
977
+ const session = {
978
+ id: sessionId,
979
+ projectId,
980
+ projectPath,
981
+ status: "running",
982
+ createdAt: Date.now(),
983
+ lastActiveAt: Date.now(),
984
+ summary: message.slice(0, 80),
985
+ agentType: "codex"
986
+ };
987
+ const resume = opts.resume ?? !!existingSessionId;
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);
999
+ session.pid = proc.pid;
1000
+ this.activeSessions.set(sessionId, {
1001
+ session,
1002
+ process: proc,
1003
+ threadId: resumeThreadId,
1004
+ turnStartTime: Date.now()
1005
+ });
1006
+ const initEvent = {
1007
+ type: "system",
1008
+ subtype: "init",
1009
+ session_id: sessionId
1010
+ };
1011
+ this.emitter.emit(this.getEventName(sessionId), initEvent);
1012
+ proc.on("error", (err) => {
1013
+ console.error(`[CodexProvider] Session ${sessionId} process error:`, err.message);
1014
+ this.activeSessions.delete(sessionId);
1015
+ this.emitError(sessionId, `Process spawn failed: ${err.message}`);
1016
+ });
1017
+ this.attachStdoutListener(sessionId, proc);
1018
+ this.attachStderrListener(sessionId, proc);
1019
+ this.attachExitListener(sessionId, proc);
1020
+ return session;
1021
+ }
1022
+ async killSession(sessionId) {
1023
+ const entry = this.activeSessions.get(sessionId);
1024
+ if (!entry) return;
1025
+ if (entry.process.exitCode === null && entry.process.signalCode === null) {
1026
+ try {
1027
+ entry.process.stdin?.end();
1028
+ } catch {
1029
+ }
1030
+ await killProcessCrossPlatform(entry.process);
1031
+ }
1032
+ this.activeSessions.delete(sessionId);
1033
+ }
1034
+ async sendMessage(sessionId, message) {
1035
+ let entry = this.activeSessions.get(sessionId);
1036
+ if (!entry) {
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);
1060
+ }
1061
+ const procAlive = entry.process.exitCode === null && entry.process.signalCode === null;
1062
+ if (procAlive) {
1063
+ try {
1064
+ entry.process.stdin?.end();
1065
+ } catch {
1066
+ }
1067
+ await killProcessCrossPlatform(entry.process);
1068
+ }
1069
+ const threadId = entry.threadId ?? this.persistedSessions.get(sessionId)?.threadId;
1070
+ if (!threadId) {
1071
+ console.warn(`[CodexProvider] Session ${sessionId} has no thread ID, falling back to new thread`);
1072
+ }
1073
+ const proc = this.spawnCodexProcess(entry.session.projectPath, message, threadId ?? void 0);
1074
+ entry.session.status = "running";
1075
+ entry.session.lastActiveAt = Date.now();
1076
+ entry.session.pid = proc.pid;
1077
+ entry.process = proc;
1078
+ entry.turnStartTime = Date.now();
1079
+ proc.on("error", (err) => {
1080
+ console.error(`[CodexProvider] Session ${sessionId} sendMessage process error:`, err.message);
1081
+ this.activeSessions.delete(sessionId);
1082
+ this.emitError(sessionId, `Failed to send message: ${err.message}`);
1083
+ });
1084
+ this.attachStdoutListener(sessionId, proc);
1085
+ this.attachStderrListener(sessionId, proc);
1086
+ this.attachExitListener(sessionId, proc);
1087
+ }
1088
+ onEvent(sessionId, callback) {
1089
+ const eventName = this.getEventName(sessionId);
1090
+ this.emitter.on(eventName, callback);
1091
+ return () => {
1092
+ this.emitter.off(eventName, callback);
1093
+ };
1094
+ }
1095
+ getActiveSessions() {
1096
+ return Array.from(this.activeSessions.values()).map((e) => e.session);
1097
+ }
1098
+ async generateSuggestion(_context) {
1099
+ return "";
1100
+ }
1101
+ async answerQuestion(_sessionId, _toolUseId, _answer) {
1102
+ }
1103
+ onQuestion(_sessionId, _callback) {
1104
+ return () => {
1105
+ };
1106
+ }
1107
+ // ============================================
1108
+ // 私有方法
1109
+ // ============================================
1110
+ nextId() {
1111
+ return `codex_${++this.idCounter}`;
1112
+ }
1113
+ /**
1114
+ * 启动 codex CLI 进程
1115
+ *
1116
+ * @param projectPath 工作目录
1117
+ * @param message 用户消息
1118
+ * @param resumeThreadId 如果提供,则使用 `codex exec resume` 恢复会话
1119
+ */
1120
+ spawnCodexProcess(projectPath, message, resumeThreadId, model) {
1121
+ const args = ["exec", "--json", "--full-auto"];
1122
+ if (model) {
1123
+ args.push("-m", model);
1124
+ }
1125
+ args.push("-C", projectPath);
1126
+ if (resumeThreadId) {
1127
+ args.push("resume", resumeThreadId);
1128
+ }
1129
+ args.push(message);
1130
+ const env = { ...process.env };
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, {
1143
+ cwd: projectPath,
1144
+ env,
1145
+ stdio: ["pipe", "pipe", "pipe"]
1146
+ });
1147
+ try {
1148
+ proc.stdin?.end();
1149
+ } catch {
1150
+ }
1151
+ return proc;
1152
+ }
1153
+ /**
1154
+ * 挂载 stdout 监听器,逐行解析 Codex NDJSON 并转换为 ClaudeStreamEvent
1155
+ */
1156
+ attachStdoutListener(sessionId, proc) {
1157
+ if (!proc.stdout) return;
1158
+ const rl = (0, import_readline2.createInterface)({ input: proc.stdout, crlfDelay: Infinity });
1159
+ const entry = this.activeSessions.get(sessionId);
1160
+ if (entry) entry.rl = rl;
1161
+ rl.on("line", (line) => {
1162
+ const trimmed = line.trim();
1163
+ if (!trimmed) return;
1164
+ console.log(`[CodexProvider] Session ${sessionId} stdout: ${trimmed.substring(0, 200)}`);
1165
+ let event;
1166
+ try {
1167
+ event = JSON.parse(trimmed);
1168
+ } catch {
1169
+ console.warn(`[CodexProvider] Session ${sessionId}: failed to parse: ${trimmed.substring(0, 100)}`);
1170
+ return;
1171
+ }
1172
+ this.handleCodexEvent(sessionId, event);
1173
+ });
1174
+ }
1175
+ /**
1176
+ * 处理单个 Codex NDJSON 事件,转换并发射 ClaudeStreamEvent
1177
+ */
1178
+ handleCodexEvent(sessionId, event) {
1179
+ const entry = this.activeSessions.get(sessionId);
1180
+ if (!entry) return;
1181
+ entry.session.lastActiveAt = Date.now();
1182
+ switch (event.type) {
1183
+ case "thread.started": {
1184
+ if (event.thread_id) {
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}`);
1194
+ }
1195
+ break;
1196
+ }
1197
+ case "turn.started": {
1198
+ entry.session.status = "running";
1199
+ entry.turnStartTime = Date.now();
1200
+ break;
1201
+ }
1202
+ case "item.completed": {
1203
+ if (!event.item) break;
1204
+ this.handleItemCompleted(sessionId, event.item);
1205
+ break;
1206
+ }
1207
+ case "turn.completed": {
1208
+ entry.session.status = "idle";
1209
+ const duration = entry.turnStartTime ? Date.now() - entry.turnStartTime : 0;
1210
+ const resultEvent = {
1211
+ type: "result",
1212
+ subtype: "success",
1213
+ session_id: sessionId,
1214
+ is_error: false,
1215
+ result: "",
1216
+ duration_ms: duration,
1217
+ num_turns: 1,
1218
+ usage: event.usage
1219
+ };
1220
+ this.emitter.emit(this.getEventName(sessionId), resultEvent);
1221
+ break;
1222
+ }
1223
+ case "turn.failed": {
1224
+ entry.session.status = "error";
1225
+ const failEvent = {
1226
+ type: "result",
1227
+ subtype: "error",
1228
+ session_id: sessionId,
1229
+ is_error: true,
1230
+ result: event.error ?? "Turn failed",
1231
+ duration_ms: entry.turnStartTime ? Date.now() - entry.turnStartTime : 0,
1232
+ num_turns: 1
1233
+ };
1234
+ this.emitter.emit(this.getEventName(sessionId), failEvent);
1235
+ break;
1236
+ }
1237
+ }
1238
+ }
1239
+ /**
1240
+ * 处理 item.completed 事件,按 item.type 转换为对应的 ClaudeStreamEvent
1241
+ */
1242
+ handleItemCompleted(sessionId, item) {
1243
+ switch (item.type) {
1244
+ case "agent_message": {
1245
+ const msgEvent = {
1246
+ type: "assistant",
1247
+ session_id: sessionId,
1248
+ message: {
1249
+ id: item.id || this.nextId(),
1250
+ model: "codex",
1251
+ role: "assistant",
1252
+ content: [{ type: "text", text: item.text ?? "" }]
1253
+ }
1254
+ };
1255
+ this.emitter.emit(this.getEventName(sessionId), msgEvent);
1256
+ break;
1257
+ }
1258
+ case "command_execution": {
1259
+ const toolUseId = item.id || this.nextId();
1260
+ const toolEvent = {
1261
+ type: "assistant",
1262
+ session_id: sessionId,
1263
+ message: {
1264
+ id: this.nextId(),
1265
+ model: "codex",
1266
+ role: "assistant",
1267
+ content: [{
1268
+ type: "tool_use",
1269
+ id: toolUseId,
1270
+ name: "Bash",
1271
+ input: { command: item.command ?? "" }
1272
+ }]
1273
+ }
1274
+ };
1275
+ this.emitter.emit(this.getEventName(sessionId), toolEvent);
1276
+ const resultContent = item.aggregated_output ?? "";
1277
+ const isError = item.exit_code != null && item.exit_code !== 0;
1278
+ const toolResultEvent = {
1279
+ type: "user",
1280
+ session_id: sessionId,
1281
+ message: {
1282
+ role: "user",
1283
+ content: [{
1284
+ type: "tool_result",
1285
+ tool_use_id: toolUseId,
1286
+ content: resultContent,
1287
+ is_error: isError
1288
+ }]
1289
+ }
1290
+ };
1291
+ this.emitter.emit(this.getEventName(sessionId), toolResultEvent);
1292
+ break;
1293
+ }
1294
+ case "file_change": {
1295
+ const editToolUseId = item.id || this.nextId();
1296
+ const toolEvent = {
1297
+ type: "assistant",
1298
+ session_id: sessionId,
1299
+ message: {
1300
+ id: this.nextId(),
1301
+ model: "codex",
1302
+ role: "assistant",
1303
+ content: [{
1304
+ type: "tool_use",
1305
+ id: editToolUseId,
1306
+ name: "Edit",
1307
+ input: { file_path: item.file_path ?? "", diff: item.diff ?? "" }
1308
+ }]
1309
+ }
1310
+ };
1311
+ this.emitter.emit(this.getEventName(sessionId), toolEvent);
1312
+ const toolResultEvent = {
1313
+ type: "user",
1314
+ session_id: sessionId,
1315
+ message: {
1316
+ role: "user",
1317
+ content: [{
1318
+ type: "tool_result",
1319
+ tool_use_id: editToolUseId,
1320
+ content: `File edited: ${item.file_path ?? "unknown"}`
1321
+ }]
1322
+ }
1323
+ };
1324
+ this.emitter.emit(this.getEventName(sessionId), toolResultEvent);
1325
+ break;
1326
+ }
1327
+ case "reasoning": {
1328
+ const thinkEvent = {
1329
+ type: "assistant",
1330
+ session_id: sessionId,
1331
+ message: {
1332
+ id: item.id || this.nextId(),
1333
+ model: "codex",
1334
+ role: "assistant",
1335
+ content: [{ type: "thinking", thinking: item.text ?? "" }]
1336
+ }
1337
+ };
1338
+ this.emitter.emit(this.getEventName(sessionId), thinkEvent);
1339
+ break;
1340
+ }
1341
+ }
1342
+ }
1343
+ attachStderrListener(sessionId, proc) {
1344
+ if (!proc.stderr) return;
1345
+ proc.stderr.on("data", (data) => {
1346
+ const text = data.toString().trim();
1347
+ if (text) {
1348
+ console.error(`[CodexProvider] Session ${sessionId} stderr: ${text}`);
1349
+ }
1350
+ });
1351
+ }
1352
+ attachExitListener(sessionId, proc) {
1353
+ proc.once("exit", (code, signal) => {
1354
+ const entry = this.activeSessions.get(sessionId);
1355
+ if (!entry) return;
1356
+ if (entry.process !== proc) return;
1357
+ console.log(`[CodexProvider] Session ${sessionId} process exited: code=${code} signal=${signal} threadId=${entry.threadId ?? "NONE"}`);
1358
+ if (entry.rl) {
1359
+ entry.rl.close();
1360
+ entry.rl = void 0;
1361
+ }
1362
+ entry.session.pid = void 0;
1363
+ entry.session.lastActiveAt = Date.now();
1364
+ const alreadyHasResult = entry.session.status === "idle" || entry.session.status === "error";
1365
+ if (alreadyHasResult) return;
1366
+ const isNormal = isNormalExit(code, signal);
1367
+ entry.session.status = isNormal ? "idle" : "error";
1368
+ if (!isNormal) {
1369
+ console.error(`[CodexProvider] Session ${sessionId}: process exited abnormally code=${code} signal=${signal}`);
1370
+ }
1371
+ const syntheticResult = {
1372
+ type: "result",
1373
+ subtype: isNormal ? "success" : "error",
1374
+ session_id: sessionId,
1375
+ is_error: !isNormal,
1376
+ result: isNormal ? "" : `Process exited code=${code} signal=${signal}`,
1377
+ duration_ms: 0,
1378
+ num_turns: 0
1379
+ };
1380
+ this.emitter.emit(this.getEventName(sessionId), syntheticResult);
1381
+ });
1382
+ }
1383
+ emitError(sessionId, message) {
1384
+ const event = {
1385
+ type: "result",
1386
+ subtype: "error",
1387
+ result: message,
1388
+ session_id: sessionId,
1389
+ duration_ms: 0,
1390
+ is_error: true,
1391
+ num_turns: 0
1392
+ };
1393
+ this.emitter.emit(this.getEventName(sessionId), event);
1394
+ }
1395
+ getEventName(sessionId) {
1396
+ return `claude:${sessionId}`;
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
+ }
1446
+ };
1447
+
1448
+ // src/providers/ProviderFactory.ts
1449
+ var import_node_child_process4 = require("child_process");
1450
+ var ProviderFactory = class {
1451
+ providers = /* @__PURE__ */ new Map();
1452
+ getProvider(agentType) {
1453
+ const existing = this.providers.get(agentType);
1454
+ if (existing) return existing;
1455
+ let provider;
1456
+ switch (agentType) {
1457
+ case "codex":
1458
+ provider = new CodexProvider();
1459
+ break;
1460
+ case "claude-code":
1461
+ default:
1462
+ provider = new ProcessProvider();
1463
+ break;
1464
+ }
1465
+ this.providers.set(agentType, provider);
1466
+ return provider;
1467
+ }
1468
+ async detectAgents() {
1469
+ const agents = [];
1470
+ try {
1471
+ const claudePath = findClaudePath();
1472
+ const claudeVersion = (0, import_node_child_process4.execSync)(`"${claudePath}" --version`, {
1473
+ encoding: "utf-8",
1474
+ timeout: 5e3
1475
+ }).trim();
1476
+ agents.push({
1477
+ type: "claude-code",
1478
+ name: "Claude Code",
1479
+ available: true,
1480
+ version: claudeVersion
1481
+ });
1482
+ } catch {
1483
+ agents.push({ type: "claude-code", name: "Claude Code", available: false });
1484
+ }
1485
+ if (isCodexAvailable()) {
1486
+ const version = await getCodexVersion();
1487
+ agents.push({ type: "codex", name: "Codex", available: true, version });
1488
+ } else {
1489
+ agents.push({ type: "codex", name: "Codex", available: false });
1490
+ }
1491
+ return agents;
1492
+ }
1493
+ };
1494
+
1495
+ // src/session/SessionManager.ts
1496
+ var import_uuid3 = require("uuid");
858
1497
  var BUFFER_MAX = 5e3;
859
1498
  var SessionManager = class {
860
1499
  provider;
1500
+ providerFactory;
1501
+ sessionAgentType = /* @__PURE__ */ new Map();
861
1502
  /** 事件回调列表(事件会被转发到 WsBridge) */
862
1503
  eventCallbacks = [];
863
1504
  /** 每个会话的事件流取消订阅函数 */
@@ -883,8 +1524,26 @@ var SessionManager = class {
883
1524
  bufferTruncated = /* @__PURE__ */ new Set();
884
1525
  /** sessionId → projectPath 映射,用于截断时从 JSONL 补全历史 */
885
1526
  sessionProjectPaths = /* @__PURE__ */ new Map();
886
- constructor(provider) {
887
- this.provider = provider;
1527
+ constructor(providerOrFactory) {
1528
+ if (providerOrFactory instanceof ProviderFactory) {
1529
+ this.providerFactory = providerOrFactory;
1530
+ this.provider = providerOrFactory.getProvider("claude-code");
1531
+ } else {
1532
+ this.provider = providerOrFactory;
1533
+ this.providerFactory = null;
1534
+ }
1535
+ }
1536
+ getProviderForSession(sessionId) {
1537
+ if (!this.providerFactory) return this.provider;
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");
888
1547
  }
889
1548
  // ============================================
890
1549
  // 公开 API
@@ -895,8 +1554,9 @@ var SessionManager = class {
895
1554
  * 调用 provider.startSession(),订阅事件流,
896
1555
  * 将 ClaudeStreamEvent 包装为 ServerEvent 转发。
897
1556
  */
898
- async createSession(projectPath, message, resumeSessionId, newSessionId, model, permissionMode, effort, images) {
899
- const session = await this.provider.startSession({
1557
+ async createSession(projectPath, message, resumeSessionId, newSessionId, model, permissionMode, effort, images, agentType = "claude-code") {
1558
+ const provider = this.providerFactory ? this.providerFactory.getProvider(agentType) : this.provider;
1559
+ const session = await provider.startSession({
900
1560
  projectPath,
901
1561
  message,
902
1562
  sessionId: resumeSessionId ?? newSessionId,
@@ -906,6 +1566,7 @@ var SessionManager = class {
906
1566
  effort,
907
1567
  images
908
1568
  });
1569
+ this.sessionAgentType.set(session.id, agentType);
909
1570
  this.lastBroadcastStatus.set(session.id, session.status);
910
1571
  this.sessionProjectPaths.set(session.id, projectPath);
911
1572
  this.unsubscribeSession(session.id);
@@ -917,7 +1578,8 @@ var SessionManager = class {
917
1578
  * 发送消息到已有会话
918
1579
  */
919
1580
  async sendMessage(sessionId, message, permissionMode, images) {
920
- await this.provider.sendMessage(sessionId, message, permissionMode, images);
1581
+ const provider = this.getProviderForSession(sessionId);
1582
+ await provider.sendMessage(sessionId, message, permissionMode, images);
921
1583
  this.updateSessionStatus(sessionId, "running");
922
1584
  console.log(`[SessionManager] Message sent to session: ${sessionId}`);
923
1585
  }
@@ -937,7 +1599,9 @@ var SessionManager = class {
937
1599
  clearTimeout(pending.timer);
938
1600
  this.pendingAssistantEvents.delete(sessionId);
939
1601
  }
940
- await this.provider.killSession(sessionId);
1602
+ const provider = this.getProviderForSession(sessionId);
1603
+ await provider.killSession(sessionId);
1604
+ this.sessionAgentType.delete(sessionId);
941
1605
  console.log(`[SessionManager] Session killed: ${sessionId}`);
942
1606
  }
943
1607
  /**
@@ -1016,7 +1680,11 @@ var SessionManager = class {
1016
1680
  * 获取所有活跃会话(含服务器端统计)
1017
1681
  */
1018
1682
  getActiveSessions() {
1019
- return this.provider.getActiveSessions().map((session) => {
1683
+ const rawSessions = this.providerFactory ? [
1684
+ ...this.providerFactory.getProvider("claude-code").getActiveSessions(),
1685
+ ...this.providerFactory.getProvider("codex").getActiveSessions()
1686
+ ] : this.provider.getActiveSessions();
1687
+ return rawSessions.map((session) => {
1020
1688
  const stats = this.getSessionStats(session.id);
1021
1689
  return stats ? { ...session, stats } : session;
1022
1690
  });
@@ -1046,6 +1714,7 @@ var SessionManager = class {
1046
1714
  this.sessionEventBuffers.clear();
1047
1715
  this.bufferTruncated.clear();
1048
1716
  this.sessionProjectPaths.clear();
1717
+ this.sessionAgentType.clear();
1049
1718
  this.sessionStats.clear();
1050
1719
  for (const [, pending] of this.pendingAssistantEvents) {
1051
1720
  clearTimeout(pending.timer);
@@ -1063,10 +1732,11 @@ var SessionManager = class {
1063
1732
  * 订阅指定会话的事件流(包括 AskUserQuestion 问题事件)
1064
1733
  */
1065
1734
  subscribeToSession(sessionId) {
1066
- const unsubscribeEvent = this.provider.onEvent(sessionId, (event) => {
1735
+ const provider = this.getProviderForSession(sessionId);
1736
+ const unsubscribeEvent = provider.onEvent(sessionId, (event) => {
1067
1737
  this.handleClaudeEvent(sessionId, event);
1068
1738
  });
1069
- const unsubscribeQuestion = this.provider.onQuestion(
1739
+ const unsubscribeQuestion = provider.onQuestion(
1070
1740
  sessionId,
1071
1741
  ({ toolUseId, question, options }) => {
1072
1742
  this.handleAskUserQuestion(sessionId, toolUseId, question, options);
@@ -1241,7 +1911,7 @@ var SessionManager = class {
1241
1911
  console.log(`[SessionManager] Session ${sessionId}: AskUserQuestion updated (requestId=${existingRequestId})`);
1242
1912
  return;
1243
1913
  }
1244
- const requestId = (0, import_uuid2.v4)();
1914
+ const requestId = (0, import_uuid3.v4)();
1245
1915
  const request = {
1246
1916
  id: requestId,
1247
1917
  sessionId,
@@ -1252,12 +1922,13 @@ var SessionManager = class {
1252
1922
  };
1253
1923
  this.updateSessionStatus(sessionId, "waiting_question");
1254
1924
  this.emit({ type: "question_request", request });
1255
- const answerPromise = new Promise((resolve) => {
1256
- 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 });
1257
1927
  });
1258
1928
  answerPromise.then(async (answer) => {
1259
1929
  try {
1260
- await this.provider.answerQuestion(sessionId, toolUseId, answer);
1930
+ const provider = this.getProviderForSession(sessionId);
1931
+ await provider.answerQuestion(sessionId, toolUseId, answer);
1261
1932
  } catch (err) {
1262
1933
  console.error(`[SessionManager] answerQuestion failed (${sessionId}):`, err);
1263
1934
  }
@@ -1494,11 +2165,11 @@ var WsBridge = class _WsBridge {
1494
2165
  * 使用此方法代替 new WsBridge(),确保 EADDRINUSE 等错误能被调用方的 try-catch 捕获。
1495
2166
  */
1496
2167
  static async create(options) {
1497
- return new Promise((resolve, reject) => {
2168
+ return new Promise((resolve2, reject) => {
1498
2169
  const bridge = new _WsBridge(options);
1499
2170
  bridge.wss.once("listening", () => {
1500
2171
  bridge.wss.on("error", (err) => console.error(`[WsBridge] ${t("ws.serverError")}:`, err));
1501
- resolve(bridge);
2172
+ resolve2(bridge);
1502
2173
  });
1503
2174
  bridge.wss.once("error", reject);
1504
2175
  });
@@ -1561,7 +2232,7 @@ var WsBridge = class _WsBridge {
1561
2232
  }
1562
2233
  /** 优雅关闭 WebSocket 服务 */
1563
2234
  close() {
1564
- return new Promise((resolve, reject) => {
2235
+ return new Promise((resolve2, reject) => {
1565
2236
  if (this.heartbeatTimer) {
1566
2237
  clearInterval(this.heartbeatTimer);
1567
2238
  this.heartbeatTimer = null;
@@ -1574,7 +2245,7 @@ var WsBridge = class _WsBridge {
1574
2245
  reject(err);
1575
2246
  } else {
1576
2247
  console.log("[WsBridge] WebSocket server closed");
1577
- resolve();
2248
+ resolve2();
1578
2249
  }
1579
2250
  });
1580
2251
  });
@@ -1680,15 +2351,15 @@ var WsBridge = class _WsBridge {
1680
2351
 
1681
2352
  // src/approval/ApprovalProxy.ts
1682
2353
  var import_node_http = __toESM(require("http"));
1683
- var import_node_fs2 = __toESM(require("fs"));
1684
- var import_node_path2 = __toESM(require("path"));
1685
- var import_node_os3 = __toESM(require("os"));
1686
- var import_uuid3 = require("uuid");
2354
+ var import_node_fs3 = __toESM(require("fs"));
2355
+ var import_node_path3 = __toESM(require("path"));
2356
+ var import_node_os4 = __toESM(require("os"));
2357
+ var import_uuid4 = require("uuid");
1687
2358
  var ApprovalProxy = class _ApprovalProxy {
1688
2359
  server;
1689
2360
  token;
1690
2361
  port;
1691
- settingsPath = import_node_path2.default.join(import_node_os3.default.homedir(), ".claude", "settings.json");
2362
+ settingsPath = import_node_path3.default.join(import_node_os4.default.homedir(), ".claude", "settings.json");
1692
2363
  /** 待处理的审批请求:requestId -> { resolve, timer, request } */
1693
2364
  pendingApprovals = /* @__PURE__ */ new Map();
1694
2365
  /** 审批请求回调(通知外部推送到手机) */
@@ -1714,11 +2385,11 @@ var ApprovalProxy = class _ApprovalProxy {
1714
2385
  * 异步工厂方法:等待端口监听成功后 resolve,端口占用等错误时 reject。
1715
2386
  */
1716
2387
  static async create(options) {
1717
- return new Promise((resolve, reject) => {
2388
+ return new Promise((resolve2, reject) => {
1718
2389
  const proxy = new _ApprovalProxy(options);
1719
2390
  proxy.server.once("listening", () => {
1720
2391
  proxy.server.on("error", (err) => console.error(`[ApprovalProxy] ${t("approval.serverError")}:`, err));
1721
- resolve(proxy);
2392
+ resolve2(proxy);
1722
2393
  });
1723
2394
  proxy.server.once("error", reject);
1724
2395
  });
@@ -1793,7 +2464,7 @@ var ApprovalProxy = class _ApprovalProxy {
1793
2464
  isToolInClaudeSettings(toolName, projectPath) {
1794
2465
  const checkPath = (filepath) => {
1795
2466
  try {
1796
- const raw = import_node_fs2.default.readFileSync(filepath, "utf-8");
2467
+ const raw = import_node_fs3.default.readFileSync(filepath, "utf-8");
1797
2468
  const settings = JSON.parse(raw);
1798
2469
  const allow = settings?.permissions?.allow ?? [];
1799
2470
  return allow.some((entry) => {
@@ -1807,24 +2478,24 @@ var ApprovalProxy = class _ApprovalProxy {
1807
2478
  }
1808
2479
  };
1809
2480
  if (projectPath) {
1810
- const projectSettingsPath = import_node_path2.default.join(projectPath, ".claude", "settings.json");
2481
+ const projectSettingsPath = import_node_path3.default.join(projectPath, ".claude", "settings.json");
1811
2482
  if (checkPath(projectSettingsPath)) return true;
1812
2483
  }
1813
2484
  return checkPath(this.settingsPath);
1814
2485
  }
1815
2486
  /** 将工具写入 settings.json permissions.allow(项目级或全局) */
1816
2487
  addToClaudeSettings(projectPath, toolName) {
1817
- const targetPath = projectPath ? import_node_path2.default.join(projectPath, ".claude", "settings.json") : this.settingsPath;
2488
+ const targetPath = projectPath ? import_node_path3.default.join(projectPath, ".claude", "settings.json") : this.settingsPath;
1818
2489
  try {
1819
2490
  if (projectPath) {
1820
- const dir = import_node_path2.default.dirname(targetPath);
1821
- if (!import_node_fs2.default.existsSync(dir)) {
1822
- import_node_fs2.default.mkdirSync(dir, { recursive: true });
2491
+ const dir = import_node_path3.default.dirname(targetPath);
2492
+ if (!import_node_fs3.default.existsSync(dir)) {
2493
+ import_node_fs3.default.mkdirSync(dir, { recursive: true });
1823
2494
  }
1824
2495
  }
1825
2496
  let settings = {};
1826
2497
  try {
1827
- settings = JSON.parse(import_node_fs2.default.readFileSync(targetPath, "utf-8"));
2498
+ settings = JSON.parse(import_node_fs3.default.readFileSync(targetPath, "utf-8"));
1828
2499
  } catch {
1829
2500
  }
1830
2501
  if (!settings.permissions) {
@@ -1838,7 +2509,7 @@ var ApprovalProxy = class _ApprovalProxy {
1838
2509
  const entry = `${toolName}(*)`;
1839
2510
  if (!allow.includes(entry)) {
1840
2511
  allow.push(entry);
1841
- import_node_fs2.default.writeFileSync(targetPath, JSON.stringify(settings, null, 2), "utf-8");
2512
+ import_node_fs3.default.writeFileSync(targetPath, JSON.stringify(settings, null, 2), "utf-8");
1842
2513
  const label = projectPath ? `${projectPath}/.claude/settings.json` : "~/.claude/settings.json";
1843
2514
  console.log(`[ApprovalProxy] ${t("approval.alwaysAllowWritten", { entry, label })}`);
1844
2515
  }
@@ -1875,7 +2546,7 @@ var ApprovalProxy = class _ApprovalProxy {
1875
2546
  }
1876
2547
  /** 优雅关闭 HTTP 服务 */
1877
2548
  close() {
1878
- return new Promise((resolve, reject) => {
2549
+ return new Promise((resolve2, reject) => {
1879
2550
  const pendingEntries = Array.from(this.pendingApprovals.entries());
1880
2551
  for (const [, pending] of pendingEntries) {
1881
2552
  clearTimeout(pending.timer);
@@ -1887,7 +2558,7 @@ var ApprovalProxy = class _ApprovalProxy {
1887
2558
  reject(err);
1888
2559
  } else {
1889
2560
  console.log(`[ApprovalProxy] ${t("approval.httpClosed")}`);
1890
- resolve();
2561
+ resolve2();
1891
2562
  }
1892
2563
  });
1893
2564
  });
@@ -1933,7 +2604,7 @@ var ApprovalProxy = class _ApprovalProxy {
1933
2604
  try {
1934
2605
  const body = await this.parseJsonBody(req);
1935
2606
  const payload = body.payload ?? body;
1936
- const requestId = (0, import_uuid3.v4)();
2607
+ const requestId = (0, import_uuid4.v4)();
1937
2608
  const projectPath = String(body.projectPath ?? "unknown");
1938
2609
  const toolName = String(payload.tool_name ?? body.tool_name ?? "unknown");
1939
2610
  const toolInput = payload.tool_input ?? body.tool_input ?? {};
@@ -1958,13 +2629,13 @@ var ApprovalProxy = class _ApprovalProxy {
1958
2629
  return;
1959
2630
  }
1960
2631
  this.notifyApprovalRequest(approvalRequest);
1961
- const decision = await new Promise((resolve) => {
2632
+ const decision = await new Promise((resolve2) => {
1962
2633
  const timer = setTimeout(() => {
1963
2634
  console.log(`[ApprovalProxy] ${t("approval.timeout", { id: requestId })}`);
1964
2635
  this.pendingApprovals.delete(requestId);
1965
- resolve({ decision: "allow" });
2636
+ resolve2({ decision: "allow" });
1966
2637
  }, 325e3);
1967
- this.pendingApprovals.set(requestId, { resolve, timer, request: approvalRequest });
2638
+ this.pendingApprovals.set(requestId, { resolve: resolve2, timer, request: approvalRequest });
1968
2639
  });
1969
2640
  this.sendJson(res, 200, decision);
1970
2641
  } catch (err) {
@@ -2025,7 +2696,7 @@ var ApprovalProxy = class _ApprovalProxy {
2025
2696
  /** 手动解析请求的 JSON body(限制最大 1MB 防止滥用) */
2026
2697
  parseJsonBody(req) {
2027
2698
  const MAX_BODY_SIZE = 1024 * 1024;
2028
- return new Promise((resolve, reject) => {
2699
+ return new Promise((resolve2, reject) => {
2029
2700
  const chunks = [];
2030
2701
  let totalSize = 0;
2031
2702
  let destroyed = false;
@@ -2043,7 +2714,7 @@ var ApprovalProxy = class _ApprovalProxy {
2043
2714
  try {
2044
2715
  const raw = Buffer.concat(chunks).toString("utf-8");
2045
2716
  const parsed = JSON.parse(raw);
2046
- resolve(parsed);
2717
+ resolve2(parsed);
2047
2718
  } catch {
2048
2719
  reject(new Error(t("approval.invalidJson")));
2049
2720
  }
@@ -2065,8 +2736,8 @@ var ApprovalProxy = class _ApprovalProxy {
2065
2736
  };
2066
2737
 
2067
2738
  // src/mdns/MdnsService.ts
2068
- var import_node_child_process3 = require("child_process");
2069
- var import_node_os4 = require("os");
2739
+ var import_node_child_process5 = require("child_process");
2740
+ var import_node_os5 = require("os");
2070
2741
  function buildTxtArgs(txt) {
2071
2742
  return Object.entries(txt).map(([k, v]) => `${k}=${v}`);
2072
2743
  }
@@ -2084,7 +2755,7 @@ var MdnsService = class {
2084
2755
  this.httpPort = options.httpPort;
2085
2756
  this.version = options.version ?? "0.1.0";
2086
2757
  this.pairing = options.pairing ?? "closed";
2087
- this.useDnsSd = (0, import_node_os4.platform)() === "darwin";
2758
+ this.useDnsSd = (0, import_node_os5.platform)() === "darwin";
2088
2759
  }
2089
2760
  getTxt() {
2090
2761
  return {
@@ -2117,7 +2788,7 @@ var MdnsService = class {
2117
2788
  String(this.wsPort),
2118
2789
  ...buildTxtArgs(this.getTxt())
2119
2790
  ];
2120
- this.proc = (0, import_node_child_process3.spawn)("dns-sd", args, { stdio: "ignore" });
2791
+ this.proc = (0, import_node_child_process5.spawn)("dns-sd", args, { stdio: "ignore" });
2121
2792
  this.proc.on("error", (err) => {
2122
2793
  console.warn(`[MdnsService] dns-sd failed, falling back to bonjour-service: ${err.message}`);
2123
2794
  this.proc = null;
@@ -2138,7 +2809,7 @@ var MdnsService = class {
2138
2809
  return;
2139
2810
  }
2140
2811
  try {
2141
- const { default: Bonjour } = await import("bonjour-service");
2812
+ const { Bonjour } = await import("bonjour-service");
2142
2813
  const { networkInterfaces } = await import("os");
2143
2814
  const lanAddrs = getLanAddresses(networkInterfaces);
2144
2815
  const opts = lanAddrs.length > 0 ? { interface: lanAddrs[0] } : {};
@@ -2229,12 +2900,12 @@ function getLanAddresses(networkInterfacesFn) {
2229
2900
 
2230
2901
  // src/hooks/HookInstaller.ts
2231
2902
  var import_promises2 = require("fs/promises");
2232
- var import_node_path3 = require("path");
2233
- var import_node_os5 = require("os");
2234
- var SESSIX_HOOKS_DIR = (0, import_node_path3.join)((0, import_node_os5.homedir)(), ".sessix", "hooks");
2235
- var HOOK_SCRIPT_PATH = (0, import_node_path3.join)(SESSIX_HOOKS_DIR, "approval-hook.js");
2236
- var PERMISSION_ACCEPT_PATH = (0, import_node_path3.join)(SESSIX_HOOKS_DIR, "permission-accept.js");
2237
- var CLAUDE_SETTINGS_PATH = (0, import_node_path3.join)((0, import_node_os5.homedir)(), ".claude", "settings.json");
2903
+ var import_node_path4 = require("path");
2904
+ var import_node_os6 = require("os");
2905
+ var SESSIX_HOOKS_DIR = (0, import_node_path4.join)((0, import_node_os6.homedir)(), ".sessix", "hooks");
2906
+ var HOOK_SCRIPT_PATH = (0, import_node_path4.join)(SESSIX_HOOKS_DIR, "approval-hook.js");
2907
+ var PERMISSION_ACCEPT_PATH = (0, import_node_path4.join)(SESSIX_HOOKS_DIR, "permission-accept.js");
2908
+ var CLAUDE_SETTINGS_PATH = (0, import_node_path4.join)((0, import_node_os6.homedir)(), ".claude", "settings.json");
2238
2909
  var HOOK_COMMAND = "node ~/.sessix/hooks/approval-hook.js";
2239
2910
  var PERMISSION_ACCEPT_COMMAND = "node ~/.sessix/hooks/permission-accept.js";
2240
2911
  var LEGACY_HOOK_COMMANDS = [
@@ -2408,7 +3079,7 @@ var HookInstaller = class {
2408
3079
  * 写入 Claude Code settings.json
2409
3080
  */
2410
3081
  async writeClaudeSettings(settings) {
2411
- await (0, import_promises2.mkdir)((0, import_node_path3.join)((0, import_node_os5.homedir)(), ".claude"), { recursive: true });
3082
+ await (0, import_promises2.mkdir)((0, import_node_path4.join)((0, import_node_os6.homedir)(), ".claude"), { recursive: true });
2412
3083
  await (0, import_promises2.writeFile)(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n", "utf-8");
2413
3084
  }
2414
3085
  /**
@@ -2435,7 +3106,7 @@ var HookInstaller = class {
2435
3106
  };
2436
3107
 
2437
3108
  // src/notification/NotificationService.ts
2438
- var import_node_path4 = require("path");
3109
+ var import_node_path5 = require("path");
2439
3110
  var NotificationService = class {
2440
3111
  constructor(sessionManager, expoChannel = null) {
2441
3112
  this.sessionManager = sessionManager;
@@ -2531,7 +3202,7 @@ var NotificationService = class {
2531
3202
  const dangerLevel = this.getDangerLevel(request.toolName);
2532
3203
  const isDangerous = dangerLevel === "danger" || dangerLevel === "write";
2533
3204
  const categoryId = isDangerous ? "APPROVAL_DANGEROUS" : "APPROVAL_NORMAL";
2534
- const projectName = (0, import_node_path4.basename)(
3205
+ const projectName = (0, import_node_path5.basename)(
2535
3206
  this.sessionManager.getActiveSessions().find((s) => s.id === request.sessionId)?.projectPath ?? ""
2536
3207
  );
2537
3208
  const pushTitle = isDangerous ? `\u26A0\uFE0F ${title}` : title;
@@ -2587,7 +3258,7 @@ var NotificationService = class {
2587
3258
  /** 从审批请求中提取操作目标的简短描述 */
2588
3259
  extractTarget(request) {
2589
3260
  const input = request.toolInput;
2590
- if (input.file_path) return (0, import_node_path4.basename)(String(input.file_path));
3261
+ if (input.file_path) return (0, import_node_path5.basename)(String(input.file_path));
2591
3262
  if (input.command) return String(input.command).slice(0, 40);
2592
3263
  return request.description.slice(0, 40);
2593
3264
  }
@@ -2690,7 +3361,7 @@ var NotificationService = class {
2690
3361
  getSessionTitle(sessionId) {
2691
3362
  const session = this.sessionManager.getActiveSessions().find((s) => s.id === sessionId);
2692
3363
  if (!session) return "Unknown";
2693
- return session.summary ?? (0, import_node_path4.basename)(session.projectPath);
3364
+ return session.summary ?? (0, import_node_path5.basename)(session.projectPath);
2694
3365
  }
2695
3366
  /** 获取会话的 YOLO 模式状态 */
2696
3367
  getYoloMode(sessionId) {
@@ -2699,7 +3370,7 @@ var NotificationService = class {
2699
3370
  };
2700
3371
 
2701
3372
  // src/notification/DesktopNotificationChannel.ts
2702
- var import_node_child_process4 = require("child_process");
3373
+ var import_node_child_process6 = require("child_process");
2703
3374
  var DesktopNotificationChannel = class {
2704
3375
  isAvailable() {
2705
3376
  return process.platform === "darwin";
@@ -2710,12 +3381,12 @@ var DesktopNotificationChannel = class {
2710
3381
  const body = payload.body.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
2711
3382
  const sound = payload.sound ?? "Ping";
2712
3383
  const script = `display notification "${body}" with title "${title}" sound name "${sound}"`;
2713
- return new Promise((resolve) => {
2714
- (0, import_node_child_process4.execFile)("osascript", ["-e", script], (err) => {
3384
+ return new Promise((resolve2) => {
3385
+ (0, import_node_child_process6.execFile)("osascript", ["-e", script], (err) => {
2715
3386
  if (err) {
2716
3387
  console.warn("[DesktopNotificationChannel] Send notification failed:", err.message);
2717
3388
  }
2718
- resolve();
3389
+ resolve2();
2719
3390
  });
2720
3391
  });
2721
3392
  }
@@ -2915,7 +3586,7 @@ var ActivityPushChannel = class {
2915
3586
  const topic = "com.kachun.sessix.push-type.liveactivity";
2916
3587
  const jwt = this.getJWT();
2917
3588
  const payloadStr = JSON.stringify(payload);
2918
- return new Promise((resolve, reject) => {
3589
+ return new Promise((resolve2, reject) => {
2919
3590
  let client;
2920
3591
  try {
2921
3592
  client = this.getHttp2Client();
@@ -2943,7 +3614,7 @@ var ActivityPushChannel = class {
2943
3614
  });
2944
3615
  req.on("end", () => {
2945
3616
  if (statusCode === 200) {
2946
- resolve();
3617
+ resolve2();
2947
3618
  } else {
2948
3619
  if (statusCode === 0) {
2949
3620
  this.http2Client?.destroy();
@@ -2985,12 +3656,12 @@ var ActivityPushChannel = class {
2985
3656
 
2986
3657
  // src/session/ProjectReader.ts
2987
3658
  var import_promises3 = require("fs/promises");
2988
- var import_readline2 = require("readline");
2989
- var import_path = require("path");
2990
- var import_os = require("os");
2991
- var CLAUDE_PROJECTS_DIR = (0, import_path.join)((0, import_os.homedir)(), ".claude", "projects");
3659
+ var import_readline3 = require("readline");
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");
2992
3663
  function getSessionFilePath(projectPath, sessionId) {
2993
- 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`);
2994
3665
  }
2995
3666
  async function getProjects() {
2996
3667
  try {
@@ -3007,7 +3678,7 @@ async function getProjects() {
3007
3678
  const encodedPath = entry.name;
3008
3679
  const decodedPath = decodeDirName(encodedPath);
3009
3680
  const name = decodedPath.split("/").filter(Boolean).pop() ?? encodedPath;
3010
- const projectDir = (0, import_path.join)(CLAUDE_PROJECTS_DIR, encodedPath);
3681
+ const projectDir = (0, import_path2.join)(CLAUDE_PROJECTS_DIR, encodedPath);
3011
3682
  const { count: sessionCount, latestMtime } = await countJsonlFilesWithMtime(projectDir);
3012
3683
  projects.push({
3013
3684
  id: encodedPath,
@@ -3029,7 +3700,7 @@ async function getProjects() {
3029
3700
  async function getHistoricalSessions(projectPath) {
3030
3701
  try {
3031
3702
  const encodedPath = encodeDirName(projectPath);
3032
- const projectDir = (0, import_path.join)(CLAUDE_PROJECTS_DIR, encodedPath);
3703
+ const projectDir = (0, import_path2.join)(CLAUDE_PROJECTS_DIR, encodedPath);
3033
3704
  const dirExists = await directoryExists(projectDir);
3034
3705
  if (!dirExists) {
3035
3706
  return { ok: true, value: [] };
@@ -3040,7 +3711,7 @@ async function getHistoricalSessions(projectPath) {
3040
3711
  await Promise.all(
3041
3712
  jsonlFiles.map(async (entry) => {
3042
3713
  const sessionId = entry.name.slice(0, -6);
3043
- const filePath = (0, import_path.join)(projectDir, entry.name);
3714
+ const filePath = (0, import_path2.join)(projectDir, entry.name);
3044
3715
  try {
3045
3716
  const contentTs = await extractLastTimestamp(filePath);
3046
3717
  if (contentTs) {
@@ -3059,13 +3730,13 @@ async function getHistoricalSessions(projectPath) {
3059
3730
  );
3060
3731
  for (const entry of uuidDirs) {
3061
3732
  try {
3062
- 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));
3063
3734
  mtimeMap.set(entry.name, fileStat.mtimeMs);
3064
3735
  } catch {
3065
3736
  mtimeMap.set(entry.name, 0);
3066
3737
  }
3067
3738
  }
3068
- const indexPath = (0, import_path.join)(projectDir, "sessions-index.json");
3739
+ const indexPath = (0, import_path2.join)(projectDir, "sessions-index.json");
3069
3740
  const sessionMap = /* @__PURE__ */ new Map();
3070
3741
  try {
3071
3742
  const indexContent = await (0, import_promises3.readFile)(indexPath, "utf-8");
@@ -3083,7 +3754,7 @@ async function getHistoricalSessions(projectPath) {
3083
3754
  }
3084
3755
  await Promise.all(
3085
3756
  Array.from(sessionMap.values()).filter((s) => (s.messageCount ?? 0) > 0 && !s.summary && !s.firstPrompt).map(async (s) => {
3086
- const filePath = (0, import_path.join)(projectDir, `${s.sessionId}.jsonl`);
3757
+ const filePath = (0, import_path2.join)(projectDir, `${s.sessionId}.jsonl`);
3087
3758
  const firstPrompt = await extractFirstPrompt(filePath).catch(() => void 0);
3088
3759
  if (firstPrompt) s.firstPrompt = firstPrompt;
3089
3760
  })
@@ -3097,7 +3768,7 @@ async function getHistoricalSessions(projectPath) {
3097
3768
  if (uuidDirSet.has(sessionId)) {
3098
3769
  sessionMap.set(sessionId, { sessionId, lastModified: mtime, messageCount: -1 });
3099
3770
  } else {
3100
- const filePath = (0, import_path.join)(projectDir, `${sessionId}.jsonl`);
3771
+ const filePath = (0, import_path2.join)(projectDir, `${sessionId}.jsonl`);
3101
3772
  const firstPrompt = await extractFirstPrompt(filePath).catch(() => void 0);
3102
3773
  sessionMap.set(sessionId, { sessionId, lastModified: mtime, firstPrompt });
3103
3774
  }
@@ -3121,7 +3792,7 @@ async function getHistoricalSessions(projectPath) {
3121
3792
  async function getSessionHistory(projectPath, sessionId) {
3122
3793
  try {
3123
3794
  const encodedPath = encodeDirName(projectPath);
3124
- 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`);
3125
3796
  const raw = await (0, import_promises3.readFile)(filePath, "utf-8").catch((err) => {
3126
3797
  if (err.code === "ENOENT") return null;
3127
3798
  throw err;
@@ -3234,7 +3905,7 @@ async function extractFirstPrompt(filePath) {
3234
3905
  let fileHandle;
3235
3906
  try {
3236
3907
  fileHandle = await (0, import_promises3.open)(filePath, "r");
3237
- const rl = (0, import_readline2.createInterface)({
3908
+ const rl = (0, import_readline3.createInterface)({
3238
3909
  input: fileHandle.createReadStream({ encoding: "utf-8" }),
3239
3910
  crlfDelay: Infinity
3240
3911
  });
@@ -3302,15 +3973,15 @@ async function countJsonlFilesWithMtime(dirPath) {
3302
3973
  await Promise.all([
3303
3974
  ...jsonlEntries.map(async (entry) => {
3304
3975
  try {
3305
- const contentTs = await extractLastTimestamp((0, import_path.join)(dirPath, entry.name));
3306
- 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;
3307
3978
  if (ts > latestMtime) latestMtime = ts;
3308
3979
  } catch {
3309
3980
  }
3310
3981
  }),
3311
3982
  ...uuidDirs.map(async (entry) => {
3312
3983
  try {
3313
- 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));
3314
3985
  if (fileStat.mtimeMs > latestMtime) latestMtime = fileStat.mtimeMs;
3315
3986
  } catch {
3316
3987
  }
@@ -3382,14 +4053,14 @@ var PairingManager = class {
3382
4053
  };
3383
4054
 
3384
4055
  // src/auth/AuthManager.ts
3385
- var import_child_process2 = require("child_process");
3386
4056
  var import_child_process3 = require("child_process");
4057
+ var import_child_process4 = require("child_process");
3387
4058
  var import_util = require("util");
3388
- var import_events2 = require("events");
3389
- var execFileAsync = (0, import_util.promisify)(import_child_process3.execFile);
4059
+ var import_events3 = require("events");
4060
+ var execFileAsync = (0, import_util.promisify)(import_child_process4.execFile);
3390
4061
  var CLAUDE_PATH2 = findClaudePath();
3391
4062
  var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
3392
- var AuthManager = class extends import_events2.EventEmitter {
4063
+ var AuthManager = class extends import_events3.EventEmitter {
3393
4064
  loginProcess = null;
3394
4065
  loginTimeout = null;
3395
4066
  urlSent = false;
@@ -3417,7 +4088,7 @@ var AuthManager = class extends import_events2.EventEmitter {
3417
4088
  }
3418
4089
  this.clearLoginTimeout();
3419
4090
  this.urlSent = false;
3420
- const proc = (0, import_child_process2.spawn)(CLAUDE_PATH2, ["auth", "login"], {
4091
+ const proc = (0, import_child_process3.spawn)(CLAUDE_PATH2, ["auth", "login"], {
3421
4092
  env: { ...process.env, BROWSER: "echo" },
3422
4093
  stdio: ["pipe", "pipe", "pipe"]
3423
4094
  });
@@ -3503,8 +4174,8 @@ var AuthManager = class extends import_events2.EventEmitter {
3503
4174
  var import_promises5 = require("fs/promises");
3504
4175
 
3505
4176
  // src/terminal/TerminalExecutor.ts
3506
- var import_node_child_process5 = require("child_process");
3507
- var import_uuid4 = require("uuid");
4177
+ var import_node_child_process7 = require("child_process");
4178
+ var import_uuid5 = require("uuid");
3508
4179
  var EXEC_TIMEOUT_MS = 5 * 60 * 1e3;
3509
4180
  var TerminalExecutor = class {
3510
4181
  processes = /* @__PURE__ */ new Map();
@@ -3526,10 +4197,10 @@ var TerminalExecutor = class {
3526
4197
  }
3527
4198
  }
3528
4199
  exec(sessionId, command, cwd) {
3529
- const execId = (0, import_uuid4.v4)();
4200
+ const execId = (0, import_uuid5.v4)();
3530
4201
  const shell = isWindows ? "powershell" : "bash";
3531
4202
  const args = isWindows ? ["-Command", command] : ["-c", command];
3532
- const proc = (0, import_node_child_process5.spawn)(shell, args, {
4203
+ const proc = (0, import_node_child_process7.spawn)(shell, args, {
3533
4204
  cwd,
3534
4205
  stdio: ["ignore", "pipe", "pipe"],
3535
4206
  env: { ...process.env }
@@ -3592,7 +4263,7 @@ var TerminalExecutor = class {
3592
4263
  // src/server.ts
3593
4264
  var WS_PORT = 3745;
3594
4265
  var HTTP_PORT = 3746;
3595
- var execAsync = (0, import_node_util.promisify)(import_node_child_process6.exec);
4266
+ var execAsync = (0, import_node_util.promisify)(import_node_child_process8.exec);
3596
4267
  async function killPortProcess(port) {
3597
4268
  try {
3598
4269
  if (isWindows) {
@@ -3616,7 +4287,7 @@ async function killPortProcess(port) {
3616
4287
  await execAsync(`kill -9 ${pids.join(" ")}`);
3617
4288
  }
3618
4289
  }
3619
- await new Promise((resolve) => setTimeout(resolve, 600));
4290
+ await new Promise((resolve2) => setTimeout(resolve2, 600));
3620
4291
  } catch {
3621
4292
  }
3622
4293
  }
@@ -3634,8 +4305,8 @@ async function createWithRetry(label, port, factory) {
3634
4305
  }
3635
4306
  }
3636
4307
  async function start(opts = {}) {
3637
- const configDir = (0, import_node_path5.join)((0, import_node_os6.homedir)(), ".sessix");
3638
- const tokenFile = (0, import_node_path5.join)(configDir, "token");
4308
+ const configDir = (0, import_node_path6.join)((0, import_node_os7.homedir)(), ".sessix");
4309
+ const tokenFile = (0, import_node_path6.join)(configDir, "token");
3639
4310
  let token;
3640
4311
  if (opts.token !== void 0) {
3641
4312
  token = opts.token;
@@ -3647,14 +4318,14 @@ async function start(opts = {}) {
3647
4318
  try {
3648
4319
  token = (await (0, import_promises4.readFile)(tokenFile, "utf8")).trim();
3649
4320
  } catch {
3650
- token = (0, import_uuid5.v4)();
4321
+ token = (0, import_uuid6.v4)();
3651
4322
  await (0, import_promises4.mkdir)(configDir, { recursive: true });
3652
4323
  await (0, import_promises4.writeFile)(tokenFile, token, "utf8");
3653
4324
  }
3654
4325
  }
3655
4326
  }
3656
- const provider = new ProcessProvider();
3657
- const sessionManager = new SessionManager(provider);
4327
+ const providerFactory = new ProviderFactory();
4328
+ const sessionManager = new SessionManager(providerFactory);
3658
4329
  const terminalExecutor = new TerminalExecutor();
3659
4330
  const wsBridge = await createWithRetry(
3660
4331
  "WsBridge",
@@ -3694,7 +4365,7 @@ async function start(opts = {}) {
3694
4365
  let mdnsService = null;
3695
4366
  const pairingManager = new PairingManager({
3696
4367
  token,
3697
- serverName: (0, import_node_os6.hostname)(),
4368
+ serverName: (0, import_node_os7.hostname)(),
3698
4369
  version: "0.2.0",
3699
4370
  onStateChange: (state) => mdnsService?.updatePairingState(state)
3700
4371
  });
@@ -3751,7 +4422,8 @@ async function start(opts = {}) {
3751
4422
  event.model,
3752
4423
  event.permissionMode,
3753
4424
  event.effort,
3754
- event.images
4425
+ event.images,
4426
+ event.agentType
3755
4427
  );
3756
4428
  wsBridge.broadcast({
3757
4429
  type: "session_list",
@@ -3927,7 +4599,7 @@ async function start(opts = {}) {
3927
4599
  return null;
3928
4600
  }).filter(Boolean).join("\n");
3929
4601
  }
3930
- const suggestion = await provider.generateSuggestion(context);
4602
+ const suggestion = await providerFactory.getProvider("claude-code").generateSuggestion(context);
3931
4603
  wsBridge.send(ws, {
3932
4604
  type: "prompt_suggestion",
3933
4605
  sessionId: event.sessionId,
@@ -4005,6 +4677,11 @@ async function start(opts = {}) {
4005
4677
  }
4006
4678
  break;
4007
4679
  }
4680
+ case "list_agents": {
4681
+ const agents = await providerFactory.detectAgents();
4682
+ wsBridge.send(ws, { type: "agent_list", agents });
4683
+ break;
4684
+ }
4008
4685
  default: {
4009
4686
  wsBridge.send(ws, {
4010
4687
  type: "error",
@@ -4168,7 +4845,7 @@ async function start(opts = {}) {
4168
4845
  openPairing: (duration) => pairingManager.open(duration),
4169
4846
  closePairing: () => pairingManager.close(),
4170
4847
  regenerateToken: async () => {
4171
- const newToken = (0, import_uuid5.v4)();
4848
+ const newToken = (0, import_uuid6.v4)();
4172
4849
  await (0, import_promises4.mkdir)(configDir, { recursive: true });
4173
4850
  await (0, import_promises4.writeFile)(tokenFile, newToken, "utf8");
4174
4851
  instance.token = newToken;