sessix-server 0.2.8 → 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.
- package/dist/index.js +693 -105
- package/dist/server.js +660 -93
- package/package.json +10 -1
- package/dist/approval/ApprovalProxy.d.ts +0 -86
- package/dist/approval/ApprovalProxy.d.ts.map +0 -1
- package/dist/approval/ApprovalProxy.js +0 -363
- package/dist/approval/ApprovalProxy.js.map +0 -1
- package/dist/hooks/HookInstaller.d.ts +0 -55
- package/dist/hooks/HookInstaller.d.ts.map +0 -1
- package/dist/hooks/HookInstaller.js +0 -215
- package/dist/hooks/HookInstaller.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/mdns/MdnsService.d.ts +0 -36
- package/dist/mdns/MdnsService.d.ts.map +0 -1
- package/dist/mdns/MdnsService.js +0 -66
- package/dist/mdns/MdnsService.js.map +0 -1
- package/dist/notification/ActivityPushChannel.d.ts +0 -54
- package/dist/notification/ActivityPushChannel.d.ts.map +0 -1
- package/dist/notification/ActivityPushChannel.js +0 -235
- package/dist/notification/ActivityPushChannel.js.map +0 -1
- package/dist/notification/ExpoNotificationChannel.d.ts +0 -17
- package/dist/notification/ExpoNotificationChannel.d.ts.map +0 -1
- package/dist/notification/ExpoNotificationChannel.js +0 -57
- package/dist/notification/ExpoNotificationChannel.js.map +0 -1
- package/dist/notification/MacNotificationChannel.d.ts +0 -22
- package/dist/notification/MacNotificationChannel.d.ts.map +0 -1
- package/dist/notification/MacNotificationChannel.js +0 -33
- package/dist/notification/MacNotificationChannel.js.map +0 -1
- package/dist/notification/NotificationService.d.ts +0 -50
- package/dist/notification/NotificationService.d.ts.map +0 -1
- package/dist/notification/NotificationService.js +0 -177
- package/dist/notification/NotificationService.js.map +0 -1
- package/dist/providers/ExecutionProvider.d.ts +0 -60
- package/dist/providers/ExecutionProvider.d.ts.map +0 -1
- package/dist/providers/ExecutionProvider.js +0 -3
- package/dist/providers/ExecutionProvider.js.map +0 -1
- package/dist/providers/ProcessProvider.d.ts +0 -117
- package/dist/providers/ProcessProvider.d.ts.map +0 -1
- package/dist/providers/ProcessProvider.js +0 -507
- package/dist/providers/ProcessProvider.js.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/session/ProjectReader.d.ts +0 -44
- package/dist/session/ProjectReader.d.ts.map +0 -1
- package/dist/session/ProjectReader.js +0 -471
- package/dist/session/ProjectReader.js.map +0 -1
- package/dist/session/SessionFileWatcher.d.ts +0 -35
- package/dist/session/SessionFileWatcher.d.ts.map +0 -1
- package/dist/session/SessionFileWatcher.js +0 -207
- package/dist/session/SessionFileWatcher.js.map +0 -1
- package/dist/session/SessionManager.d.ts +0 -114
- package/dist/session/SessionManager.d.ts.map +0 -1
- package/dist/session/SessionManager.js +0 -356
- package/dist/session/SessionManager.js.map +0 -1
- package/dist/ws/WsBridge.d.ts +0 -55
- package/dist/ws/WsBridge.d.ts.map +0 -1
- package/dist/ws/WsBridge.js +0 -220
- package/dist/ws/WsBridge.js.map +0 -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
|
|
28
|
-
var
|
|
29
|
-
var
|
|
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
|
|
305
|
+
var import_uuid6 = require("uuid");
|
|
299
306
|
var import_promises4 = require("fs/promises");
|
|
300
|
-
var
|
|
301
|
-
var
|
|
302
|
-
var
|
|
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"
|
|
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/
|
|
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(
|
|
881
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1580
|
+
const provider = this.getProviderForSession(sessionId);
|
|
1581
|
+
const unsubscribeEvent = provider.onEvent(sessionId, (event) => {
|
|
1061
1582
|
this.handleClaudeEvent(sessionId, event);
|
|
1062
1583
|
});
|
|
1063
|
-
const unsubscribeQuestion =
|
|
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,
|
|
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
|
-
|
|
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
|
|
1678
|
-
var
|
|
1679
|
-
var
|
|
1680
|
-
var
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 ?
|
|
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 =
|
|
1815
|
-
if (!
|
|
1816
|
-
|
|
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(
|
|
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
|
-
|
|
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,
|
|
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
|
|
2063
|
-
var
|
|
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,
|
|
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,
|
|
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 {
|
|
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
|
|
2227
|
-
var
|
|
2228
|
-
var SESSIX_HOOKS_DIR = (0,
|
|
2229
|
-
var HOOK_SCRIPT_PATH = (0,
|
|
2230
|
-
var PERMISSION_ACCEPT_PATH = (0,
|
|
2231
|
-
var CLAUDE_SETTINGS_PATH = (0,
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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");
|
|
@@ -3031,16 +3553,23 @@ async function getHistoricalSessions(projectPath) {
|
|
|
3031
3553
|
const entries = await (0, import_promises3.readdir)(projectDir, { withFileTypes: true });
|
|
3032
3554
|
const jsonlFiles = entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl"));
|
|
3033
3555
|
const mtimeMap = /* @__PURE__ */ new Map();
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3556
|
+
await Promise.all(
|
|
3557
|
+
jsonlFiles.map(async (entry) => {
|
|
3558
|
+
const sessionId = entry.name.slice(0, -6);
|
|
3559
|
+
const filePath = (0, import_path.join)(projectDir, entry.name);
|
|
3560
|
+
try {
|
|
3561
|
+
const contentTs = await extractLastTimestamp(filePath);
|
|
3562
|
+
if (contentTs) {
|
|
3563
|
+
mtimeMap.set(sessionId, contentTs);
|
|
3564
|
+
} else {
|
|
3565
|
+
const fileStat = await (0, import_promises3.stat)(filePath);
|
|
3566
|
+
mtimeMap.set(sessionId, fileStat.mtimeMs);
|
|
3567
|
+
}
|
|
3568
|
+
} catch {
|
|
3569
|
+
mtimeMap.set(sessionId, 0);
|
|
3570
|
+
}
|
|
3571
|
+
})
|
|
3572
|
+
);
|
|
3044
3573
|
const uuidDirs = entries.filter(
|
|
3045
3574
|
(e) => e.isDirectory() && UUID_RE.test(e.name) && !mtimeMap.has(e.name)
|
|
3046
3575
|
);
|
|
@@ -3191,11 +3720,37 @@ async function getSessionHistory(projectPath, sessionId) {
|
|
|
3191
3720
|
};
|
|
3192
3721
|
}
|
|
3193
3722
|
}
|
|
3723
|
+
async function extractLastTimestamp(filePath) {
|
|
3724
|
+
let fileHandle;
|
|
3725
|
+
try {
|
|
3726
|
+
fileHandle = await (0, import_promises3.open)(filePath, "r");
|
|
3727
|
+
const fileStat = await fileHandle.stat();
|
|
3728
|
+
const readSize = Math.min(fileStat.size, 8192);
|
|
3729
|
+
const buffer = Buffer.alloc(readSize);
|
|
3730
|
+
await fileHandle.read(buffer, 0, readSize, fileStat.size - readSize);
|
|
3731
|
+
const tail = buffer.toString("utf-8");
|
|
3732
|
+
const lines = tail.split("\n").filter((l) => l.trim());
|
|
3733
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
3734
|
+
try {
|
|
3735
|
+
const obj = JSON.parse(lines[i]);
|
|
3736
|
+
if (obj.timestamp) {
|
|
3737
|
+
const ts = new Date(obj.timestamp).getTime();
|
|
3738
|
+
if (!isNaN(ts)) return ts;
|
|
3739
|
+
}
|
|
3740
|
+
} catch {
|
|
3741
|
+
}
|
|
3742
|
+
}
|
|
3743
|
+
} catch {
|
|
3744
|
+
} finally {
|
|
3745
|
+
await fileHandle?.close();
|
|
3746
|
+
}
|
|
3747
|
+
return void 0;
|
|
3748
|
+
}
|
|
3194
3749
|
async function extractFirstPrompt(filePath) {
|
|
3195
3750
|
let fileHandle;
|
|
3196
3751
|
try {
|
|
3197
3752
|
fileHandle = await (0, import_promises3.open)(filePath, "r");
|
|
3198
|
-
const rl = (0,
|
|
3753
|
+
const rl = (0, import_readline3.createInterface)({
|
|
3199
3754
|
input: fileHandle.createReadStream({ encoding: "utf-8" }),
|
|
3200
3755
|
crlfDelay: Infinity
|
|
3201
3756
|
});
|
|
@@ -3259,17 +3814,24 @@ async function countJsonlFilesWithMtime(dirPath) {
|
|
|
3259
3814
|
(e) => e.isDirectory() && UUID_RE.test(e.name) && !jsonlNames.has(e.name)
|
|
3260
3815
|
);
|
|
3261
3816
|
let latestMtime = 0;
|
|
3262
|
-
const
|
|
3263
|
-
|
|
3264
|
-
...
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
}
|
|
3272
|
-
|
|
3817
|
+
const jsonlEntries = entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl"));
|
|
3818
|
+
await Promise.all([
|
|
3819
|
+
...jsonlEntries.map(async (entry) => {
|
|
3820
|
+
try {
|
|
3821
|
+
const contentTs = await extractLastTimestamp((0, import_path.join)(dirPath, entry.name));
|
|
3822
|
+
const ts = contentTs ?? (await (0, import_promises3.stat)((0, import_path.join)(dirPath, entry.name))).mtimeMs;
|
|
3823
|
+
if (ts > latestMtime) latestMtime = ts;
|
|
3824
|
+
} catch {
|
|
3825
|
+
}
|
|
3826
|
+
}),
|
|
3827
|
+
...uuidDirs.map(async (entry) => {
|
|
3828
|
+
try {
|
|
3829
|
+
const fileStat = await (0, import_promises3.stat)((0, import_path.join)(dirPath, entry.name));
|
|
3830
|
+
if (fileStat.mtimeMs > latestMtime) latestMtime = fileStat.mtimeMs;
|
|
3831
|
+
} catch {
|
|
3832
|
+
}
|
|
3833
|
+
})
|
|
3834
|
+
]);
|
|
3273
3835
|
return { count: jsonlNames.size + uuidDirs.length, latestMtime };
|
|
3274
3836
|
} catch {
|
|
3275
3837
|
return { count: 0, latestMtime: 0 };
|
|
@@ -3336,14 +3898,14 @@ var PairingManager = class {
|
|
|
3336
3898
|
};
|
|
3337
3899
|
|
|
3338
3900
|
// src/auth/AuthManager.ts
|
|
3339
|
-
var import_child_process2 = require("child_process");
|
|
3340
3901
|
var import_child_process3 = require("child_process");
|
|
3902
|
+
var import_child_process4 = require("child_process");
|
|
3341
3903
|
var import_util = require("util");
|
|
3342
|
-
var
|
|
3343
|
-
var execFileAsync = (0, import_util.promisify)(
|
|
3904
|
+
var import_events3 = require("events");
|
|
3905
|
+
var execFileAsync = (0, import_util.promisify)(import_child_process4.execFile);
|
|
3344
3906
|
var CLAUDE_PATH2 = findClaudePath();
|
|
3345
3907
|
var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
3346
|
-
var AuthManager = class extends
|
|
3908
|
+
var AuthManager = class extends import_events3.EventEmitter {
|
|
3347
3909
|
loginProcess = null;
|
|
3348
3910
|
loginTimeout = null;
|
|
3349
3911
|
urlSent = false;
|
|
@@ -3371,7 +3933,7 @@ var AuthManager = class extends import_events2.EventEmitter {
|
|
|
3371
3933
|
}
|
|
3372
3934
|
this.clearLoginTimeout();
|
|
3373
3935
|
this.urlSent = false;
|
|
3374
|
-
const proc = (0,
|
|
3936
|
+
const proc = (0, import_child_process3.spawn)(CLAUDE_PATH2, ["auth", "login"], {
|
|
3375
3937
|
env: { ...process.env, BROWSER: "echo" },
|
|
3376
3938
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3377
3939
|
});
|
|
@@ -3457,8 +4019,8 @@ var AuthManager = class extends import_events2.EventEmitter {
|
|
|
3457
4019
|
var import_promises5 = require("fs/promises");
|
|
3458
4020
|
|
|
3459
4021
|
// src/terminal/TerminalExecutor.ts
|
|
3460
|
-
var
|
|
3461
|
-
var
|
|
4022
|
+
var import_node_child_process7 = require("child_process");
|
|
4023
|
+
var import_uuid5 = require("uuid");
|
|
3462
4024
|
var EXEC_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
3463
4025
|
var TerminalExecutor = class {
|
|
3464
4026
|
processes = /* @__PURE__ */ new Map();
|
|
@@ -3480,10 +4042,10 @@ var TerminalExecutor = class {
|
|
|
3480
4042
|
}
|
|
3481
4043
|
}
|
|
3482
4044
|
exec(sessionId, command, cwd) {
|
|
3483
|
-
const execId = (0,
|
|
4045
|
+
const execId = (0, import_uuid5.v4)();
|
|
3484
4046
|
const shell = isWindows ? "powershell" : "bash";
|
|
3485
4047
|
const args = isWindows ? ["-Command", command] : ["-c", command];
|
|
3486
|
-
const proc = (0,
|
|
4048
|
+
const proc = (0, import_node_child_process7.spawn)(shell, args, {
|
|
3487
4049
|
cwd,
|
|
3488
4050
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3489
4051
|
env: { ...process.env }
|
|
@@ -3546,7 +4108,7 @@ var TerminalExecutor = class {
|
|
|
3546
4108
|
// src/server.ts
|
|
3547
4109
|
var WS_PORT = 3745;
|
|
3548
4110
|
var HTTP_PORT = 3746;
|
|
3549
|
-
var execAsync = (0, import_node_util.promisify)(
|
|
4111
|
+
var execAsync = (0, import_node_util.promisify)(import_node_child_process8.exec);
|
|
3550
4112
|
async function killPortProcess(port) {
|
|
3551
4113
|
try {
|
|
3552
4114
|
if (isWindows) {
|
|
@@ -3588,8 +4150,8 @@ async function createWithRetry(label, port, factory) {
|
|
|
3588
4150
|
}
|
|
3589
4151
|
}
|
|
3590
4152
|
async function start(opts = {}) {
|
|
3591
|
-
const configDir = (0,
|
|
3592
|
-
const tokenFile = (0,
|
|
4153
|
+
const configDir = (0, import_node_path6.join)((0, import_node_os7.homedir)(), ".sessix");
|
|
4154
|
+
const tokenFile = (0, import_node_path6.join)(configDir, "token");
|
|
3593
4155
|
let token;
|
|
3594
4156
|
if (opts.token !== void 0) {
|
|
3595
4157
|
token = opts.token;
|
|
@@ -3601,14 +4163,14 @@ async function start(opts = {}) {
|
|
|
3601
4163
|
try {
|
|
3602
4164
|
token = (await (0, import_promises4.readFile)(tokenFile, "utf8")).trim();
|
|
3603
4165
|
} catch {
|
|
3604
|
-
token = (0,
|
|
4166
|
+
token = (0, import_uuid6.v4)();
|
|
3605
4167
|
await (0, import_promises4.mkdir)(configDir, { recursive: true });
|
|
3606
4168
|
await (0, import_promises4.writeFile)(tokenFile, token, "utf8");
|
|
3607
4169
|
}
|
|
3608
4170
|
}
|
|
3609
4171
|
}
|
|
3610
|
-
const
|
|
3611
|
-
const sessionManager = new SessionManager(
|
|
4172
|
+
const providerFactory = new ProviderFactory();
|
|
4173
|
+
const sessionManager = new SessionManager(providerFactory);
|
|
3612
4174
|
const terminalExecutor = new TerminalExecutor();
|
|
3613
4175
|
const wsBridge = await createWithRetry(
|
|
3614
4176
|
"WsBridge",
|
|
@@ -3648,7 +4210,7 @@ async function start(opts = {}) {
|
|
|
3648
4210
|
let mdnsService = null;
|
|
3649
4211
|
const pairingManager = new PairingManager({
|
|
3650
4212
|
token,
|
|
3651
|
-
serverName: (0,
|
|
4213
|
+
serverName: (0, import_node_os7.hostname)(),
|
|
3652
4214
|
version: "0.2.0",
|
|
3653
4215
|
onStateChange: (state) => mdnsService?.updatePairingState(state)
|
|
3654
4216
|
});
|
|
@@ -3705,7 +4267,8 @@ async function start(opts = {}) {
|
|
|
3705
4267
|
event.model,
|
|
3706
4268
|
event.permissionMode,
|
|
3707
4269
|
event.effort,
|
|
3708
|
-
event.images
|
|
4270
|
+
event.images,
|
|
4271
|
+
event.agentType
|
|
3709
4272
|
);
|
|
3710
4273
|
wsBridge.broadcast({
|
|
3711
4274
|
type: "session_list",
|
|
@@ -3881,7 +4444,7 @@ async function start(opts = {}) {
|
|
|
3881
4444
|
return null;
|
|
3882
4445
|
}).filter(Boolean).join("\n");
|
|
3883
4446
|
}
|
|
3884
|
-
const suggestion = await
|
|
4447
|
+
const suggestion = await providerFactory.getProvider("claude-code").generateSuggestion(context);
|
|
3885
4448
|
wsBridge.send(ws, {
|
|
3886
4449
|
type: "prompt_suggestion",
|
|
3887
4450
|
sessionId: event.sessionId,
|
|
@@ -3903,9 +4466,9 @@ async function start(opts = {}) {
|
|
|
3903
4466
|
}
|
|
3904
4467
|
case "terminal_exec": {
|
|
3905
4468
|
const activeSession = sessionManager.getActiveSessions().find((s) => s.id === event.sessionId);
|
|
3906
|
-
const cwd = activeSession?.projectPath ?? sessionManager.getSessionProjectPath(event.sessionId);
|
|
4469
|
+
const cwd = activeSession?.projectPath ?? sessionManager.getSessionProjectPath(event.sessionId) ?? event.projectPath;
|
|
3907
4470
|
if (!cwd) {
|
|
3908
|
-
wsBridge.send(ws, { type: "error", code: "TERMINAL_EXEC_ERROR", message:
|
|
4471
|
+
wsBridge.send(ws, { type: "error", code: "TERMINAL_EXEC_ERROR", message: `Session not found (id: ${event.sessionId.slice(0, 8)}\u2026)`, sessionId: event.sessionId });
|
|
3909
4472
|
break;
|
|
3910
4473
|
}
|
|
3911
4474
|
terminalExecutor.exec(event.sessionId, event.command, cwd);
|
|
@@ -3959,6 +4522,11 @@ async function start(opts = {}) {
|
|
|
3959
4522
|
}
|
|
3960
4523
|
break;
|
|
3961
4524
|
}
|
|
4525
|
+
case "list_agents": {
|
|
4526
|
+
const agents = await providerFactory.detectAgents();
|
|
4527
|
+
wsBridge.send(ws, { type: "agent_list", agents });
|
|
4528
|
+
break;
|
|
4529
|
+
}
|
|
3962
4530
|
default: {
|
|
3963
4531
|
wsBridge.send(ws, {
|
|
3964
4532
|
type: "error",
|
|
@@ -4122,7 +4690,7 @@ async function start(opts = {}) {
|
|
|
4122
4690
|
openPairing: (duration) => pairingManager.open(duration),
|
|
4123
4691
|
closePairing: () => pairingManager.close(),
|
|
4124
4692
|
regenerateToken: async () => {
|
|
4125
|
-
const newToken = (0,
|
|
4693
|
+
const newToken = (0, import_uuid6.v4)();
|
|
4126
4694
|
await (0, import_promises4.mkdir)(configDir, { recursive: true });
|
|
4127
4695
|
await (0, import_promises4.writeFile)(tokenFile, newToken, "utf8");
|
|
4128
4696
|
instance.token = newToken;
|
|
@@ -4141,7 +4709,7 @@ async function start(opts = {}) {
|
|
|
4141
4709
|
var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
|
|
4142
4710
|
function getPackageVersion() {
|
|
4143
4711
|
try {
|
|
4144
|
-
const pkg = JSON.parse((0,
|
|
4712
|
+
const pkg = JSON.parse((0, import_node_fs4.readFileSync)((0, import_node_path7.join)(__dirname, "..", "package.json"), "utf8"));
|
|
4145
4713
|
return pkg.version ?? "0.0.0";
|
|
4146
4714
|
} catch {
|
|
4147
4715
|
return "0.0.0";
|
|
@@ -4163,11 +4731,38 @@ async function fetchLatestVersion() {
|
|
|
4163
4731
|
return null;
|
|
4164
4732
|
}
|
|
4165
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
|
+
}
|
|
4166
4760
|
async function main() {
|
|
4167
4761
|
console.log("=".repeat(50));
|
|
4168
4762
|
console.log(`${t("startup.banner")} v${PKG_VERSION}`);
|
|
4169
4763
|
console.log("=".repeat(50));
|
|
4170
4764
|
console.log();
|
|
4765
|
+
await autoUpdateIfNeeded();
|
|
4171
4766
|
const enableAutoConnect = process.env.SESSIX_AUTO_CONNECT !== "false";
|
|
4172
4767
|
const server = await start({ enableAutoConnect });
|
|
4173
4768
|
const localIp = getLocalIp();
|
|
@@ -4208,13 +4803,6 @@ async function main() {
|
|
|
4208
4803
|
console.log(t("startup.pairingOpen"));
|
|
4209
4804
|
console.log(t("startup.pressT"));
|
|
4210
4805
|
console.log();
|
|
4211
|
-
fetchLatestVersion().then((latest) => {
|
|
4212
|
-
if (!latest || latest === PKG_VERSION) return;
|
|
4213
|
-
console.log();
|
|
4214
|
-
console.log(` \u{1F4E6} ${t("startup.updateAvailable", { current: PKG_VERSION, latest })}`);
|
|
4215
|
-
console.log(` npx sessix-server@latest`);
|
|
4216
|
-
console.log();
|
|
4217
|
-
});
|
|
4218
4806
|
const shutdown = async (signal) => {
|
|
4219
4807
|
console.log(`
|
|
4220
4808
|
[Main] ${t("startup.receivedSignal", { signal })}`);
|
|
@@ -4265,7 +4853,7 @@ ${t("startup.pairingReopened")}`);
|
|
|
4265
4853
|
}
|
|
4266
4854
|
}
|
|
4267
4855
|
function getLocalIp() {
|
|
4268
|
-
const interfaces = (0,
|
|
4856
|
+
const interfaces = (0, import_node_os8.networkInterfaces)();
|
|
4269
4857
|
for (const iface of Object.values(interfaces)) {
|
|
4270
4858
|
for (const addr of iface ?? []) {
|
|
4271
4859
|
if (addr.family === "IPv4" && !addr.internal) {
|