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/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");
|
|
@@ -3234,7 +3755,7 @@ async function extractFirstPrompt(filePath) {
|
|
|
3234
3755
|
let fileHandle;
|
|
3235
3756
|
try {
|
|
3236
3757
|
fileHandle = await (0, import_promises3.open)(filePath, "r");
|
|
3237
|
-
const rl = (0,
|
|
3758
|
+
const rl = (0, import_readline3.createInterface)({
|
|
3238
3759
|
input: fileHandle.createReadStream({ encoding: "utf-8" }),
|
|
3239
3760
|
crlfDelay: Infinity
|
|
3240
3761
|
});
|
|
@@ -3382,14 +3903,14 @@ var PairingManager = class {
|
|
|
3382
3903
|
};
|
|
3383
3904
|
|
|
3384
3905
|
// src/auth/AuthManager.ts
|
|
3385
|
-
var import_child_process2 = require("child_process");
|
|
3386
3906
|
var import_child_process3 = require("child_process");
|
|
3907
|
+
var import_child_process4 = require("child_process");
|
|
3387
3908
|
var import_util = require("util");
|
|
3388
|
-
var
|
|
3389
|
-
var execFileAsync = (0, import_util.promisify)(
|
|
3909
|
+
var import_events3 = require("events");
|
|
3910
|
+
var execFileAsync = (0, import_util.promisify)(import_child_process4.execFile);
|
|
3390
3911
|
var CLAUDE_PATH2 = findClaudePath();
|
|
3391
3912
|
var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
3392
|
-
var AuthManager = class extends
|
|
3913
|
+
var AuthManager = class extends import_events3.EventEmitter {
|
|
3393
3914
|
loginProcess = null;
|
|
3394
3915
|
loginTimeout = null;
|
|
3395
3916
|
urlSent = false;
|
|
@@ -3417,7 +3938,7 @@ var AuthManager = class extends import_events2.EventEmitter {
|
|
|
3417
3938
|
}
|
|
3418
3939
|
this.clearLoginTimeout();
|
|
3419
3940
|
this.urlSent = false;
|
|
3420
|
-
const proc = (0,
|
|
3941
|
+
const proc = (0, import_child_process3.spawn)(CLAUDE_PATH2, ["auth", "login"], {
|
|
3421
3942
|
env: { ...process.env, BROWSER: "echo" },
|
|
3422
3943
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3423
3944
|
});
|
|
@@ -3503,8 +4024,8 @@ var AuthManager = class extends import_events2.EventEmitter {
|
|
|
3503
4024
|
var import_promises5 = require("fs/promises");
|
|
3504
4025
|
|
|
3505
4026
|
// src/terminal/TerminalExecutor.ts
|
|
3506
|
-
var
|
|
3507
|
-
var
|
|
4027
|
+
var import_node_child_process7 = require("child_process");
|
|
4028
|
+
var import_uuid5 = require("uuid");
|
|
3508
4029
|
var EXEC_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
3509
4030
|
var TerminalExecutor = class {
|
|
3510
4031
|
processes = /* @__PURE__ */ new Map();
|
|
@@ -3526,10 +4047,10 @@ var TerminalExecutor = class {
|
|
|
3526
4047
|
}
|
|
3527
4048
|
}
|
|
3528
4049
|
exec(sessionId, command, cwd) {
|
|
3529
|
-
const execId = (0,
|
|
4050
|
+
const execId = (0, import_uuid5.v4)();
|
|
3530
4051
|
const shell = isWindows ? "powershell" : "bash";
|
|
3531
4052
|
const args = isWindows ? ["-Command", command] : ["-c", command];
|
|
3532
|
-
const proc = (0,
|
|
4053
|
+
const proc = (0, import_node_child_process7.spawn)(shell, args, {
|
|
3533
4054
|
cwd,
|
|
3534
4055
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3535
4056
|
env: { ...process.env }
|
|
@@ -3592,7 +4113,7 @@ var TerminalExecutor = class {
|
|
|
3592
4113
|
// src/server.ts
|
|
3593
4114
|
var WS_PORT = 3745;
|
|
3594
4115
|
var HTTP_PORT = 3746;
|
|
3595
|
-
var execAsync = (0, import_node_util.promisify)(
|
|
4116
|
+
var execAsync = (0, import_node_util.promisify)(import_node_child_process8.exec);
|
|
3596
4117
|
async function killPortProcess(port) {
|
|
3597
4118
|
try {
|
|
3598
4119
|
if (isWindows) {
|
|
@@ -3634,8 +4155,8 @@ async function createWithRetry(label, port, factory) {
|
|
|
3634
4155
|
}
|
|
3635
4156
|
}
|
|
3636
4157
|
async function start(opts = {}) {
|
|
3637
|
-
const configDir = (0,
|
|
3638
|
-
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");
|
|
3639
4160
|
let token;
|
|
3640
4161
|
if (opts.token !== void 0) {
|
|
3641
4162
|
token = opts.token;
|
|
@@ -3647,14 +4168,14 @@ async function start(opts = {}) {
|
|
|
3647
4168
|
try {
|
|
3648
4169
|
token = (await (0, import_promises4.readFile)(tokenFile, "utf8")).trim();
|
|
3649
4170
|
} catch {
|
|
3650
|
-
token = (0,
|
|
4171
|
+
token = (0, import_uuid6.v4)();
|
|
3651
4172
|
await (0, import_promises4.mkdir)(configDir, { recursive: true });
|
|
3652
4173
|
await (0, import_promises4.writeFile)(tokenFile, token, "utf8");
|
|
3653
4174
|
}
|
|
3654
4175
|
}
|
|
3655
4176
|
}
|
|
3656
|
-
const
|
|
3657
|
-
const sessionManager = new SessionManager(
|
|
4177
|
+
const providerFactory = new ProviderFactory();
|
|
4178
|
+
const sessionManager = new SessionManager(providerFactory);
|
|
3658
4179
|
const terminalExecutor = new TerminalExecutor();
|
|
3659
4180
|
const wsBridge = await createWithRetry(
|
|
3660
4181
|
"WsBridge",
|
|
@@ -3694,7 +4215,7 @@ async function start(opts = {}) {
|
|
|
3694
4215
|
let mdnsService = null;
|
|
3695
4216
|
const pairingManager = new PairingManager({
|
|
3696
4217
|
token,
|
|
3697
|
-
serverName: (0,
|
|
4218
|
+
serverName: (0, import_node_os7.hostname)(),
|
|
3698
4219
|
version: "0.2.0",
|
|
3699
4220
|
onStateChange: (state) => mdnsService?.updatePairingState(state)
|
|
3700
4221
|
});
|
|
@@ -3751,7 +4272,8 @@ async function start(opts = {}) {
|
|
|
3751
4272
|
event.model,
|
|
3752
4273
|
event.permissionMode,
|
|
3753
4274
|
event.effort,
|
|
3754
|
-
event.images
|
|
4275
|
+
event.images,
|
|
4276
|
+
event.agentType
|
|
3755
4277
|
);
|
|
3756
4278
|
wsBridge.broadcast({
|
|
3757
4279
|
type: "session_list",
|
|
@@ -3927,7 +4449,7 @@ async function start(opts = {}) {
|
|
|
3927
4449
|
return null;
|
|
3928
4450
|
}).filter(Boolean).join("\n");
|
|
3929
4451
|
}
|
|
3930
|
-
const suggestion = await
|
|
4452
|
+
const suggestion = await providerFactory.getProvider("claude-code").generateSuggestion(context);
|
|
3931
4453
|
wsBridge.send(ws, {
|
|
3932
4454
|
type: "prompt_suggestion",
|
|
3933
4455
|
sessionId: event.sessionId,
|
|
@@ -4005,6 +4527,11 @@ async function start(opts = {}) {
|
|
|
4005
4527
|
}
|
|
4006
4528
|
break;
|
|
4007
4529
|
}
|
|
4530
|
+
case "list_agents": {
|
|
4531
|
+
const agents = await providerFactory.detectAgents();
|
|
4532
|
+
wsBridge.send(ws, { type: "agent_list", agents });
|
|
4533
|
+
break;
|
|
4534
|
+
}
|
|
4008
4535
|
default: {
|
|
4009
4536
|
wsBridge.send(ws, {
|
|
4010
4537
|
type: "error",
|
|
@@ -4168,7 +4695,7 @@ async function start(opts = {}) {
|
|
|
4168
4695
|
openPairing: (duration) => pairingManager.open(duration),
|
|
4169
4696
|
closePairing: () => pairingManager.close(),
|
|
4170
4697
|
regenerateToken: async () => {
|
|
4171
|
-
const newToken = (0,
|
|
4698
|
+
const newToken = (0, import_uuid6.v4)();
|
|
4172
4699
|
await (0, import_promises4.mkdir)(configDir, { recursive: true });
|
|
4173
4700
|
await (0, import_promises4.writeFile)(tokenFile, newToken, "utf8");
|
|
4174
4701
|
instance.token = newToken;
|