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/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
@@ -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,
@@ -853,11 +862,500 @@ ${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");
857
869
  var import_uuid2 = require("uuid");
870
+
871
+ // src/utils/codexPath.ts
872
+ var import_node_child_process3 = require("child_process");
873
+ var import_node_fs2 = require("fs");
874
+ var import_node_path2 = require("path");
875
+ var import_node_os3 = require("os");
876
+ function findCodexPath() {
877
+ try {
878
+ const cmd = isWindows ? "where codex" : "which codex";
879
+ return (0, import_node_child_process3.execSync)(cmd, { encoding: "utf-8" }).trim().split("\n")[0];
880
+ } catch {
881
+ }
882
+ const candidates = isWindows ? [
883
+ (0, import_node_path2.join)(process.env.LOCALAPPDATA ?? "", "Programs", "codex", "codex.exe"),
884
+ (0, import_node_path2.join)((0, import_node_os3.homedir)(), ".codex", "local", "codex.exe")
885
+ ] : [
886
+ "/opt/homebrew/bin/codex",
887
+ "/usr/local/bin/codex",
888
+ (0, import_node_path2.join)((0, import_node_os3.homedir)(), ".local", "bin", "codex")
889
+ ];
890
+ for (const candidate of candidates) {
891
+ try {
892
+ (0, import_node_fs2.accessSync)(candidate, import_node_fs2.constants.X_OK);
893
+ return candidate;
894
+ } catch {
895
+ }
896
+ }
897
+ return "codex";
898
+ }
899
+ var CODEX_PATH = findCodexPath();
900
+ async function getCodexVersion() {
901
+ try {
902
+ const output = (0, import_node_child_process3.execSync)(`"${CODEX_PATH}" --version`, {
903
+ encoding: "utf-8",
904
+ timeout: 5e3
905
+ }).trim();
906
+ return output.replace(/^codex-cli\s+/, "");
907
+ } catch {
908
+ return void 0;
909
+ }
910
+ }
911
+ function isCodexAvailable() {
912
+ try {
913
+ (0, import_node_fs2.accessSync)(CODEX_PATH, import_node_fs2.constants.X_OK);
914
+ return true;
915
+ } catch {
916
+ try {
917
+ (0, import_node_child_process3.execSync)(`"${CODEX_PATH}" --version`, { timeout: 5e3 });
918
+ return true;
919
+ } catch {
920
+ return false;
921
+ }
922
+ }
923
+ }
924
+
925
+ // src/providers/CodexProvider.ts
926
+ var CodexProvider = class {
927
+ activeSessions = /* @__PURE__ */ new Map();
928
+ emitter = new import_events2.EventEmitter();
929
+ /** 自增 ID 计数器,用于生成 ClaudeStreamEvent 中的 message/block ID */
930
+ idCounter = 0;
931
+ async startSession(opts) {
932
+ const { projectPath, message, sessionId: existingSessionId } = opts;
933
+ const sessionId = existingSessionId ?? (0, import_uuid2.v4)();
934
+ if (this.activeSessions.has(sessionId)) {
935
+ await this.killSession(sessionId);
936
+ }
937
+ const projectId = projectPath.split("/").filter(Boolean).pop() ?? "unknown";
938
+ const session = {
939
+ id: sessionId,
940
+ projectId,
941
+ projectPath,
942
+ status: "running",
943
+ createdAt: Date.now(),
944
+ lastActiveAt: Date.now(),
945
+ summary: message.slice(0, 80),
946
+ agentType: "codex"
947
+ };
948
+ const resume = opts.resume ?? !!existingSessionId;
949
+ const proc = this.spawnCodexProcess(projectPath, message, void 0);
950
+ session.pid = proc.pid;
951
+ this.activeSessions.set(sessionId, {
952
+ session,
953
+ process: proc,
954
+ threadId: void 0,
955
+ turnStartTime: Date.now()
956
+ });
957
+ const initEvent = {
958
+ type: "system",
959
+ subtype: "init",
960
+ session_id: sessionId
961
+ };
962
+ this.emitter.emit(this.getEventName(sessionId), initEvent);
963
+ proc.on("error", (err) => {
964
+ console.error(`[CodexProvider] Session ${sessionId} process error:`, err.message);
965
+ this.activeSessions.delete(sessionId);
966
+ this.emitError(sessionId, `Process spawn failed: ${err.message}`);
967
+ });
968
+ this.attachStdoutListener(sessionId, proc);
969
+ this.attachStderrListener(sessionId, proc);
970
+ this.attachExitListener(sessionId, proc);
971
+ return session;
972
+ }
973
+ async killSession(sessionId) {
974
+ const entry = this.activeSessions.get(sessionId);
975
+ if (!entry) return;
976
+ if (entry.process.exitCode === null && entry.process.signalCode === null) {
977
+ try {
978
+ entry.process.stdin?.end();
979
+ } catch {
980
+ }
981
+ await killProcessCrossPlatform(entry.process);
982
+ }
983
+ this.activeSessions.delete(sessionId);
984
+ }
985
+ async sendMessage(sessionId, message) {
986
+ const entry = this.activeSessions.get(sessionId);
987
+ if (!entry) {
988
+ throw new Error(`Session ${sessionId} not found or already ended`);
989
+ }
990
+ if (entry.process.exitCode === null && entry.process.signalCode === null) {
991
+ try {
992
+ entry.process.stdin?.end();
993
+ } catch {
994
+ }
995
+ await killProcessCrossPlatform(entry.process);
996
+ }
997
+ const threadId = entry.threadId;
998
+ if (!threadId) {
999
+ throw new Error(`Session ${sessionId} has no thread ID, cannot resume`);
1000
+ }
1001
+ const proc = this.spawnCodexProcess(entry.session.projectPath, message, threadId);
1002
+ entry.session.status = "running";
1003
+ entry.session.lastActiveAt = Date.now();
1004
+ entry.session.pid = proc.pid;
1005
+ entry.process = proc;
1006
+ entry.turnStartTime = Date.now();
1007
+ proc.on("error", (err) => {
1008
+ console.error(`[CodexProvider] Session ${sessionId} sendMessage process error:`, err.message);
1009
+ this.activeSessions.delete(sessionId);
1010
+ this.emitError(sessionId, `Failed to send message: ${err.message}`);
1011
+ });
1012
+ this.attachStdoutListener(sessionId, proc);
1013
+ this.attachStderrListener(sessionId, proc);
1014
+ this.attachExitListener(sessionId, proc);
1015
+ }
1016
+ onEvent(sessionId, callback) {
1017
+ const eventName = this.getEventName(sessionId);
1018
+ this.emitter.on(eventName, callback);
1019
+ return () => {
1020
+ this.emitter.off(eventName, callback);
1021
+ };
1022
+ }
1023
+ getActiveSessions() {
1024
+ return Array.from(this.activeSessions.values()).map((e) => e.session);
1025
+ }
1026
+ async generateSuggestion(_context) {
1027
+ return "";
1028
+ }
1029
+ async answerQuestion(_sessionId, _toolUseId, _answer) {
1030
+ }
1031
+ onQuestion(_sessionId, _callback) {
1032
+ return () => {
1033
+ };
1034
+ }
1035
+ // ============================================
1036
+ // 私有方法
1037
+ // ============================================
1038
+ nextId() {
1039
+ return `codex_${++this.idCounter}`;
1040
+ }
1041
+ /**
1042
+ * 启动 codex CLI 进程
1043
+ *
1044
+ * @param projectPath 工作目录
1045
+ * @param message 用户消息
1046
+ * @param resumeThreadId 如果提供,则使用 `codex exec resume` 恢复会话
1047
+ */
1048
+ spawnCodexProcess(projectPath, message, resumeThreadId) {
1049
+ const args = ["exec", "--json", "--full-auto"];
1050
+ if (resumeThreadId) {
1051
+ args.push("resume", resumeThreadId);
1052
+ } else {
1053
+ args.push("-C", projectPath);
1054
+ }
1055
+ args.push(message);
1056
+ const env = { ...process.env };
1057
+ const proc = (0, import_child_process2.spawn)(CODEX_PATH, args, {
1058
+ cwd: projectPath,
1059
+ env,
1060
+ stdio: ["pipe", "pipe", "pipe"]
1061
+ });
1062
+ try {
1063
+ proc.stdin?.end();
1064
+ } catch {
1065
+ }
1066
+ return proc;
1067
+ }
1068
+ /**
1069
+ * 挂载 stdout 监听器,逐行解析 Codex NDJSON 并转换为 ClaudeStreamEvent
1070
+ */
1071
+ attachStdoutListener(sessionId, proc) {
1072
+ if (!proc.stdout) return;
1073
+ const rl = (0, import_readline2.createInterface)({ input: proc.stdout, crlfDelay: Infinity });
1074
+ const entry = this.activeSessions.get(sessionId);
1075
+ if (entry) entry.rl = rl;
1076
+ rl.on("line", (line) => {
1077
+ const trimmed = line.trim();
1078
+ if (!trimmed) return;
1079
+ let event;
1080
+ try {
1081
+ event = JSON.parse(trimmed);
1082
+ } catch {
1083
+ console.warn(`[CodexProvider] Session ${sessionId}: failed to parse: ${trimmed.substring(0, 100)}`);
1084
+ return;
1085
+ }
1086
+ this.handleCodexEvent(sessionId, event);
1087
+ });
1088
+ }
1089
+ /**
1090
+ * 处理单个 Codex NDJSON 事件,转换并发射 ClaudeStreamEvent
1091
+ */
1092
+ handleCodexEvent(sessionId, event) {
1093
+ const entry = this.activeSessions.get(sessionId);
1094
+ if (!entry) return;
1095
+ entry.session.lastActiveAt = Date.now();
1096
+ switch (event.type) {
1097
+ case "thread.started": {
1098
+ if (event.thread_id) {
1099
+ entry.threadId = event.thread_id;
1100
+ }
1101
+ break;
1102
+ }
1103
+ case "turn.started": {
1104
+ entry.session.status = "running";
1105
+ entry.turnStartTime = Date.now();
1106
+ break;
1107
+ }
1108
+ case "item.completed": {
1109
+ if (!event.item) break;
1110
+ this.handleItemCompleted(sessionId, event.item);
1111
+ break;
1112
+ }
1113
+ case "turn.completed": {
1114
+ entry.session.status = "idle";
1115
+ const duration = entry.turnStartTime ? Date.now() - entry.turnStartTime : 0;
1116
+ const resultEvent = {
1117
+ type: "result",
1118
+ subtype: "success",
1119
+ session_id: sessionId,
1120
+ is_error: false,
1121
+ result: "",
1122
+ duration_ms: duration,
1123
+ num_turns: 1,
1124
+ usage: event.usage
1125
+ };
1126
+ this.emitter.emit(this.getEventName(sessionId), resultEvent);
1127
+ break;
1128
+ }
1129
+ case "turn.failed": {
1130
+ entry.session.status = "error";
1131
+ const failEvent = {
1132
+ type: "result",
1133
+ subtype: "error",
1134
+ session_id: sessionId,
1135
+ is_error: true,
1136
+ result: event.error ?? "Turn failed",
1137
+ duration_ms: entry.turnStartTime ? Date.now() - entry.turnStartTime : 0,
1138
+ num_turns: 1
1139
+ };
1140
+ this.emitter.emit(this.getEventName(sessionId), failEvent);
1141
+ break;
1142
+ }
1143
+ }
1144
+ }
1145
+ /**
1146
+ * 处理 item.completed 事件,按 item.type 转换为对应的 ClaudeStreamEvent
1147
+ */
1148
+ handleItemCompleted(sessionId, item) {
1149
+ switch (item.type) {
1150
+ case "agent_message": {
1151
+ const msgEvent = {
1152
+ type: "assistant",
1153
+ session_id: sessionId,
1154
+ message: {
1155
+ id: item.id || this.nextId(),
1156
+ model: "codex",
1157
+ role: "assistant",
1158
+ content: [{ type: "text", text: item.text ?? "" }]
1159
+ }
1160
+ };
1161
+ this.emitter.emit(this.getEventName(sessionId), msgEvent);
1162
+ break;
1163
+ }
1164
+ case "command_execution": {
1165
+ const toolUseId = item.id || this.nextId();
1166
+ const toolEvent = {
1167
+ type: "assistant",
1168
+ session_id: sessionId,
1169
+ message: {
1170
+ id: this.nextId(),
1171
+ model: "codex",
1172
+ role: "assistant",
1173
+ content: [{
1174
+ type: "tool_use",
1175
+ id: toolUseId,
1176
+ name: "Bash",
1177
+ input: { command: item.command ?? "" }
1178
+ }]
1179
+ }
1180
+ };
1181
+ this.emitter.emit(this.getEventName(sessionId), toolEvent);
1182
+ const resultContent = item.aggregated_output ?? "";
1183
+ const isError = item.exit_code != null && item.exit_code !== 0;
1184
+ const toolResultEvent = {
1185
+ type: "user",
1186
+ session_id: sessionId,
1187
+ message: {
1188
+ role: "user",
1189
+ content: [{
1190
+ type: "tool_result",
1191
+ tool_use_id: toolUseId,
1192
+ content: resultContent,
1193
+ is_error: isError
1194
+ }]
1195
+ }
1196
+ };
1197
+ this.emitter.emit(this.getEventName(sessionId), toolResultEvent);
1198
+ break;
1199
+ }
1200
+ case "file_change": {
1201
+ const editToolUseId = item.id || this.nextId();
1202
+ const toolEvent = {
1203
+ type: "assistant",
1204
+ session_id: sessionId,
1205
+ message: {
1206
+ id: this.nextId(),
1207
+ model: "codex",
1208
+ role: "assistant",
1209
+ content: [{
1210
+ type: "tool_use",
1211
+ id: editToolUseId,
1212
+ name: "Edit",
1213
+ input: { file_path: item.file_path ?? "", diff: item.diff ?? "" }
1214
+ }]
1215
+ }
1216
+ };
1217
+ this.emitter.emit(this.getEventName(sessionId), toolEvent);
1218
+ const toolResultEvent = {
1219
+ type: "user",
1220
+ session_id: sessionId,
1221
+ message: {
1222
+ role: "user",
1223
+ content: [{
1224
+ type: "tool_result",
1225
+ tool_use_id: editToolUseId,
1226
+ content: `File edited: ${item.file_path ?? "unknown"}`
1227
+ }]
1228
+ }
1229
+ };
1230
+ this.emitter.emit(this.getEventName(sessionId), toolResultEvent);
1231
+ break;
1232
+ }
1233
+ case "reasoning": {
1234
+ const thinkEvent = {
1235
+ type: "assistant",
1236
+ session_id: sessionId,
1237
+ message: {
1238
+ id: item.id || this.nextId(),
1239
+ model: "codex",
1240
+ role: "assistant",
1241
+ content: [{ type: "thinking", thinking: item.text ?? "" }]
1242
+ }
1243
+ };
1244
+ this.emitter.emit(this.getEventName(sessionId), thinkEvent);
1245
+ break;
1246
+ }
1247
+ }
1248
+ }
1249
+ attachStderrListener(sessionId, proc) {
1250
+ if (!proc.stderr) return;
1251
+ proc.stderr.on("data", (data) => {
1252
+ const text = data.toString().trim();
1253
+ if (text) {
1254
+ console.error(`[CodexProvider] Session ${sessionId} stderr: ${text}`);
1255
+ }
1256
+ });
1257
+ }
1258
+ attachExitListener(sessionId, proc) {
1259
+ proc.once("exit", (code, signal) => {
1260
+ const entry = this.activeSessions.get(sessionId);
1261
+ if (!entry) return;
1262
+ if (entry.process !== proc) return;
1263
+ if (entry.rl) {
1264
+ entry.rl.close();
1265
+ entry.rl = void 0;
1266
+ }
1267
+ entry.session.pid = void 0;
1268
+ entry.session.lastActiveAt = Date.now();
1269
+ const alreadyHasResult = entry.session.status === "idle" || entry.session.status === "error";
1270
+ if (alreadyHasResult) return;
1271
+ const isNormal = isNormalExit(code, signal);
1272
+ entry.session.status = isNormal ? "idle" : "error";
1273
+ if (!isNormal) {
1274
+ console.error(`[CodexProvider] Session ${sessionId}: process exited abnormally code=${code} signal=${signal}`);
1275
+ }
1276
+ const syntheticResult = {
1277
+ type: "result",
1278
+ subtype: isNormal ? "success" : "error",
1279
+ session_id: sessionId,
1280
+ is_error: !isNormal,
1281
+ result: isNormal ? "" : `Process exited code=${code} signal=${signal}`,
1282
+ duration_ms: 0,
1283
+ num_turns: 0
1284
+ };
1285
+ this.emitter.emit(this.getEventName(sessionId), syntheticResult);
1286
+ });
1287
+ }
1288
+ emitError(sessionId, message) {
1289
+ const event = {
1290
+ type: "result",
1291
+ subtype: "error",
1292
+ result: message,
1293
+ session_id: sessionId,
1294
+ duration_ms: 0,
1295
+ is_error: true,
1296
+ num_turns: 0
1297
+ };
1298
+ this.emitter.emit(this.getEventName(sessionId), event);
1299
+ }
1300
+ getEventName(sessionId) {
1301
+ return `claude:${sessionId}`;
1302
+ }
1303
+ };
1304
+
1305
+ // src/providers/ProviderFactory.ts
1306
+ var import_node_child_process4 = require("child_process");
1307
+ var ProviderFactory = class {
1308
+ providers = /* @__PURE__ */ new Map();
1309
+ getProvider(agentType) {
1310
+ const existing = this.providers.get(agentType);
1311
+ if (existing) return existing;
1312
+ let provider;
1313
+ switch (agentType) {
1314
+ case "codex":
1315
+ provider = new CodexProvider();
1316
+ break;
1317
+ case "claude-code":
1318
+ default:
1319
+ provider = new ProcessProvider();
1320
+ break;
1321
+ }
1322
+ this.providers.set(agentType, provider);
1323
+ return provider;
1324
+ }
1325
+ async detectAgents() {
1326
+ const agents = [];
1327
+ try {
1328
+ const claudePath = findClaudePath();
1329
+ const claudeVersion = (0, import_node_child_process4.execSync)(`"${claudePath}" --version`, {
1330
+ encoding: "utf-8",
1331
+ timeout: 5e3
1332
+ }).trim();
1333
+ agents.push({
1334
+ type: "claude-code",
1335
+ name: "Claude Code",
1336
+ available: true,
1337
+ version: claudeVersion
1338
+ });
1339
+ } catch {
1340
+ agents.push({ type: "claude-code", name: "Claude Code", available: false });
1341
+ }
1342
+ if (isCodexAvailable()) {
1343
+ const version = await getCodexVersion();
1344
+ agents.push({ type: "codex", name: "Codex", available: true, version });
1345
+ } else {
1346
+ agents.push({ type: "codex", name: "Codex", available: false });
1347
+ }
1348
+ return agents;
1349
+ }
1350
+ };
1351
+
1352
+ // src/session/SessionManager.ts
1353
+ var import_uuid3 = require("uuid");
858
1354
  var BUFFER_MAX = 5e3;
859
1355
  var SessionManager = class {
860
1356
  provider;
1357
+ providerFactory;
1358
+ sessionAgentType = /* @__PURE__ */ new Map();
861
1359
  /** 事件回调列表(事件会被转发到 WsBridge) */
862
1360
  eventCallbacks = [];
863
1361
  /** 每个会话的事件流取消订阅函数 */
@@ -883,8 +1381,19 @@ var SessionManager = class {
883
1381
  bufferTruncated = /* @__PURE__ */ new Set();
884
1382
  /** sessionId → projectPath 映射,用于截断时从 JSONL 补全历史 */
885
1383
  sessionProjectPaths = /* @__PURE__ */ new Map();
886
- constructor(provider) {
887
- this.provider = provider;
1384
+ constructor(providerOrFactory) {
1385
+ if (providerOrFactory instanceof ProviderFactory) {
1386
+ this.providerFactory = providerOrFactory;
1387
+ this.provider = providerOrFactory.getProvider("claude-code");
1388
+ } else {
1389
+ this.provider = providerOrFactory;
1390
+ this.providerFactory = null;
1391
+ }
1392
+ }
1393
+ getProviderForSession(sessionId) {
1394
+ if (!this.providerFactory) return this.provider;
1395
+ const agentType = this.sessionAgentType.get(sessionId) ?? "claude-code";
1396
+ return this.providerFactory.getProvider(agentType);
888
1397
  }
889
1398
  // ============================================
890
1399
  // 公开 API
@@ -895,8 +1404,9 @@ var SessionManager = class {
895
1404
  * 调用 provider.startSession(),订阅事件流,
896
1405
  * 将 ClaudeStreamEvent 包装为 ServerEvent 转发。
897
1406
  */
898
- async createSession(projectPath, message, resumeSessionId, newSessionId, model, permissionMode, effort, images) {
899
- const session = await this.provider.startSession({
1407
+ async createSession(projectPath, message, resumeSessionId, newSessionId, model, permissionMode, effort, images, agentType = "claude-code") {
1408
+ const provider = this.providerFactory ? this.providerFactory.getProvider(agentType) : this.provider;
1409
+ const session = await provider.startSession({
900
1410
  projectPath,
901
1411
  message,
902
1412
  sessionId: resumeSessionId ?? newSessionId,
@@ -906,6 +1416,7 @@ var SessionManager = class {
906
1416
  effort,
907
1417
  images
908
1418
  });
1419
+ this.sessionAgentType.set(session.id, agentType);
909
1420
  this.lastBroadcastStatus.set(session.id, session.status);
910
1421
  this.sessionProjectPaths.set(session.id, projectPath);
911
1422
  this.unsubscribeSession(session.id);
@@ -917,7 +1428,8 @@ var SessionManager = class {
917
1428
  * 发送消息到已有会话
918
1429
  */
919
1430
  async sendMessage(sessionId, message, permissionMode, images) {
920
- await this.provider.sendMessage(sessionId, message, permissionMode, images);
1431
+ const provider = this.getProviderForSession(sessionId);
1432
+ await provider.sendMessage(sessionId, message, permissionMode, images);
921
1433
  this.updateSessionStatus(sessionId, "running");
922
1434
  console.log(`[SessionManager] Message sent to session: ${sessionId}`);
923
1435
  }
@@ -937,7 +1449,9 @@ var SessionManager = class {
937
1449
  clearTimeout(pending.timer);
938
1450
  this.pendingAssistantEvents.delete(sessionId);
939
1451
  }
940
- await this.provider.killSession(sessionId);
1452
+ const provider = this.getProviderForSession(sessionId);
1453
+ await provider.killSession(sessionId);
1454
+ this.sessionAgentType.delete(sessionId);
941
1455
  console.log(`[SessionManager] Session killed: ${sessionId}`);
942
1456
  }
943
1457
  /**
@@ -1016,7 +1530,11 @@ var SessionManager = class {
1016
1530
  * 获取所有活跃会话(含服务器端统计)
1017
1531
  */
1018
1532
  getActiveSessions() {
1019
- return this.provider.getActiveSessions().map((session) => {
1533
+ const rawSessions = this.providerFactory ? [
1534
+ ...this.providerFactory.getProvider("claude-code").getActiveSessions(),
1535
+ ...this.providerFactory.getProvider("codex").getActiveSessions()
1536
+ ] : this.provider.getActiveSessions();
1537
+ return rawSessions.map((session) => {
1020
1538
  const stats = this.getSessionStats(session.id);
1021
1539
  return stats ? { ...session, stats } : session;
1022
1540
  });
@@ -1046,6 +1564,7 @@ var SessionManager = class {
1046
1564
  this.sessionEventBuffers.clear();
1047
1565
  this.bufferTruncated.clear();
1048
1566
  this.sessionProjectPaths.clear();
1567
+ this.sessionAgentType.clear();
1049
1568
  this.sessionStats.clear();
1050
1569
  for (const [, pending] of this.pendingAssistantEvents) {
1051
1570
  clearTimeout(pending.timer);
@@ -1063,10 +1582,11 @@ var SessionManager = class {
1063
1582
  * 订阅指定会话的事件流(包括 AskUserQuestion 问题事件)
1064
1583
  */
1065
1584
  subscribeToSession(sessionId) {
1066
- const unsubscribeEvent = this.provider.onEvent(sessionId, (event) => {
1585
+ const provider = this.getProviderForSession(sessionId);
1586
+ const unsubscribeEvent = provider.onEvent(sessionId, (event) => {
1067
1587
  this.handleClaudeEvent(sessionId, event);
1068
1588
  });
1069
- const unsubscribeQuestion = this.provider.onQuestion(
1589
+ const unsubscribeQuestion = provider.onQuestion(
1070
1590
  sessionId,
1071
1591
  ({ toolUseId, question, options }) => {
1072
1592
  this.handleAskUserQuestion(sessionId, toolUseId, question, options);
@@ -1241,7 +1761,7 @@ var SessionManager = class {
1241
1761
  console.log(`[SessionManager] Session ${sessionId}: AskUserQuestion updated (requestId=${existingRequestId})`);
1242
1762
  return;
1243
1763
  }
1244
- const requestId = (0, import_uuid2.v4)();
1764
+ const requestId = (0, import_uuid3.v4)();
1245
1765
  const request = {
1246
1766
  id: requestId,
1247
1767
  sessionId,
@@ -1257,7 +1777,8 @@ var SessionManager = class {
1257
1777
  });
1258
1778
  answerPromise.then(async (answer) => {
1259
1779
  try {
1260
- await this.provider.answerQuestion(sessionId, toolUseId, answer);
1780
+ const provider = this.getProviderForSession(sessionId);
1781
+ await provider.answerQuestion(sessionId, toolUseId, answer);
1261
1782
  } catch (err) {
1262
1783
  console.error(`[SessionManager] answerQuestion failed (${sessionId}):`, err);
1263
1784
  }
@@ -1680,15 +2201,15 @@ var WsBridge = class _WsBridge {
1680
2201
 
1681
2202
  // src/approval/ApprovalProxy.ts
1682
2203
  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");
2204
+ var import_node_fs3 = __toESM(require("fs"));
2205
+ var import_node_path3 = __toESM(require("path"));
2206
+ var import_node_os4 = __toESM(require("os"));
2207
+ var import_uuid4 = require("uuid");
1687
2208
  var ApprovalProxy = class _ApprovalProxy {
1688
2209
  server;
1689
2210
  token;
1690
2211
  port;
1691
- settingsPath = import_node_path2.default.join(import_node_os3.default.homedir(), ".claude", "settings.json");
2212
+ settingsPath = import_node_path3.default.join(import_node_os4.default.homedir(), ".claude", "settings.json");
1692
2213
  /** 待处理的审批请求:requestId -> { resolve, timer, request } */
1693
2214
  pendingApprovals = /* @__PURE__ */ new Map();
1694
2215
  /** 审批请求回调(通知外部推送到手机) */
@@ -1793,7 +2314,7 @@ var ApprovalProxy = class _ApprovalProxy {
1793
2314
  isToolInClaudeSettings(toolName, projectPath) {
1794
2315
  const checkPath = (filepath) => {
1795
2316
  try {
1796
- const raw = import_node_fs2.default.readFileSync(filepath, "utf-8");
2317
+ const raw = import_node_fs3.default.readFileSync(filepath, "utf-8");
1797
2318
  const settings = JSON.parse(raw);
1798
2319
  const allow = settings?.permissions?.allow ?? [];
1799
2320
  return allow.some((entry) => {
@@ -1807,24 +2328,24 @@ var ApprovalProxy = class _ApprovalProxy {
1807
2328
  }
1808
2329
  };
1809
2330
  if (projectPath) {
1810
- const projectSettingsPath = import_node_path2.default.join(projectPath, ".claude", "settings.json");
2331
+ const projectSettingsPath = import_node_path3.default.join(projectPath, ".claude", "settings.json");
1811
2332
  if (checkPath(projectSettingsPath)) return true;
1812
2333
  }
1813
2334
  return checkPath(this.settingsPath);
1814
2335
  }
1815
2336
  /** 将工具写入 settings.json permissions.allow(项目级或全局) */
1816
2337
  addToClaudeSettings(projectPath, toolName) {
1817
- const targetPath = projectPath ? import_node_path2.default.join(projectPath, ".claude", "settings.json") : this.settingsPath;
2338
+ const targetPath = projectPath ? import_node_path3.default.join(projectPath, ".claude", "settings.json") : this.settingsPath;
1818
2339
  try {
1819
2340
  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 });
2341
+ const dir = import_node_path3.default.dirname(targetPath);
2342
+ if (!import_node_fs3.default.existsSync(dir)) {
2343
+ import_node_fs3.default.mkdirSync(dir, { recursive: true });
1823
2344
  }
1824
2345
  }
1825
2346
  let settings = {};
1826
2347
  try {
1827
- settings = JSON.parse(import_node_fs2.default.readFileSync(targetPath, "utf-8"));
2348
+ settings = JSON.parse(import_node_fs3.default.readFileSync(targetPath, "utf-8"));
1828
2349
  } catch {
1829
2350
  }
1830
2351
  if (!settings.permissions) {
@@ -1838,7 +2359,7 @@ var ApprovalProxy = class _ApprovalProxy {
1838
2359
  const entry = `${toolName}(*)`;
1839
2360
  if (!allow.includes(entry)) {
1840
2361
  allow.push(entry);
1841
- import_node_fs2.default.writeFileSync(targetPath, JSON.stringify(settings, null, 2), "utf-8");
2362
+ import_node_fs3.default.writeFileSync(targetPath, JSON.stringify(settings, null, 2), "utf-8");
1842
2363
  const label = projectPath ? `${projectPath}/.claude/settings.json` : "~/.claude/settings.json";
1843
2364
  console.log(`[ApprovalProxy] ${t("approval.alwaysAllowWritten", { entry, label })}`);
1844
2365
  }
@@ -1933,7 +2454,7 @@ var ApprovalProxy = class _ApprovalProxy {
1933
2454
  try {
1934
2455
  const body = await this.parseJsonBody(req);
1935
2456
  const payload = body.payload ?? body;
1936
- const requestId = (0, import_uuid3.v4)();
2457
+ const requestId = (0, import_uuid4.v4)();
1937
2458
  const projectPath = String(body.projectPath ?? "unknown");
1938
2459
  const toolName = String(payload.tool_name ?? body.tool_name ?? "unknown");
1939
2460
  const toolInput = payload.tool_input ?? body.tool_input ?? {};
@@ -2065,8 +2586,8 @@ var ApprovalProxy = class _ApprovalProxy {
2065
2586
  };
2066
2587
 
2067
2588
  // src/mdns/MdnsService.ts
2068
- var import_node_child_process3 = require("child_process");
2069
- var import_node_os4 = require("os");
2589
+ var import_node_child_process5 = require("child_process");
2590
+ var import_node_os5 = require("os");
2070
2591
  function buildTxtArgs(txt) {
2071
2592
  return Object.entries(txt).map(([k, v]) => `${k}=${v}`);
2072
2593
  }
@@ -2084,7 +2605,7 @@ var MdnsService = class {
2084
2605
  this.httpPort = options.httpPort;
2085
2606
  this.version = options.version ?? "0.1.0";
2086
2607
  this.pairing = options.pairing ?? "closed";
2087
- this.useDnsSd = (0, import_node_os4.platform)() === "darwin";
2608
+ this.useDnsSd = (0, import_node_os5.platform)() === "darwin";
2088
2609
  }
2089
2610
  getTxt() {
2090
2611
  return {
@@ -2117,7 +2638,7 @@ var MdnsService = class {
2117
2638
  String(this.wsPort),
2118
2639
  ...buildTxtArgs(this.getTxt())
2119
2640
  ];
2120
- this.proc = (0, import_node_child_process3.spawn)("dns-sd", args, { stdio: "ignore" });
2641
+ this.proc = (0, import_node_child_process5.spawn)("dns-sd", args, { stdio: "ignore" });
2121
2642
  this.proc.on("error", (err) => {
2122
2643
  console.warn(`[MdnsService] dns-sd failed, falling back to bonjour-service: ${err.message}`);
2123
2644
  this.proc = null;
@@ -2138,7 +2659,7 @@ var MdnsService = class {
2138
2659
  return;
2139
2660
  }
2140
2661
  try {
2141
- const { default: Bonjour } = await import("bonjour-service");
2662
+ const { Bonjour } = await import("bonjour-service");
2142
2663
  const { networkInterfaces } = await import("os");
2143
2664
  const lanAddrs = getLanAddresses(networkInterfaces);
2144
2665
  const opts = lanAddrs.length > 0 ? { interface: lanAddrs[0] } : {};
@@ -2229,12 +2750,12 @@ function getLanAddresses(networkInterfacesFn) {
2229
2750
 
2230
2751
  // src/hooks/HookInstaller.ts
2231
2752
  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");
2753
+ var import_node_path4 = require("path");
2754
+ var import_node_os6 = require("os");
2755
+ var SESSIX_HOOKS_DIR = (0, import_node_path4.join)((0, import_node_os6.homedir)(), ".sessix", "hooks");
2756
+ var HOOK_SCRIPT_PATH = (0, import_node_path4.join)(SESSIX_HOOKS_DIR, "approval-hook.js");
2757
+ var PERMISSION_ACCEPT_PATH = (0, import_node_path4.join)(SESSIX_HOOKS_DIR, "permission-accept.js");
2758
+ var CLAUDE_SETTINGS_PATH = (0, import_node_path4.join)((0, import_node_os6.homedir)(), ".claude", "settings.json");
2238
2759
  var HOOK_COMMAND = "node ~/.sessix/hooks/approval-hook.js";
2239
2760
  var PERMISSION_ACCEPT_COMMAND = "node ~/.sessix/hooks/permission-accept.js";
2240
2761
  var LEGACY_HOOK_COMMANDS = [
@@ -2408,7 +2929,7 @@ var HookInstaller = class {
2408
2929
  * 写入 Claude Code settings.json
2409
2930
  */
2410
2931
  async writeClaudeSettings(settings) {
2411
- await (0, import_promises2.mkdir)((0, import_node_path3.join)((0, import_node_os5.homedir)(), ".claude"), { recursive: true });
2932
+ await (0, import_promises2.mkdir)((0, import_node_path4.join)((0, import_node_os6.homedir)(), ".claude"), { recursive: true });
2412
2933
  await (0, import_promises2.writeFile)(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n", "utf-8");
2413
2934
  }
2414
2935
  /**
@@ -2435,7 +2956,7 @@ var HookInstaller = class {
2435
2956
  };
2436
2957
 
2437
2958
  // src/notification/NotificationService.ts
2438
- var import_node_path4 = require("path");
2959
+ var import_node_path5 = require("path");
2439
2960
  var NotificationService = class {
2440
2961
  constructor(sessionManager, expoChannel = null) {
2441
2962
  this.sessionManager = sessionManager;
@@ -2531,7 +3052,7 @@ var NotificationService = class {
2531
3052
  const dangerLevel = this.getDangerLevel(request.toolName);
2532
3053
  const isDangerous = dangerLevel === "danger" || dangerLevel === "write";
2533
3054
  const categoryId = isDangerous ? "APPROVAL_DANGEROUS" : "APPROVAL_NORMAL";
2534
- const projectName = (0, import_node_path4.basename)(
3055
+ const projectName = (0, import_node_path5.basename)(
2535
3056
  this.sessionManager.getActiveSessions().find((s) => s.id === request.sessionId)?.projectPath ?? ""
2536
3057
  );
2537
3058
  const pushTitle = isDangerous ? `\u26A0\uFE0F ${title}` : title;
@@ -2587,7 +3108,7 @@ var NotificationService = class {
2587
3108
  /** 从审批请求中提取操作目标的简短描述 */
2588
3109
  extractTarget(request) {
2589
3110
  const input = request.toolInput;
2590
- if (input.file_path) return (0, import_node_path4.basename)(String(input.file_path));
3111
+ if (input.file_path) return (0, import_node_path5.basename)(String(input.file_path));
2591
3112
  if (input.command) return String(input.command).slice(0, 40);
2592
3113
  return request.description.slice(0, 40);
2593
3114
  }
@@ -2690,7 +3211,7 @@ var NotificationService = class {
2690
3211
  getSessionTitle(sessionId) {
2691
3212
  const session = this.sessionManager.getActiveSessions().find((s) => s.id === sessionId);
2692
3213
  if (!session) return "Unknown";
2693
- return session.summary ?? (0, import_node_path4.basename)(session.projectPath);
3214
+ return session.summary ?? (0, import_node_path5.basename)(session.projectPath);
2694
3215
  }
2695
3216
  /** 获取会话的 YOLO 模式状态 */
2696
3217
  getYoloMode(sessionId) {
@@ -2699,7 +3220,7 @@ var NotificationService = class {
2699
3220
  };
2700
3221
 
2701
3222
  // src/notification/DesktopNotificationChannel.ts
2702
- var import_node_child_process4 = require("child_process");
3223
+ var import_node_child_process6 = require("child_process");
2703
3224
  var DesktopNotificationChannel = class {
2704
3225
  isAvailable() {
2705
3226
  return process.platform === "darwin";
@@ -2711,7 +3232,7 @@ var DesktopNotificationChannel = class {
2711
3232
  const sound = payload.sound ?? "Ping";
2712
3233
  const script = `display notification "${body}" with title "${title}" sound name "${sound}"`;
2713
3234
  return new Promise((resolve) => {
2714
- (0, import_node_child_process4.execFile)("osascript", ["-e", script], (err) => {
3235
+ (0, import_node_child_process6.execFile)("osascript", ["-e", script], (err) => {
2715
3236
  if (err) {
2716
3237
  console.warn("[DesktopNotificationChannel] Send notification failed:", err.message);
2717
3238
  }
@@ -2985,7 +3506,7 @@ var ActivityPushChannel = class {
2985
3506
 
2986
3507
  // src/session/ProjectReader.ts
2987
3508
  var import_promises3 = require("fs/promises");
2988
- var import_readline2 = require("readline");
3509
+ var import_readline3 = require("readline");
2989
3510
  var import_path = require("path");
2990
3511
  var import_os = require("os");
2991
3512
  var CLAUDE_PROJECTS_DIR = (0, import_path.join)((0, import_os.homedir)(), ".claude", "projects");
@@ -3234,7 +3755,7 @@ async function extractFirstPrompt(filePath) {
3234
3755
  let fileHandle;
3235
3756
  try {
3236
3757
  fileHandle = await (0, import_promises3.open)(filePath, "r");
3237
- const rl = (0, import_readline2.createInterface)({
3758
+ const rl = (0, import_readline3.createInterface)({
3238
3759
  input: fileHandle.createReadStream({ encoding: "utf-8" }),
3239
3760
  crlfDelay: Infinity
3240
3761
  });
@@ -3382,14 +3903,14 @@ var PairingManager = class {
3382
3903
  };
3383
3904
 
3384
3905
  // src/auth/AuthManager.ts
3385
- var import_child_process2 = require("child_process");
3386
3906
  var import_child_process3 = require("child_process");
3907
+ var import_child_process4 = require("child_process");
3387
3908
  var import_util = require("util");
3388
- var import_events2 = require("events");
3389
- var execFileAsync = (0, import_util.promisify)(import_child_process3.execFile);
3909
+ var import_events3 = require("events");
3910
+ var execFileAsync = (0, import_util.promisify)(import_child_process4.execFile);
3390
3911
  var CLAUDE_PATH2 = findClaudePath();
3391
3912
  var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
3392
- var AuthManager = class extends import_events2.EventEmitter {
3913
+ var AuthManager = class extends import_events3.EventEmitter {
3393
3914
  loginProcess = null;
3394
3915
  loginTimeout = null;
3395
3916
  urlSent = false;
@@ -3417,7 +3938,7 @@ var AuthManager = class extends import_events2.EventEmitter {
3417
3938
  }
3418
3939
  this.clearLoginTimeout();
3419
3940
  this.urlSent = false;
3420
- const proc = (0, import_child_process2.spawn)(CLAUDE_PATH2, ["auth", "login"], {
3941
+ const proc = (0, import_child_process3.spawn)(CLAUDE_PATH2, ["auth", "login"], {
3421
3942
  env: { ...process.env, BROWSER: "echo" },
3422
3943
  stdio: ["pipe", "pipe", "pipe"]
3423
3944
  });
@@ -3503,8 +4024,8 @@ var AuthManager = class extends import_events2.EventEmitter {
3503
4024
  var import_promises5 = require("fs/promises");
3504
4025
 
3505
4026
  // src/terminal/TerminalExecutor.ts
3506
- var import_node_child_process5 = require("child_process");
3507
- var import_uuid4 = require("uuid");
4027
+ var import_node_child_process7 = require("child_process");
4028
+ var import_uuid5 = require("uuid");
3508
4029
  var EXEC_TIMEOUT_MS = 5 * 60 * 1e3;
3509
4030
  var TerminalExecutor = class {
3510
4031
  processes = /* @__PURE__ */ new Map();
@@ -3526,10 +4047,10 @@ var TerminalExecutor = class {
3526
4047
  }
3527
4048
  }
3528
4049
  exec(sessionId, command, cwd) {
3529
- const execId = (0, import_uuid4.v4)();
4050
+ const execId = (0, import_uuid5.v4)();
3530
4051
  const shell = isWindows ? "powershell" : "bash";
3531
4052
  const args = isWindows ? ["-Command", command] : ["-c", command];
3532
- const proc = (0, import_node_child_process5.spawn)(shell, args, {
4053
+ const proc = (0, import_node_child_process7.spawn)(shell, args, {
3533
4054
  cwd,
3534
4055
  stdio: ["ignore", "pipe", "pipe"],
3535
4056
  env: { ...process.env }
@@ -3592,7 +4113,7 @@ var TerminalExecutor = class {
3592
4113
  // src/server.ts
3593
4114
  var WS_PORT = 3745;
3594
4115
  var HTTP_PORT = 3746;
3595
- var execAsync = (0, import_node_util.promisify)(import_node_child_process6.exec);
4116
+ var execAsync = (0, import_node_util.promisify)(import_node_child_process8.exec);
3596
4117
  async function killPortProcess(port) {
3597
4118
  try {
3598
4119
  if (isWindows) {
@@ -3634,8 +4155,8 @@ async function createWithRetry(label, port, factory) {
3634
4155
  }
3635
4156
  }
3636
4157
  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");
4158
+ const configDir = (0, import_node_path6.join)((0, import_node_os7.homedir)(), ".sessix");
4159
+ const tokenFile = (0, import_node_path6.join)(configDir, "token");
3639
4160
  let token;
3640
4161
  if (opts.token !== void 0) {
3641
4162
  token = opts.token;
@@ -3647,14 +4168,14 @@ async function start(opts = {}) {
3647
4168
  try {
3648
4169
  token = (await (0, import_promises4.readFile)(tokenFile, "utf8")).trim();
3649
4170
  } catch {
3650
- token = (0, import_uuid5.v4)();
4171
+ token = (0, import_uuid6.v4)();
3651
4172
  await (0, import_promises4.mkdir)(configDir, { recursive: true });
3652
4173
  await (0, import_promises4.writeFile)(tokenFile, token, "utf8");
3653
4174
  }
3654
4175
  }
3655
4176
  }
3656
- const provider = new ProcessProvider();
3657
- const sessionManager = new SessionManager(provider);
4177
+ const providerFactory = new ProviderFactory();
4178
+ const sessionManager = new SessionManager(providerFactory);
3658
4179
  const terminalExecutor = new TerminalExecutor();
3659
4180
  const wsBridge = await createWithRetry(
3660
4181
  "WsBridge",
@@ -3694,7 +4215,7 @@ async function start(opts = {}) {
3694
4215
  let mdnsService = null;
3695
4216
  const pairingManager = new PairingManager({
3696
4217
  token,
3697
- serverName: (0, import_node_os6.hostname)(),
4218
+ serverName: (0, import_node_os7.hostname)(),
3698
4219
  version: "0.2.0",
3699
4220
  onStateChange: (state) => mdnsService?.updatePairingState(state)
3700
4221
  });
@@ -3751,7 +4272,8 @@ async function start(opts = {}) {
3751
4272
  event.model,
3752
4273
  event.permissionMode,
3753
4274
  event.effort,
3754
- event.images
4275
+ event.images,
4276
+ event.agentType
3755
4277
  );
3756
4278
  wsBridge.broadcast({
3757
4279
  type: "session_list",
@@ -3927,7 +4449,7 @@ async function start(opts = {}) {
3927
4449
  return null;
3928
4450
  }).filter(Boolean).join("\n");
3929
4451
  }
3930
- const suggestion = await provider.generateSuggestion(context);
4452
+ const suggestion = await providerFactory.getProvider("claude-code").generateSuggestion(context);
3931
4453
  wsBridge.send(ws, {
3932
4454
  type: "prompt_suggestion",
3933
4455
  sessionId: event.sessionId,
@@ -4005,6 +4527,11 @@ async function start(opts = {}) {
4005
4527
  }
4006
4528
  break;
4007
4529
  }
4530
+ case "list_agents": {
4531
+ const agents = await providerFactory.detectAgents();
4532
+ wsBridge.send(ws, { type: "agent_list", agents });
4533
+ break;
4534
+ }
4008
4535
  default: {
4009
4536
  wsBridge.send(ws, {
4010
4537
  type: "error",
@@ -4168,7 +4695,7 @@ async function start(opts = {}) {
4168
4695
  openPairing: (duration) => pairingManager.open(duration),
4169
4696
  closePairing: () => pairingManager.close(),
4170
4697
  regenerateToken: async () => {
4171
- const newToken = (0, import_uuid5.v4)();
4698
+ const newToken = (0, import_uuid6.v4)();
4172
4699
  await (0, import_promises4.mkdir)(configDir, { recursive: true });
4173
4700
  await (0, import_promises4.writeFile)(tokenFile, newToken, "utf8");
4174
4701
  instance.token = newToken;