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/server.js
CHANGED
|
@@ -57,6 +57,9 @@ var zh = {
|
|
|
57
57
|
tokenRegenerated: "\u{1F511} Token \u5DF2\u91CD\u7F6E\uFF0C\u6240\u6709\u5BA2\u6237\u7AEF\u5DF2\u65AD\u5F00\uFF0C\u8BF7\u91CD\u65B0\u626B\u7801\u914D\u5BF9",
|
|
58
58
|
tokenRegenerateFailed: "Token \u91CD\u7F6E\u5931\u8D25:",
|
|
59
59
|
updateAvailable: "\u53D1\u73B0\u65B0\u7248\u672C v{{latest}}\uFF08\u5F53\u524D v{{current}}\uFF09\uFF0C\u8FD0\u884C\u4EE5\u4E0B\u547D\u4EE4\u66F4\u65B0\uFF1A",
|
|
60
|
+
autoUpdating: "\u53D1\u73B0\u65B0\u7248\u672C v{{latest}}\uFF08\u5F53\u524D v{{current}}\uFF09\uFF0C\u6B63\u5728\u81EA\u52A8\u66F4\u65B0...",
|
|
61
|
+
autoUpdateFailed: "\u81EA\u52A8\u66F4\u65B0\u5931\u8D25\uFF0C\u7EE7\u7EED\u4F7F\u7528\u5F53\u524D\u7248\u672C",
|
|
62
|
+
skipUpdateCheck: "\u8DF3\u8FC7\u7248\u672C\u68C0\u67E5\uFF08SESSIX_NO_UPDATE_CHECK=1\uFF09",
|
|
60
63
|
receivedSignal: "\u6536\u5230 {{signal}}\uFF0C\u6B63\u5728\u4F18\u96C5\u5173\u95ED...",
|
|
61
64
|
goodbye: "\u6240\u6709\u670D\u52A1\u5DF2\u5173\u95ED\uFF0C\u518D\u89C1\uFF01",
|
|
62
65
|
shutdownError: "\u5173\u95ED\u8FC7\u7A0B\u51FA\u9519:",
|
|
@@ -167,6 +170,9 @@ var en = {
|
|
|
167
170
|
tokenRegenerated: "\u{1F511} Token regenerated, all clients disconnected. Scan QR to re-pair",
|
|
168
171
|
tokenRegenerateFailed: "Token regeneration failed:",
|
|
169
172
|
updateAvailable: "New version v{{latest}} available (current v{{current}}). Update with:",
|
|
173
|
+
autoUpdating: "New version v{{latest}} available (current v{{current}}), auto-updating...",
|
|
174
|
+
autoUpdateFailed: "Auto-update failed, continuing with current version",
|
|
175
|
+
skipUpdateCheck: "Skipping update check (SESSIX_NO_UPDATE_CHECK=1)",
|
|
170
176
|
receivedSignal: "Received {{signal}}, graceful shutdown...",
|
|
171
177
|
goodbye: "All services closed, goodbye!",
|
|
172
178
|
shutdownError: "Shutdown error:",
|
|
@@ -301,11 +307,11 @@ function t(key, params) {
|
|
|
301
307
|
}
|
|
302
308
|
|
|
303
309
|
// src/server.ts
|
|
304
|
-
var
|
|
310
|
+
var import_uuid6 = require("uuid");
|
|
305
311
|
var import_promises4 = require("fs/promises");
|
|
306
|
-
var
|
|
307
|
-
var
|
|
308
|
-
var
|
|
312
|
+
var import_node_os7 = require("os");
|
|
313
|
+
var import_node_path6 = require("path");
|
|
314
|
+
var import_node_child_process8 = require("child_process");
|
|
309
315
|
var import_node_util = require("util");
|
|
310
316
|
|
|
311
317
|
// src/providers/ProcessProvider.ts
|
|
@@ -650,7 +656,9 @@ var ProcessProvider = class {
|
|
|
650
656
|
const event = result.value;
|
|
651
657
|
if (event.type === "assistant") {
|
|
652
658
|
for (const block of event.message.content) {
|
|
653
|
-
if (block.type === "tool_use"
|
|
659
|
+
if (block.type === "tool_use") {
|
|
660
|
+
const isQuestion = block.name === "AskUserQuestion" || block.name === "AskFollowupQuestion";
|
|
661
|
+
if (!isQuestion) continue;
|
|
654
662
|
const input = block.input;
|
|
655
663
|
const question = input.question ?? "";
|
|
656
664
|
if (!question) continue;
|
|
@@ -662,6 +670,7 @@ var ProcessProvider = class {
|
|
|
662
670
|
}
|
|
663
671
|
if (sessionSet.has(prevKey)) continue;
|
|
664
672
|
sessionSet.add(prevKey);
|
|
673
|
+
console.log(`[ProcessProvider] Session ${sessionId}: detected ${block.name} (toolUseId=${block.id})`);
|
|
665
674
|
this.emitter.emit(this.getQuestionEventName(sessionId), {
|
|
666
675
|
toolUseId: block.id,
|
|
667
676
|
question,
|
|
@@ -853,11 +862,500 @@ ${context}`;
|
|
|
853
862
|
}
|
|
854
863
|
};
|
|
855
864
|
|
|
856
|
-
// src/
|
|
865
|
+
// src/providers/CodexProvider.ts
|
|
866
|
+
var import_child_process2 = require("child_process");
|
|
867
|
+
var import_readline2 = require("readline");
|
|
868
|
+
var import_events2 = require("events");
|
|
857
869
|
var import_uuid2 = require("uuid");
|
|
870
|
+
|
|
871
|
+
// src/utils/codexPath.ts
|
|
872
|
+
var import_node_child_process3 = require("child_process");
|
|
873
|
+
var import_node_fs2 = require("fs");
|
|
874
|
+
var import_node_path2 = require("path");
|
|
875
|
+
var import_node_os3 = require("os");
|
|
876
|
+
function findCodexPath() {
|
|
877
|
+
try {
|
|
878
|
+
const cmd = isWindows ? "where codex" : "which codex";
|
|
879
|
+
return (0, import_node_child_process3.execSync)(cmd, { encoding: "utf-8" }).trim().split("\n")[0];
|
|
880
|
+
} catch {
|
|
881
|
+
}
|
|
882
|
+
const candidates = isWindows ? [
|
|
883
|
+
(0, import_node_path2.join)(process.env.LOCALAPPDATA ?? "", "Programs", "codex", "codex.exe"),
|
|
884
|
+
(0, import_node_path2.join)((0, import_node_os3.homedir)(), ".codex", "local", "codex.exe")
|
|
885
|
+
] : [
|
|
886
|
+
"/opt/homebrew/bin/codex",
|
|
887
|
+
"/usr/local/bin/codex",
|
|
888
|
+
(0, import_node_path2.join)((0, import_node_os3.homedir)(), ".local", "bin", "codex")
|
|
889
|
+
];
|
|
890
|
+
for (const candidate of candidates) {
|
|
891
|
+
try {
|
|
892
|
+
(0, import_node_fs2.accessSync)(candidate, import_node_fs2.constants.X_OK);
|
|
893
|
+
return candidate;
|
|
894
|
+
} catch {
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
return "codex";
|
|
898
|
+
}
|
|
899
|
+
var CODEX_PATH = findCodexPath();
|
|
900
|
+
async function getCodexVersion() {
|
|
901
|
+
try {
|
|
902
|
+
const output = (0, import_node_child_process3.execSync)(`"${CODEX_PATH}" --version`, {
|
|
903
|
+
encoding: "utf-8",
|
|
904
|
+
timeout: 5e3
|
|
905
|
+
}).trim();
|
|
906
|
+
return output.replace(/^codex-cli\s+/, "");
|
|
907
|
+
} catch {
|
|
908
|
+
return void 0;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
function isCodexAvailable() {
|
|
912
|
+
try {
|
|
913
|
+
(0, import_node_fs2.accessSync)(CODEX_PATH, import_node_fs2.constants.X_OK);
|
|
914
|
+
return true;
|
|
915
|
+
} catch {
|
|
916
|
+
try {
|
|
917
|
+
(0, import_node_child_process3.execSync)(`"${CODEX_PATH}" --version`, { timeout: 5e3 });
|
|
918
|
+
return true;
|
|
919
|
+
} catch {
|
|
920
|
+
return false;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// src/providers/CodexProvider.ts
|
|
926
|
+
var CodexProvider = class {
|
|
927
|
+
activeSessions = /* @__PURE__ */ new Map();
|
|
928
|
+
emitter = new import_events2.EventEmitter();
|
|
929
|
+
/** 自增 ID 计数器,用于生成 ClaudeStreamEvent 中的 message/block ID */
|
|
930
|
+
idCounter = 0;
|
|
931
|
+
async startSession(opts) {
|
|
932
|
+
const { projectPath, message, sessionId: existingSessionId } = opts;
|
|
933
|
+
const sessionId = existingSessionId ?? (0, import_uuid2.v4)();
|
|
934
|
+
if (this.activeSessions.has(sessionId)) {
|
|
935
|
+
await this.killSession(sessionId);
|
|
936
|
+
}
|
|
937
|
+
const projectId = projectPath.split("/").filter(Boolean).pop() ?? "unknown";
|
|
938
|
+
const session = {
|
|
939
|
+
id: sessionId,
|
|
940
|
+
projectId,
|
|
941
|
+
projectPath,
|
|
942
|
+
status: "running",
|
|
943
|
+
createdAt: Date.now(),
|
|
944
|
+
lastActiveAt: Date.now(),
|
|
945
|
+
summary: message.slice(0, 80),
|
|
946
|
+
agentType: "codex"
|
|
947
|
+
};
|
|
948
|
+
const resume = opts.resume ?? !!existingSessionId;
|
|
949
|
+
const proc = this.spawnCodexProcess(projectPath, message, void 0);
|
|
950
|
+
session.pid = proc.pid;
|
|
951
|
+
this.activeSessions.set(sessionId, {
|
|
952
|
+
session,
|
|
953
|
+
process: proc,
|
|
954
|
+
threadId: void 0,
|
|
955
|
+
turnStartTime: Date.now()
|
|
956
|
+
});
|
|
957
|
+
const initEvent = {
|
|
958
|
+
type: "system",
|
|
959
|
+
subtype: "init",
|
|
960
|
+
session_id: sessionId
|
|
961
|
+
};
|
|
962
|
+
this.emitter.emit(this.getEventName(sessionId), initEvent);
|
|
963
|
+
proc.on("error", (err) => {
|
|
964
|
+
console.error(`[CodexProvider] Session ${sessionId} process error:`, err.message);
|
|
965
|
+
this.activeSessions.delete(sessionId);
|
|
966
|
+
this.emitError(sessionId, `Process spawn failed: ${err.message}`);
|
|
967
|
+
});
|
|
968
|
+
this.attachStdoutListener(sessionId, proc);
|
|
969
|
+
this.attachStderrListener(sessionId, proc);
|
|
970
|
+
this.attachExitListener(sessionId, proc);
|
|
971
|
+
return session;
|
|
972
|
+
}
|
|
973
|
+
async killSession(sessionId) {
|
|
974
|
+
const entry = this.activeSessions.get(sessionId);
|
|
975
|
+
if (!entry) return;
|
|
976
|
+
if (entry.process.exitCode === null && entry.process.signalCode === null) {
|
|
977
|
+
try {
|
|
978
|
+
entry.process.stdin?.end();
|
|
979
|
+
} catch {
|
|
980
|
+
}
|
|
981
|
+
await killProcessCrossPlatform(entry.process);
|
|
982
|
+
}
|
|
983
|
+
this.activeSessions.delete(sessionId);
|
|
984
|
+
}
|
|
985
|
+
async sendMessage(sessionId, message) {
|
|
986
|
+
const entry = this.activeSessions.get(sessionId);
|
|
987
|
+
if (!entry) {
|
|
988
|
+
throw new Error(`Session ${sessionId} not found or already ended`);
|
|
989
|
+
}
|
|
990
|
+
if (entry.process.exitCode === null && entry.process.signalCode === null) {
|
|
991
|
+
try {
|
|
992
|
+
entry.process.stdin?.end();
|
|
993
|
+
} catch {
|
|
994
|
+
}
|
|
995
|
+
await killProcessCrossPlatform(entry.process);
|
|
996
|
+
}
|
|
997
|
+
const threadId = entry.threadId;
|
|
998
|
+
if (!threadId) {
|
|
999
|
+
throw new Error(`Session ${sessionId} has no thread ID, cannot resume`);
|
|
1000
|
+
}
|
|
1001
|
+
const proc = this.spawnCodexProcess(entry.session.projectPath, message, threadId);
|
|
1002
|
+
entry.session.status = "running";
|
|
1003
|
+
entry.session.lastActiveAt = Date.now();
|
|
1004
|
+
entry.session.pid = proc.pid;
|
|
1005
|
+
entry.process = proc;
|
|
1006
|
+
entry.turnStartTime = Date.now();
|
|
1007
|
+
proc.on("error", (err) => {
|
|
1008
|
+
console.error(`[CodexProvider] Session ${sessionId} sendMessage process error:`, err.message);
|
|
1009
|
+
this.activeSessions.delete(sessionId);
|
|
1010
|
+
this.emitError(sessionId, `Failed to send message: ${err.message}`);
|
|
1011
|
+
});
|
|
1012
|
+
this.attachStdoutListener(sessionId, proc);
|
|
1013
|
+
this.attachStderrListener(sessionId, proc);
|
|
1014
|
+
this.attachExitListener(sessionId, proc);
|
|
1015
|
+
}
|
|
1016
|
+
onEvent(sessionId, callback) {
|
|
1017
|
+
const eventName = this.getEventName(sessionId);
|
|
1018
|
+
this.emitter.on(eventName, callback);
|
|
1019
|
+
return () => {
|
|
1020
|
+
this.emitter.off(eventName, callback);
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
getActiveSessions() {
|
|
1024
|
+
return Array.from(this.activeSessions.values()).map((e) => e.session);
|
|
1025
|
+
}
|
|
1026
|
+
async generateSuggestion(_context) {
|
|
1027
|
+
return "";
|
|
1028
|
+
}
|
|
1029
|
+
async answerQuestion(_sessionId, _toolUseId, _answer) {
|
|
1030
|
+
}
|
|
1031
|
+
onQuestion(_sessionId, _callback) {
|
|
1032
|
+
return () => {
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
// ============================================
|
|
1036
|
+
// 私有方法
|
|
1037
|
+
// ============================================
|
|
1038
|
+
nextId() {
|
|
1039
|
+
return `codex_${++this.idCounter}`;
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* 启动 codex CLI 进程
|
|
1043
|
+
*
|
|
1044
|
+
* @param projectPath 工作目录
|
|
1045
|
+
* @param message 用户消息
|
|
1046
|
+
* @param resumeThreadId 如果提供,则使用 `codex exec resume` 恢复会话
|
|
1047
|
+
*/
|
|
1048
|
+
spawnCodexProcess(projectPath, message, resumeThreadId) {
|
|
1049
|
+
const args = ["exec", "--json", "--full-auto"];
|
|
1050
|
+
if (resumeThreadId) {
|
|
1051
|
+
args.push("resume", resumeThreadId);
|
|
1052
|
+
} else {
|
|
1053
|
+
args.push("-C", projectPath);
|
|
1054
|
+
}
|
|
1055
|
+
args.push(message);
|
|
1056
|
+
const env = { ...process.env };
|
|
1057
|
+
const proc = (0, import_child_process2.spawn)(CODEX_PATH, args, {
|
|
1058
|
+
cwd: projectPath,
|
|
1059
|
+
env,
|
|
1060
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1061
|
+
});
|
|
1062
|
+
try {
|
|
1063
|
+
proc.stdin?.end();
|
|
1064
|
+
} catch {
|
|
1065
|
+
}
|
|
1066
|
+
return proc;
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* 挂载 stdout 监听器,逐行解析 Codex NDJSON 并转换为 ClaudeStreamEvent
|
|
1070
|
+
*/
|
|
1071
|
+
attachStdoutListener(sessionId, proc) {
|
|
1072
|
+
if (!proc.stdout) return;
|
|
1073
|
+
const rl = (0, import_readline2.createInterface)({ input: proc.stdout, crlfDelay: Infinity });
|
|
1074
|
+
const entry = this.activeSessions.get(sessionId);
|
|
1075
|
+
if (entry) entry.rl = rl;
|
|
1076
|
+
rl.on("line", (line) => {
|
|
1077
|
+
const trimmed = line.trim();
|
|
1078
|
+
if (!trimmed) return;
|
|
1079
|
+
let event;
|
|
1080
|
+
try {
|
|
1081
|
+
event = JSON.parse(trimmed);
|
|
1082
|
+
} catch {
|
|
1083
|
+
console.warn(`[CodexProvider] Session ${sessionId}: failed to parse: ${trimmed.substring(0, 100)}`);
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
this.handleCodexEvent(sessionId, event);
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
1090
|
+
* 处理单个 Codex NDJSON 事件,转换并发射 ClaudeStreamEvent
|
|
1091
|
+
*/
|
|
1092
|
+
handleCodexEvent(sessionId, event) {
|
|
1093
|
+
const entry = this.activeSessions.get(sessionId);
|
|
1094
|
+
if (!entry) return;
|
|
1095
|
+
entry.session.lastActiveAt = Date.now();
|
|
1096
|
+
switch (event.type) {
|
|
1097
|
+
case "thread.started": {
|
|
1098
|
+
if (event.thread_id) {
|
|
1099
|
+
entry.threadId = event.thread_id;
|
|
1100
|
+
}
|
|
1101
|
+
break;
|
|
1102
|
+
}
|
|
1103
|
+
case "turn.started": {
|
|
1104
|
+
entry.session.status = "running";
|
|
1105
|
+
entry.turnStartTime = Date.now();
|
|
1106
|
+
break;
|
|
1107
|
+
}
|
|
1108
|
+
case "item.completed": {
|
|
1109
|
+
if (!event.item) break;
|
|
1110
|
+
this.handleItemCompleted(sessionId, event.item);
|
|
1111
|
+
break;
|
|
1112
|
+
}
|
|
1113
|
+
case "turn.completed": {
|
|
1114
|
+
entry.session.status = "idle";
|
|
1115
|
+
const duration = entry.turnStartTime ? Date.now() - entry.turnStartTime : 0;
|
|
1116
|
+
const resultEvent = {
|
|
1117
|
+
type: "result",
|
|
1118
|
+
subtype: "success",
|
|
1119
|
+
session_id: sessionId,
|
|
1120
|
+
is_error: false,
|
|
1121
|
+
result: "",
|
|
1122
|
+
duration_ms: duration,
|
|
1123
|
+
num_turns: 1,
|
|
1124
|
+
usage: event.usage
|
|
1125
|
+
};
|
|
1126
|
+
this.emitter.emit(this.getEventName(sessionId), resultEvent);
|
|
1127
|
+
break;
|
|
1128
|
+
}
|
|
1129
|
+
case "turn.failed": {
|
|
1130
|
+
entry.session.status = "error";
|
|
1131
|
+
const failEvent = {
|
|
1132
|
+
type: "result",
|
|
1133
|
+
subtype: "error",
|
|
1134
|
+
session_id: sessionId,
|
|
1135
|
+
is_error: true,
|
|
1136
|
+
result: event.error ?? "Turn failed",
|
|
1137
|
+
duration_ms: entry.turnStartTime ? Date.now() - entry.turnStartTime : 0,
|
|
1138
|
+
num_turns: 1
|
|
1139
|
+
};
|
|
1140
|
+
this.emitter.emit(this.getEventName(sessionId), failEvent);
|
|
1141
|
+
break;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* 处理 item.completed 事件,按 item.type 转换为对应的 ClaudeStreamEvent
|
|
1147
|
+
*/
|
|
1148
|
+
handleItemCompleted(sessionId, item) {
|
|
1149
|
+
switch (item.type) {
|
|
1150
|
+
case "agent_message": {
|
|
1151
|
+
const msgEvent = {
|
|
1152
|
+
type: "assistant",
|
|
1153
|
+
session_id: sessionId,
|
|
1154
|
+
message: {
|
|
1155
|
+
id: item.id || this.nextId(),
|
|
1156
|
+
model: "codex",
|
|
1157
|
+
role: "assistant",
|
|
1158
|
+
content: [{ type: "text", text: item.text ?? "" }]
|
|
1159
|
+
}
|
|
1160
|
+
};
|
|
1161
|
+
this.emitter.emit(this.getEventName(sessionId), msgEvent);
|
|
1162
|
+
break;
|
|
1163
|
+
}
|
|
1164
|
+
case "command_execution": {
|
|
1165
|
+
const toolUseId = item.id || this.nextId();
|
|
1166
|
+
const toolEvent = {
|
|
1167
|
+
type: "assistant",
|
|
1168
|
+
session_id: sessionId,
|
|
1169
|
+
message: {
|
|
1170
|
+
id: this.nextId(),
|
|
1171
|
+
model: "codex",
|
|
1172
|
+
role: "assistant",
|
|
1173
|
+
content: [{
|
|
1174
|
+
type: "tool_use",
|
|
1175
|
+
id: toolUseId,
|
|
1176
|
+
name: "Bash",
|
|
1177
|
+
input: { command: item.command ?? "" }
|
|
1178
|
+
}]
|
|
1179
|
+
}
|
|
1180
|
+
};
|
|
1181
|
+
this.emitter.emit(this.getEventName(sessionId), toolEvent);
|
|
1182
|
+
const resultContent = item.aggregated_output ?? "";
|
|
1183
|
+
const isError = item.exit_code != null && item.exit_code !== 0;
|
|
1184
|
+
const toolResultEvent = {
|
|
1185
|
+
type: "user",
|
|
1186
|
+
session_id: sessionId,
|
|
1187
|
+
message: {
|
|
1188
|
+
role: "user",
|
|
1189
|
+
content: [{
|
|
1190
|
+
type: "tool_result",
|
|
1191
|
+
tool_use_id: toolUseId,
|
|
1192
|
+
content: resultContent,
|
|
1193
|
+
is_error: isError
|
|
1194
|
+
}]
|
|
1195
|
+
}
|
|
1196
|
+
};
|
|
1197
|
+
this.emitter.emit(this.getEventName(sessionId), toolResultEvent);
|
|
1198
|
+
break;
|
|
1199
|
+
}
|
|
1200
|
+
case "file_change": {
|
|
1201
|
+
const editToolUseId = item.id || this.nextId();
|
|
1202
|
+
const toolEvent = {
|
|
1203
|
+
type: "assistant",
|
|
1204
|
+
session_id: sessionId,
|
|
1205
|
+
message: {
|
|
1206
|
+
id: this.nextId(),
|
|
1207
|
+
model: "codex",
|
|
1208
|
+
role: "assistant",
|
|
1209
|
+
content: [{
|
|
1210
|
+
type: "tool_use",
|
|
1211
|
+
id: editToolUseId,
|
|
1212
|
+
name: "Edit",
|
|
1213
|
+
input: { file_path: item.file_path ?? "", diff: item.diff ?? "" }
|
|
1214
|
+
}]
|
|
1215
|
+
}
|
|
1216
|
+
};
|
|
1217
|
+
this.emitter.emit(this.getEventName(sessionId), toolEvent);
|
|
1218
|
+
const toolResultEvent = {
|
|
1219
|
+
type: "user",
|
|
1220
|
+
session_id: sessionId,
|
|
1221
|
+
message: {
|
|
1222
|
+
role: "user",
|
|
1223
|
+
content: [{
|
|
1224
|
+
type: "tool_result",
|
|
1225
|
+
tool_use_id: editToolUseId,
|
|
1226
|
+
content: `File edited: ${item.file_path ?? "unknown"}`
|
|
1227
|
+
}]
|
|
1228
|
+
}
|
|
1229
|
+
};
|
|
1230
|
+
this.emitter.emit(this.getEventName(sessionId), toolResultEvent);
|
|
1231
|
+
break;
|
|
1232
|
+
}
|
|
1233
|
+
case "reasoning": {
|
|
1234
|
+
const thinkEvent = {
|
|
1235
|
+
type: "assistant",
|
|
1236
|
+
session_id: sessionId,
|
|
1237
|
+
message: {
|
|
1238
|
+
id: item.id || this.nextId(),
|
|
1239
|
+
model: "codex",
|
|
1240
|
+
role: "assistant",
|
|
1241
|
+
content: [{ type: "thinking", thinking: item.text ?? "" }]
|
|
1242
|
+
}
|
|
1243
|
+
};
|
|
1244
|
+
this.emitter.emit(this.getEventName(sessionId), thinkEvent);
|
|
1245
|
+
break;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
attachStderrListener(sessionId, proc) {
|
|
1250
|
+
if (!proc.stderr) return;
|
|
1251
|
+
proc.stderr.on("data", (data) => {
|
|
1252
|
+
const text = data.toString().trim();
|
|
1253
|
+
if (text) {
|
|
1254
|
+
console.error(`[CodexProvider] Session ${sessionId} stderr: ${text}`);
|
|
1255
|
+
}
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
attachExitListener(sessionId, proc) {
|
|
1259
|
+
proc.once("exit", (code, signal) => {
|
|
1260
|
+
const entry = this.activeSessions.get(sessionId);
|
|
1261
|
+
if (!entry) return;
|
|
1262
|
+
if (entry.process !== proc) return;
|
|
1263
|
+
if (entry.rl) {
|
|
1264
|
+
entry.rl.close();
|
|
1265
|
+
entry.rl = void 0;
|
|
1266
|
+
}
|
|
1267
|
+
entry.session.pid = void 0;
|
|
1268
|
+
entry.session.lastActiveAt = Date.now();
|
|
1269
|
+
const alreadyHasResult = entry.session.status === "idle" || entry.session.status === "error";
|
|
1270
|
+
if (alreadyHasResult) return;
|
|
1271
|
+
const isNormal = isNormalExit(code, signal);
|
|
1272
|
+
entry.session.status = isNormal ? "idle" : "error";
|
|
1273
|
+
if (!isNormal) {
|
|
1274
|
+
console.error(`[CodexProvider] Session ${sessionId}: process exited abnormally code=${code} signal=${signal}`);
|
|
1275
|
+
}
|
|
1276
|
+
const syntheticResult = {
|
|
1277
|
+
type: "result",
|
|
1278
|
+
subtype: isNormal ? "success" : "error",
|
|
1279
|
+
session_id: sessionId,
|
|
1280
|
+
is_error: !isNormal,
|
|
1281
|
+
result: isNormal ? "" : `Process exited code=${code} signal=${signal}`,
|
|
1282
|
+
duration_ms: 0,
|
|
1283
|
+
num_turns: 0
|
|
1284
|
+
};
|
|
1285
|
+
this.emitter.emit(this.getEventName(sessionId), syntheticResult);
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1288
|
+
emitError(sessionId, message) {
|
|
1289
|
+
const event = {
|
|
1290
|
+
type: "result",
|
|
1291
|
+
subtype: "error",
|
|
1292
|
+
result: message,
|
|
1293
|
+
session_id: sessionId,
|
|
1294
|
+
duration_ms: 0,
|
|
1295
|
+
is_error: true,
|
|
1296
|
+
num_turns: 0
|
|
1297
|
+
};
|
|
1298
|
+
this.emitter.emit(this.getEventName(sessionId), event);
|
|
1299
|
+
}
|
|
1300
|
+
getEventName(sessionId) {
|
|
1301
|
+
return `claude:${sessionId}`;
|
|
1302
|
+
}
|
|
1303
|
+
};
|
|
1304
|
+
|
|
1305
|
+
// src/providers/ProviderFactory.ts
|
|
1306
|
+
var import_node_child_process4 = require("child_process");
|
|
1307
|
+
var ProviderFactory = class {
|
|
1308
|
+
providers = /* @__PURE__ */ new Map();
|
|
1309
|
+
getProvider(agentType) {
|
|
1310
|
+
const existing = this.providers.get(agentType);
|
|
1311
|
+
if (existing) return existing;
|
|
1312
|
+
let provider;
|
|
1313
|
+
switch (agentType) {
|
|
1314
|
+
case "codex":
|
|
1315
|
+
provider = new CodexProvider();
|
|
1316
|
+
break;
|
|
1317
|
+
case "claude-code":
|
|
1318
|
+
default:
|
|
1319
|
+
provider = new ProcessProvider();
|
|
1320
|
+
break;
|
|
1321
|
+
}
|
|
1322
|
+
this.providers.set(agentType, provider);
|
|
1323
|
+
return provider;
|
|
1324
|
+
}
|
|
1325
|
+
async detectAgents() {
|
|
1326
|
+
const agents = [];
|
|
1327
|
+
try {
|
|
1328
|
+
const claudePath = findClaudePath();
|
|
1329
|
+
const claudeVersion = (0, import_node_child_process4.execSync)(`"${claudePath}" --version`, {
|
|
1330
|
+
encoding: "utf-8",
|
|
1331
|
+
timeout: 5e3
|
|
1332
|
+
}).trim();
|
|
1333
|
+
agents.push({
|
|
1334
|
+
type: "claude-code",
|
|
1335
|
+
name: "Claude Code",
|
|
1336
|
+
available: true,
|
|
1337
|
+
version: claudeVersion
|
|
1338
|
+
});
|
|
1339
|
+
} catch {
|
|
1340
|
+
agents.push({ type: "claude-code", name: "Claude Code", available: false });
|
|
1341
|
+
}
|
|
1342
|
+
if (isCodexAvailable()) {
|
|
1343
|
+
const version = await getCodexVersion();
|
|
1344
|
+
agents.push({ type: "codex", name: "Codex", available: true, version });
|
|
1345
|
+
} else {
|
|
1346
|
+
agents.push({ type: "codex", name: "Codex", available: false });
|
|
1347
|
+
}
|
|
1348
|
+
return agents;
|
|
1349
|
+
}
|
|
1350
|
+
};
|
|
1351
|
+
|
|
1352
|
+
// src/session/SessionManager.ts
|
|
1353
|
+
var import_uuid3 = require("uuid");
|
|
858
1354
|
var BUFFER_MAX = 5e3;
|
|
859
1355
|
var SessionManager = class {
|
|
860
1356
|
provider;
|
|
1357
|
+
providerFactory;
|
|
1358
|
+
sessionAgentType = /* @__PURE__ */ new Map();
|
|
861
1359
|
/** 事件回调列表(事件会被转发到 WsBridge) */
|
|
862
1360
|
eventCallbacks = [];
|
|
863
1361
|
/** 每个会话的事件流取消订阅函数 */
|
|
@@ -883,8 +1381,19 @@ var SessionManager = class {
|
|
|
883
1381
|
bufferTruncated = /* @__PURE__ */ new Set();
|
|
884
1382
|
/** sessionId → projectPath 映射,用于截断时从 JSONL 补全历史 */
|
|
885
1383
|
sessionProjectPaths = /* @__PURE__ */ new Map();
|
|
886
|
-
constructor(
|
|
887
|
-
|
|
1384
|
+
constructor(providerOrFactory) {
|
|
1385
|
+
if (providerOrFactory instanceof ProviderFactory) {
|
|
1386
|
+
this.providerFactory = providerOrFactory;
|
|
1387
|
+
this.provider = providerOrFactory.getProvider("claude-code");
|
|
1388
|
+
} else {
|
|
1389
|
+
this.provider = providerOrFactory;
|
|
1390
|
+
this.providerFactory = null;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
getProviderForSession(sessionId) {
|
|
1394
|
+
if (!this.providerFactory) return this.provider;
|
|
1395
|
+
const agentType = this.sessionAgentType.get(sessionId) ?? "claude-code";
|
|
1396
|
+
return this.providerFactory.getProvider(agentType);
|
|
888
1397
|
}
|
|
889
1398
|
// ============================================
|
|
890
1399
|
// 公开 API
|
|
@@ -895,8 +1404,9 @@ var SessionManager = class {
|
|
|
895
1404
|
* 调用 provider.startSession(),订阅事件流,
|
|
896
1405
|
* 将 ClaudeStreamEvent 包装为 ServerEvent 转发。
|
|
897
1406
|
*/
|
|
898
|
-
async createSession(projectPath, message, resumeSessionId, newSessionId, model, permissionMode, effort, images) {
|
|
899
|
-
const
|
|
1407
|
+
async createSession(projectPath, message, resumeSessionId, newSessionId, model, permissionMode, effort, images, agentType = "claude-code") {
|
|
1408
|
+
const provider = this.providerFactory ? this.providerFactory.getProvider(agentType) : this.provider;
|
|
1409
|
+
const session = await provider.startSession({
|
|
900
1410
|
projectPath,
|
|
901
1411
|
message,
|
|
902
1412
|
sessionId: resumeSessionId ?? newSessionId,
|
|
@@ -906,6 +1416,7 @@ var SessionManager = class {
|
|
|
906
1416
|
effort,
|
|
907
1417
|
images
|
|
908
1418
|
});
|
|
1419
|
+
this.sessionAgentType.set(session.id, agentType);
|
|
909
1420
|
this.lastBroadcastStatus.set(session.id, session.status);
|
|
910
1421
|
this.sessionProjectPaths.set(session.id, projectPath);
|
|
911
1422
|
this.unsubscribeSession(session.id);
|
|
@@ -917,7 +1428,8 @@ var SessionManager = class {
|
|
|
917
1428
|
* 发送消息到已有会话
|
|
918
1429
|
*/
|
|
919
1430
|
async sendMessage(sessionId, message, permissionMode, images) {
|
|
920
|
-
|
|
1431
|
+
const provider = this.getProviderForSession(sessionId);
|
|
1432
|
+
await provider.sendMessage(sessionId, message, permissionMode, images);
|
|
921
1433
|
this.updateSessionStatus(sessionId, "running");
|
|
922
1434
|
console.log(`[SessionManager] Message sent to session: ${sessionId}`);
|
|
923
1435
|
}
|
|
@@ -937,7 +1449,9 @@ var SessionManager = class {
|
|
|
937
1449
|
clearTimeout(pending.timer);
|
|
938
1450
|
this.pendingAssistantEvents.delete(sessionId);
|
|
939
1451
|
}
|
|
940
|
-
|
|
1452
|
+
const provider = this.getProviderForSession(sessionId);
|
|
1453
|
+
await provider.killSession(sessionId);
|
|
1454
|
+
this.sessionAgentType.delete(sessionId);
|
|
941
1455
|
console.log(`[SessionManager] Session killed: ${sessionId}`);
|
|
942
1456
|
}
|
|
943
1457
|
/**
|
|
@@ -1016,7 +1530,11 @@ var SessionManager = class {
|
|
|
1016
1530
|
* 获取所有活跃会话(含服务器端统计)
|
|
1017
1531
|
*/
|
|
1018
1532
|
getActiveSessions() {
|
|
1019
|
-
|
|
1533
|
+
const rawSessions = this.providerFactory ? [
|
|
1534
|
+
...this.providerFactory.getProvider("claude-code").getActiveSessions(),
|
|
1535
|
+
...this.providerFactory.getProvider("codex").getActiveSessions()
|
|
1536
|
+
] : this.provider.getActiveSessions();
|
|
1537
|
+
return rawSessions.map((session) => {
|
|
1020
1538
|
const stats = this.getSessionStats(session.id);
|
|
1021
1539
|
return stats ? { ...session, stats } : session;
|
|
1022
1540
|
});
|
|
@@ -1046,6 +1564,7 @@ var SessionManager = class {
|
|
|
1046
1564
|
this.sessionEventBuffers.clear();
|
|
1047
1565
|
this.bufferTruncated.clear();
|
|
1048
1566
|
this.sessionProjectPaths.clear();
|
|
1567
|
+
this.sessionAgentType.clear();
|
|
1049
1568
|
this.sessionStats.clear();
|
|
1050
1569
|
for (const [, pending] of this.pendingAssistantEvents) {
|
|
1051
1570
|
clearTimeout(pending.timer);
|
|
@@ -1063,10 +1582,11 @@ var SessionManager = class {
|
|
|
1063
1582
|
* 订阅指定会话的事件流(包括 AskUserQuestion 问题事件)
|
|
1064
1583
|
*/
|
|
1065
1584
|
subscribeToSession(sessionId) {
|
|
1066
|
-
const
|
|
1585
|
+
const provider = this.getProviderForSession(sessionId);
|
|
1586
|
+
const unsubscribeEvent = provider.onEvent(sessionId, (event) => {
|
|
1067
1587
|
this.handleClaudeEvent(sessionId, event);
|
|
1068
1588
|
});
|
|
1069
|
-
const unsubscribeQuestion =
|
|
1589
|
+
const unsubscribeQuestion = provider.onQuestion(
|
|
1070
1590
|
sessionId,
|
|
1071
1591
|
({ toolUseId, question, options }) => {
|
|
1072
1592
|
this.handleAskUserQuestion(sessionId, toolUseId, question, options);
|
|
@@ -1241,7 +1761,7 @@ var SessionManager = class {
|
|
|
1241
1761
|
console.log(`[SessionManager] Session ${sessionId}: AskUserQuestion updated (requestId=${existingRequestId})`);
|
|
1242
1762
|
return;
|
|
1243
1763
|
}
|
|
1244
|
-
const requestId = (0,
|
|
1764
|
+
const requestId = (0, import_uuid3.v4)();
|
|
1245
1765
|
const request = {
|
|
1246
1766
|
id: requestId,
|
|
1247
1767
|
sessionId,
|
|
@@ -1257,7 +1777,8 @@ var SessionManager = class {
|
|
|
1257
1777
|
});
|
|
1258
1778
|
answerPromise.then(async (answer) => {
|
|
1259
1779
|
try {
|
|
1260
|
-
|
|
1780
|
+
const provider = this.getProviderForSession(sessionId);
|
|
1781
|
+
await provider.answerQuestion(sessionId, toolUseId, answer);
|
|
1261
1782
|
} catch (err) {
|
|
1262
1783
|
console.error(`[SessionManager] answerQuestion failed (${sessionId}):`, err);
|
|
1263
1784
|
}
|
|
@@ -1680,15 +2201,15 @@ var WsBridge = class _WsBridge {
|
|
|
1680
2201
|
|
|
1681
2202
|
// src/approval/ApprovalProxy.ts
|
|
1682
2203
|
var import_node_http = __toESM(require("http"));
|
|
1683
|
-
var
|
|
1684
|
-
var
|
|
1685
|
-
var
|
|
1686
|
-
var
|
|
2204
|
+
var import_node_fs3 = __toESM(require("fs"));
|
|
2205
|
+
var import_node_path3 = __toESM(require("path"));
|
|
2206
|
+
var import_node_os4 = __toESM(require("os"));
|
|
2207
|
+
var import_uuid4 = require("uuid");
|
|
1687
2208
|
var ApprovalProxy = class _ApprovalProxy {
|
|
1688
2209
|
server;
|
|
1689
2210
|
token;
|
|
1690
2211
|
port;
|
|
1691
|
-
settingsPath =
|
|
2212
|
+
settingsPath = import_node_path3.default.join(import_node_os4.default.homedir(), ".claude", "settings.json");
|
|
1692
2213
|
/** 待处理的审批请求:requestId -> { resolve, timer, request } */
|
|
1693
2214
|
pendingApprovals = /* @__PURE__ */ new Map();
|
|
1694
2215
|
/** 审批请求回调(通知外部推送到手机) */
|
|
@@ -1793,7 +2314,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
1793
2314
|
isToolInClaudeSettings(toolName, projectPath) {
|
|
1794
2315
|
const checkPath = (filepath) => {
|
|
1795
2316
|
try {
|
|
1796
|
-
const raw =
|
|
2317
|
+
const raw = import_node_fs3.default.readFileSync(filepath, "utf-8");
|
|
1797
2318
|
const settings = JSON.parse(raw);
|
|
1798
2319
|
const allow = settings?.permissions?.allow ?? [];
|
|
1799
2320
|
return allow.some((entry) => {
|
|
@@ -1807,24 +2328,24 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
1807
2328
|
}
|
|
1808
2329
|
};
|
|
1809
2330
|
if (projectPath) {
|
|
1810
|
-
const projectSettingsPath =
|
|
2331
|
+
const projectSettingsPath = import_node_path3.default.join(projectPath, ".claude", "settings.json");
|
|
1811
2332
|
if (checkPath(projectSettingsPath)) return true;
|
|
1812
2333
|
}
|
|
1813
2334
|
return checkPath(this.settingsPath);
|
|
1814
2335
|
}
|
|
1815
2336
|
/** 将工具写入 settings.json permissions.allow(项目级或全局) */
|
|
1816
2337
|
addToClaudeSettings(projectPath, toolName) {
|
|
1817
|
-
const targetPath = projectPath ?
|
|
2338
|
+
const targetPath = projectPath ? import_node_path3.default.join(projectPath, ".claude", "settings.json") : this.settingsPath;
|
|
1818
2339
|
try {
|
|
1819
2340
|
if (projectPath) {
|
|
1820
|
-
const dir =
|
|
1821
|
-
if (!
|
|
1822
|
-
|
|
2341
|
+
const dir = import_node_path3.default.dirname(targetPath);
|
|
2342
|
+
if (!import_node_fs3.default.existsSync(dir)) {
|
|
2343
|
+
import_node_fs3.default.mkdirSync(dir, { recursive: true });
|
|
1823
2344
|
}
|
|
1824
2345
|
}
|
|
1825
2346
|
let settings = {};
|
|
1826
2347
|
try {
|
|
1827
|
-
settings = JSON.parse(
|
|
2348
|
+
settings = JSON.parse(import_node_fs3.default.readFileSync(targetPath, "utf-8"));
|
|
1828
2349
|
} catch {
|
|
1829
2350
|
}
|
|
1830
2351
|
if (!settings.permissions) {
|
|
@@ -1838,7 +2359,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
1838
2359
|
const entry = `${toolName}(*)`;
|
|
1839
2360
|
if (!allow.includes(entry)) {
|
|
1840
2361
|
allow.push(entry);
|
|
1841
|
-
|
|
2362
|
+
import_node_fs3.default.writeFileSync(targetPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
1842
2363
|
const label = projectPath ? `${projectPath}/.claude/settings.json` : "~/.claude/settings.json";
|
|
1843
2364
|
console.log(`[ApprovalProxy] ${t("approval.alwaysAllowWritten", { entry, label })}`);
|
|
1844
2365
|
}
|
|
@@ -1933,7 +2454,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
1933
2454
|
try {
|
|
1934
2455
|
const body = await this.parseJsonBody(req);
|
|
1935
2456
|
const payload = body.payload ?? body;
|
|
1936
|
-
const requestId = (0,
|
|
2457
|
+
const requestId = (0, import_uuid4.v4)();
|
|
1937
2458
|
const projectPath = String(body.projectPath ?? "unknown");
|
|
1938
2459
|
const toolName = String(payload.tool_name ?? body.tool_name ?? "unknown");
|
|
1939
2460
|
const toolInput = payload.tool_input ?? body.tool_input ?? {};
|
|
@@ -2065,8 +2586,8 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2065
2586
|
};
|
|
2066
2587
|
|
|
2067
2588
|
// src/mdns/MdnsService.ts
|
|
2068
|
-
var
|
|
2069
|
-
var
|
|
2589
|
+
var import_node_child_process5 = require("child_process");
|
|
2590
|
+
var import_node_os5 = require("os");
|
|
2070
2591
|
function buildTxtArgs(txt) {
|
|
2071
2592
|
return Object.entries(txt).map(([k, v]) => `${k}=${v}`);
|
|
2072
2593
|
}
|
|
@@ -2084,7 +2605,7 @@ var MdnsService = class {
|
|
|
2084
2605
|
this.httpPort = options.httpPort;
|
|
2085
2606
|
this.version = options.version ?? "0.1.0";
|
|
2086
2607
|
this.pairing = options.pairing ?? "closed";
|
|
2087
|
-
this.useDnsSd = (0,
|
|
2608
|
+
this.useDnsSd = (0, import_node_os5.platform)() === "darwin";
|
|
2088
2609
|
}
|
|
2089
2610
|
getTxt() {
|
|
2090
2611
|
return {
|
|
@@ -2117,7 +2638,7 @@ var MdnsService = class {
|
|
|
2117
2638
|
String(this.wsPort),
|
|
2118
2639
|
...buildTxtArgs(this.getTxt())
|
|
2119
2640
|
];
|
|
2120
|
-
this.proc = (0,
|
|
2641
|
+
this.proc = (0, import_node_child_process5.spawn)("dns-sd", args, { stdio: "ignore" });
|
|
2121
2642
|
this.proc.on("error", (err) => {
|
|
2122
2643
|
console.warn(`[MdnsService] dns-sd failed, falling back to bonjour-service: ${err.message}`);
|
|
2123
2644
|
this.proc = null;
|
|
@@ -2138,7 +2659,7 @@ var MdnsService = class {
|
|
|
2138
2659
|
return;
|
|
2139
2660
|
}
|
|
2140
2661
|
try {
|
|
2141
|
-
const {
|
|
2662
|
+
const { Bonjour } = await import("bonjour-service");
|
|
2142
2663
|
const { networkInterfaces } = await import("os");
|
|
2143
2664
|
const lanAddrs = getLanAddresses(networkInterfaces);
|
|
2144
2665
|
const opts = lanAddrs.length > 0 ? { interface: lanAddrs[0] } : {};
|
|
@@ -2229,12 +2750,12 @@ function getLanAddresses(networkInterfacesFn) {
|
|
|
2229
2750
|
|
|
2230
2751
|
// src/hooks/HookInstaller.ts
|
|
2231
2752
|
var import_promises2 = require("fs/promises");
|
|
2232
|
-
var
|
|
2233
|
-
var
|
|
2234
|
-
var SESSIX_HOOKS_DIR = (0,
|
|
2235
|
-
var HOOK_SCRIPT_PATH = (0,
|
|
2236
|
-
var PERMISSION_ACCEPT_PATH = (0,
|
|
2237
|
-
var CLAUDE_SETTINGS_PATH = (0,
|
|
2753
|
+
var import_node_path4 = require("path");
|
|
2754
|
+
var import_node_os6 = require("os");
|
|
2755
|
+
var SESSIX_HOOKS_DIR = (0, import_node_path4.join)((0, import_node_os6.homedir)(), ".sessix", "hooks");
|
|
2756
|
+
var HOOK_SCRIPT_PATH = (0, import_node_path4.join)(SESSIX_HOOKS_DIR, "approval-hook.js");
|
|
2757
|
+
var PERMISSION_ACCEPT_PATH = (0, import_node_path4.join)(SESSIX_HOOKS_DIR, "permission-accept.js");
|
|
2758
|
+
var CLAUDE_SETTINGS_PATH = (0, import_node_path4.join)((0, import_node_os6.homedir)(), ".claude", "settings.json");
|
|
2238
2759
|
var HOOK_COMMAND = "node ~/.sessix/hooks/approval-hook.js";
|
|
2239
2760
|
var PERMISSION_ACCEPT_COMMAND = "node ~/.sessix/hooks/permission-accept.js";
|
|
2240
2761
|
var LEGACY_HOOK_COMMANDS = [
|
|
@@ -2408,7 +2929,7 @@ var HookInstaller = class {
|
|
|
2408
2929
|
* 写入 Claude Code settings.json
|
|
2409
2930
|
*/
|
|
2410
2931
|
async writeClaudeSettings(settings) {
|
|
2411
|
-
await (0, import_promises2.mkdir)((0,
|
|
2932
|
+
await (0, import_promises2.mkdir)((0, import_node_path4.join)((0, import_node_os6.homedir)(), ".claude"), { recursive: true });
|
|
2412
2933
|
await (0, import_promises2.writeFile)(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
2413
2934
|
}
|
|
2414
2935
|
/**
|
|
@@ -2435,7 +2956,7 @@ var HookInstaller = class {
|
|
|
2435
2956
|
};
|
|
2436
2957
|
|
|
2437
2958
|
// src/notification/NotificationService.ts
|
|
2438
|
-
var
|
|
2959
|
+
var import_node_path5 = require("path");
|
|
2439
2960
|
var NotificationService = class {
|
|
2440
2961
|
constructor(sessionManager, expoChannel = null) {
|
|
2441
2962
|
this.sessionManager = sessionManager;
|
|
@@ -2531,7 +3052,7 @@ var NotificationService = class {
|
|
|
2531
3052
|
const dangerLevel = this.getDangerLevel(request.toolName);
|
|
2532
3053
|
const isDangerous = dangerLevel === "danger" || dangerLevel === "write";
|
|
2533
3054
|
const categoryId = isDangerous ? "APPROVAL_DANGEROUS" : "APPROVAL_NORMAL";
|
|
2534
|
-
const projectName = (0,
|
|
3055
|
+
const projectName = (0, import_node_path5.basename)(
|
|
2535
3056
|
this.sessionManager.getActiveSessions().find((s) => s.id === request.sessionId)?.projectPath ?? ""
|
|
2536
3057
|
);
|
|
2537
3058
|
const pushTitle = isDangerous ? `\u26A0\uFE0F ${title}` : title;
|
|
@@ -2587,7 +3108,7 @@ var NotificationService = class {
|
|
|
2587
3108
|
/** 从审批请求中提取操作目标的简短描述 */
|
|
2588
3109
|
extractTarget(request) {
|
|
2589
3110
|
const input = request.toolInput;
|
|
2590
|
-
if (input.file_path) return (0,
|
|
3111
|
+
if (input.file_path) return (0, import_node_path5.basename)(String(input.file_path));
|
|
2591
3112
|
if (input.command) return String(input.command).slice(0, 40);
|
|
2592
3113
|
return request.description.slice(0, 40);
|
|
2593
3114
|
}
|
|
@@ -2690,7 +3211,7 @@ var NotificationService = class {
|
|
|
2690
3211
|
getSessionTitle(sessionId) {
|
|
2691
3212
|
const session = this.sessionManager.getActiveSessions().find((s) => s.id === sessionId);
|
|
2692
3213
|
if (!session) return "Unknown";
|
|
2693
|
-
return session.summary ?? (0,
|
|
3214
|
+
return session.summary ?? (0, import_node_path5.basename)(session.projectPath);
|
|
2694
3215
|
}
|
|
2695
3216
|
/** 获取会话的 YOLO 模式状态 */
|
|
2696
3217
|
getYoloMode(sessionId) {
|
|
@@ -2699,7 +3220,7 @@ var NotificationService = class {
|
|
|
2699
3220
|
};
|
|
2700
3221
|
|
|
2701
3222
|
// src/notification/DesktopNotificationChannel.ts
|
|
2702
|
-
var
|
|
3223
|
+
var import_node_child_process6 = require("child_process");
|
|
2703
3224
|
var DesktopNotificationChannel = class {
|
|
2704
3225
|
isAvailable() {
|
|
2705
3226
|
return process.platform === "darwin";
|
|
@@ -2711,7 +3232,7 @@ var DesktopNotificationChannel = class {
|
|
|
2711
3232
|
const sound = payload.sound ?? "Ping";
|
|
2712
3233
|
const script = `display notification "${body}" with title "${title}" sound name "${sound}"`;
|
|
2713
3234
|
return new Promise((resolve) => {
|
|
2714
|
-
(0,
|
|
3235
|
+
(0, import_node_child_process6.execFile)("osascript", ["-e", script], (err) => {
|
|
2715
3236
|
if (err) {
|
|
2716
3237
|
console.warn("[DesktopNotificationChannel] Send notification failed:", err.message);
|
|
2717
3238
|
}
|
|
@@ -2985,7 +3506,7 @@ var ActivityPushChannel = class {
|
|
|
2985
3506
|
|
|
2986
3507
|
// src/session/ProjectReader.ts
|
|
2987
3508
|
var import_promises3 = require("fs/promises");
|
|
2988
|
-
var
|
|
3509
|
+
var import_readline3 = require("readline");
|
|
2989
3510
|
var import_path = require("path");
|
|
2990
3511
|
var import_os = require("os");
|
|
2991
3512
|
var CLAUDE_PROJECTS_DIR = (0, import_path.join)((0, import_os.homedir)(), ".claude", "projects");
|
|
@@ -3037,16 +3558,23 @@ async function getHistoricalSessions(projectPath) {
|
|
|
3037
3558
|
const entries = await (0, import_promises3.readdir)(projectDir, { withFileTypes: true });
|
|
3038
3559
|
const jsonlFiles = entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl"));
|
|
3039
3560
|
const mtimeMap = /* @__PURE__ */ new Map();
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3561
|
+
await Promise.all(
|
|
3562
|
+
jsonlFiles.map(async (entry) => {
|
|
3563
|
+
const sessionId = entry.name.slice(0, -6);
|
|
3564
|
+
const filePath = (0, import_path.join)(projectDir, entry.name);
|
|
3565
|
+
try {
|
|
3566
|
+
const contentTs = await extractLastTimestamp(filePath);
|
|
3567
|
+
if (contentTs) {
|
|
3568
|
+
mtimeMap.set(sessionId, contentTs);
|
|
3569
|
+
} else {
|
|
3570
|
+
const fileStat = await (0, import_promises3.stat)(filePath);
|
|
3571
|
+
mtimeMap.set(sessionId, fileStat.mtimeMs);
|
|
3572
|
+
}
|
|
3573
|
+
} catch {
|
|
3574
|
+
mtimeMap.set(sessionId, 0);
|
|
3575
|
+
}
|
|
3576
|
+
})
|
|
3577
|
+
);
|
|
3050
3578
|
const uuidDirs = entries.filter(
|
|
3051
3579
|
(e) => e.isDirectory() && UUID_RE.test(e.name) && !mtimeMap.has(e.name)
|
|
3052
3580
|
);
|
|
@@ -3197,11 +3725,37 @@ async function getSessionHistory(projectPath, sessionId) {
|
|
|
3197
3725
|
};
|
|
3198
3726
|
}
|
|
3199
3727
|
}
|
|
3728
|
+
async function extractLastTimestamp(filePath) {
|
|
3729
|
+
let fileHandle;
|
|
3730
|
+
try {
|
|
3731
|
+
fileHandle = await (0, import_promises3.open)(filePath, "r");
|
|
3732
|
+
const fileStat = await fileHandle.stat();
|
|
3733
|
+
const readSize = Math.min(fileStat.size, 8192);
|
|
3734
|
+
const buffer = Buffer.alloc(readSize);
|
|
3735
|
+
await fileHandle.read(buffer, 0, readSize, fileStat.size - readSize);
|
|
3736
|
+
const tail = buffer.toString("utf-8");
|
|
3737
|
+
const lines = tail.split("\n").filter((l) => l.trim());
|
|
3738
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
3739
|
+
try {
|
|
3740
|
+
const obj = JSON.parse(lines[i]);
|
|
3741
|
+
if (obj.timestamp) {
|
|
3742
|
+
const ts = new Date(obj.timestamp).getTime();
|
|
3743
|
+
if (!isNaN(ts)) return ts;
|
|
3744
|
+
}
|
|
3745
|
+
} catch {
|
|
3746
|
+
}
|
|
3747
|
+
}
|
|
3748
|
+
} catch {
|
|
3749
|
+
} finally {
|
|
3750
|
+
await fileHandle?.close();
|
|
3751
|
+
}
|
|
3752
|
+
return void 0;
|
|
3753
|
+
}
|
|
3200
3754
|
async function extractFirstPrompt(filePath) {
|
|
3201
3755
|
let fileHandle;
|
|
3202
3756
|
try {
|
|
3203
3757
|
fileHandle = await (0, import_promises3.open)(filePath, "r");
|
|
3204
|
-
const rl = (0,
|
|
3758
|
+
const rl = (0, import_readline3.createInterface)({
|
|
3205
3759
|
input: fileHandle.createReadStream({ encoding: "utf-8" }),
|
|
3206
3760
|
crlfDelay: Infinity
|
|
3207
3761
|
});
|
|
@@ -3265,17 +3819,24 @@ async function countJsonlFilesWithMtime(dirPath) {
|
|
|
3265
3819
|
(e) => e.isDirectory() && UUID_RE.test(e.name) && !jsonlNames.has(e.name)
|
|
3266
3820
|
);
|
|
3267
3821
|
let latestMtime = 0;
|
|
3268
|
-
const
|
|
3269
|
-
|
|
3270
|
-
...
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
}
|
|
3278
|
-
|
|
3822
|
+
const jsonlEntries = entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl"));
|
|
3823
|
+
await Promise.all([
|
|
3824
|
+
...jsonlEntries.map(async (entry) => {
|
|
3825
|
+
try {
|
|
3826
|
+
const contentTs = await extractLastTimestamp((0, import_path.join)(dirPath, entry.name));
|
|
3827
|
+
const ts = contentTs ?? (await (0, import_promises3.stat)((0, import_path.join)(dirPath, entry.name))).mtimeMs;
|
|
3828
|
+
if (ts > latestMtime) latestMtime = ts;
|
|
3829
|
+
} catch {
|
|
3830
|
+
}
|
|
3831
|
+
}),
|
|
3832
|
+
...uuidDirs.map(async (entry) => {
|
|
3833
|
+
try {
|
|
3834
|
+
const fileStat = await (0, import_promises3.stat)((0, import_path.join)(dirPath, entry.name));
|
|
3835
|
+
if (fileStat.mtimeMs > latestMtime) latestMtime = fileStat.mtimeMs;
|
|
3836
|
+
} catch {
|
|
3837
|
+
}
|
|
3838
|
+
})
|
|
3839
|
+
]);
|
|
3279
3840
|
return { count: jsonlNames.size + uuidDirs.length, latestMtime };
|
|
3280
3841
|
} catch {
|
|
3281
3842
|
return { count: 0, latestMtime: 0 };
|
|
@@ -3342,14 +3903,14 @@ var PairingManager = class {
|
|
|
3342
3903
|
};
|
|
3343
3904
|
|
|
3344
3905
|
// src/auth/AuthManager.ts
|
|
3345
|
-
var import_child_process2 = require("child_process");
|
|
3346
3906
|
var import_child_process3 = require("child_process");
|
|
3907
|
+
var import_child_process4 = require("child_process");
|
|
3347
3908
|
var import_util = require("util");
|
|
3348
|
-
var
|
|
3349
|
-
var execFileAsync = (0, import_util.promisify)(
|
|
3909
|
+
var import_events3 = require("events");
|
|
3910
|
+
var execFileAsync = (0, import_util.promisify)(import_child_process4.execFile);
|
|
3350
3911
|
var CLAUDE_PATH2 = findClaudePath();
|
|
3351
3912
|
var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
3352
|
-
var AuthManager = class extends
|
|
3913
|
+
var AuthManager = class extends import_events3.EventEmitter {
|
|
3353
3914
|
loginProcess = null;
|
|
3354
3915
|
loginTimeout = null;
|
|
3355
3916
|
urlSent = false;
|
|
@@ -3377,7 +3938,7 @@ var AuthManager = class extends import_events2.EventEmitter {
|
|
|
3377
3938
|
}
|
|
3378
3939
|
this.clearLoginTimeout();
|
|
3379
3940
|
this.urlSent = false;
|
|
3380
|
-
const proc = (0,
|
|
3941
|
+
const proc = (0, import_child_process3.spawn)(CLAUDE_PATH2, ["auth", "login"], {
|
|
3381
3942
|
env: { ...process.env, BROWSER: "echo" },
|
|
3382
3943
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3383
3944
|
});
|
|
@@ -3463,8 +4024,8 @@ var AuthManager = class extends import_events2.EventEmitter {
|
|
|
3463
4024
|
var import_promises5 = require("fs/promises");
|
|
3464
4025
|
|
|
3465
4026
|
// src/terminal/TerminalExecutor.ts
|
|
3466
|
-
var
|
|
3467
|
-
var
|
|
4027
|
+
var import_node_child_process7 = require("child_process");
|
|
4028
|
+
var import_uuid5 = require("uuid");
|
|
3468
4029
|
var EXEC_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
3469
4030
|
var TerminalExecutor = class {
|
|
3470
4031
|
processes = /* @__PURE__ */ new Map();
|
|
@@ -3486,10 +4047,10 @@ var TerminalExecutor = class {
|
|
|
3486
4047
|
}
|
|
3487
4048
|
}
|
|
3488
4049
|
exec(sessionId, command, cwd) {
|
|
3489
|
-
const execId = (0,
|
|
4050
|
+
const execId = (0, import_uuid5.v4)();
|
|
3490
4051
|
const shell = isWindows ? "powershell" : "bash";
|
|
3491
4052
|
const args = isWindows ? ["-Command", command] : ["-c", command];
|
|
3492
|
-
const proc = (0,
|
|
4053
|
+
const proc = (0, import_node_child_process7.spawn)(shell, args, {
|
|
3493
4054
|
cwd,
|
|
3494
4055
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3495
4056
|
env: { ...process.env }
|
|
@@ -3552,7 +4113,7 @@ var TerminalExecutor = class {
|
|
|
3552
4113
|
// src/server.ts
|
|
3553
4114
|
var WS_PORT = 3745;
|
|
3554
4115
|
var HTTP_PORT = 3746;
|
|
3555
|
-
var execAsync = (0, import_node_util.promisify)(
|
|
4116
|
+
var execAsync = (0, import_node_util.promisify)(import_node_child_process8.exec);
|
|
3556
4117
|
async function killPortProcess(port) {
|
|
3557
4118
|
try {
|
|
3558
4119
|
if (isWindows) {
|
|
@@ -3594,8 +4155,8 @@ async function createWithRetry(label, port, factory) {
|
|
|
3594
4155
|
}
|
|
3595
4156
|
}
|
|
3596
4157
|
async function start(opts = {}) {
|
|
3597
|
-
const configDir = (0,
|
|
3598
|
-
const tokenFile = (0,
|
|
4158
|
+
const configDir = (0, import_node_path6.join)((0, import_node_os7.homedir)(), ".sessix");
|
|
4159
|
+
const tokenFile = (0, import_node_path6.join)(configDir, "token");
|
|
3599
4160
|
let token;
|
|
3600
4161
|
if (opts.token !== void 0) {
|
|
3601
4162
|
token = opts.token;
|
|
@@ -3607,14 +4168,14 @@ async function start(opts = {}) {
|
|
|
3607
4168
|
try {
|
|
3608
4169
|
token = (await (0, import_promises4.readFile)(tokenFile, "utf8")).trim();
|
|
3609
4170
|
} catch {
|
|
3610
|
-
token = (0,
|
|
4171
|
+
token = (0, import_uuid6.v4)();
|
|
3611
4172
|
await (0, import_promises4.mkdir)(configDir, { recursive: true });
|
|
3612
4173
|
await (0, import_promises4.writeFile)(tokenFile, token, "utf8");
|
|
3613
4174
|
}
|
|
3614
4175
|
}
|
|
3615
4176
|
}
|
|
3616
|
-
const
|
|
3617
|
-
const sessionManager = new SessionManager(
|
|
4177
|
+
const providerFactory = new ProviderFactory();
|
|
4178
|
+
const sessionManager = new SessionManager(providerFactory);
|
|
3618
4179
|
const terminalExecutor = new TerminalExecutor();
|
|
3619
4180
|
const wsBridge = await createWithRetry(
|
|
3620
4181
|
"WsBridge",
|
|
@@ -3654,7 +4215,7 @@ async function start(opts = {}) {
|
|
|
3654
4215
|
let mdnsService = null;
|
|
3655
4216
|
const pairingManager = new PairingManager({
|
|
3656
4217
|
token,
|
|
3657
|
-
serverName: (0,
|
|
4218
|
+
serverName: (0, import_node_os7.hostname)(),
|
|
3658
4219
|
version: "0.2.0",
|
|
3659
4220
|
onStateChange: (state) => mdnsService?.updatePairingState(state)
|
|
3660
4221
|
});
|
|
@@ -3711,7 +4272,8 @@ async function start(opts = {}) {
|
|
|
3711
4272
|
event.model,
|
|
3712
4273
|
event.permissionMode,
|
|
3713
4274
|
event.effort,
|
|
3714
|
-
event.images
|
|
4275
|
+
event.images,
|
|
4276
|
+
event.agentType
|
|
3715
4277
|
);
|
|
3716
4278
|
wsBridge.broadcast({
|
|
3717
4279
|
type: "session_list",
|
|
@@ -3887,7 +4449,7 @@ async function start(opts = {}) {
|
|
|
3887
4449
|
return null;
|
|
3888
4450
|
}).filter(Boolean).join("\n");
|
|
3889
4451
|
}
|
|
3890
|
-
const suggestion = await
|
|
4452
|
+
const suggestion = await providerFactory.getProvider("claude-code").generateSuggestion(context);
|
|
3891
4453
|
wsBridge.send(ws, {
|
|
3892
4454
|
type: "prompt_suggestion",
|
|
3893
4455
|
sessionId: event.sessionId,
|
|
@@ -3909,9 +4471,9 @@ async function start(opts = {}) {
|
|
|
3909
4471
|
}
|
|
3910
4472
|
case "terminal_exec": {
|
|
3911
4473
|
const activeSession = sessionManager.getActiveSessions().find((s) => s.id === event.sessionId);
|
|
3912
|
-
const cwd = activeSession?.projectPath ?? sessionManager.getSessionProjectPath(event.sessionId);
|
|
4474
|
+
const cwd = activeSession?.projectPath ?? sessionManager.getSessionProjectPath(event.sessionId) ?? event.projectPath;
|
|
3913
4475
|
if (!cwd) {
|
|
3914
|
-
wsBridge.send(ws, { type: "error", code: "TERMINAL_EXEC_ERROR", message:
|
|
4476
|
+
wsBridge.send(ws, { type: "error", code: "TERMINAL_EXEC_ERROR", message: `Session not found (id: ${event.sessionId.slice(0, 8)}\u2026)`, sessionId: event.sessionId });
|
|
3915
4477
|
break;
|
|
3916
4478
|
}
|
|
3917
4479
|
terminalExecutor.exec(event.sessionId, event.command, cwd);
|
|
@@ -3965,6 +4527,11 @@ async function start(opts = {}) {
|
|
|
3965
4527
|
}
|
|
3966
4528
|
break;
|
|
3967
4529
|
}
|
|
4530
|
+
case "list_agents": {
|
|
4531
|
+
const agents = await providerFactory.detectAgents();
|
|
4532
|
+
wsBridge.send(ws, { type: "agent_list", agents });
|
|
4533
|
+
break;
|
|
4534
|
+
}
|
|
3968
4535
|
default: {
|
|
3969
4536
|
wsBridge.send(ws, {
|
|
3970
4537
|
type: "error",
|
|
@@ -4128,7 +4695,7 @@ async function start(opts = {}) {
|
|
|
4128
4695
|
openPairing: (duration) => pairingManager.open(duration),
|
|
4129
4696
|
closePairing: () => pairingManager.close(),
|
|
4130
4697
|
regenerateToken: async () => {
|
|
4131
|
-
const newToken = (0,
|
|
4698
|
+
const newToken = (0, import_uuid6.v4)();
|
|
4132
4699
|
await (0, import_promises4.mkdir)(configDir, { recursive: true });
|
|
4133
4700
|
await (0, import_promises4.writeFile)(tokenFile, newToken, "utf8");
|
|
4134
4701
|
instance.token = newToken;
|