sessix-server 0.3.0 → 0.3.1
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 +208 -58
- package/dist/server.js +208 -58
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -326,14 +326,14 @@ var import_node_os = require("os");
|
|
|
326
326
|
var import_node_child_process = require("child_process");
|
|
327
327
|
var isWindows = process.platform === "win32";
|
|
328
328
|
function killProcessCrossPlatform(proc, timeoutMs = 3e3) {
|
|
329
|
-
return new Promise((
|
|
329
|
+
return new Promise((resolve2) => {
|
|
330
330
|
if (proc.exitCode !== null || proc.signalCode !== null) {
|
|
331
|
-
|
|
331
|
+
resolve2();
|
|
332
332
|
return;
|
|
333
333
|
}
|
|
334
334
|
const onExit = () => {
|
|
335
335
|
clearTimeout(timer);
|
|
336
|
-
|
|
336
|
+
resolve2();
|
|
337
337
|
};
|
|
338
338
|
proc.once("exit", onExit);
|
|
339
339
|
if (isWindows) {
|
|
@@ -349,7 +349,7 @@ function killProcessCrossPlatform(proc, timeoutMs = 3e3) {
|
|
|
349
349
|
proc.kill("SIGKILL");
|
|
350
350
|
}
|
|
351
351
|
}
|
|
352
|
-
|
|
352
|
+
resolve2();
|
|
353
353
|
}, timeoutMs);
|
|
354
354
|
});
|
|
355
355
|
}
|
|
@@ -781,7 +781,7 @@ var ProcessProvider = class {
|
|
|
781
781
|
const prompt = `You are an AI coding assistant. Based on the following Claude Code conversation context, suggest the most valuable next instruction for the user (give the instruction directly, no explanation, no quotes):
|
|
782
782
|
|
|
783
783
|
${context}`;
|
|
784
|
-
return new Promise((
|
|
784
|
+
return new Promise((resolve2, reject) => {
|
|
785
785
|
const env = { ...process.env };
|
|
786
786
|
delete env.CLAUDECODE;
|
|
787
787
|
const proc = (0, import_child_process.spawn)(CLAUDE_PATH, ["-p", prompt, "--output-format", "text"], {
|
|
@@ -796,7 +796,7 @@ ${context}`;
|
|
|
796
796
|
});
|
|
797
797
|
proc.once("exit", (code) => {
|
|
798
798
|
if (code === 0) {
|
|
799
|
-
|
|
799
|
+
resolve2(output.trim());
|
|
800
800
|
} else {
|
|
801
801
|
reject(new Error(`generateSuggestion process exit code: ${code}`));
|
|
802
802
|
}
|
|
@@ -823,10 +823,10 @@ ${context}`;
|
|
|
823
823
|
tool_use_id: toolUseId,
|
|
824
824
|
content: answer
|
|
825
825
|
});
|
|
826
|
-
await new Promise((
|
|
826
|
+
await new Promise((resolve2, reject) => {
|
|
827
827
|
entry.process.stdin.write(toolResult + "\n", (err) => {
|
|
828
828
|
if (err) reject(err);
|
|
829
|
-
else
|
|
829
|
+
else resolve2();
|
|
830
830
|
});
|
|
831
831
|
});
|
|
832
832
|
console.log(`[ProcessProvider] Session ${sessionId}: AskUserQuestion answered (toolUseId=${toolUseId})`);
|
|
@@ -861,6 +861,9 @@ ${context}`;
|
|
|
861
861
|
var import_child_process2 = require("child_process");
|
|
862
862
|
var import_readline2 = require("readline");
|
|
863
863
|
var import_events2 = require("events");
|
|
864
|
+
var import_fs = require("fs");
|
|
865
|
+
var import_path = require("path");
|
|
866
|
+
var import_os = require("os");
|
|
864
867
|
var import_uuid2 = require("uuid");
|
|
865
868
|
|
|
866
869
|
// src/utils/codexPath.ts
|
|
@@ -891,10 +894,31 @@ function findCodexPath() {
|
|
|
891
894
|
}
|
|
892
895
|
return "codex";
|
|
893
896
|
}
|
|
897
|
+
function resolveCodexJsEntry(codexPath) {
|
|
898
|
+
try {
|
|
899
|
+
let realPath;
|
|
900
|
+
try {
|
|
901
|
+
realPath = (0, import_node_fs2.readlinkSync)(codexPath);
|
|
902
|
+
if (!realPath.startsWith("/")) {
|
|
903
|
+
realPath = (0, import_node_path2.resolve)((0, import_node_path2.dirname)(codexPath), realPath);
|
|
904
|
+
}
|
|
905
|
+
} catch {
|
|
906
|
+
realPath = codexPath;
|
|
907
|
+
}
|
|
908
|
+
const head = (0, import_node_fs2.readFileSync)(realPath, { encoding: "utf-8", flag: "r" }).slice(0, 100);
|
|
909
|
+
if (head.startsWith("#!/usr/bin/env node") || head.startsWith("#!/usr/bin/node")) {
|
|
910
|
+
return realPath;
|
|
911
|
+
}
|
|
912
|
+
} catch {
|
|
913
|
+
}
|
|
914
|
+
return void 0;
|
|
915
|
+
}
|
|
894
916
|
var CODEX_PATH = findCodexPath();
|
|
917
|
+
var CODEX_JS_ENTRY = resolveCodexJsEntry(CODEX_PATH);
|
|
895
918
|
async function getCodexVersion() {
|
|
896
919
|
try {
|
|
897
|
-
const
|
|
920
|
+
const cmd = CODEX_JS_ENTRY ? `"${process.execPath}" "${CODEX_JS_ENTRY}" --version` : `"${CODEX_PATH}" --version`;
|
|
921
|
+
const output = (0, import_node_child_process3.execSync)(cmd, {
|
|
898
922
|
encoding: "utf-8",
|
|
899
923
|
timeout: 5e3
|
|
900
924
|
}).trim();
|
|
@@ -904,6 +928,14 @@ async function getCodexVersion() {
|
|
|
904
928
|
}
|
|
905
929
|
}
|
|
906
930
|
function isCodexAvailable() {
|
|
931
|
+
if (CODEX_JS_ENTRY) {
|
|
932
|
+
try {
|
|
933
|
+
(0, import_node_fs2.accessSync)(CODEX_JS_ENTRY, import_node_fs2.constants.R_OK);
|
|
934
|
+
return true;
|
|
935
|
+
} catch {
|
|
936
|
+
return false;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
907
939
|
try {
|
|
908
940
|
(0, import_node_fs2.accessSync)(CODEX_PATH, import_node_fs2.constants.X_OK);
|
|
909
941
|
return true;
|
|
@@ -918,11 +950,18 @@ function isCodexAvailable() {
|
|
|
918
950
|
}
|
|
919
951
|
|
|
920
952
|
// src/providers/CodexProvider.ts
|
|
953
|
+
var SESSIX_DIR = (0, import_path.join)((0, import_os.homedir)(), ".sessix");
|
|
954
|
+
var CODEX_SESSIONS_FILE = (0, import_path.join)(SESSIX_DIR, "codex-sessions.json");
|
|
921
955
|
var CodexProvider = class {
|
|
922
956
|
activeSessions = /* @__PURE__ */ new Map();
|
|
923
957
|
emitter = new import_events2.EventEmitter();
|
|
958
|
+
/** 持久化的会话元数据(sessionId → metadata) */
|
|
959
|
+
persistedSessions = /* @__PURE__ */ new Map();
|
|
924
960
|
/** 自增 ID 计数器,用于生成 ClaudeStreamEvent 中的 message/block ID */
|
|
925
961
|
idCounter = 0;
|
|
962
|
+
constructor() {
|
|
963
|
+
this.loadPersistedSessions();
|
|
964
|
+
}
|
|
926
965
|
async startSession(opts) {
|
|
927
966
|
const { projectPath, message, sessionId: existingSessionId } = opts;
|
|
928
967
|
const sessionId = existingSessionId ?? (0, import_uuid2.v4)();
|
|
@@ -941,12 +980,22 @@ var CodexProvider = class {
|
|
|
941
980
|
agentType: "codex"
|
|
942
981
|
};
|
|
943
982
|
const resume = opts.resume ?? !!existingSessionId;
|
|
944
|
-
|
|
983
|
+
let resumeThreadId;
|
|
984
|
+
if (resume && existingSessionId) {
|
|
985
|
+
const persisted = this.persistedSessions.get(existingSessionId);
|
|
986
|
+
if (persisted?.threadId) {
|
|
987
|
+
resumeThreadId = persisted.threadId;
|
|
988
|
+
console.log(`[CodexProvider] Resuming session ${sessionId} with threadId: ${resumeThreadId}`);
|
|
989
|
+
} else {
|
|
990
|
+
console.warn(`[CodexProvider] Session ${sessionId} resume requested but no persisted threadId found, creating new session`);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
const proc = this.spawnCodexProcess(projectPath, message, resumeThreadId, opts.model);
|
|
945
994
|
session.pid = proc.pid;
|
|
946
995
|
this.activeSessions.set(sessionId, {
|
|
947
996
|
session,
|
|
948
997
|
process: proc,
|
|
949
|
-
threadId:
|
|
998
|
+
threadId: resumeThreadId,
|
|
950
999
|
turnStartTime: Date.now()
|
|
951
1000
|
});
|
|
952
1001
|
const initEvent = {
|
|
@@ -978,22 +1027,45 @@ var CodexProvider = class {
|
|
|
978
1027
|
this.activeSessions.delete(sessionId);
|
|
979
1028
|
}
|
|
980
1029
|
async sendMessage(sessionId, message) {
|
|
981
|
-
|
|
1030
|
+
let entry = this.activeSessions.get(sessionId);
|
|
982
1031
|
if (!entry) {
|
|
983
|
-
|
|
1032
|
+
const persisted = this.persistedSessions.get(sessionId);
|
|
1033
|
+
if (!persisted?.threadId) {
|
|
1034
|
+
throw new Error(`Session ${sessionId} not found or already ended`);
|
|
1035
|
+
}
|
|
1036
|
+
const projectId = persisted.projectPath.split("/").filter(Boolean).pop() ?? "unknown";
|
|
1037
|
+
const session = {
|
|
1038
|
+
id: sessionId,
|
|
1039
|
+
projectId,
|
|
1040
|
+
projectPath: persisted.projectPath,
|
|
1041
|
+
status: "running",
|
|
1042
|
+
createdAt: persisted.createdAt,
|
|
1043
|
+
lastActiveAt: Date.now(),
|
|
1044
|
+
summary: persisted.summary,
|
|
1045
|
+
agentType: "codex"
|
|
1046
|
+
};
|
|
1047
|
+
const placeholderProc = (0, import_child_process2.spawn)("true", [], { stdio: "ignore" });
|
|
1048
|
+
entry = {
|
|
1049
|
+
session,
|
|
1050
|
+
process: placeholderProc,
|
|
1051
|
+
threadId: persisted.threadId,
|
|
1052
|
+
turnStartTime: Date.now()
|
|
1053
|
+
};
|
|
1054
|
+
this.activeSessions.set(sessionId, entry);
|
|
984
1055
|
}
|
|
985
|
-
|
|
1056
|
+
const procAlive = entry.process.exitCode === null && entry.process.signalCode === null;
|
|
1057
|
+
if (procAlive) {
|
|
986
1058
|
try {
|
|
987
1059
|
entry.process.stdin?.end();
|
|
988
1060
|
} catch {
|
|
989
1061
|
}
|
|
990
1062
|
await killProcessCrossPlatform(entry.process);
|
|
991
1063
|
}
|
|
992
|
-
const threadId = entry.threadId;
|
|
1064
|
+
const threadId = entry.threadId ?? this.persistedSessions.get(sessionId)?.threadId;
|
|
993
1065
|
if (!threadId) {
|
|
994
|
-
|
|
1066
|
+
console.warn(`[CodexProvider] Session ${sessionId} has no thread ID, falling back to new thread`);
|
|
995
1067
|
}
|
|
996
|
-
const proc = this.spawnCodexProcess(entry.session.projectPath, message, threadId);
|
|
1068
|
+
const proc = this.spawnCodexProcess(entry.session.projectPath, message, threadId ?? void 0);
|
|
997
1069
|
entry.session.status = "running";
|
|
998
1070
|
entry.session.lastActiveAt = Date.now();
|
|
999
1071
|
entry.session.pid = proc.pid;
|
|
@@ -1040,16 +1112,29 @@ var CodexProvider = class {
|
|
|
1040
1112
|
* @param message 用户消息
|
|
1041
1113
|
* @param resumeThreadId 如果提供,则使用 `codex exec resume` 恢复会话
|
|
1042
1114
|
*/
|
|
1043
|
-
spawnCodexProcess(projectPath, message, resumeThreadId) {
|
|
1115
|
+
spawnCodexProcess(projectPath, message, resumeThreadId, model) {
|
|
1044
1116
|
const args = ["exec", "--json", "--full-auto"];
|
|
1117
|
+
if (model) {
|
|
1118
|
+
args.push("-m", model);
|
|
1119
|
+
}
|
|
1120
|
+
args.push("-C", projectPath);
|
|
1045
1121
|
if (resumeThreadId) {
|
|
1046
1122
|
args.push("resume", resumeThreadId);
|
|
1047
|
-
} else {
|
|
1048
|
-
args.push("-C", projectPath);
|
|
1049
1123
|
}
|
|
1050
1124
|
args.push(message);
|
|
1051
1125
|
const env = { ...process.env };
|
|
1052
|
-
|
|
1126
|
+
let cmd;
|
|
1127
|
+
let spawnArgs;
|
|
1128
|
+
if (CODEX_JS_ENTRY) {
|
|
1129
|
+
cmd = process.execPath;
|
|
1130
|
+
spawnArgs = [CODEX_JS_ENTRY, ...args];
|
|
1131
|
+
console.log(`[CodexProvider] Spawning via node: ${cmd} ${CODEX_JS_ENTRY} ${args.join(" ")}`);
|
|
1132
|
+
} else {
|
|
1133
|
+
cmd = CODEX_PATH;
|
|
1134
|
+
spawnArgs = args;
|
|
1135
|
+
console.log(`[CodexProvider] Spawning: ${CODEX_PATH} ${args.join(" ")}`);
|
|
1136
|
+
}
|
|
1137
|
+
const proc = (0, import_child_process2.spawn)(cmd, spawnArgs, {
|
|
1053
1138
|
cwd: projectPath,
|
|
1054
1139
|
env,
|
|
1055
1140
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -1071,6 +1156,7 @@ var CodexProvider = class {
|
|
|
1071
1156
|
rl.on("line", (line) => {
|
|
1072
1157
|
const trimmed = line.trim();
|
|
1073
1158
|
if (!trimmed) return;
|
|
1159
|
+
console.log(`[CodexProvider] Session ${sessionId} stdout: ${trimmed.substring(0, 200)}`);
|
|
1074
1160
|
let event;
|
|
1075
1161
|
try {
|
|
1076
1162
|
event = JSON.parse(trimmed);
|
|
@@ -1092,6 +1178,14 @@ var CodexProvider = class {
|
|
|
1092
1178
|
case "thread.started": {
|
|
1093
1179
|
if (event.thread_id) {
|
|
1094
1180
|
entry.threadId = event.thread_id;
|
|
1181
|
+
this.persistSession(sessionId, {
|
|
1182
|
+
threadId: event.thread_id,
|
|
1183
|
+
projectPath: entry.session.projectPath,
|
|
1184
|
+
summary: entry.session.summary,
|
|
1185
|
+
createdAt: entry.session.createdAt,
|
|
1186
|
+
lastActiveAt: Date.now()
|
|
1187
|
+
});
|
|
1188
|
+
console.log(`[CodexProvider] Session ${sessionId} threadId persisted: ${event.thread_id}`);
|
|
1095
1189
|
}
|
|
1096
1190
|
break;
|
|
1097
1191
|
}
|
|
@@ -1255,6 +1349,7 @@ var CodexProvider = class {
|
|
|
1255
1349
|
const entry = this.activeSessions.get(sessionId);
|
|
1256
1350
|
if (!entry) return;
|
|
1257
1351
|
if (entry.process !== proc) return;
|
|
1352
|
+
console.log(`[CodexProvider] Session ${sessionId} process exited: code=${code} signal=${signal} threadId=${entry.threadId ?? "NONE"}`);
|
|
1258
1353
|
if (entry.rl) {
|
|
1259
1354
|
entry.rl.close();
|
|
1260
1355
|
entry.rl = void 0;
|
|
@@ -1295,6 +1390,54 @@ var CodexProvider = class {
|
|
|
1295
1390
|
getEventName(sessionId) {
|
|
1296
1391
|
return `claude:${sessionId}`;
|
|
1297
1392
|
}
|
|
1393
|
+
// ============================================
|
|
1394
|
+
// 持久化方法
|
|
1395
|
+
// ============================================
|
|
1396
|
+
/**
|
|
1397
|
+
* 从磁盘加载持久化的 Codex 会话元数据
|
|
1398
|
+
*/
|
|
1399
|
+
loadPersistedSessions() {
|
|
1400
|
+
try {
|
|
1401
|
+
if (!(0, import_fs.existsSync)(CODEX_SESSIONS_FILE)) return;
|
|
1402
|
+
const data = JSON.parse((0, import_fs.readFileSync)(CODEX_SESSIONS_FILE, "utf-8"));
|
|
1403
|
+
for (const [sessionId, meta] of Object.entries(data)) {
|
|
1404
|
+
this.persistedSessions.set(sessionId, meta);
|
|
1405
|
+
}
|
|
1406
|
+
console.log(`[CodexProvider] Loaded ${this.persistedSessions.size} persisted sessions`);
|
|
1407
|
+
} catch (err) {
|
|
1408
|
+
console.warn("[CodexProvider] Failed to load persisted sessions:", err);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
/**
|
|
1412
|
+
* 持久化单个会话的元数据到磁盘
|
|
1413
|
+
*/
|
|
1414
|
+
persistSession(sessionId, meta) {
|
|
1415
|
+
this.persistedSessions.set(sessionId, meta);
|
|
1416
|
+
this.flushPersistedSessions();
|
|
1417
|
+
}
|
|
1418
|
+
/**
|
|
1419
|
+
* 将所有持久化数据写入磁盘
|
|
1420
|
+
*/
|
|
1421
|
+
flushPersistedSessions() {
|
|
1422
|
+
try {
|
|
1423
|
+
if (!(0, import_fs.existsSync)(SESSIX_DIR)) {
|
|
1424
|
+
(0, import_fs.mkdirSync)(SESSIX_DIR, { recursive: true });
|
|
1425
|
+
}
|
|
1426
|
+
const data = {};
|
|
1427
|
+
for (const [sessionId, meta] of this.persistedSessions) {
|
|
1428
|
+
data[sessionId] = meta;
|
|
1429
|
+
}
|
|
1430
|
+
(0, import_fs.writeFileSync)(CODEX_SESSIONS_FILE, JSON.stringify(data, null, 2), "utf-8");
|
|
1431
|
+
} catch (err) {
|
|
1432
|
+
console.error("[CodexProvider] Failed to persist sessions:", err);
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
/**
|
|
1436
|
+
* 检查某个 sessionId 是否为已知的 Codex 会话(供 SessionManager 查询 agentType)
|
|
1437
|
+
*/
|
|
1438
|
+
isKnownSession(sessionId) {
|
|
1439
|
+
return this.activeSessions.has(sessionId) || this.persistedSessions.has(sessionId);
|
|
1440
|
+
}
|
|
1298
1441
|
};
|
|
1299
1442
|
|
|
1300
1443
|
// src/providers/ProviderFactory.ts
|
|
@@ -1387,8 +1530,15 @@ var SessionManager = class {
|
|
|
1387
1530
|
}
|
|
1388
1531
|
getProviderForSession(sessionId) {
|
|
1389
1532
|
if (!this.providerFactory) return this.provider;
|
|
1390
|
-
|
|
1391
|
-
|
|
1533
|
+
let agentType = this.sessionAgentType.get(sessionId);
|
|
1534
|
+
if (!agentType) {
|
|
1535
|
+
const codexProvider = this.providerFactory.getProvider("codex");
|
|
1536
|
+
if (codexProvider instanceof CodexProvider && codexProvider.isKnownSession(sessionId)) {
|
|
1537
|
+
agentType = "codex";
|
|
1538
|
+
this.sessionAgentType.set(sessionId, agentType);
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
return this.providerFactory.getProvider(agentType ?? "claude-code");
|
|
1392
1542
|
}
|
|
1393
1543
|
// ============================================
|
|
1394
1544
|
// 公开 API
|
|
@@ -1767,8 +1917,8 @@ var SessionManager = class {
|
|
|
1767
1917
|
};
|
|
1768
1918
|
this.updateSessionStatus(sessionId, "waiting_question");
|
|
1769
1919
|
this.emit({ type: "question_request", request });
|
|
1770
|
-
const answerPromise = new Promise((
|
|
1771
|
-
this.pendingQuestions.set(requestId, { sessionId, toolUseId, question, options, createdAt: request.createdAt, resolve });
|
|
1920
|
+
const answerPromise = new Promise((resolve2) => {
|
|
1921
|
+
this.pendingQuestions.set(requestId, { sessionId, toolUseId, question, options, createdAt: request.createdAt, resolve: resolve2 });
|
|
1772
1922
|
});
|
|
1773
1923
|
answerPromise.then(async (answer) => {
|
|
1774
1924
|
try {
|
|
@@ -2010,11 +2160,11 @@ var WsBridge = class _WsBridge {
|
|
|
2010
2160
|
* 使用此方法代替 new WsBridge(),确保 EADDRINUSE 等错误能被调用方的 try-catch 捕获。
|
|
2011
2161
|
*/
|
|
2012
2162
|
static async create(options) {
|
|
2013
|
-
return new Promise((
|
|
2163
|
+
return new Promise((resolve2, reject) => {
|
|
2014
2164
|
const bridge = new _WsBridge(options);
|
|
2015
2165
|
bridge.wss.once("listening", () => {
|
|
2016
2166
|
bridge.wss.on("error", (err) => console.error(`[WsBridge] ${t("ws.serverError")}:`, err));
|
|
2017
|
-
|
|
2167
|
+
resolve2(bridge);
|
|
2018
2168
|
});
|
|
2019
2169
|
bridge.wss.once("error", reject);
|
|
2020
2170
|
});
|
|
@@ -2077,7 +2227,7 @@ var WsBridge = class _WsBridge {
|
|
|
2077
2227
|
}
|
|
2078
2228
|
/** 优雅关闭 WebSocket 服务 */
|
|
2079
2229
|
close() {
|
|
2080
|
-
return new Promise((
|
|
2230
|
+
return new Promise((resolve2, reject) => {
|
|
2081
2231
|
if (this.heartbeatTimer) {
|
|
2082
2232
|
clearInterval(this.heartbeatTimer);
|
|
2083
2233
|
this.heartbeatTimer = null;
|
|
@@ -2090,7 +2240,7 @@ var WsBridge = class _WsBridge {
|
|
|
2090
2240
|
reject(err);
|
|
2091
2241
|
} else {
|
|
2092
2242
|
console.log("[WsBridge] WebSocket server closed");
|
|
2093
|
-
|
|
2243
|
+
resolve2();
|
|
2094
2244
|
}
|
|
2095
2245
|
});
|
|
2096
2246
|
});
|
|
@@ -2230,11 +2380,11 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2230
2380
|
* 异步工厂方法:等待端口监听成功后 resolve,端口占用等错误时 reject。
|
|
2231
2381
|
*/
|
|
2232
2382
|
static async create(options) {
|
|
2233
|
-
return new Promise((
|
|
2383
|
+
return new Promise((resolve2, reject) => {
|
|
2234
2384
|
const proxy = new _ApprovalProxy(options);
|
|
2235
2385
|
proxy.server.once("listening", () => {
|
|
2236
2386
|
proxy.server.on("error", (err) => console.error(`[ApprovalProxy] ${t("approval.serverError")}:`, err));
|
|
2237
|
-
|
|
2387
|
+
resolve2(proxy);
|
|
2238
2388
|
});
|
|
2239
2389
|
proxy.server.once("error", reject);
|
|
2240
2390
|
});
|
|
@@ -2391,7 +2541,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2391
2541
|
}
|
|
2392
2542
|
/** 优雅关闭 HTTP 服务 */
|
|
2393
2543
|
close() {
|
|
2394
|
-
return new Promise((
|
|
2544
|
+
return new Promise((resolve2, reject) => {
|
|
2395
2545
|
const pendingEntries = Array.from(this.pendingApprovals.entries());
|
|
2396
2546
|
for (const [, pending] of pendingEntries) {
|
|
2397
2547
|
clearTimeout(pending.timer);
|
|
@@ -2403,7 +2553,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2403
2553
|
reject(err);
|
|
2404
2554
|
} else {
|
|
2405
2555
|
console.log(`[ApprovalProxy] ${t("approval.httpClosed")}`);
|
|
2406
|
-
|
|
2556
|
+
resolve2();
|
|
2407
2557
|
}
|
|
2408
2558
|
});
|
|
2409
2559
|
});
|
|
@@ -2474,13 +2624,13 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2474
2624
|
return;
|
|
2475
2625
|
}
|
|
2476
2626
|
this.notifyApprovalRequest(approvalRequest);
|
|
2477
|
-
const decision = await new Promise((
|
|
2627
|
+
const decision = await new Promise((resolve2) => {
|
|
2478
2628
|
const timer = setTimeout(() => {
|
|
2479
2629
|
console.log(`[ApprovalProxy] ${t("approval.timeout", { id: requestId })}`);
|
|
2480
2630
|
this.pendingApprovals.delete(requestId);
|
|
2481
|
-
|
|
2631
|
+
resolve2({ decision: "allow" });
|
|
2482
2632
|
}, 325e3);
|
|
2483
|
-
this.pendingApprovals.set(requestId, { resolve, timer, request: approvalRequest });
|
|
2633
|
+
this.pendingApprovals.set(requestId, { resolve: resolve2, timer, request: approvalRequest });
|
|
2484
2634
|
});
|
|
2485
2635
|
this.sendJson(res, 200, decision);
|
|
2486
2636
|
} catch (err) {
|
|
@@ -2541,7 +2691,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2541
2691
|
/** 手动解析请求的 JSON body(限制最大 1MB 防止滥用) */
|
|
2542
2692
|
parseJsonBody(req) {
|
|
2543
2693
|
const MAX_BODY_SIZE = 1024 * 1024;
|
|
2544
|
-
return new Promise((
|
|
2694
|
+
return new Promise((resolve2, reject) => {
|
|
2545
2695
|
const chunks = [];
|
|
2546
2696
|
let totalSize = 0;
|
|
2547
2697
|
let destroyed = false;
|
|
@@ -2559,7 +2709,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2559
2709
|
try {
|
|
2560
2710
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
2561
2711
|
const parsed = JSON.parse(raw);
|
|
2562
|
-
|
|
2712
|
+
resolve2(parsed);
|
|
2563
2713
|
} catch {
|
|
2564
2714
|
reject(new Error(t("approval.invalidJson")));
|
|
2565
2715
|
}
|
|
@@ -3226,12 +3376,12 @@ var DesktopNotificationChannel = class {
|
|
|
3226
3376
|
const body = payload.body.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
3227
3377
|
const sound = payload.sound ?? "Ping";
|
|
3228
3378
|
const script = `display notification "${body}" with title "${title}" sound name "${sound}"`;
|
|
3229
|
-
return new Promise((
|
|
3379
|
+
return new Promise((resolve2) => {
|
|
3230
3380
|
(0, import_node_child_process6.execFile)("osascript", ["-e", script], (err) => {
|
|
3231
3381
|
if (err) {
|
|
3232
3382
|
console.warn("[DesktopNotificationChannel] Send notification failed:", err.message);
|
|
3233
3383
|
}
|
|
3234
|
-
|
|
3384
|
+
resolve2();
|
|
3235
3385
|
});
|
|
3236
3386
|
});
|
|
3237
3387
|
}
|
|
@@ -3431,7 +3581,7 @@ var ActivityPushChannel = class {
|
|
|
3431
3581
|
const topic = "com.kachun.sessix.push-type.liveactivity";
|
|
3432
3582
|
const jwt = this.getJWT();
|
|
3433
3583
|
const payloadStr = JSON.stringify(payload);
|
|
3434
|
-
return new Promise((
|
|
3584
|
+
return new Promise((resolve2, reject) => {
|
|
3435
3585
|
let client;
|
|
3436
3586
|
try {
|
|
3437
3587
|
client = this.getHttp2Client();
|
|
@@ -3459,7 +3609,7 @@ var ActivityPushChannel = class {
|
|
|
3459
3609
|
});
|
|
3460
3610
|
req.on("end", () => {
|
|
3461
3611
|
if (statusCode === 200) {
|
|
3462
|
-
|
|
3612
|
+
resolve2();
|
|
3463
3613
|
} else {
|
|
3464
3614
|
if (statusCode === 0) {
|
|
3465
3615
|
this.http2Client?.destroy();
|
|
@@ -3502,11 +3652,11 @@ var ActivityPushChannel = class {
|
|
|
3502
3652
|
// src/session/ProjectReader.ts
|
|
3503
3653
|
var import_promises3 = require("fs/promises");
|
|
3504
3654
|
var import_readline3 = require("readline");
|
|
3505
|
-
var
|
|
3506
|
-
var
|
|
3507
|
-
var CLAUDE_PROJECTS_DIR = (0,
|
|
3655
|
+
var import_path2 = require("path");
|
|
3656
|
+
var import_os2 = require("os");
|
|
3657
|
+
var CLAUDE_PROJECTS_DIR = (0, import_path2.join)((0, import_os2.homedir)(), ".claude", "projects");
|
|
3508
3658
|
function getSessionFilePath(projectPath, sessionId) {
|
|
3509
|
-
return (0,
|
|
3659
|
+
return (0, import_path2.join)(CLAUDE_PROJECTS_DIR, encodeDirName(projectPath), `${sessionId}.jsonl`);
|
|
3510
3660
|
}
|
|
3511
3661
|
async function getProjects() {
|
|
3512
3662
|
try {
|
|
@@ -3523,7 +3673,7 @@ async function getProjects() {
|
|
|
3523
3673
|
const encodedPath = entry.name;
|
|
3524
3674
|
const decodedPath = decodeDirName(encodedPath);
|
|
3525
3675
|
const name = decodedPath.split("/").filter(Boolean).pop() ?? encodedPath;
|
|
3526
|
-
const projectDir = (0,
|
|
3676
|
+
const projectDir = (0, import_path2.join)(CLAUDE_PROJECTS_DIR, encodedPath);
|
|
3527
3677
|
const { count: sessionCount, latestMtime } = await countJsonlFilesWithMtime(projectDir);
|
|
3528
3678
|
projects.push({
|
|
3529
3679
|
id: encodedPath,
|
|
@@ -3545,7 +3695,7 @@ async function getProjects() {
|
|
|
3545
3695
|
async function getHistoricalSessions(projectPath) {
|
|
3546
3696
|
try {
|
|
3547
3697
|
const encodedPath = encodeDirName(projectPath);
|
|
3548
|
-
const projectDir = (0,
|
|
3698
|
+
const projectDir = (0, import_path2.join)(CLAUDE_PROJECTS_DIR, encodedPath);
|
|
3549
3699
|
const dirExists = await directoryExists(projectDir);
|
|
3550
3700
|
if (!dirExists) {
|
|
3551
3701
|
return { ok: true, value: [] };
|
|
@@ -3556,7 +3706,7 @@ async function getHistoricalSessions(projectPath) {
|
|
|
3556
3706
|
await Promise.all(
|
|
3557
3707
|
jsonlFiles.map(async (entry) => {
|
|
3558
3708
|
const sessionId = entry.name.slice(0, -6);
|
|
3559
|
-
const filePath = (0,
|
|
3709
|
+
const filePath = (0, import_path2.join)(projectDir, entry.name);
|
|
3560
3710
|
try {
|
|
3561
3711
|
const contentTs = await extractLastTimestamp(filePath);
|
|
3562
3712
|
if (contentTs) {
|
|
@@ -3575,13 +3725,13 @@ async function getHistoricalSessions(projectPath) {
|
|
|
3575
3725
|
);
|
|
3576
3726
|
for (const entry of uuidDirs) {
|
|
3577
3727
|
try {
|
|
3578
|
-
const fileStat = await (0, import_promises3.stat)((0,
|
|
3728
|
+
const fileStat = await (0, import_promises3.stat)((0, import_path2.join)(projectDir, entry.name));
|
|
3579
3729
|
mtimeMap.set(entry.name, fileStat.mtimeMs);
|
|
3580
3730
|
} catch {
|
|
3581
3731
|
mtimeMap.set(entry.name, 0);
|
|
3582
3732
|
}
|
|
3583
3733
|
}
|
|
3584
|
-
const indexPath = (0,
|
|
3734
|
+
const indexPath = (0, import_path2.join)(projectDir, "sessions-index.json");
|
|
3585
3735
|
const sessionMap = /* @__PURE__ */ new Map();
|
|
3586
3736
|
try {
|
|
3587
3737
|
const indexContent = await (0, import_promises3.readFile)(indexPath, "utf-8");
|
|
@@ -3599,7 +3749,7 @@ async function getHistoricalSessions(projectPath) {
|
|
|
3599
3749
|
}
|
|
3600
3750
|
await Promise.all(
|
|
3601
3751
|
Array.from(sessionMap.values()).filter((s) => (s.messageCount ?? 0) > 0 && !s.summary && !s.firstPrompt).map(async (s) => {
|
|
3602
|
-
const filePath = (0,
|
|
3752
|
+
const filePath = (0, import_path2.join)(projectDir, `${s.sessionId}.jsonl`);
|
|
3603
3753
|
const firstPrompt = await extractFirstPrompt(filePath).catch(() => void 0);
|
|
3604
3754
|
if (firstPrompt) s.firstPrompt = firstPrompt;
|
|
3605
3755
|
})
|
|
@@ -3613,7 +3763,7 @@ async function getHistoricalSessions(projectPath) {
|
|
|
3613
3763
|
if (uuidDirSet.has(sessionId)) {
|
|
3614
3764
|
sessionMap.set(sessionId, { sessionId, lastModified: mtime, messageCount: -1 });
|
|
3615
3765
|
} else {
|
|
3616
|
-
const filePath = (0,
|
|
3766
|
+
const filePath = (0, import_path2.join)(projectDir, `${sessionId}.jsonl`);
|
|
3617
3767
|
const firstPrompt = await extractFirstPrompt(filePath).catch(() => void 0);
|
|
3618
3768
|
sessionMap.set(sessionId, { sessionId, lastModified: mtime, firstPrompt });
|
|
3619
3769
|
}
|
|
@@ -3637,7 +3787,7 @@ async function getHistoricalSessions(projectPath) {
|
|
|
3637
3787
|
async function getSessionHistory(projectPath, sessionId) {
|
|
3638
3788
|
try {
|
|
3639
3789
|
const encodedPath = encodeDirName(projectPath);
|
|
3640
|
-
const filePath = (0,
|
|
3790
|
+
const filePath = (0, import_path2.join)(CLAUDE_PROJECTS_DIR, encodedPath, `${sessionId}.jsonl`);
|
|
3641
3791
|
const raw = await (0, import_promises3.readFile)(filePath, "utf-8").catch((err) => {
|
|
3642
3792
|
if (err.code === "ENOENT") return null;
|
|
3643
3793
|
throw err;
|
|
@@ -3818,15 +3968,15 @@ async function countJsonlFilesWithMtime(dirPath) {
|
|
|
3818
3968
|
await Promise.all([
|
|
3819
3969
|
...jsonlEntries.map(async (entry) => {
|
|
3820
3970
|
try {
|
|
3821
|
-
const contentTs = await extractLastTimestamp((0,
|
|
3822
|
-
const ts = contentTs ?? (await (0, import_promises3.stat)((0,
|
|
3971
|
+
const contentTs = await extractLastTimestamp((0, import_path2.join)(dirPath, entry.name));
|
|
3972
|
+
const ts = contentTs ?? (await (0, import_promises3.stat)((0, import_path2.join)(dirPath, entry.name))).mtimeMs;
|
|
3823
3973
|
if (ts > latestMtime) latestMtime = ts;
|
|
3824
3974
|
} catch {
|
|
3825
3975
|
}
|
|
3826
3976
|
}),
|
|
3827
3977
|
...uuidDirs.map(async (entry) => {
|
|
3828
3978
|
try {
|
|
3829
|
-
const fileStat = await (0, import_promises3.stat)((0,
|
|
3979
|
+
const fileStat = await (0, import_promises3.stat)((0, import_path2.join)(dirPath, entry.name));
|
|
3830
3980
|
if (fileStat.mtimeMs > latestMtime) latestMtime = fileStat.mtimeMs;
|
|
3831
3981
|
} catch {
|
|
3832
3982
|
}
|
|
@@ -4132,7 +4282,7 @@ async function killPortProcess(port) {
|
|
|
4132
4282
|
await execAsync(`kill -9 ${pids.join(" ")}`);
|
|
4133
4283
|
}
|
|
4134
4284
|
}
|
|
4135
|
-
await new Promise((
|
|
4285
|
+
await new Promise((resolve2) => setTimeout(resolve2, 600));
|
|
4136
4286
|
} catch {
|
|
4137
4287
|
}
|
|
4138
4288
|
}
|
package/dist/server.js
CHANGED
|
@@ -331,14 +331,14 @@ var import_node_os = require("os");
|
|
|
331
331
|
var import_node_child_process = require("child_process");
|
|
332
332
|
var isWindows = process.platform === "win32";
|
|
333
333
|
function killProcessCrossPlatform(proc, timeoutMs = 3e3) {
|
|
334
|
-
return new Promise((
|
|
334
|
+
return new Promise((resolve2) => {
|
|
335
335
|
if (proc.exitCode !== null || proc.signalCode !== null) {
|
|
336
|
-
|
|
336
|
+
resolve2();
|
|
337
337
|
return;
|
|
338
338
|
}
|
|
339
339
|
const onExit = () => {
|
|
340
340
|
clearTimeout(timer);
|
|
341
|
-
|
|
341
|
+
resolve2();
|
|
342
342
|
};
|
|
343
343
|
proc.once("exit", onExit);
|
|
344
344
|
if (isWindows) {
|
|
@@ -354,7 +354,7 @@ function killProcessCrossPlatform(proc, timeoutMs = 3e3) {
|
|
|
354
354
|
proc.kill("SIGKILL");
|
|
355
355
|
}
|
|
356
356
|
}
|
|
357
|
-
|
|
357
|
+
resolve2();
|
|
358
358
|
}, timeoutMs);
|
|
359
359
|
});
|
|
360
360
|
}
|
|
@@ -786,7 +786,7 @@ var ProcessProvider = class {
|
|
|
786
786
|
const prompt = `You are an AI coding assistant. Based on the following Claude Code conversation context, suggest the most valuable next instruction for the user (give the instruction directly, no explanation, no quotes):
|
|
787
787
|
|
|
788
788
|
${context}`;
|
|
789
|
-
return new Promise((
|
|
789
|
+
return new Promise((resolve2, reject) => {
|
|
790
790
|
const env = { ...process.env };
|
|
791
791
|
delete env.CLAUDECODE;
|
|
792
792
|
const proc = (0, import_child_process.spawn)(CLAUDE_PATH, ["-p", prompt, "--output-format", "text"], {
|
|
@@ -801,7 +801,7 @@ ${context}`;
|
|
|
801
801
|
});
|
|
802
802
|
proc.once("exit", (code) => {
|
|
803
803
|
if (code === 0) {
|
|
804
|
-
|
|
804
|
+
resolve2(output.trim());
|
|
805
805
|
} else {
|
|
806
806
|
reject(new Error(`generateSuggestion process exit code: ${code}`));
|
|
807
807
|
}
|
|
@@ -828,10 +828,10 @@ ${context}`;
|
|
|
828
828
|
tool_use_id: toolUseId,
|
|
829
829
|
content: answer
|
|
830
830
|
});
|
|
831
|
-
await new Promise((
|
|
831
|
+
await new Promise((resolve2, reject) => {
|
|
832
832
|
entry.process.stdin.write(toolResult + "\n", (err) => {
|
|
833
833
|
if (err) reject(err);
|
|
834
|
-
else
|
|
834
|
+
else resolve2();
|
|
835
835
|
});
|
|
836
836
|
});
|
|
837
837
|
console.log(`[ProcessProvider] Session ${sessionId}: AskUserQuestion answered (toolUseId=${toolUseId})`);
|
|
@@ -866,6 +866,9 @@ ${context}`;
|
|
|
866
866
|
var import_child_process2 = require("child_process");
|
|
867
867
|
var import_readline2 = require("readline");
|
|
868
868
|
var import_events2 = require("events");
|
|
869
|
+
var import_fs = require("fs");
|
|
870
|
+
var import_path = require("path");
|
|
871
|
+
var import_os = require("os");
|
|
869
872
|
var import_uuid2 = require("uuid");
|
|
870
873
|
|
|
871
874
|
// src/utils/codexPath.ts
|
|
@@ -896,10 +899,31 @@ function findCodexPath() {
|
|
|
896
899
|
}
|
|
897
900
|
return "codex";
|
|
898
901
|
}
|
|
902
|
+
function resolveCodexJsEntry(codexPath) {
|
|
903
|
+
try {
|
|
904
|
+
let realPath;
|
|
905
|
+
try {
|
|
906
|
+
realPath = (0, import_node_fs2.readlinkSync)(codexPath);
|
|
907
|
+
if (!realPath.startsWith("/")) {
|
|
908
|
+
realPath = (0, import_node_path2.resolve)((0, import_node_path2.dirname)(codexPath), realPath);
|
|
909
|
+
}
|
|
910
|
+
} catch {
|
|
911
|
+
realPath = codexPath;
|
|
912
|
+
}
|
|
913
|
+
const head = (0, import_node_fs2.readFileSync)(realPath, { encoding: "utf-8", flag: "r" }).slice(0, 100);
|
|
914
|
+
if (head.startsWith("#!/usr/bin/env node") || head.startsWith("#!/usr/bin/node")) {
|
|
915
|
+
return realPath;
|
|
916
|
+
}
|
|
917
|
+
} catch {
|
|
918
|
+
}
|
|
919
|
+
return void 0;
|
|
920
|
+
}
|
|
899
921
|
var CODEX_PATH = findCodexPath();
|
|
922
|
+
var CODEX_JS_ENTRY = resolveCodexJsEntry(CODEX_PATH);
|
|
900
923
|
async function getCodexVersion() {
|
|
901
924
|
try {
|
|
902
|
-
const
|
|
925
|
+
const cmd = CODEX_JS_ENTRY ? `"${process.execPath}" "${CODEX_JS_ENTRY}" --version` : `"${CODEX_PATH}" --version`;
|
|
926
|
+
const output = (0, import_node_child_process3.execSync)(cmd, {
|
|
903
927
|
encoding: "utf-8",
|
|
904
928
|
timeout: 5e3
|
|
905
929
|
}).trim();
|
|
@@ -909,6 +933,14 @@ async function getCodexVersion() {
|
|
|
909
933
|
}
|
|
910
934
|
}
|
|
911
935
|
function isCodexAvailable() {
|
|
936
|
+
if (CODEX_JS_ENTRY) {
|
|
937
|
+
try {
|
|
938
|
+
(0, import_node_fs2.accessSync)(CODEX_JS_ENTRY, import_node_fs2.constants.R_OK);
|
|
939
|
+
return true;
|
|
940
|
+
} catch {
|
|
941
|
+
return false;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
912
944
|
try {
|
|
913
945
|
(0, import_node_fs2.accessSync)(CODEX_PATH, import_node_fs2.constants.X_OK);
|
|
914
946
|
return true;
|
|
@@ -923,11 +955,18 @@ function isCodexAvailable() {
|
|
|
923
955
|
}
|
|
924
956
|
|
|
925
957
|
// src/providers/CodexProvider.ts
|
|
958
|
+
var SESSIX_DIR = (0, import_path.join)((0, import_os.homedir)(), ".sessix");
|
|
959
|
+
var CODEX_SESSIONS_FILE = (0, import_path.join)(SESSIX_DIR, "codex-sessions.json");
|
|
926
960
|
var CodexProvider = class {
|
|
927
961
|
activeSessions = /* @__PURE__ */ new Map();
|
|
928
962
|
emitter = new import_events2.EventEmitter();
|
|
963
|
+
/** 持久化的会话元数据(sessionId → metadata) */
|
|
964
|
+
persistedSessions = /* @__PURE__ */ new Map();
|
|
929
965
|
/** 自增 ID 计数器,用于生成 ClaudeStreamEvent 中的 message/block ID */
|
|
930
966
|
idCounter = 0;
|
|
967
|
+
constructor() {
|
|
968
|
+
this.loadPersistedSessions();
|
|
969
|
+
}
|
|
931
970
|
async startSession(opts) {
|
|
932
971
|
const { projectPath, message, sessionId: existingSessionId } = opts;
|
|
933
972
|
const sessionId = existingSessionId ?? (0, import_uuid2.v4)();
|
|
@@ -946,12 +985,22 @@ var CodexProvider = class {
|
|
|
946
985
|
agentType: "codex"
|
|
947
986
|
};
|
|
948
987
|
const resume = opts.resume ?? !!existingSessionId;
|
|
949
|
-
|
|
988
|
+
let resumeThreadId;
|
|
989
|
+
if (resume && existingSessionId) {
|
|
990
|
+
const persisted = this.persistedSessions.get(existingSessionId);
|
|
991
|
+
if (persisted?.threadId) {
|
|
992
|
+
resumeThreadId = persisted.threadId;
|
|
993
|
+
console.log(`[CodexProvider] Resuming session ${sessionId} with threadId: ${resumeThreadId}`);
|
|
994
|
+
} else {
|
|
995
|
+
console.warn(`[CodexProvider] Session ${sessionId} resume requested but no persisted threadId found, creating new session`);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
const proc = this.spawnCodexProcess(projectPath, message, resumeThreadId, opts.model);
|
|
950
999
|
session.pid = proc.pid;
|
|
951
1000
|
this.activeSessions.set(sessionId, {
|
|
952
1001
|
session,
|
|
953
1002
|
process: proc,
|
|
954
|
-
threadId:
|
|
1003
|
+
threadId: resumeThreadId,
|
|
955
1004
|
turnStartTime: Date.now()
|
|
956
1005
|
});
|
|
957
1006
|
const initEvent = {
|
|
@@ -983,22 +1032,45 @@ var CodexProvider = class {
|
|
|
983
1032
|
this.activeSessions.delete(sessionId);
|
|
984
1033
|
}
|
|
985
1034
|
async sendMessage(sessionId, message) {
|
|
986
|
-
|
|
1035
|
+
let entry = this.activeSessions.get(sessionId);
|
|
987
1036
|
if (!entry) {
|
|
988
|
-
|
|
1037
|
+
const persisted = this.persistedSessions.get(sessionId);
|
|
1038
|
+
if (!persisted?.threadId) {
|
|
1039
|
+
throw new Error(`Session ${sessionId} not found or already ended`);
|
|
1040
|
+
}
|
|
1041
|
+
const projectId = persisted.projectPath.split("/").filter(Boolean).pop() ?? "unknown";
|
|
1042
|
+
const session = {
|
|
1043
|
+
id: sessionId,
|
|
1044
|
+
projectId,
|
|
1045
|
+
projectPath: persisted.projectPath,
|
|
1046
|
+
status: "running",
|
|
1047
|
+
createdAt: persisted.createdAt,
|
|
1048
|
+
lastActiveAt: Date.now(),
|
|
1049
|
+
summary: persisted.summary,
|
|
1050
|
+
agentType: "codex"
|
|
1051
|
+
};
|
|
1052
|
+
const placeholderProc = (0, import_child_process2.spawn)("true", [], { stdio: "ignore" });
|
|
1053
|
+
entry = {
|
|
1054
|
+
session,
|
|
1055
|
+
process: placeholderProc,
|
|
1056
|
+
threadId: persisted.threadId,
|
|
1057
|
+
turnStartTime: Date.now()
|
|
1058
|
+
};
|
|
1059
|
+
this.activeSessions.set(sessionId, entry);
|
|
989
1060
|
}
|
|
990
|
-
|
|
1061
|
+
const procAlive = entry.process.exitCode === null && entry.process.signalCode === null;
|
|
1062
|
+
if (procAlive) {
|
|
991
1063
|
try {
|
|
992
1064
|
entry.process.stdin?.end();
|
|
993
1065
|
} catch {
|
|
994
1066
|
}
|
|
995
1067
|
await killProcessCrossPlatform(entry.process);
|
|
996
1068
|
}
|
|
997
|
-
const threadId = entry.threadId;
|
|
1069
|
+
const threadId = entry.threadId ?? this.persistedSessions.get(sessionId)?.threadId;
|
|
998
1070
|
if (!threadId) {
|
|
999
|
-
|
|
1071
|
+
console.warn(`[CodexProvider] Session ${sessionId} has no thread ID, falling back to new thread`);
|
|
1000
1072
|
}
|
|
1001
|
-
const proc = this.spawnCodexProcess(entry.session.projectPath, message, threadId);
|
|
1073
|
+
const proc = this.spawnCodexProcess(entry.session.projectPath, message, threadId ?? void 0);
|
|
1002
1074
|
entry.session.status = "running";
|
|
1003
1075
|
entry.session.lastActiveAt = Date.now();
|
|
1004
1076
|
entry.session.pid = proc.pid;
|
|
@@ -1045,16 +1117,29 @@ var CodexProvider = class {
|
|
|
1045
1117
|
* @param message 用户消息
|
|
1046
1118
|
* @param resumeThreadId 如果提供,则使用 `codex exec resume` 恢复会话
|
|
1047
1119
|
*/
|
|
1048
|
-
spawnCodexProcess(projectPath, message, resumeThreadId) {
|
|
1120
|
+
spawnCodexProcess(projectPath, message, resumeThreadId, model) {
|
|
1049
1121
|
const args = ["exec", "--json", "--full-auto"];
|
|
1122
|
+
if (model) {
|
|
1123
|
+
args.push("-m", model);
|
|
1124
|
+
}
|
|
1125
|
+
args.push("-C", projectPath);
|
|
1050
1126
|
if (resumeThreadId) {
|
|
1051
1127
|
args.push("resume", resumeThreadId);
|
|
1052
|
-
} else {
|
|
1053
|
-
args.push("-C", projectPath);
|
|
1054
1128
|
}
|
|
1055
1129
|
args.push(message);
|
|
1056
1130
|
const env = { ...process.env };
|
|
1057
|
-
|
|
1131
|
+
let cmd;
|
|
1132
|
+
let spawnArgs;
|
|
1133
|
+
if (CODEX_JS_ENTRY) {
|
|
1134
|
+
cmd = process.execPath;
|
|
1135
|
+
spawnArgs = [CODEX_JS_ENTRY, ...args];
|
|
1136
|
+
console.log(`[CodexProvider] Spawning via node: ${cmd} ${CODEX_JS_ENTRY} ${args.join(" ")}`);
|
|
1137
|
+
} else {
|
|
1138
|
+
cmd = CODEX_PATH;
|
|
1139
|
+
spawnArgs = args;
|
|
1140
|
+
console.log(`[CodexProvider] Spawning: ${CODEX_PATH} ${args.join(" ")}`);
|
|
1141
|
+
}
|
|
1142
|
+
const proc = (0, import_child_process2.spawn)(cmd, spawnArgs, {
|
|
1058
1143
|
cwd: projectPath,
|
|
1059
1144
|
env,
|
|
1060
1145
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -1076,6 +1161,7 @@ var CodexProvider = class {
|
|
|
1076
1161
|
rl.on("line", (line) => {
|
|
1077
1162
|
const trimmed = line.trim();
|
|
1078
1163
|
if (!trimmed) return;
|
|
1164
|
+
console.log(`[CodexProvider] Session ${sessionId} stdout: ${trimmed.substring(0, 200)}`);
|
|
1079
1165
|
let event;
|
|
1080
1166
|
try {
|
|
1081
1167
|
event = JSON.parse(trimmed);
|
|
@@ -1097,6 +1183,14 @@ var CodexProvider = class {
|
|
|
1097
1183
|
case "thread.started": {
|
|
1098
1184
|
if (event.thread_id) {
|
|
1099
1185
|
entry.threadId = event.thread_id;
|
|
1186
|
+
this.persistSession(sessionId, {
|
|
1187
|
+
threadId: event.thread_id,
|
|
1188
|
+
projectPath: entry.session.projectPath,
|
|
1189
|
+
summary: entry.session.summary,
|
|
1190
|
+
createdAt: entry.session.createdAt,
|
|
1191
|
+
lastActiveAt: Date.now()
|
|
1192
|
+
});
|
|
1193
|
+
console.log(`[CodexProvider] Session ${sessionId} threadId persisted: ${event.thread_id}`);
|
|
1100
1194
|
}
|
|
1101
1195
|
break;
|
|
1102
1196
|
}
|
|
@@ -1260,6 +1354,7 @@ var CodexProvider = class {
|
|
|
1260
1354
|
const entry = this.activeSessions.get(sessionId);
|
|
1261
1355
|
if (!entry) return;
|
|
1262
1356
|
if (entry.process !== proc) return;
|
|
1357
|
+
console.log(`[CodexProvider] Session ${sessionId} process exited: code=${code} signal=${signal} threadId=${entry.threadId ?? "NONE"}`);
|
|
1263
1358
|
if (entry.rl) {
|
|
1264
1359
|
entry.rl.close();
|
|
1265
1360
|
entry.rl = void 0;
|
|
@@ -1300,6 +1395,54 @@ var CodexProvider = class {
|
|
|
1300
1395
|
getEventName(sessionId) {
|
|
1301
1396
|
return `claude:${sessionId}`;
|
|
1302
1397
|
}
|
|
1398
|
+
// ============================================
|
|
1399
|
+
// 持久化方法
|
|
1400
|
+
// ============================================
|
|
1401
|
+
/**
|
|
1402
|
+
* 从磁盘加载持久化的 Codex 会话元数据
|
|
1403
|
+
*/
|
|
1404
|
+
loadPersistedSessions() {
|
|
1405
|
+
try {
|
|
1406
|
+
if (!(0, import_fs.existsSync)(CODEX_SESSIONS_FILE)) return;
|
|
1407
|
+
const data = JSON.parse((0, import_fs.readFileSync)(CODEX_SESSIONS_FILE, "utf-8"));
|
|
1408
|
+
for (const [sessionId, meta] of Object.entries(data)) {
|
|
1409
|
+
this.persistedSessions.set(sessionId, meta);
|
|
1410
|
+
}
|
|
1411
|
+
console.log(`[CodexProvider] Loaded ${this.persistedSessions.size} persisted sessions`);
|
|
1412
|
+
} catch (err) {
|
|
1413
|
+
console.warn("[CodexProvider] Failed to load persisted sessions:", err);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* 持久化单个会话的元数据到磁盘
|
|
1418
|
+
*/
|
|
1419
|
+
persistSession(sessionId, meta) {
|
|
1420
|
+
this.persistedSessions.set(sessionId, meta);
|
|
1421
|
+
this.flushPersistedSessions();
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* 将所有持久化数据写入磁盘
|
|
1425
|
+
*/
|
|
1426
|
+
flushPersistedSessions() {
|
|
1427
|
+
try {
|
|
1428
|
+
if (!(0, import_fs.existsSync)(SESSIX_DIR)) {
|
|
1429
|
+
(0, import_fs.mkdirSync)(SESSIX_DIR, { recursive: true });
|
|
1430
|
+
}
|
|
1431
|
+
const data = {};
|
|
1432
|
+
for (const [sessionId, meta] of this.persistedSessions) {
|
|
1433
|
+
data[sessionId] = meta;
|
|
1434
|
+
}
|
|
1435
|
+
(0, import_fs.writeFileSync)(CODEX_SESSIONS_FILE, JSON.stringify(data, null, 2), "utf-8");
|
|
1436
|
+
} catch (err) {
|
|
1437
|
+
console.error("[CodexProvider] Failed to persist sessions:", err);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
/**
|
|
1441
|
+
* 检查某个 sessionId 是否为已知的 Codex 会话(供 SessionManager 查询 agentType)
|
|
1442
|
+
*/
|
|
1443
|
+
isKnownSession(sessionId) {
|
|
1444
|
+
return this.activeSessions.has(sessionId) || this.persistedSessions.has(sessionId);
|
|
1445
|
+
}
|
|
1303
1446
|
};
|
|
1304
1447
|
|
|
1305
1448
|
// src/providers/ProviderFactory.ts
|
|
@@ -1392,8 +1535,15 @@ var SessionManager = class {
|
|
|
1392
1535
|
}
|
|
1393
1536
|
getProviderForSession(sessionId) {
|
|
1394
1537
|
if (!this.providerFactory) return this.provider;
|
|
1395
|
-
|
|
1396
|
-
|
|
1538
|
+
let agentType = this.sessionAgentType.get(sessionId);
|
|
1539
|
+
if (!agentType) {
|
|
1540
|
+
const codexProvider = this.providerFactory.getProvider("codex");
|
|
1541
|
+
if (codexProvider instanceof CodexProvider && codexProvider.isKnownSession(sessionId)) {
|
|
1542
|
+
agentType = "codex";
|
|
1543
|
+
this.sessionAgentType.set(sessionId, agentType);
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
return this.providerFactory.getProvider(agentType ?? "claude-code");
|
|
1397
1547
|
}
|
|
1398
1548
|
// ============================================
|
|
1399
1549
|
// 公开 API
|
|
@@ -1772,8 +1922,8 @@ var SessionManager = class {
|
|
|
1772
1922
|
};
|
|
1773
1923
|
this.updateSessionStatus(sessionId, "waiting_question");
|
|
1774
1924
|
this.emit({ type: "question_request", request });
|
|
1775
|
-
const answerPromise = new Promise((
|
|
1776
|
-
this.pendingQuestions.set(requestId, { sessionId, toolUseId, question, options, createdAt: request.createdAt, resolve });
|
|
1925
|
+
const answerPromise = new Promise((resolve2) => {
|
|
1926
|
+
this.pendingQuestions.set(requestId, { sessionId, toolUseId, question, options, createdAt: request.createdAt, resolve: resolve2 });
|
|
1777
1927
|
});
|
|
1778
1928
|
answerPromise.then(async (answer) => {
|
|
1779
1929
|
try {
|
|
@@ -2015,11 +2165,11 @@ var WsBridge = class _WsBridge {
|
|
|
2015
2165
|
* 使用此方法代替 new WsBridge(),确保 EADDRINUSE 等错误能被调用方的 try-catch 捕获。
|
|
2016
2166
|
*/
|
|
2017
2167
|
static async create(options) {
|
|
2018
|
-
return new Promise((
|
|
2168
|
+
return new Promise((resolve2, reject) => {
|
|
2019
2169
|
const bridge = new _WsBridge(options);
|
|
2020
2170
|
bridge.wss.once("listening", () => {
|
|
2021
2171
|
bridge.wss.on("error", (err) => console.error(`[WsBridge] ${t("ws.serverError")}:`, err));
|
|
2022
|
-
|
|
2172
|
+
resolve2(bridge);
|
|
2023
2173
|
});
|
|
2024
2174
|
bridge.wss.once("error", reject);
|
|
2025
2175
|
});
|
|
@@ -2082,7 +2232,7 @@ var WsBridge = class _WsBridge {
|
|
|
2082
2232
|
}
|
|
2083
2233
|
/** 优雅关闭 WebSocket 服务 */
|
|
2084
2234
|
close() {
|
|
2085
|
-
return new Promise((
|
|
2235
|
+
return new Promise((resolve2, reject) => {
|
|
2086
2236
|
if (this.heartbeatTimer) {
|
|
2087
2237
|
clearInterval(this.heartbeatTimer);
|
|
2088
2238
|
this.heartbeatTimer = null;
|
|
@@ -2095,7 +2245,7 @@ var WsBridge = class _WsBridge {
|
|
|
2095
2245
|
reject(err);
|
|
2096
2246
|
} else {
|
|
2097
2247
|
console.log("[WsBridge] WebSocket server closed");
|
|
2098
|
-
|
|
2248
|
+
resolve2();
|
|
2099
2249
|
}
|
|
2100
2250
|
});
|
|
2101
2251
|
});
|
|
@@ -2235,11 +2385,11 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2235
2385
|
* 异步工厂方法:等待端口监听成功后 resolve,端口占用等错误时 reject。
|
|
2236
2386
|
*/
|
|
2237
2387
|
static async create(options) {
|
|
2238
|
-
return new Promise((
|
|
2388
|
+
return new Promise((resolve2, reject) => {
|
|
2239
2389
|
const proxy = new _ApprovalProxy(options);
|
|
2240
2390
|
proxy.server.once("listening", () => {
|
|
2241
2391
|
proxy.server.on("error", (err) => console.error(`[ApprovalProxy] ${t("approval.serverError")}:`, err));
|
|
2242
|
-
|
|
2392
|
+
resolve2(proxy);
|
|
2243
2393
|
});
|
|
2244
2394
|
proxy.server.once("error", reject);
|
|
2245
2395
|
});
|
|
@@ -2396,7 +2546,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2396
2546
|
}
|
|
2397
2547
|
/** 优雅关闭 HTTP 服务 */
|
|
2398
2548
|
close() {
|
|
2399
|
-
return new Promise((
|
|
2549
|
+
return new Promise((resolve2, reject) => {
|
|
2400
2550
|
const pendingEntries = Array.from(this.pendingApprovals.entries());
|
|
2401
2551
|
for (const [, pending] of pendingEntries) {
|
|
2402
2552
|
clearTimeout(pending.timer);
|
|
@@ -2408,7 +2558,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2408
2558
|
reject(err);
|
|
2409
2559
|
} else {
|
|
2410
2560
|
console.log(`[ApprovalProxy] ${t("approval.httpClosed")}`);
|
|
2411
|
-
|
|
2561
|
+
resolve2();
|
|
2412
2562
|
}
|
|
2413
2563
|
});
|
|
2414
2564
|
});
|
|
@@ -2479,13 +2629,13 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2479
2629
|
return;
|
|
2480
2630
|
}
|
|
2481
2631
|
this.notifyApprovalRequest(approvalRequest);
|
|
2482
|
-
const decision = await new Promise((
|
|
2632
|
+
const decision = await new Promise((resolve2) => {
|
|
2483
2633
|
const timer = setTimeout(() => {
|
|
2484
2634
|
console.log(`[ApprovalProxy] ${t("approval.timeout", { id: requestId })}`);
|
|
2485
2635
|
this.pendingApprovals.delete(requestId);
|
|
2486
|
-
|
|
2636
|
+
resolve2({ decision: "allow" });
|
|
2487
2637
|
}, 325e3);
|
|
2488
|
-
this.pendingApprovals.set(requestId, { resolve, timer, request: approvalRequest });
|
|
2638
|
+
this.pendingApprovals.set(requestId, { resolve: resolve2, timer, request: approvalRequest });
|
|
2489
2639
|
});
|
|
2490
2640
|
this.sendJson(res, 200, decision);
|
|
2491
2641
|
} catch (err) {
|
|
@@ -2546,7 +2696,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2546
2696
|
/** 手动解析请求的 JSON body(限制最大 1MB 防止滥用) */
|
|
2547
2697
|
parseJsonBody(req) {
|
|
2548
2698
|
const MAX_BODY_SIZE = 1024 * 1024;
|
|
2549
|
-
return new Promise((
|
|
2699
|
+
return new Promise((resolve2, reject) => {
|
|
2550
2700
|
const chunks = [];
|
|
2551
2701
|
let totalSize = 0;
|
|
2552
2702
|
let destroyed = false;
|
|
@@ -2564,7 +2714,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2564
2714
|
try {
|
|
2565
2715
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
2566
2716
|
const parsed = JSON.parse(raw);
|
|
2567
|
-
|
|
2717
|
+
resolve2(parsed);
|
|
2568
2718
|
} catch {
|
|
2569
2719
|
reject(new Error(t("approval.invalidJson")));
|
|
2570
2720
|
}
|
|
@@ -3231,12 +3381,12 @@ var DesktopNotificationChannel = class {
|
|
|
3231
3381
|
const body = payload.body.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
3232
3382
|
const sound = payload.sound ?? "Ping";
|
|
3233
3383
|
const script = `display notification "${body}" with title "${title}" sound name "${sound}"`;
|
|
3234
|
-
return new Promise((
|
|
3384
|
+
return new Promise((resolve2) => {
|
|
3235
3385
|
(0, import_node_child_process6.execFile)("osascript", ["-e", script], (err) => {
|
|
3236
3386
|
if (err) {
|
|
3237
3387
|
console.warn("[DesktopNotificationChannel] Send notification failed:", err.message);
|
|
3238
3388
|
}
|
|
3239
|
-
|
|
3389
|
+
resolve2();
|
|
3240
3390
|
});
|
|
3241
3391
|
});
|
|
3242
3392
|
}
|
|
@@ -3436,7 +3586,7 @@ var ActivityPushChannel = class {
|
|
|
3436
3586
|
const topic = "com.kachun.sessix.push-type.liveactivity";
|
|
3437
3587
|
const jwt = this.getJWT();
|
|
3438
3588
|
const payloadStr = JSON.stringify(payload);
|
|
3439
|
-
return new Promise((
|
|
3589
|
+
return new Promise((resolve2, reject) => {
|
|
3440
3590
|
let client;
|
|
3441
3591
|
try {
|
|
3442
3592
|
client = this.getHttp2Client();
|
|
@@ -3464,7 +3614,7 @@ var ActivityPushChannel = class {
|
|
|
3464
3614
|
});
|
|
3465
3615
|
req.on("end", () => {
|
|
3466
3616
|
if (statusCode === 200) {
|
|
3467
|
-
|
|
3617
|
+
resolve2();
|
|
3468
3618
|
} else {
|
|
3469
3619
|
if (statusCode === 0) {
|
|
3470
3620
|
this.http2Client?.destroy();
|
|
@@ -3507,11 +3657,11 @@ var ActivityPushChannel = class {
|
|
|
3507
3657
|
// src/session/ProjectReader.ts
|
|
3508
3658
|
var import_promises3 = require("fs/promises");
|
|
3509
3659
|
var import_readline3 = require("readline");
|
|
3510
|
-
var
|
|
3511
|
-
var
|
|
3512
|
-
var CLAUDE_PROJECTS_DIR = (0,
|
|
3660
|
+
var import_path2 = require("path");
|
|
3661
|
+
var import_os2 = require("os");
|
|
3662
|
+
var CLAUDE_PROJECTS_DIR = (0, import_path2.join)((0, import_os2.homedir)(), ".claude", "projects");
|
|
3513
3663
|
function getSessionFilePath(projectPath, sessionId) {
|
|
3514
|
-
return (0,
|
|
3664
|
+
return (0, import_path2.join)(CLAUDE_PROJECTS_DIR, encodeDirName(projectPath), `${sessionId}.jsonl`);
|
|
3515
3665
|
}
|
|
3516
3666
|
async function getProjects() {
|
|
3517
3667
|
try {
|
|
@@ -3528,7 +3678,7 @@ async function getProjects() {
|
|
|
3528
3678
|
const encodedPath = entry.name;
|
|
3529
3679
|
const decodedPath = decodeDirName(encodedPath);
|
|
3530
3680
|
const name = decodedPath.split("/").filter(Boolean).pop() ?? encodedPath;
|
|
3531
|
-
const projectDir = (0,
|
|
3681
|
+
const projectDir = (0, import_path2.join)(CLAUDE_PROJECTS_DIR, encodedPath);
|
|
3532
3682
|
const { count: sessionCount, latestMtime } = await countJsonlFilesWithMtime(projectDir);
|
|
3533
3683
|
projects.push({
|
|
3534
3684
|
id: encodedPath,
|
|
@@ -3550,7 +3700,7 @@ async function getProjects() {
|
|
|
3550
3700
|
async function getHistoricalSessions(projectPath) {
|
|
3551
3701
|
try {
|
|
3552
3702
|
const encodedPath = encodeDirName(projectPath);
|
|
3553
|
-
const projectDir = (0,
|
|
3703
|
+
const projectDir = (0, import_path2.join)(CLAUDE_PROJECTS_DIR, encodedPath);
|
|
3554
3704
|
const dirExists = await directoryExists(projectDir);
|
|
3555
3705
|
if (!dirExists) {
|
|
3556
3706
|
return { ok: true, value: [] };
|
|
@@ -3561,7 +3711,7 @@ async function getHistoricalSessions(projectPath) {
|
|
|
3561
3711
|
await Promise.all(
|
|
3562
3712
|
jsonlFiles.map(async (entry) => {
|
|
3563
3713
|
const sessionId = entry.name.slice(0, -6);
|
|
3564
|
-
const filePath = (0,
|
|
3714
|
+
const filePath = (0, import_path2.join)(projectDir, entry.name);
|
|
3565
3715
|
try {
|
|
3566
3716
|
const contentTs = await extractLastTimestamp(filePath);
|
|
3567
3717
|
if (contentTs) {
|
|
@@ -3580,13 +3730,13 @@ async function getHistoricalSessions(projectPath) {
|
|
|
3580
3730
|
);
|
|
3581
3731
|
for (const entry of uuidDirs) {
|
|
3582
3732
|
try {
|
|
3583
|
-
const fileStat = await (0, import_promises3.stat)((0,
|
|
3733
|
+
const fileStat = await (0, import_promises3.stat)((0, import_path2.join)(projectDir, entry.name));
|
|
3584
3734
|
mtimeMap.set(entry.name, fileStat.mtimeMs);
|
|
3585
3735
|
} catch {
|
|
3586
3736
|
mtimeMap.set(entry.name, 0);
|
|
3587
3737
|
}
|
|
3588
3738
|
}
|
|
3589
|
-
const indexPath = (0,
|
|
3739
|
+
const indexPath = (0, import_path2.join)(projectDir, "sessions-index.json");
|
|
3590
3740
|
const sessionMap = /* @__PURE__ */ new Map();
|
|
3591
3741
|
try {
|
|
3592
3742
|
const indexContent = await (0, import_promises3.readFile)(indexPath, "utf-8");
|
|
@@ -3604,7 +3754,7 @@ async function getHistoricalSessions(projectPath) {
|
|
|
3604
3754
|
}
|
|
3605
3755
|
await Promise.all(
|
|
3606
3756
|
Array.from(sessionMap.values()).filter((s) => (s.messageCount ?? 0) > 0 && !s.summary && !s.firstPrompt).map(async (s) => {
|
|
3607
|
-
const filePath = (0,
|
|
3757
|
+
const filePath = (0, import_path2.join)(projectDir, `${s.sessionId}.jsonl`);
|
|
3608
3758
|
const firstPrompt = await extractFirstPrompt(filePath).catch(() => void 0);
|
|
3609
3759
|
if (firstPrompt) s.firstPrompt = firstPrompt;
|
|
3610
3760
|
})
|
|
@@ -3618,7 +3768,7 @@ async function getHistoricalSessions(projectPath) {
|
|
|
3618
3768
|
if (uuidDirSet.has(sessionId)) {
|
|
3619
3769
|
sessionMap.set(sessionId, { sessionId, lastModified: mtime, messageCount: -1 });
|
|
3620
3770
|
} else {
|
|
3621
|
-
const filePath = (0,
|
|
3771
|
+
const filePath = (0, import_path2.join)(projectDir, `${sessionId}.jsonl`);
|
|
3622
3772
|
const firstPrompt = await extractFirstPrompt(filePath).catch(() => void 0);
|
|
3623
3773
|
sessionMap.set(sessionId, { sessionId, lastModified: mtime, firstPrompt });
|
|
3624
3774
|
}
|
|
@@ -3642,7 +3792,7 @@ async function getHistoricalSessions(projectPath) {
|
|
|
3642
3792
|
async function getSessionHistory(projectPath, sessionId) {
|
|
3643
3793
|
try {
|
|
3644
3794
|
const encodedPath = encodeDirName(projectPath);
|
|
3645
|
-
const filePath = (0,
|
|
3795
|
+
const filePath = (0, import_path2.join)(CLAUDE_PROJECTS_DIR, encodedPath, `${sessionId}.jsonl`);
|
|
3646
3796
|
const raw = await (0, import_promises3.readFile)(filePath, "utf-8").catch((err) => {
|
|
3647
3797
|
if (err.code === "ENOENT") return null;
|
|
3648
3798
|
throw err;
|
|
@@ -3823,15 +3973,15 @@ async function countJsonlFilesWithMtime(dirPath) {
|
|
|
3823
3973
|
await Promise.all([
|
|
3824
3974
|
...jsonlEntries.map(async (entry) => {
|
|
3825
3975
|
try {
|
|
3826
|
-
const contentTs = await extractLastTimestamp((0,
|
|
3827
|
-
const ts = contentTs ?? (await (0, import_promises3.stat)((0,
|
|
3976
|
+
const contentTs = await extractLastTimestamp((0, import_path2.join)(dirPath, entry.name));
|
|
3977
|
+
const ts = contentTs ?? (await (0, import_promises3.stat)((0, import_path2.join)(dirPath, entry.name))).mtimeMs;
|
|
3828
3978
|
if (ts > latestMtime) latestMtime = ts;
|
|
3829
3979
|
} catch {
|
|
3830
3980
|
}
|
|
3831
3981
|
}),
|
|
3832
3982
|
...uuidDirs.map(async (entry) => {
|
|
3833
3983
|
try {
|
|
3834
|
-
const fileStat = await (0, import_promises3.stat)((0,
|
|
3984
|
+
const fileStat = await (0, import_promises3.stat)((0, import_path2.join)(dirPath, entry.name));
|
|
3835
3985
|
if (fileStat.mtimeMs > latestMtime) latestMtime = fileStat.mtimeMs;
|
|
3836
3986
|
} catch {
|
|
3837
3987
|
}
|
|
@@ -4137,7 +4287,7 @@ async function killPortProcess(port) {
|
|
|
4137
4287
|
await execAsync(`kill -9 ${pids.join(" ")}`);
|
|
4138
4288
|
}
|
|
4139
4289
|
}
|
|
4140
|
-
await new Promise((
|
|
4290
|
+
await new Promise((resolve2) => setTimeout(resolve2, 600));
|
|
4141
4291
|
} catch {
|
|
4142
4292
|
}
|
|
4143
4293
|
}
|