sessix-server 0.2.9 → 0.3.0

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 +630 -82
  2. package/dist/server.js +597 -70
  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
@@ -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,
@@ -847,11 +857,500 @@ ${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");
851
864
  var import_uuid2 = require("uuid");
865
+
866
+ // src/utils/codexPath.ts
867
+ var import_node_child_process3 = require("child_process");
868
+ var import_node_fs2 = require("fs");
869
+ var import_node_path2 = require("path");
870
+ var import_node_os3 = require("os");
871
+ function findCodexPath() {
872
+ try {
873
+ const cmd = isWindows ? "where codex" : "which codex";
874
+ return (0, import_node_child_process3.execSync)(cmd, { encoding: "utf-8" }).trim().split("\n")[0];
875
+ } catch {
876
+ }
877
+ const candidates = isWindows ? [
878
+ (0, import_node_path2.join)(process.env.LOCALAPPDATA ?? "", "Programs", "codex", "codex.exe"),
879
+ (0, import_node_path2.join)((0, import_node_os3.homedir)(), ".codex", "local", "codex.exe")
880
+ ] : [
881
+ "/opt/homebrew/bin/codex",
882
+ "/usr/local/bin/codex",
883
+ (0, import_node_path2.join)((0, import_node_os3.homedir)(), ".local", "bin", "codex")
884
+ ];
885
+ for (const candidate of candidates) {
886
+ try {
887
+ (0, import_node_fs2.accessSync)(candidate, import_node_fs2.constants.X_OK);
888
+ return candidate;
889
+ } catch {
890
+ }
891
+ }
892
+ return "codex";
893
+ }
894
+ var CODEX_PATH = findCodexPath();
895
+ async function getCodexVersion() {
896
+ try {
897
+ const output = (0, import_node_child_process3.execSync)(`"${CODEX_PATH}" --version`, {
898
+ encoding: "utf-8",
899
+ timeout: 5e3
900
+ }).trim();
901
+ return output.replace(/^codex-cli\s+/, "");
902
+ } catch {
903
+ return void 0;
904
+ }
905
+ }
906
+ function isCodexAvailable() {
907
+ try {
908
+ (0, import_node_fs2.accessSync)(CODEX_PATH, import_node_fs2.constants.X_OK);
909
+ return true;
910
+ } catch {
911
+ try {
912
+ (0, import_node_child_process3.execSync)(`"${CODEX_PATH}" --version`, { timeout: 5e3 });
913
+ return true;
914
+ } catch {
915
+ return false;
916
+ }
917
+ }
918
+ }
919
+
920
+ // src/providers/CodexProvider.ts
921
+ var CodexProvider = class {
922
+ activeSessions = /* @__PURE__ */ new Map();
923
+ emitter = new import_events2.EventEmitter();
924
+ /** 自增 ID 计数器,用于生成 ClaudeStreamEvent 中的 message/block ID */
925
+ idCounter = 0;
926
+ async startSession(opts) {
927
+ const { projectPath, message, sessionId: existingSessionId } = opts;
928
+ const sessionId = existingSessionId ?? (0, import_uuid2.v4)();
929
+ if (this.activeSessions.has(sessionId)) {
930
+ await this.killSession(sessionId);
931
+ }
932
+ const projectId = projectPath.split("/").filter(Boolean).pop() ?? "unknown";
933
+ const session = {
934
+ id: sessionId,
935
+ projectId,
936
+ projectPath,
937
+ status: "running",
938
+ createdAt: Date.now(),
939
+ lastActiveAt: Date.now(),
940
+ summary: message.slice(0, 80),
941
+ agentType: "codex"
942
+ };
943
+ const resume = opts.resume ?? !!existingSessionId;
944
+ const proc = this.spawnCodexProcess(projectPath, message, void 0);
945
+ session.pid = proc.pid;
946
+ this.activeSessions.set(sessionId, {
947
+ session,
948
+ process: proc,
949
+ threadId: void 0,
950
+ turnStartTime: Date.now()
951
+ });
952
+ const initEvent = {
953
+ type: "system",
954
+ subtype: "init",
955
+ session_id: sessionId
956
+ };
957
+ this.emitter.emit(this.getEventName(sessionId), initEvent);
958
+ proc.on("error", (err) => {
959
+ console.error(`[CodexProvider] Session ${sessionId} process error:`, err.message);
960
+ this.activeSessions.delete(sessionId);
961
+ this.emitError(sessionId, `Process spawn failed: ${err.message}`);
962
+ });
963
+ this.attachStdoutListener(sessionId, proc);
964
+ this.attachStderrListener(sessionId, proc);
965
+ this.attachExitListener(sessionId, proc);
966
+ return session;
967
+ }
968
+ async killSession(sessionId) {
969
+ const entry = this.activeSessions.get(sessionId);
970
+ if (!entry) return;
971
+ if (entry.process.exitCode === null && entry.process.signalCode === null) {
972
+ try {
973
+ entry.process.stdin?.end();
974
+ } catch {
975
+ }
976
+ await killProcessCrossPlatform(entry.process);
977
+ }
978
+ this.activeSessions.delete(sessionId);
979
+ }
980
+ async sendMessage(sessionId, message) {
981
+ const entry = this.activeSessions.get(sessionId);
982
+ if (!entry) {
983
+ throw new Error(`Session ${sessionId} not found or already ended`);
984
+ }
985
+ if (entry.process.exitCode === null && entry.process.signalCode === null) {
986
+ try {
987
+ entry.process.stdin?.end();
988
+ } catch {
989
+ }
990
+ await killProcessCrossPlatform(entry.process);
991
+ }
992
+ const threadId = entry.threadId;
993
+ if (!threadId) {
994
+ throw new Error(`Session ${sessionId} has no thread ID, cannot resume`);
995
+ }
996
+ const proc = this.spawnCodexProcess(entry.session.projectPath, message, threadId);
997
+ entry.session.status = "running";
998
+ entry.session.lastActiveAt = Date.now();
999
+ entry.session.pid = proc.pid;
1000
+ entry.process = proc;
1001
+ entry.turnStartTime = Date.now();
1002
+ proc.on("error", (err) => {
1003
+ console.error(`[CodexProvider] Session ${sessionId} sendMessage process error:`, err.message);
1004
+ this.activeSessions.delete(sessionId);
1005
+ this.emitError(sessionId, `Failed to send message: ${err.message}`);
1006
+ });
1007
+ this.attachStdoutListener(sessionId, proc);
1008
+ this.attachStderrListener(sessionId, proc);
1009
+ this.attachExitListener(sessionId, proc);
1010
+ }
1011
+ onEvent(sessionId, callback) {
1012
+ const eventName = this.getEventName(sessionId);
1013
+ this.emitter.on(eventName, callback);
1014
+ return () => {
1015
+ this.emitter.off(eventName, callback);
1016
+ };
1017
+ }
1018
+ getActiveSessions() {
1019
+ return Array.from(this.activeSessions.values()).map((e) => e.session);
1020
+ }
1021
+ async generateSuggestion(_context) {
1022
+ return "";
1023
+ }
1024
+ async answerQuestion(_sessionId, _toolUseId, _answer) {
1025
+ }
1026
+ onQuestion(_sessionId, _callback) {
1027
+ return () => {
1028
+ };
1029
+ }
1030
+ // ============================================
1031
+ // 私有方法
1032
+ // ============================================
1033
+ nextId() {
1034
+ return `codex_${++this.idCounter}`;
1035
+ }
1036
+ /**
1037
+ * 启动 codex CLI 进程
1038
+ *
1039
+ * @param projectPath 工作目录
1040
+ * @param message 用户消息
1041
+ * @param resumeThreadId 如果提供,则使用 `codex exec resume` 恢复会话
1042
+ */
1043
+ spawnCodexProcess(projectPath, message, resumeThreadId) {
1044
+ const args = ["exec", "--json", "--full-auto"];
1045
+ if (resumeThreadId) {
1046
+ args.push("resume", resumeThreadId);
1047
+ } else {
1048
+ args.push("-C", projectPath);
1049
+ }
1050
+ args.push(message);
1051
+ const env = { ...process.env };
1052
+ const proc = (0, import_child_process2.spawn)(CODEX_PATH, args, {
1053
+ cwd: projectPath,
1054
+ env,
1055
+ stdio: ["pipe", "pipe", "pipe"]
1056
+ });
1057
+ try {
1058
+ proc.stdin?.end();
1059
+ } catch {
1060
+ }
1061
+ return proc;
1062
+ }
1063
+ /**
1064
+ * 挂载 stdout 监听器,逐行解析 Codex NDJSON 并转换为 ClaudeStreamEvent
1065
+ */
1066
+ attachStdoutListener(sessionId, proc) {
1067
+ if (!proc.stdout) return;
1068
+ const rl = (0, import_readline2.createInterface)({ input: proc.stdout, crlfDelay: Infinity });
1069
+ const entry = this.activeSessions.get(sessionId);
1070
+ if (entry) entry.rl = rl;
1071
+ rl.on("line", (line) => {
1072
+ const trimmed = line.trim();
1073
+ if (!trimmed) return;
1074
+ let event;
1075
+ try {
1076
+ event = JSON.parse(trimmed);
1077
+ } catch {
1078
+ console.warn(`[CodexProvider] Session ${sessionId}: failed to parse: ${trimmed.substring(0, 100)}`);
1079
+ return;
1080
+ }
1081
+ this.handleCodexEvent(sessionId, event);
1082
+ });
1083
+ }
1084
+ /**
1085
+ * 处理单个 Codex NDJSON 事件,转换并发射 ClaudeStreamEvent
1086
+ */
1087
+ handleCodexEvent(sessionId, event) {
1088
+ const entry = this.activeSessions.get(sessionId);
1089
+ if (!entry) return;
1090
+ entry.session.lastActiveAt = Date.now();
1091
+ switch (event.type) {
1092
+ case "thread.started": {
1093
+ if (event.thread_id) {
1094
+ entry.threadId = event.thread_id;
1095
+ }
1096
+ break;
1097
+ }
1098
+ case "turn.started": {
1099
+ entry.session.status = "running";
1100
+ entry.turnStartTime = Date.now();
1101
+ break;
1102
+ }
1103
+ case "item.completed": {
1104
+ if (!event.item) break;
1105
+ this.handleItemCompleted(sessionId, event.item);
1106
+ break;
1107
+ }
1108
+ case "turn.completed": {
1109
+ entry.session.status = "idle";
1110
+ const duration = entry.turnStartTime ? Date.now() - entry.turnStartTime : 0;
1111
+ const resultEvent = {
1112
+ type: "result",
1113
+ subtype: "success",
1114
+ session_id: sessionId,
1115
+ is_error: false,
1116
+ result: "",
1117
+ duration_ms: duration,
1118
+ num_turns: 1,
1119
+ usage: event.usage
1120
+ };
1121
+ this.emitter.emit(this.getEventName(sessionId), resultEvent);
1122
+ break;
1123
+ }
1124
+ case "turn.failed": {
1125
+ entry.session.status = "error";
1126
+ const failEvent = {
1127
+ type: "result",
1128
+ subtype: "error",
1129
+ session_id: sessionId,
1130
+ is_error: true,
1131
+ result: event.error ?? "Turn failed",
1132
+ duration_ms: entry.turnStartTime ? Date.now() - entry.turnStartTime : 0,
1133
+ num_turns: 1
1134
+ };
1135
+ this.emitter.emit(this.getEventName(sessionId), failEvent);
1136
+ break;
1137
+ }
1138
+ }
1139
+ }
1140
+ /**
1141
+ * 处理 item.completed 事件,按 item.type 转换为对应的 ClaudeStreamEvent
1142
+ */
1143
+ handleItemCompleted(sessionId, item) {
1144
+ switch (item.type) {
1145
+ case "agent_message": {
1146
+ const msgEvent = {
1147
+ type: "assistant",
1148
+ session_id: sessionId,
1149
+ message: {
1150
+ id: item.id || this.nextId(),
1151
+ model: "codex",
1152
+ role: "assistant",
1153
+ content: [{ type: "text", text: item.text ?? "" }]
1154
+ }
1155
+ };
1156
+ this.emitter.emit(this.getEventName(sessionId), msgEvent);
1157
+ break;
1158
+ }
1159
+ case "command_execution": {
1160
+ const toolUseId = item.id || this.nextId();
1161
+ const toolEvent = {
1162
+ type: "assistant",
1163
+ session_id: sessionId,
1164
+ message: {
1165
+ id: this.nextId(),
1166
+ model: "codex",
1167
+ role: "assistant",
1168
+ content: [{
1169
+ type: "tool_use",
1170
+ id: toolUseId,
1171
+ name: "Bash",
1172
+ input: { command: item.command ?? "" }
1173
+ }]
1174
+ }
1175
+ };
1176
+ this.emitter.emit(this.getEventName(sessionId), toolEvent);
1177
+ const resultContent = item.aggregated_output ?? "";
1178
+ const isError = item.exit_code != null && item.exit_code !== 0;
1179
+ const toolResultEvent = {
1180
+ type: "user",
1181
+ session_id: sessionId,
1182
+ message: {
1183
+ role: "user",
1184
+ content: [{
1185
+ type: "tool_result",
1186
+ tool_use_id: toolUseId,
1187
+ content: resultContent,
1188
+ is_error: isError
1189
+ }]
1190
+ }
1191
+ };
1192
+ this.emitter.emit(this.getEventName(sessionId), toolResultEvent);
1193
+ break;
1194
+ }
1195
+ case "file_change": {
1196
+ const editToolUseId = item.id || this.nextId();
1197
+ const toolEvent = {
1198
+ type: "assistant",
1199
+ session_id: sessionId,
1200
+ message: {
1201
+ id: this.nextId(),
1202
+ model: "codex",
1203
+ role: "assistant",
1204
+ content: [{
1205
+ type: "tool_use",
1206
+ id: editToolUseId,
1207
+ name: "Edit",
1208
+ input: { file_path: item.file_path ?? "", diff: item.diff ?? "" }
1209
+ }]
1210
+ }
1211
+ };
1212
+ this.emitter.emit(this.getEventName(sessionId), toolEvent);
1213
+ const toolResultEvent = {
1214
+ type: "user",
1215
+ session_id: sessionId,
1216
+ message: {
1217
+ role: "user",
1218
+ content: [{
1219
+ type: "tool_result",
1220
+ tool_use_id: editToolUseId,
1221
+ content: `File edited: ${item.file_path ?? "unknown"}`
1222
+ }]
1223
+ }
1224
+ };
1225
+ this.emitter.emit(this.getEventName(sessionId), toolResultEvent);
1226
+ break;
1227
+ }
1228
+ case "reasoning": {
1229
+ const thinkEvent = {
1230
+ type: "assistant",
1231
+ session_id: sessionId,
1232
+ message: {
1233
+ id: item.id || this.nextId(),
1234
+ model: "codex",
1235
+ role: "assistant",
1236
+ content: [{ type: "thinking", thinking: item.text ?? "" }]
1237
+ }
1238
+ };
1239
+ this.emitter.emit(this.getEventName(sessionId), thinkEvent);
1240
+ break;
1241
+ }
1242
+ }
1243
+ }
1244
+ attachStderrListener(sessionId, proc) {
1245
+ if (!proc.stderr) return;
1246
+ proc.stderr.on("data", (data) => {
1247
+ const text = data.toString().trim();
1248
+ if (text) {
1249
+ console.error(`[CodexProvider] Session ${sessionId} stderr: ${text}`);
1250
+ }
1251
+ });
1252
+ }
1253
+ attachExitListener(sessionId, proc) {
1254
+ proc.once("exit", (code, signal) => {
1255
+ const entry = this.activeSessions.get(sessionId);
1256
+ if (!entry) return;
1257
+ if (entry.process !== proc) return;
1258
+ if (entry.rl) {
1259
+ entry.rl.close();
1260
+ entry.rl = void 0;
1261
+ }
1262
+ entry.session.pid = void 0;
1263
+ entry.session.lastActiveAt = Date.now();
1264
+ const alreadyHasResult = entry.session.status === "idle" || entry.session.status === "error";
1265
+ if (alreadyHasResult) return;
1266
+ const isNormal = isNormalExit(code, signal);
1267
+ entry.session.status = isNormal ? "idle" : "error";
1268
+ if (!isNormal) {
1269
+ console.error(`[CodexProvider] Session ${sessionId}: process exited abnormally code=${code} signal=${signal}`);
1270
+ }
1271
+ const syntheticResult = {
1272
+ type: "result",
1273
+ subtype: isNormal ? "success" : "error",
1274
+ session_id: sessionId,
1275
+ is_error: !isNormal,
1276
+ result: isNormal ? "" : `Process exited code=${code} signal=${signal}`,
1277
+ duration_ms: 0,
1278
+ num_turns: 0
1279
+ };
1280
+ this.emitter.emit(this.getEventName(sessionId), syntheticResult);
1281
+ });
1282
+ }
1283
+ emitError(sessionId, message) {
1284
+ const event = {
1285
+ type: "result",
1286
+ subtype: "error",
1287
+ result: message,
1288
+ session_id: sessionId,
1289
+ duration_ms: 0,
1290
+ is_error: true,
1291
+ num_turns: 0
1292
+ };
1293
+ this.emitter.emit(this.getEventName(sessionId), event);
1294
+ }
1295
+ getEventName(sessionId) {
1296
+ return `claude:${sessionId}`;
1297
+ }
1298
+ };
1299
+
1300
+ // src/providers/ProviderFactory.ts
1301
+ var import_node_child_process4 = require("child_process");
1302
+ var ProviderFactory = class {
1303
+ providers = /* @__PURE__ */ new Map();
1304
+ getProvider(agentType) {
1305
+ const existing = this.providers.get(agentType);
1306
+ if (existing) return existing;
1307
+ let provider;
1308
+ switch (agentType) {
1309
+ case "codex":
1310
+ provider = new CodexProvider();
1311
+ break;
1312
+ case "claude-code":
1313
+ default:
1314
+ provider = new ProcessProvider();
1315
+ break;
1316
+ }
1317
+ this.providers.set(agentType, provider);
1318
+ return provider;
1319
+ }
1320
+ async detectAgents() {
1321
+ const agents = [];
1322
+ try {
1323
+ const claudePath = findClaudePath();
1324
+ const claudeVersion = (0, import_node_child_process4.execSync)(`"${claudePath}" --version`, {
1325
+ encoding: "utf-8",
1326
+ timeout: 5e3
1327
+ }).trim();
1328
+ agents.push({
1329
+ type: "claude-code",
1330
+ name: "Claude Code",
1331
+ available: true,
1332
+ version: claudeVersion
1333
+ });
1334
+ } catch {
1335
+ agents.push({ type: "claude-code", name: "Claude Code", available: false });
1336
+ }
1337
+ if (isCodexAvailable()) {
1338
+ const version = await getCodexVersion();
1339
+ agents.push({ type: "codex", name: "Codex", available: true, version });
1340
+ } else {
1341
+ agents.push({ type: "codex", name: "Codex", available: false });
1342
+ }
1343
+ return agents;
1344
+ }
1345
+ };
1346
+
1347
+ // src/session/SessionManager.ts
1348
+ var import_uuid3 = require("uuid");
852
1349
  var BUFFER_MAX = 5e3;
853
1350
  var SessionManager = class {
854
1351
  provider;
1352
+ providerFactory;
1353
+ sessionAgentType = /* @__PURE__ */ new Map();
855
1354
  /** 事件回调列表(事件会被转发到 WsBridge) */
856
1355
  eventCallbacks = [];
857
1356
  /** 每个会话的事件流取消订阅函数 */
@@ -877,8 +1376,19 @@ var SessionManager = class {
877
1376
  bufferTruncated = /* @__PURE__ */ new Set();
878
1377
  /** sessionId → projectPath 映射,用于截断时从 JSONL 补全历史 */
879
1378
  sessionProjectPaths = /* @__PURE__ */ new Map();
880
- constructor(provider) {
881
- this.provider = provider;
1379
+ constructor(providerOrFactory) {
1380
+ if (providerOrFactory instanceof ProviderFactory) {
1381
+ this.providerFactory = providerOrFactory;
1382
+ this.provider = providerOrFactory.getProvider("claude-code");
1383
+ } else {
1384
+ this.provider = providerOrFactory;
1385
+ this.providerFactory = null;
1386
+ }
1387
+ }
1388
+ getProviderForSession(sessionId) {
1389
+ if (!this.providerFactory) return this.provider;
1390
+ const agentType = this.sessionAgentType.get(sessionId) ?? "claude-code";
1391
+ return this.providerFactory.getProvider(agentType);
882
1392
  }
883
1393
  // ============================================
884
1394
  // 公开 API
@@ -889,8 +1399,9 @@ var SessionManager = class {
889
1399
  * 调用 provider.startSession(),订阅事件流,
890
1400
  * 将 ClaudeStreamEvent 包装为 ServerEvent 转发。
891
1401
  */
892
- async createSession(projectPath, message, resumeSessionId, newSessionId, model, permissionMode, effort, images) {
893
- const session = await this.provider.startSession({
1402
+ async createSession(projectPath, message, resumeSessionId, newSessionId, model, permissionMode, effort, images, agentType = "claude-code") {
1403
+ const provider = this.providerFactory ? this.providerFactory.getProvider(agentType) : this.provider;
1404
+ const session = await provider.startSession({
894
1405
  projectPath,
895
1406
  message,
896
1407
  sessionId: resumeSessionId ?? newSessionId,
@@ -900,6 +1411,7 @@ var SessionManager = class {
900
1411
  effort,
901
1412
  images
902
1413
  });
1414
+ this.sessionAgentType.set(session.id, agentType);
903
1415
  this.lastBroadcastStatus.set(session.id, session.status);
904
1416
  this.sessionProjectPaths.set(session.id, projectPath);
905
1417
  this.unsubscribeSession(session.id);
@@ -911,7 +1423,8 @@ var SessionManager = class {
911
1423
  * 发送消息到已有会话
912
1424
  */
913
1425
  async sendMessage(sessionId, message, permissionMode, images) {
914
- await this.provider.sendMessage(sessionId, message, permissionMode, images);
1426
+ const provider = this.getProviderForSession(sessionId);
1427
+ await provider.sendMessage(sessionId, message, permissionMode, images);
915
1428
  this.updateSessionStatus(sessionId, "running");
916
1429
  console.log(`[SessionManager] Message sent to session: ${sessionId}`);
917
1430
  }
@@ -931,7 +1444,9 @@ var SessionManager = class {
931
1444
  clearTimeout(pending.timer);
932
1445
  this.pendingAssistantEvents.delete(sessionId);
933
1446
  }
934
- await this.provider.killSession(sessionId);
1447
+ const provider = this.getProviderForSession(sessionId);
1448
+ await provider.killSession(sessionId);
1449
+ this.sessionAgentType.delete(sessionId);
935
1450
  console.log(`[SessionManager] Session killed: ${sessionId}`);
936
1451
  }
937
1452
  /**
@@ -1010,7 +1525,11 @@ var SessionManager = class {
1010
1525
  * 获取所有活跃会话(含服务器端统计)
1011
1526
  */
1012
1527
  getActiveSessions() {
1013
- return this.provider.getActiveSessions().map((session) => {
1528
+ const rawSessions = this.providerFactory ? [
1529
+ ...this.providerFactory.getProvider("claude-code").getActiveSessions(),
1530
+ ...this.providerFactory.getProvider("codex").getActiveSessions()
1531
+ ] : this.provider.getActiveSessions();
1532
+ return rawSessions.map((session) => {
1014
1533
  const stats = this.getSessionStats(session.id);
1015
1534
  return stats ? { ...session, stats } : session;
1016
1535
  });
@@ -1040,6 +1559,7 @@ var SessionManager = class {
1040
1559
  this.sessionEventBuffers.clear();
1041
1560
  this.bufferTruncated.clear();
1042
1561
  this.sessionProjectPaths.clear();
1562
+ this.sessionAgentType.clear();
1043
1563
  this.sessionStats.clear();
1044
1564
  for (const [, pending] of this.pendingAssistantEvents) {
1045
1565
  clearTimeout(pending.timer);
@@ -1057,10 +1577,11 @@ var SessionManager = class {
1057
1577
  * 订阅指定会话的事件流(包括 AskUserQuestion 问题事件)
1058
1578
  */
1059
1579
  subscribeToSession(sessionId) {
1060
- const unsubscribeEvent = this.provider.onEvent(sessionId, (event) => {
1580
+ const provider = this.getProviderForSession(sessionId);
1581
+ const unsubscribeEvent = provider.onEvent(sessionId, (event) => {
1061
1582
  this.handleClaudeEvent(sessionId, event);
1062
1583
  });
1063
- const unsubscribeQuestion = this.provider.onQuestion(
1584
+ const unsubscribeQuestion = provider.onQuestion(
1064
1585
  sessionId,
1065
1586
  ({ toolUseId, question, options }) => {
1066
1587
  this.handleAskUserQuestion(sessionId, toolUseId, question, options);
@@ -1235,7 +1756,7 @@ var SessionManager = class {
1235
1756
  console.log(`[SessionManager] Session ${sessionId}: AskUserQuestion updated (requestId=${existingRequestId})`);
1236
1757
  return;
1237
1758
  }
1238
- const requestId = (0, import_uuid2.v4)();
1759
+ const requestId = (0, import_uuid3.v4)();
1239
1760
  const request = {
1240
1761
  id: requestId,
1241
1762
  sessionId,
@@ -1251,7 +1772,8 @@ var SessionManager = class {
1251
1772
  });
1252
1773
  answerPromise.then(async (answer) => {
1253
1774
  try {
1254
- await this.provider.answerQuestion(sessionId, toolUseId, answer);
1775
+ const provider = this.getProviderForSession(sessionId);
1776
+ await provider.answerQuestion(sessionId, toolUseId, answer);
1255
1777
  } catch (err) {
1256
1778
  console.error(`[SessionManager] answerQuestion failed (${sessionId}):`, err);
1257
1779
  }
@@ -1674,15 +2196,15 @@ var WsBridge = class _WsBridge {
1674
2196
 
1675
2197
  // src/approval/ApprovalProxy.ts
1676
2198
  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");
2199
+ var import_node_fs3 = __toESM(require("fs"));
2200
+ var import_node_path3 = __toESM(require("path"));
2201
+ var import_node_os4 = __toESM(require("os"));
2202
+ var import_uuid4 = require("uuid");
1681
2203
  var ApprovalProxy = class _ApprovalProxy {
1682
2204
  server;
1683
2205
  token;
1684
2206
  port;
1685
- settingsPath = import_node_path2.default.join(import_node_os3.default.homedir(), ".claude", "settings.json");
2207
+ settingsPath = import_node_path3.default.join(import_node_os4.default.homedir(), ".claude", "settings.json");
1686
2208
  /** 待处理的审批请求:requestId -> { resolve, timer, request } */
1687
2209
  pendingApprovals = /* @__PURE__ */ new Map();
1688
2210
  /** 审批请求回调(通知外部推送到手机) */
@@ -1787,7 +2309,7 @@ var ApprovalProxy = class _ApprovalProxy {
1787
2309
  isToolInClaudeSettings(toolName, projectPath) {
1788
2310
  const checkPath = (filepath) => {
1789
2311
  try {
1790
- const raw = import_node_fs2.default.readFileSync(filepath, "utf-8");
2312
+ const raw = import_node_fs3.default.readFileSync(filepath, "utf-8");
1791
2313
  const settings = JSON.parse(raw);
1792
2314
  const allow = settings?.permissions?.allow ?? [];
1793
2315
  return allow.some((entry) => {
@@ -1801,24 +2323,24 @@ var ApprovalProxy = class _ApprovalProxy {
1801
2323
  }
1802
2324
  };
1803
2325
  if (projectPath) {
1804
- const projectSettingsPath = import_node_path2.default.join(projectPath, ".claude", "settings.json");
2326
+ const projectSettingsPath = import_node_path3.default.join(projectPath, ".claude", "settings.json");
1805
2327
  if (checkPath(projectSettingsPath)) return true;
1806
2328
  }
1807
2329
  return checkPath(this.settingsPath);
1808
2330
  }
1809
2331
  /** 将工具写入 settings.json permissions.allow(项目级或全局) */
1810
2332
  addToClaudeSettings(projectPath, toolName) {
1811
- const targetPath = projectPath ? import_node_path2.default.join(projectPath, ".claude", "settings.json") : this.settingsPath;
2333
+ const targetPath = projectPath ? import_node_path3.default.join(projectPath, ".claude", "settings.json") : this.settingsPath;
1812
2334
  try {
1813
2335
  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 });
2336
+ const dir = import_node_path3.default.dirname(targetPath);
2337
+ if (!import_node_fs3.default.existsSync(dir)) {
2338
+ import_node_fs3.default.mkdirSync(dir, { recursive: true });
1817
2339
  }
1818
2340
  }
1819
2341
  let settings = {};
1820
2342
  try {
1821
- settings = JSON.parse(import_node_fs2.default.readFileSync(targetPath, "utf-8"));
2343
+ settings = JSON.parse(import_node_fs3.default.readFileSync(targetPath, "utf-8"));
1822
2344
  } catch {
1823
2345
  }
1824
2346
  if (!settings.permissions) {
@@ -1832,7 +2354,7 @@ var ApprovalProxy = class _ApprovalProxy {
1832
2354
  const entry = `${toolName}(*)`;
1833
2355
  if (!allow.includes(entry)) {
1834
2356
  allow.push(entry);
1835
- import_node_fs2.default.writeFileSync(targetPath, JSON.stringify(settings, null, 2), "utf-8");
2357
+ import_node_fs3.default.writeFileSync(targetPath, JSON.stringify(settings, null, 2), "utf-8");
1836
2358
  const label = projectPath ? `${projectPath}/.claude/settings.json` : "~/.claude/settings.json";
1837
2359
  console.log(`[ApprovalProxy] ${t("approval.alwaysAllowWritten", { entry, label })}`);
1838
2360
  }
@@ -1927,7 +2449,7 @@ var ApprovalProxy = class _ApprovalProxy {
1927
2449
  try {
1928
2450
  const body = await this.parseJsonBody(req);
1929
2451
  const payload = body.payload ?? body;
1930
- const requestId = (0, import_uuid3.v4)();
2452
+ const requestId = (0, import_uuid4.v4)();
1931
2453
  const projectPath = String(body.projectPath ?? "unknown");
1932
2454
  const toolName = String(payload.tool_name ?? body.tool_name ?? "unknown");
1933
2455
  const toolInput = payload.tool_input ?? body.tool_input ?? {};
@@ -2059,8 +2581,8 @@ var ApprovalProxy = class _ApprovalProxy {
2059
2581
  };
2060
2582
 
2061
2583
  // src/mdns/MdnsService.ts
2062
- var import_node_child_process3 = require("child_process");
2063
- var import_node_os4 = require("os");
2584
+ var import_node_child_process5 = require("child_process");
2585
+ var import_node_os5 = require("os");
2064
2586
  function buildTxtArgs(txt) {
2065
2587
  return Object.entries(txt).map(([k, v]) => `${k}=${v}`);
2066
2588
  }
@@ -2078,7 +2600,7 @@ var MdnsService = class {
2078
2600
  this.httpPort = options.httpPort;
2079
2601
  this.version = options.version ?? "0.1.0";
2080
2602
  this.pairing = options.pairing ?? "closed";
2081
- this.useDnsSd = (0, import_node_os4.platform)() === "darwin";
2603
+ this.useDnsSd = (0, import_node_os5.platform)() === "darwin";
2082
2604
  }
2083
2605
  getTxt() {
2084
2606
  return {
@@ -2111,7 +2633,7 @@ var MdnsService = class {
2111
2633
  String(this.wsPort),
2112
2634
  ...buildTxtArgs(this.getTxt())
2113
2635
  ];
2114
- this.proc = (0, import_node_child_process3.spawn)("dns-sd", args, { stdio: "ignore" });
2636
+ this.proc = (0, import_node_child_process5.spawn)("dns-sd", args, { stdio: "ignore" });
2115
2637
  this.proc.on("error", (err) => {
2116
2638
  console.warn(`[MdnsService] dns-sd failed, falling back to bonjour-service: ${err.message}`);
2117
2639
  this.proc = null;
@@ -2132,7 +2654,7 @@ var MdnsService = class {
2132
2654
  return;
2133
2655
  }
2134
2656
  try {
2135
- const { default: Bonjour } = await import("bonjour-service");
2657
+ const { Bonjour } = await import("bonjour-service");
2136
2658
  const { networkInterfaces: networkInterfaces2 } = await import("os");
2137
2659
  const lanAddrs = getLanAddresses(networkInterfaces2);
2138
2660
  const opts = lanAddrs.length > 0 ? { interface: lanAddrs[0] } : {};
@@ -2223,12 +2745,12 @@ function getLanAddresses(networkInterfacesFn) {
2223
2745
 
2224
2746
  // src/hooks/HookInstaller.ts
2225
2747
  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");
2748
+ var import_node_path4 = require("path");
2749
+ var import_node_os6 = require("os");
2750
+ var SESSIX_HOOKS_DIR = (0, import_node_path4.join)((0, import_node_os6.homedir)(), ".sessix", "hooks");
2751
+ var HOOK_SCRIPT_PATH = (0, import_node_path4.join)(SESSIX_HOOKS_DIR, "approval-hook.js");
2752
+ var PERMISSION_ACCEPT_PATH = (0, import_node_path4.join)(SESSIX_HOOKS_DIR, "permission-accept.js");
2753
+ var CLAUDE_SETTINGS_PATH = (0, import_node_path4.join)((0, import_node_os6.homedir)(), ".claude", "settings.json");
2232
2754
  var HOOK_COMMAND = "node ~/.sessix/hooks/approval-hook.js";
2233
2755
  var PERMISSION_ACCEPT_COMMAND = "node ~/.sessix/hooks/permission-accept.js";
2234
2756
  var LEGACY_HOOK_COMMANDS = [
@@ -2402,7 +2924,7 @@ var HookInstaller = class {
2402
2924
  * 写入 Claude Code settings.json
2403
2925
  */
2404
2926
  async writeClaudeSettings(settings) {
2405
- await (0, import_promises2.mkdir)((0, import_node_path3.join)((0, import_node_os5.homedir)(), ".claude"), { recursive: true });
2927
+ await (0, import_promises2.mkdir)((0, import_node_path4.join)((0, import_node_os6.homedir)(), ".claude"), { recursive: true });
2406
2928
  await (0, import_promises2.writeFile)(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n", "utf-8");
2407
2929
  }
2408
2930
  /**
@@ -2429,7 +2951,7 @@ var HookInstaller = class {
2429
2951
  };
2430
2952
 
2431
2953
  // src/notification/NotificationService.ts
2432
- var import_node_path4 = require("path");
2954
+ var import_node_path5 = require("path");
2433
2955
  var NotificationService = class {
2434
2956
  constructor(sessionManager, expoChannel = null) {
2435
2957
  this.sessionManager = sessionManager;
@@ -2525,7 +3047,7 @@ var NotificationService = class {
2525
3047
  const dangerLevel = this.getDangerLevel(request.toolName);
2526
3048
  const isDangerous = dangerLevel === "danger" || dangerLevel === "write";
2527
3049
  const categoryId = isDangerous ? "APPROVAL_DANGEROUS" : "APPROVAL_NORMAL";
2528
- const projectName = (0, import_node_path4.basename)(
3050
+ const projectName = (0, import_node_path5.basename)(
2529
3051
  this.sessionManager.getActiveSessions().find((s) => s.id === request.sessionId)?.projectPath ?? ""
2530
3052
  );
2531
3053
  const pushTitle = isDangerous ? `\u26A0\uFE0F ${title}` : title;
@@ -2581,7 +3103,7 @@ var NotificationService = class {
2581
3103
  /** 从审批请求中提取操作目标的简短描述 */
2582
3104
  extractTarget(request) {
2583
3105
  const input = request.toolInput;
2584
- if (input.file_path) return (0, import_node_path4.basename)(String(input.file_path));
3106
+ if (input.file_path) return (0, import_node_path5.basename)(String(input.file_path));
2585
3107
  if (input.command) return String(input.command).slice(0, 40);
2586
3108
  return request.description.slice(0, 40);
2587
3109
  }
@@ -2684,7 +3206,7 @@ var NotificationService = class {
2684
3206
  getSessionTitle(sessionId) {
2685
3207
  const session = this.sessionManager.getActiveSessions().find((s) => s.id === sessionId);
2686
3208
  if (!session) return "Unknown";
2687
- return session.summary ?? (0, import_node_path4.basename)(session.projectPath);
3209
+ return session.summary ?? (0, import_node_path5.basename)(session.projectPath);
2688
3210
  }
2689
3211
  /** 获取会话的 YOLO 模式状态 */
2690
3212
  getYoloMode(sessionId) {
@@ -2693,7 +3215,7 @@ var NotificationService = class {
2693
3215
  };
2694
3216
 
2695
3217
  // src/notification/DesktopNotificationChannel.ts
2696
- var import_node_child_process4 = require("child_process");
3218
+ var import_node_child_process6 = require("child_process");
2697
3219
  var DesktopNotificationChannel = class {
2698
3220
  isAvailable() {
2699
3221
  return process.platform === "darwin";
@@ -2705,7 +3227,7 @@ var DesktopNotificationChannel = class {
2705
3227
  const sound = payload.sound ?? "Ping";
2706
3228
  const script = `display notification "${body}" with title "${title}" sound name "${sound}"`;
2707
3229
  return new Promise((resolve) => {
2708
- (0, import_node_child_process4.execFile)("osascript", ["-e", script], (err) => {
3230
+ (0, import_node_child_process6.execFile)("osascript", ["-e", script], (err) => {
2709
3231
  if (err) {
2710
3232
  console.warn("[DesktopNotificationChannel] Send notification failed:", err.message);
2711
3233
  }
@@ -2979,7 +3501,7 @@ var ActivityPushChannel = class {
2979
3501
 
2980
3502
  // src/session/ProjectReader.ts
2981
3503
  var import_promises3 = require("fs/promises");
2982
- var import_readline2 = require("readline");
3504
+ var import_readline3 = require("readline");
2983
3505
  var import_path = require("path");
2984
3506
  var import_os = require("os");
2985
3507
  var CLAUDE_PROJECTS_DIR = (0, import_path.join)((0, import_os.homedir)(), ".claude", "projects");
@@ -3228,7 +3750,7 @@ async function extractFirstPrompt(filePath) {
3228
3750
  let fileHandle;
3229
3751
  try {
3230
3752
  fileHandle = await (0, import_promises3.open)(filePath, "r");
3231
- const rl = (0, import_readline2.createInterface)({
3753
+ const rl = (0, import_readline3.createInterface)({
3232
3754
  input: fileHandle.createReadStream({ encoding: "utf-8" }),
3233
3755
  crlfDelay: Infinity
3234
3756
  });
@@ -3376,14 +3898,14 @@ var PairingManager = class {
3376
3898
  };
3377
3899
 
3378
3900
  // src/auth/AuthManager.ts
3379
- var import_child_process2 = require("child_process");
3380
3901
  var import_child_process3 = require("child_process");
3902
+ var import_child_process4 = require("child_process");
3381
3903
  var import_util = require("util");
3382
- var import_events2 = require("events");
3383
- var execFileAsync = (0, import_util.promisify)(import_child_process3.execFile);
3904
+ var import_events3 = require("events");
3905
+ var execFileAsync = (0, import_util.promisify)(import_child_process4.execFile);
3384
3906
  var CLAUDE_PATH2 = findClaudePath();
3385
3907
  var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
3386
- var AuthManager = class extends import_events2.EventEmitter {
3908
+ var AuthManager = class extends import_events3.EventEmitter {
3387
3909
  loginProcess = null;
3388
3910
  loginTimeout = null;
3389
3911
  urlSent = false;
@@ -3411,7 +3933,7 @@ var AuthManager = class extends import_events2.EventEmitter {
3411
3933
  }
3412
3934
  this.clearLoginTimeout();
3413
3935
  this.urlSent = false;
3414
- const proc = (0, import_child_process2.spawn)(CLAUDE_PATH2, ["auth", "login"], {
3936
+ const proc = (0, import_child_process3.spawn)(CLAUDE_PATH2, ["auth", "login"], {
3415
3937
  env: { ...process.env, BROWSER: "echo" },
3416
3938
  stdio: ["pipe", "pipe", "pipe"]
3417
3939
  });
@@ -3497,8 +4019,8 @@ var AuthManager = class extends import_events2.EventEmitter {
3497
4019
  var import_promises5 = require("fs/promises");
3498
4020
 
3499
4021
  // src/terminal/TerminalExecutor.ts
3500
- var import_node_child_process5 = require("child_process");
3501
- var import_uuid4 = require("uuid");
4022
+ var import_node_child_process7 = require("child_process");
4023
+ var import_uuid5 = require("uuid");
3502
4024
  var EXEC_TIMEOUT_MS = 5 * 60 * 1e3;
3503
4025
  var TerminalExecutor = class {
3504
4026
  processes = /* @__PURE__ */ new Map();
@@ -3520,10 +4042,10 @@ var TerminalExecutor = class {
3520
4042
  }
3521
4043
  }
3522
4044
  exec(sessionId, command, cwd) {
3523
- const execId = (0, import_uuid4.v4)();
4045
+ const execId = (0, import_uuid5.v4)();
3524
4046
  const shell = isWindows ? "powershell" : "bash";
3525
4047
  const args = isWindows ? ["-Command", command] : ["-c", command];
3526
- const proc = (0, import_node_child_process5.spawn)(shell, args, {
4048
+ const proc = (0, import_node_child_process7.spawn)(shell, args, {
3527
4049
  cwd,
3528
4050
  stdio: ["ignore", "pipe", "pipe"],
3529
4051
  env: { ...process.env }
@@ -3586,7 +4108,7 @@ var TerminalExecutor = class {
3586
4108
  // src/server.ts
3587
4109
  var WS_PORT = 3745;
3588
4110
  var HTTP_PORT = 3746;
3589
- var execAsync = (0, import_node_util.promisify)(import_node_child_process6.exec);
4111
+ var execAsync = (0, import_node_util.promisify)(import_node_child_process8.exec);
3590
4112
  async function killPortProcess(port) {
3591
4113
  try {
3592
4114
  if (isWindows) {
@@ -3628,8 +4150,8 @@ async function createWithRetry(label, port, factory) {
3628
4150
  }
3629
4151
  }
3630
4152
  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");
4153
+ const configDir = (0, import_node_path6.join)((0, import_node_os7.homedir)(), ".sessix");
4154
+ const tokenFile = (0, import_node_path6.join)(configDir, "token");
3633
4155
  let token;
3634
4156
  if (opts.token !== void 0) {
3635
4157
  token = opts.token;
@@ -3641,14 +4163,14 @@ async function start(opts = {}) {
3641
4163
  try {
3642
4164
  token = (await (0, import_promises4.readFile)(tokenFile, "utf8")).trim();
3643
4165
  } catch {
3644
- token = (0, import_uuid5.v4)();
4166
+ token = (0, import_uuid6.v4)();
3645
4167
  await (0, import_promises4.mkdir)(configDir, { recursive: true });
3646
4168
  await (0, import_promises4.writeFile)(tokenFile, token, "utf8");
3647
4169
  }
3648
4170
  }
3649
4171
  }
3650
- const provider = new ProcessProvider();
3651
- const sessionManager = new SessionManager(provider);
4172
+ const providerFactory = new ProviderFactory();
4173
+ const sessionManager = new SessionManager(providerFactory);
3652
4174
  const terminalExecutor = new TerminalExecutor();
3653
4175
  const wsBridge = await createWithRetry(
3654
4176
  "WsBridge",
@@ -3688,7 +4210,7 @@ async function start(opts = {}) {
3688
4210
  let mdnsService = null;
3689
4211
  const pairingManager = new PairingManager({
3690
4212
  token,
3691
- serverName: (0, import_node_os6.hostname)(),
4213
+ serverName: (0, import_node_os7.hostname)(),
3692
4214
  version: "0.2.0",
3693
4215
  onStateChange: (state) => mdnsService?.updatePairingState(state)
3694
4216
  });
@@ -3745,7 +4267,8 @@ async function start(opts = {}) {
3745
4267
  event.model,
3746
4268
  event.permissionMode,
3747
4269
  event.effort,
3748
- event.images
4270
+ event.images,
4271
+ event.agentType
3749
4272
  );
3750
4273
  wsBridge.broadcast({
3751
4274
  type: "session_list",
@@ -3921,7 +4444,7 @@ async function start(opts = {}) {
3921
4444
  return null;
3922
4445
  }).filter(Boolean).join("\n");
3923
4446
  }
3924
- const suggestion = await provider.generateSuggestion(context);
4447
+ const suggestion = await providerFactory.getProvider("claude-code").generateSuggestion(context);
3925
4448
  wsBridge.send(ws, {
3926
4449
  type: "prompt_suggestion",
3927
4450
  sessionId: event.sessionId,
@@ -3999,6 +4522,11 @@ async function start(opts = {}) {
3999
4522
  }
4000
4523
  break;
4001
4524
  }
4525
+ case "list_agents": {
4526
+ const agents = await providerFactory.detectAgents();
4527
+ wsBridge.send(ws, { type: "agent_list", agents });
4528
+ break;
4529
+ }
4002
4530
  default: {
4003
4531
  wsBridge.send(ws, {
4004
4532
  type: "error",
@@ -4162,7 +4690,7 @@ async function start(opts = {}) {
4162
4690
  openPairing: (duration) => pairingManager.open(duration),
4163
4691
  closePairing: () => pairingManager.close(),
4164
4692
  regenerateToken: async () => {
4165
- const newToken = (0, import_uuid5.v4)();
4693
+ const newToken = (0, import_uuid6.v4)();
4166
4694
  await (0, import_promises4.mkdir)(configDir, { recursive: true });
4167
4695
  await (0, import_promises4.writeFile)(tokenFile, newToken, "utf8");
4168
4696
  instance.token = newToken;
@@ -4181,7 +4709,7 @@ async function start(opts = {}) {
4181
4709
  var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
4182
4710
  function getPackageVersion() {
4183
4711
  try {
4184
- const pkg = JSON.parse((0, import_node_fs3.readFileSync)((0, import_node_path6.join)(__dirname, "..", "package.json"), "utf8"));
4712
+ const pkg = JSON.parse((0, import_node_fs4.readFileSync)((0, import_node_path7.join)(__dirname, "..", "package.json"), "utf8"));
4185
4713
  return pkg.version ?? "0.0.0";
4186
4714
  } catch {
4187
4715
  return "0.0.0";
@@ -4203,11 +4731,38 @@ async function fetchLatestVersion() {
4203
4731
  return null;
4204
4732
  }
4205
4733
  }
4734
+ function isNewer(latest, current) {
4735
+ const a = latest.split(".").map(Number);
4736
+ const b = current.split(".").map(Number);
4737
+ for (let i = 0; i < 3; i++) {
4738
+ if ((a[i] ?? 0) > (b[i] ?? 0)) return true;
4739
+ if ((a[i] ?? 0) < (b[i] ?? 0)) return false;
4740
+ }
4741
+ return false;
4742
+ }
4743
+ async function autoUpdateIfNeeded() {
4744
+ if (process.env.SESSIX_NO_UPDATE_CHECK === "1" || process.env.__SESSIX_UPDATED === "1") return;
4745
+ const latest = await fetchLatestVersion();
4746
+ if (!latest || !isNewer(latest, PKG_VERSION)) return;
4747
+ console.log(` \u{1F4E6} ${t("startup.autoUpdating", { current: PKG_VERSION, latest })}`);
4748
+ console.log();
4749
+ try {
4750
+ (0, import_node_child_process9.execFileSync)("npx", [`sessix-server@${latest}`], {
4751
+ stdio: "inherit",
4752
+ env: { ...process.env, __SESSIX_UPDATED: "1" }
4753
+ });
4754
+ process.exit(0);
4755
+ } catch {
4756
+ console.log(` \u26A0\uFE0F ${t("startup.autoUpdateFailed")}`);
4757
+ console.log();
4758
+ }
4759
+ }
4206
4760
  async function main() {
4207
4761
  console.log("=".repeat(50));
4208
4762
  console.log(`${t("startup.banner")} v${PKG_VERSION}`);
4209
4763
  console.log("=".repeat(50));
4210
4764
  console.log();
4765
+ await autoUpdateIfNeeded();
4211
4766
  const enableAutoConnect = process.env.SESSIX_AUTO_CONNECT !== "false";
4212
4767
  const server = await start({ enableAutoConnect });
4213
4768
  const localIp = getLocalIp();
@@ -4248,13 +4803,6 @@ async function main() {
4248
4803
  console.log(t("startup.pairingOpen"));
4249
4804
  console.log(t("startup.pressT"));
4250
4805
  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
4806
  const shutdown = async (signal) => {
4259
4807
  console.log(`
4260
4808
  [Main] ${t("startup.receivedSignal", { signal })}`);
@@ -4305,7 +4853,7 @@ ${t("startup.pairingReopened")}`);
4305
4853
  }
4306
4854
  }
4307
4855
  function getLocalIp() {
4308
- const interfaces = (0, import_node_os7.networkInterfaces)();
4856
+ const interfaces = (0, import_node_os8.networkInterfaces)();
4309
4857
  for (const iface of Object.values(interfaces)) {
4310
4858
  for (const addr of iface ?? []) {
4311
4859
  if (addr.family === "IPv4" && !addr.internal) {