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.
- package/dist/index.js +630 -82
- package/dist/server.js +597 -70
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -24,9 +24,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// src/index.ts
|
|
27
|
-
var
|
|
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");
|
|
@@ -3228,7 +3750,7 @@ async function extractFirstPrompt(filePath) {
|
|
|
3228
3750
|
let fileHandle;
|
|
3229
3751
|
try {
|
|
3230
3752
|
fileHandle = await (0, import_promises3.open)(filePath, "r");
|
|
3231
|
-
const rl = (0,
|
|
3753
|
+
const rl = (0, import_readline3.createInterface)({
|
|
3232
3754
|
input: fileHandle.createReadStream({ encoding: "utf-8" }),
|
|
3233
3755
|
crlfDelay: Infinity
|
|
3234
3756
|
});
|
|
@@ -3376,14 +3898,14 @@ var PairingManager = class {
|
|
|
3376
3898
|
};
|
|
3377
3899
|
|
|
3378
3900
|
// src/auth/AuthManager.ts
|
|
3379
|
-
var import_child_process2 = require("child_process");
|
|
3380
3901
|
var import_child_process3 = require("child_process");
|
|
3902
|
+
var import_child_process4 = require("child_process");
|
|
3381
3903
|
var import_util = require("util");
|
|
3382
|
-
var
|
|
3383
|
-
var execFileAsync = (0, import_util.promisify)(
|
|
3904
|
+
var import_events3 = require("events");
|
|
3905
|
+
var execFileAsync = (0, import_util.promisify)(import_child_process4.execFile);
|
|
3384
3906
|
var CLAUDE_PATH2 = findClaudePath();
|
|
3385
3907
|
var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
3386
|
-
var AuthManager = class extends
|
|
3908
|
+
var AuthManager = class extends import_events3.EventEmitter {
|
|
3387
3909
|
loginProcess = null;
|
|
3388
3910
|
loginTimeout = null;
|
|
3389
3911
|
urlSent = false;
|
|
@@ -3411,7 +3933,7 @@ var AuthManager = class extends import_events2.EventEmitter {
|
|
|
3411
3933
|
}
|
|
3412
3934
|
this.clearLoginTimeout();
|
|
3413
3935
|
this.urlSent = false;
|
|
3414
|
-
const proc = (0,
|
|
3936
|
+
const proc = (0, import_child_process3.spawn)(CLAUDE_PATH2, ["auth", "login"], {
|
|
3415
3937
|
env: { ...process.env, BROWSER: "echo" },
|
|
3416
3938
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3417
3939
|
});
|
|
@@ -3497,8 +4019,8 @@ var AuthManager = class extends import_events2.EventEmitter {
|
|
|
3497
4019
|
var import_promises5 = require("fs/promises");
|
|
3498
4020
|
|
|
3499
4021
|
// src/terminal/TerminalExecutor.ts
|
|
3500
|
-
var
|
|
3501
|
-
var
|
|
4022
|
+
var import_node_child_process7 = require("child_process");
|
|
4023
|
+
var import_uuid5 = require("uuid");
|
|
3502
4024
|
var EXEC_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
3503
4025
|
var TerminalExecutor = class {
|
|
3504
4026
|
processes = /* @__PURE__ */ new Map();
|
|
@@ -3520,10 +4042,10 @@ var TerminalExecutor = class {
|
|
|
3520
4042
|
}
|
|
3521
4043
|
}
|
|
3522
4044
|
exec(sessionId, command, cwd) {
|
|
3523
|
-
const execId = (0,
|
|
4045
|
+
const execId = (0, import_uuid5.v4)();
|
|
3524
4046
|
const shell = isWindows ? "powershell" : "bash";
|
|
3525
4047
|
const args = isWindows ? ["-Command", command] : ["-c", command];
|
|
3526
|
-
const proc = (0,
|
|
4048
|
+
const proc = (0, import_node_child_process7.spawn)(shell, args, {
|
|
3527
4049
|
cwd,
|
|
3528
4050
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3529
4051
|
env: { ...process.env }
|
|
@@ -3586,7 +4108,7 @@ var TerminalExecutor = class {
|
|
|
3586
4108
|
// src/server.ts
|
|
3587
4109
|
var WS_PORT = 3745;
|
|
3588
4110
|
var HTTP_PORT = 3746;
|
|
3589
|
-
var execAsync = (0, import_node_util.promisify)(
|
|
4111
|
+
var execAsync = (0, import_node_util.promisify)(import_node_child_process8.exec);
|
|
3590
4112
|
async function killPortProcess(port) {
|
|
3591
4113
|
try {
|
|
3592
4114
|
if (isWindows) {
|
|
@@ -3628,8 +4150,8 @@ async function createWithRetry(label, port, factory) {
|
|
|
3628
4150
|
}
|
|
3629
4151
|
}
|
|
3630
4152
|
async function start(opts = {}) {
|
|
3631
|
-
const configDir = (0,
|
|
3632
|
-
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");
|
|
3633
4155
|
let token;
|
|
3634
4156
|
if (opts.token !== void 0) {
|
|
3635
4157
|
token = opts.token;
|
|
@@ -3641,14 +4163,14 @@ async function start(opts = {}) {
|
|
|
3641
4163
|
try {
|
|
3642
4164
|
token = (await (0, import_promises4.readFile)(tokenFile, "utf8")).trim();
|
|
3643
4165
|
} catch {
|
|
3644
|
-
token = (0,
|
|
4166
|
+
token = (0, import_uuid6.v4)();
|
|
3645
4167
|
await (0, import_promises4.mkdir)(configDir, { recursive: true });
|
|
3646
4168
|
await (0, import_promises4.writeFile)(tokenFile, token, "utf8");
|
|
3647
4169
|
}
|
|
3648
4170
|
}
|
|
3649
4171
|
}
|
|
3650
|
-
const
|
|
3651
|
-
const sessionManager = new SessionManager(
|
|
4172
|
+
const providerFactory = new ProviderFactory();
|
|
4173
|
+
const sessionManager = new SessionManager(providerFactory);
|
|
3652
4174
|
const terminalExecutor = new TerminalExecutor();
|
|
3653
4175
|
const wsBridge = await createWithRetry(
|
|
3654
4176
|
"WsBridge",
|
|
@@ -3688,7 +4210,7 @@ async function start(opts = {}) {
|
|
|
3688
4210
|
let mdnsService = null;
|
|
3689
4211
|
const pairingManager = new PairingManager({
|
|
3690
4212
|
token,
|
|
3691
|
-
serverName: (0,
|
|
4213
|
+
serverName: (0, import_node_os7.hostname)(),
|
|
3692
4214
|
version: "0.2.0",
|
|
3693
4215
|
onStateChange: (state) => mdnsService?.updatePairingState(state)
|
|
3694
4216
|
});
|
|
@@ -3745,7 +4267,8 @@ async function start(opts = {}) {
|
|
|
3745
4267
|
event.model,
|
|
3746
4268
|
event.permissionMode,
|
|
3747
4269
|
event.effort,
|
|
3748
|
-
event.images
|
|
4270
|
+
event.images,
|
|
4271
|
+
event.agentType
|
|
3749
4272
|
);
|
|
3750
4273
|
wsBridge.broadcast({
|
|
3751
4274
|
type: "session_list",
|
|
@@ -3921,7 +4444,7 @@ async function start(opts = {}) {
|
|
|
3921
4444
|
return null;
|
|
3922
4445
|
}).filter(Boolean).join("\n");
|
|
3923
4446
|
}
|
|
3924
|
-
const suggestion = await
|
|
4447
|
+
const suggestion = await providerFactory.getProvider("claude-code").generateSuggestion(context);
|
|
3925
4448
|
wsBridge.send(ws, {
|
|
3926
4449
|
type: "prompt_suggestion",
|
|
3927
4450
|
sessionId: event.sessionId,
|
|
@@ -3999,6 +4522,11 @@ async function start(opts = {}) {
|
|
|
3999
4522
|
}
|
|
4000
4523
|
break;
|
|
4001
4524
|
}
|
|
4525
|
+
case "list_agents": {
|
|
4526
|
+
const agents = await providerFactory.detectAgents();
|
|
4527
|
+
wsBridge.send(ws, { type: "agent_list", agents });
|
|
4528
|
+
break;
|
|
4529
|
+
}
|
|
4002
4530
|
default: {
|
|
4003
4531
|
wsBridge.send(ws, {
|
|
4004
4532
|
type: "error",
|
|
@@ -4162,7 +4690,7 @@ async function start(opts = {}) {
|
|
|
4162
4690
|
openPairing: (duration) => pairingManager.open(duration),
|
|
4163
4691
|
closePairing: () => pairingManager.close(),
|
|
4164
4692
|
regenerateToken: async () => {
|
|
4165
|
-
const newToken = (0,
|
|
4693
|
+
const newToken = (0, import_uuid6.v4)();
|
|
4166
4694
|
await (0, import_promises4.mkdir)(configDir, { recursive: true });
|
|
4167
4695
|
await (0, import_promises4.writeFile)(tokenFile, newToken, "utf8");
|
|
4168
4696
|
instance.token = newToken;
|
|
@@ -4181,7 +4709,7 @@ async function start(opts = {}) {
|
|
|
4181
4709
|
var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
|
|
4182
4710
|
function getPackageVersion() {
|
|
4183
4711
|
try {
|
|
4184
|
-
const pkg = JSON.parse((0,
|
|
4712
|
+
const pkg = JSON.parse((0, import_node_fs4.readFileSync)((0, import_node_path7.join)(__dirname, "..", "package.json"), "utf8"));
|
|
4185
4713
|
return pkg.version ?? "0.0.0";
|
|
4186
4714
|
} catch {
|
|
4187
4715
|
return "0.0.0";
|
|
@@ -4203,11 +4731,38 @@ async function fetchLatestVersion() {
|
|
|
4203
4731
|
return null;
|
|
4204
4732
|
}
|
|
4205
4733
|
}
|
|
4734
|
+
function isNewer(latest, current) {
|
|
4735
|
+
const a = latest.split(".").map(Number);
|
|
4736
|
+
const b = current.split(".").map(Number);
|
|
4737
|
+
for (let i = 0; i < 3; i++) {
|
|
4738
|
+
if ((a[i] ?? 0) > (b[i] ?? 0)) return true;
|
|
4739
|
+
if ((a[i] ?? 0) < (b[i] ?? 0)) return false;
|
|
4740
|
+
}
|
|
4741
|
+
return false;
|
|
4742
|
+
}
|
|
4743
|
+
async function autoUpdateIfNeeded() {
|
|
4744
|
+
if (process.env.SESSIX_NO_UPDATE_CHECK === "1" || process.env.__SESSIX_UPDATED === "1") return;
|
|
4745
|
+
const latest = await fetchLatestVersion();
|
|
4746
|
+
if (!latest || !isNewer(latest, PKG_VERSION)) return;
|
|
4747
|
+
console.log(` \u{1F4E6} ${t("startup.autoUpdating", { current: PKG_VERSION, latest })}`);
|
|
4748
|
+
console.log();
|
|
4749
|
+
try {
|
|
4750
|
+
(0, import_node_child_process9.execFileSync)("npx", [`sessix-server@${latest}`], {
|
|
4751
|
+
stdio: "inherit",
|
|
4752
|
+
env: { ...process.env, __SESSIX_UPDATED: "1" }
|
|
4753
|
+
});
|
|
4754
|
+
process.exit(0);
|
|
4755
|
+
} catch {
|
|
4756
|
+
console.log(` \u26A0\uFE0F ${t("startup.autoUpdateFailed")}`);
|
|
4757
|
+
console.log();
|
|
4758
|
+
}
|
|
4759
|
+
}
|
|
4206
4760
|
async function main() {
|
|
4207
4761
|
console.log("=".repeat(50));
|
|
4208
4762
|
console.log(`${t("startup.banner")} v${PKG_VERSION}`);
|
|
4209
4763
|
console.log("=".repeat(50));
|
|
4210
4764
|
console.log();
|
|
4765
|
+
await autoUpdateIfNeeded();
|
|
4211
4766
|
const enableAutoConnect = process.env.SESSIX_AUTO_CONNECT !== "false";
|
|
4212
4767
|
const server = await start({ enableAutoConnect });
|
|
4213
4768
|
const localIp = getLocalIp();
|
|
@@ -4248,13 +4803,6 @@ async function main() {
|
|
|
4248
4803
|
console.log(t("startup.pairingOpen"));
|
|
4249
4804
|
console.log(t("startup.pressT"));
|
|
4250
4805
|
console.log();
|
|
4251
|
-
fetchLatestVersion().then((latest) => {
|
|
4252
|
-
if (!latest || latest === PKG_VERSION) return;
|
|
4253
|
-
console.log();
|
|
4254
|
-
console.log(` \u{1F4E6} ${t("startup.updateAvailable", { current: PKG_VERSION, latest })}`);
|
|
4255
|
-
console.log(` npx sessix-server@latest`);
|
|
4256
|
-
console.log();
|
|
4257
|
-
});
|
|
4258
4806
|
const shutdown = async (signal) => {
|
|
4259
4807
|
console.log(`
|
|
4260
4808
|
[Main] ${t("startup.receivedSignal", { signal })}`);
|
|
@@ -4305,7 +4853,7 @@ ${t("startup.pairingReopened")}`);
|
|
|
4305
4853
|
}
|
|
4306
4854
|
}
|
|
4307
4855
|
function getLocalIp() {
|
|
4308
|
-
const interfaces = (0,
|
|
4856
|
+
const interfaces = (0, import_node_os8.networkInterfaces)();
|
|
4309
4857
|
for (const iface of Object.values(interfaces)) {
|
|
4310
4858
|
for (const addr of iface ?? []) {
|
|
4311
4859
|
if (addr.family === "IPv4" && !addr.internal) {
|