sessix-server 0.3.1 → 0.3.3
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 +120 -44
- package/dist/server.js +120 -44
- 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((resolve) => {
|
|
330
330
|
if (proc.exitCode !== null || proc.signalCode !== null) {
|
|
331
|
-
|
|
331
|
+
resolve();
|
|
332
332
|
return;
|
|
333
333
|
}
|
|
334
334
|
const onExit = () => {
|
|
335
335
|
clearTimeout(timer);
|
|
336
|
-
|
|
336
|
+
resolve();
|
|
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
|
+
resolve();
|
|
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((resolve, 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
|
+
resolve(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((resolve, reject) => {
|
|
827
827
|
entry.process.stdin.write(toolResult + "\n", (err) => {
|
|
828
828
|
if (err) reject(err);
|
|
829
|
-
else
|
|
829
|
+
else resolve();
|
|
830
830
|
});
|
|
831
831
|
});
|
|
832
832
|
console.log(`[ProcessProvider] Session ${sessionId}: AskUserQuestion answered (toolUseId=${toolUseId})`);
|
|
@@ -896,15 +896,7 @@ function findCodexPath() {
|
|
|
896
896
|
}
|
|
897
897
|
function resolveCodexJsEntry(codexPath) {
|
|
898
898
|
try {
|
|
899
|
-
|
|
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
|
-
}
|
|
899
|
+
const realPath = (0, import_node_fs2.realpathSync)(codexPath);
|
|
908
900
|
const head = (0, import_node_fs2.readFileSync)(realPath, { encoding: "utf-8", flag: "r" }).slice(0, 100);
|
|
909
901
|
if (head.startsWith("#!/usr/bin/env node") || head.startsWith("#!/usr/bin/node")) {
|
|
910
902
|
return realPath;
|
|
@@ -1004,6 +996,7 @@ var CodexProvider = class {
|
|
|
1004
996
|
session_id: sessionId
|
|
1005
997
|
};
|
|
1006
998
|
this.emitter.emit(this.getEventName(sessionId), initEvent);
|
|
999
|
+
this.emitUserMessage(sessionId, message);
|
|
1007
1000
|
proc.on("error", (err) => {
|
|
1008
1001
|
console.error(`[CodexProvider] Session ${sessionId} process error:`, err.message);
|
|
1009
1002
|
this.activeSessions.delete(sessionId);
|
|
@@ -1044,7 +1037,7 @@ var CodexProvider = class {
|
|
|
1044
1037
|
summary: persisted.summary,
|
|
1045
1038
|
agentType: "codex"
|
|
1046
1039
|
};
|
|
1047
|
-
const placeholderProc = (0, import_child_process2.spawn)("
|
|
1040
|
+
const placeholderProc = (0, import_child_process2.spawn)(process.execPath, ["-e", ""], { stdio: "ignore" });
|
|
1048
1041
|
entry = {
|
|
1049
1042
|
session,
|
|
1050
1043
|
process: placeholderProc,
|
|
@@ -1065,6 +1058,7 @@ var CodexProvider = class {
|
|
|
1065
1058
|
if (!threadId) {
|
|
1066
1059
|
console.warn(`[CodexProvider] Session ${sessionId} has no thread ID, falling back to new thread`);
|
|
1067
1060
|
}
|
|
1061
|
+
this.emitUserMessage(sessionId, message);
|
|
1068
1062
|
const proc = this.spawnCodexProcess(entry.session.projectPath, message, threadId ?? void 0);
|
|
1069
1063
|
entry.session.status = "running";
|
|
1070
1064
|
entry.session.lastActiveAt = Date.now();
|
|
@@ -1088,7 +1082,26 @@ var CodexProvider = class {
|
|
|
1088
1082
|
};
|
|
1089
1083
|
}
|
|
1090
1084
|
getActiveSessions() {
|
|
1091
|
-
|
|
1085
|
+
const active = /* @__PURE__ */ new Map();
|
|
1086
|
+
for (const [id, entry] of this.activeSessions) {
|
|
1087
|
+
active.set(id, entry.session);
|
|
1088
|
+
}
|
|
1089
|
+
for (const [id, persisted] of this.persistedSessions) {
|
|
1090
|
+
if (!active.has(id) && persisted.threadId) {
|
|
1091
|
+
const projectId = persisted.projectPath.split("/").filter(Boolean).pop() ?? "unknown";
|
|
1092
|
+
active.set(id, {
|
|
1093
|
+
id,
|
|
1094
|
+
projectId,
|
|
1095
|
+
projectPath: persisted.projectPath,
|
|
1096
|
+
status: "idle",
|
|
1097
|
+
createdAt: persisted.createdAt,
|
|
1098
|
+
lastActiveAt: persisted.lastActiveAt,
|
|
1099
|
+
summary: persisted.summary,
|
|
1100
|
+
agentType: "codex"
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
return Array.from(active.values());
|
|
1092
1105
|
}
|
|
1093
1106
|
async generateSuggestion(_context) {
|
|
1094
1107
|
return "";
|
|
@@ -1213,6 +1226,15 @@ var CodexProvider = class {
|
|
|
1213
1226
|
usage: event.usage
|
|
1214
1227
|
};
|
|
1215
1228
|
this.emitter.emit(this.getEventName(sessionId), resultEvent);
|
|
1229
|
+
if (entry.threadId) {
|
|
1230
|
+
this.persistSession(sessionId, {
|
|
1231
|
+
threadId: entry.threadId,
|
|
1232
|
+
projectPath: entry.session.projectPath,
|
|
1233
|
+
summary: entry.session.summary,
|
|
1234
|
+
createdAt: entry.session.createdAt,
|
|
1235
|
+
lastActiveAt: Date.now()
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1216
1238
|
break;
|
|
1217
1239
|
}
|
|
1218
1240
|
case "turn.failed": {
|
|
@@ -1375,6 +1397,20 @@ var CodexProvider = class {
|
|
|
1375
1397
|
this.emitter.emit(this.getEventName(sessionId), syntheticResult);
|
|
1376
1398
|
});
|
|
1377
1399
|
}
|
|
1400
|
+
/**
|
|
1401
|
+
* 合成用户消息事件(Codex CLI 不会在 stdout 回显用户输入,需手动补充到事件流)
|
|
1402
|
+
*/
|
|
1403
|
+
emitUserMessage(sessionId, message) {
|
|
1404
|
+
const event = {
|
|
1405
|
+
type: "user",
|
|
1406
|
+
session_id: sessionId,
|
|
1407
|
+
message: {
|
|
1408
|
+
role: "user",
|
|
1409
|
+
content: [{ type: "text", text: message }]
|
|
1410
|
+
}
|
|
1411
|
+
};
|
|
1412
|
+
this.emitter.emit(this.getEventName(sessionId), event);
|
|
1413
|
+
}
|
|
1378
1414
|
emitError(sessionId, message) {
|
|
1379
1415
|
const event = {
|
|
1380
1416
|
type: "result",
|
|
@@ -1438,6 +1474,22 @@ var CodexProvider = class {
|
|
|
1438
1474
|
isKnownSession(sessionId) {
|
|
1439
1475
|
return this.activeSessions.has(sessionId) || this.persistedSessions.has(sessionId);
|
|
1440
1476
|
}
|
|
1477
|
+
/**
|
|
1478
|
+
* 获取指定项目路径下的 Codex 持久化会话列表(供 project_sessions 合并)
|
|
1479
|
+
*/
|
|
1480
|
+
getPersistedSessionsForProject(projectPath) {
|
|
1481
|
+
const result = [];
|
|
1482
|
+
for (const [sessionId, meta] of this.persistedSessions) {
|
|
1483
|
+
if (meta.projectPath === projectPath && meta.threadId) {
|
|
1484
|
+
result.push({
|
|
1485
|
+
sessionId,
|
|
1486
|
+
lastModified: meta.lastActiveAt,
|
|
1487
|
+
summary: meta.summary
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
return result;
|
|
1492
|
+
}
|
|
1441
1493
|
};
|
|
1442
1494
|
|
|
1443
1495
|
// src/providers/ProviderFactory.ts
|
|
@@ -1549,8 +1601,15 @@ var SessionManager = class {
|
|
|
1549
1601
|
* 调用 provider.startSession(),订阅事件流,
|
|
1550
1602
|
* 将 ClaudeStreamEvent 包装为 ServerEvent 转发。
|
|
1551
1603
|
*/
|
|
1552
|
-
async createSession(projectPath, message, resumeSessionId, newSessionId, model, permissionMode, effort, images, agentType
|
|
1553
|
-
|
|
1604
|
+
async createSession(projectPath, message, resumeSessionId, newSessionId, model, permissionMode, effort, images, agentType) {
|
|
1605
|
+
let resolvedAgentType = agentType ?? "claude-code";
|
|
1606
|
+
if (!agentType && resumeSessionId && this.providerFactory) {
|
|
1607
|
+
const codexProvider = this.providerFactory.getProvider("codex");
|
|
1608
|
+
if (codexProvider instanceof CodexProvider && codexProvider.isKnownSession(resumeSessionId)) {
|
|
1609
|
+
resolvedAgentType = "codex";
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
const provider = this.providerFactory ? this.providerFactory.getProvider(resolvedAgentType) : this.provider;
|
|
1554
1613
|
const session = await provider.startSession({
|
|
1555
1614
|
projectPath,
|
|
1556
1615
|
message,
|
|
@@ -1561,7 +1620,7 @@ var SessionManager = class {
|
|
|
1561
1620
|
effort,
|
|
1562
1621
|
images
|
|
1563
1622
|
});
|
|
1564
|
-
this.sessionAgentType.set(session.id,
|
|
1623
|
+
this.sessionAgentType.set(session.id, resolvedAgentType);
|
|
1565
1624
|
this.lastBroadcastStatus.set(session.id, session.status);
|
|
1566
1625
|
this.sessionProjectPaths.set(session.id, projectPath);
|
|
1567
1626
|
this.unsubscribeSession(session.id);
|
|
@@ -1917,8 +1976,8 @@ var SessionManager = class {
|
|
|
1917
1976
|
};
|
|
1918
1977
|
this.updateSessionStatus(sessionId, "waiting_question");
|
|
1919
1978
|
this.emit({ type: "question_request", request });
|
|
1920
|
-
const answerPromise = new Promise((
|
|
1921
|
-
this.pendingQuestions.set(requestId, { sessionId, toolUseId, question, options, createdAt: request.createdAt, resolve
|
|
1979
|
+
const answerPromise = new Promise((resolve) => {
|
|
1980
|
+
this.pendingQuestions.set(requestId, { sessionId, toolUseId, question, options, createdAt: request.createdAt, resolve });
|
|
1922
1981
|
});
|
|
1923
1982
|
answerPromise.then(async (answer) => {
|
|
1924
1983
|
try {
|
|
@@ -2160,11 +2219,11 @@ var WsBridge = class _WsBridge {
|
|
|
2160
2219
|
* 使用此方法代替 new WsBridge(),确保 EADDRINUSE 等错误能被调用方的 try-catch 捕获。
|
|
2161
2220
|
*/
|
|
2162
2221
|
static async create(options) {
|
|
2163
|
-
return new Promise((
|
|
2222
|
+
return new Promise((resolve, reject) => {
|
|
2164
2223
|
const bridge = new _WsBridge(options);
|
|
2165
2224
|
bridge.wss.once("listening", () => {
|
|
2166
2225
|
bridge.wss.on("error", (err) => console.error(`[WsBridge] ${t("ws.serverError")}:`, err));
|
|
2167
|
-
|
|
2226
|
+
resolve(bridge);
|
|
2168
2227
|
});
|
|
2169
2228
|
bridge.wss.once("error", reject);
|
|
2170
2229
|
});
|
|
@@ -2227,7 +2286,7 @@ var WsBridge = class _WsBridge {
|
|
|
2227
2286
|
}
|
|
2228
2287
|
/** 优雅关闭 WebSocket 服务 */
|
|
2229
2288
|
close() {
|
|
2230
|
-
return new Promise((
|
|
2289
|
+
return new Promise((resolve, reject) => {
|
|
2231
2290
|
if (this.heartbeatTimer) {
|
|
2232
2291
|
clearInterval(this.heartbeatTimer);
|
|
2233
2292
|
this.heartbeatTimer = null;
|
|
@@ -2240,7 +2299,7 @@ var WsBridge = class _WsBridge {
|
|
|
2240
2299
|
reject(err);
|
|
2241
2300
|
} else {
|
|
2242
2301
|
console.log("[WsBridge] WebSocket server closed");
|
|
2243
|
-
|
|
2302
|
+
resolve();
|
|
2244
2303
|
}
|
|
2245
2304
|
});
|
|
2246
2305
|
});
|
|
@@ -2380,11 +2439,11 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2380
2439
|
* 异步工厂方法:等待端口监听成功后 resolve,端口占用等错误时 reject。
|
|
2381
2440
|
*/
|
|
2382
2441
|
static async create(options) {
|
|
2383
|
-
return new Promise((
|
|
2442
|
+
return new Promise((resolve, reject) => {
|
|
2384
2443
|
const proxy = new _ApprovalProxy(options);
|
|
2385
2444
|
proxy.server.once("listening", () => {
|
|
2386
2445
|
proxy.server.on("error", (err) => console.error(`[ApprovalProxy] ${t("approval.serverError")}:`, err));
|
|
2387
|
-
|
|
2446
|
+
resolve(proxy);
|
|
2388
2447
|
});
|
|
2389
2448
|
proxy.server.once("error", reject);
|
|
2390
2449
|
});
|
|
@@ -2541,7 +2600,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2541
2600
|
}
|
|
2542
2601
|
/** 优雅关闭 HTTP 服务 */
|
|
2543
2602
|
close() {
|
|
2544
|
-
return new Promise((
|
|
2603
|
+
return new Promise((resolve, reject) => {
|
|
2545
2604
|
const pendingEntries = Array.from(this.pendingApprovals.entries());
|
|
2546
2605
|
for (const [, pending] of pendingEntries) {
|
|
2547
2606
|
clearTimeout(pending.timer);
|
|
@@ -2553,7 +2612,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2553
2612
|
reject(err);
|
|
2554
2613
|
} else {
|
|
2555
2614
|
console.log(`[ApprovalProxy] ${t("approval.httpClosed")}`);
|
|
2556
|
-
|
|
2615
|
+
resolve();
|
|
2557
2616
|
}
|
|
2558
2617
|
});
|
|
2559
2618
|
});
|
|
@@ -2624,13 +2683,13 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2624
2683
|
return;
|
|
2625
2684
|
}
|
|
2626
2685
|
this.notifyApprovalRequest(approvalRequest);
|
|
2627
|
-
const decision = await new Promise((
|
|
2686
|
+
const decision = await new Promise((resolve) => {
|
|
2628
2687
|
const timer = setTimeout(() => {
|
|
2629
2688
|
console.log(`[ApprovalProxy] ${t("approval.timeout", { id: requestId })}`);
|
|
2630
2689
|
this.pendingApprovals.delete(requestId);
|
|
2631
|
-
|
|
2690
|
+
resolve({ decision: "allow" });
|
|
2632
2691
|
}, 325e3);
|
|
2633
|
-
this.pendingApprovals.set(requestId, { resolve
|
|
2692
|
+
this.pendingApprovals.set(requestId, { resolve, timer, request: approvalRequest });
|
|
2634
2693
|
});
|
|
2635
2694
|
this.sendJson(res, 200, decision);
|
|
2636
2695
|
} catch (err) {
|
|
@@ -2691,7 +2750,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2691
2750
|
/** 手动解析请求的 JSON body(限制最大 1MB 防止滥用) */
|
|
2692
2751
|
parseJsonBody(req) {
|
|
2693
2752
|
const MAX_BODY_SIZE = 1024 * 1024;
|
|
2694
|
-
return new Promise((
|
|
2753
|
+
return new Promise((resolve, reject) => {
|
|
2695
2754
|
const chunks = [];
|
|
2696
2755
|
let totalSize = 0;
|
|
2697
2756
|
let destroyed = false;
|
|
@@ -2709,7 +2768,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2709
2768
|
try {
|
|
2710
2769
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
2711
2770
|
const parsed = JSON.parse(raw);
|
|
2712
|
-
|
|
2771
|
+
resolve(parsed);
|
|
2713
2772
|
} catch {
|
|
2714
2773
|
reject(new Error(t("approval.invalidJson")));
|
|
2715
2774
|
}
|
|
@@ -3376,12 +3435,12 @@ var DesktopNotificationChannel = class {
|
|
|
3376
3435
|
const body = payload.body.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
3377
3436
|
const sound = payload.sound ?? "Ping";
|
|
3378
3437
|
const script = `display notification "${body}" with title "${title}" sound name "${sound}"`;
|
|
3379
|
-
return new Promise((
|
|
3438
|
+
return new Promise((resolve) => {
|
|
3380
3439
|
(0, import_node_child_process6.execFile)("osascript", ["-e", script], (err) => {
|
|
3381
3440
|
if (err) {
|
|
3382
3441
|
console.warn("[DesktopNotificationChannel] Send notification failed:", err.message);
|
|
3383
3442
|
}
|
|
3384
|
-
|
|
3443
|
+
resolve();
|
|
3385
3444
|
});
|
|
3386
3445
|
});
|
|
3387
3446
|
}
|
|
@@ -3581,7 +3640,7 @@ var ActivityPushChannel = class {
|
|
|
3581
3640
|
const topic = "com.kachun.sessix.push-type.liveactivity";
|
|
3582
3641
|
const jwt = this.getJWT();
|
|
3583
3642
|
const payloadStr = JSON.stringify(payload);
|
|
3584
|
-
return new Promise((
|
|
3643
|
+
return new Promise((resolve, reject) => {
|
|
3585
3644
|
let client;
|
|
3586
3645
|
try {
|
|
3587
3646
|
client = this.getHttp2Client();
|
|
@@ -3609,7 +3668,7 @@ var ActivityPushChannel = class {
|
|
|
3609
3668
|
});
|
|
3610
3669
|
req.on("end", () => {
|
|
3611
3670
|
if (statusCode === 200) {
|
|
3612
|
-
|
|
3671
|
+
resolve();
|
|
3613
3672
|
} else {
|
|
3614
3673
|
if (statusCode === 0) {
|
|
3615
3674
|
this.http2Client?.destroy();
|
|
@@ -3792,7 +3851,7 @@ async function getSessionHistory(projectPath, sessionId) {
|
|
|
3792
3851
|
if (err.code === "ENOENT") return null;
|
|
3793
3852
|
throw err;
|
|
3794
3853
|
});
|
|
3795
|
-
if (raw === null) return { ok:
|
|
3854
|
+
if (raw === null) return { ok: true, value: [] };
|
|
3796
3855
|
const lines = raw.split("\n").filter((l) => l.trim());
|
|
3797
3856
|
const events = [];
|
|
3798
3857
|
for (const line of lines) {
|
|
@@ -4282,7 +4341,7 @@ async function killPortProcess(port) {
|
|
|
4282
4341
|
await execAsync(`kill -9 ${pids.join(" ")}`);
|
|
4283
4342
|
}
|
|
4284
4343
|
}
|
|
4285
|
-
await new Promise((
|
|
4344
|
+
await new Promise((resolve) => setTimeout(resolve, 600));
|
|
4286
4345
|
} catch {
|
|
4287
4346
|
}
|
|
4288
4347
|
}
|
|
@@ -4534,10 +4593,27 @@ async function start(opts = {}) {
|
|
|
4534
4593
|
case "list_project_sessions": {
|
|
4535
4594
|
const histResult = await getHistoricalSessions(event.projectPath);
|
|
4536
4595
|
if (histResult.ok) {
|
|
4596
|
+
const sessions = histResult.value;
|
|
4597
|
+
const codexProvider = providerFactory.getProvider("codex");
|
|
4598
|
+
if (codexProvider instanceof CodexProvider) {
|
|
4599
|
+
const codexSessions = codexProvider.getPersistedSessionsForProject(event.projectPath);
|
|
4600
|
+
const existingIds = new Set(sessions.map((s) => s.sessionId));
|
|
4601
|
+
for (const cs of codexSessions) {
|
|
4602
|
+
if (!existingIds.has(cs.sessionId)) {
|
|
4603
|
+
sessions.push({
|
|
4604
|
+
sessionId: cs.sessionId,
|
|
4605
|
+
lastModified: cs.lastModified,
|
|
4606
|
+
summary: cs.summary,
|
|
4607
|
+
agentType: "codex"
|
|
4608
|
+
});
|
|
4609
|
+
}
|
|
4610
|
+
}
|
|
4611
|
+
sessions.sort((a, b) => b.lastModified - a.lastModified);
|
|
4612
|
+
}
|
|
4537
4613
|
wsBridge.send(ws, {
|
|
4538
4614
|
type: "project_sessions",
|
|
4539
4615
|
projectPath: event.projectPath,
|
|
4540
|
-
sessions
|
|
4616
|
+
sessions
|
|
4541
4617
|
});
|
|
4542
4618
|
} else {
|
|
4543
4619
|
wsBridge.send(ws, {
|
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((resolve) => {
|
|
335
335
|
if (proc.exitCode !== null || proc.signalCode !== null) {
|
|
336
|
-
|
|
336
|
+
resolve();
|
|
337
337
|
return;
|
|
338
338
|
}
|
|
339
339
|
const onExit = () => {
|
|
340
340
|
clearTimeout(timer);
|
|
341
|
-
|
|
341
|
+
resolve();
|
|
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
|
+
resolve();
|
|
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((resolve, 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
|
+
resolve(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((resolve, reject) => {
|
|
832
832
|
entry.process.stdin.write(toolResult + "\n", (err) => {
|
|
833
833
|
if (err) reject(err);
|
|
834
|
-
else
|
|
834
|
+
else resolve();
|
|
835
835
|
});
|
|
836
836
|
});
|
|
837
837
|
console.log(`[ProcessProvider] Session ${sessionId}: AskUserQuestion answered (toolUseId=${toolUseId})`);
|
|
@@ -901,15 +901,7 @@ function findCodexPath() {
|
|
|
901
901
|
}
|
|
902
902
|
function resolveCodexJsEntry(codexPath) {
|
|
903
903
|
try {
|
|
904
|
-
|
|
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
|
-
}
|
|
904
|
+
const realPath = (0, import_node_fs2.realpathSync)(codexPath);
|
|
913
905
|
const head = (0, import_node_fs2.readFileSync)(realPath, { encoding: "utf-8", flag: "r" }).slice(0, 100);
|
|
914
906
|
if (head.startsWith("#!/usr/bin/env node") || head.startsWith("#!/usr/bin/node")) {
|
|
915
907
|
return realPath;
|
|
@@ -1009,6 +1001,7 @@ var CodexProvider = class {
|
|
|
1009
1001
|
session_id: sessionId
|
|
1010
1002
|
};
|
|
1011
1003
|
this.emitter.emit(this.getEventName(sessionId), initEvent);
|
|
1004
|
+
this.emitUserMessage(sessionId, message);
|
|
1012
1005
|
proc.on("error", (err) => {
|
|
1013
1006
|
console.error(`[CodexProvider] Session ${sessionId} process error:`, err.message);
|
|
1014
1007
|
this.activeSessions.delete(sessionId);
|
|
@@ -1049,7 +1042,7 @@ var CodexProvider = class {
|
|
|
1049
1042
|
summary: persisted.summary,
|
|
1050
1043
|
agentType: "codex"
|
|
1051
1044
|
};
|
|
1052
|
-
const placeholderProc = (0, import_child_process2.spawn)("
|
|
1045
|
+
const placeholderProc = (0, import_child_process2.spawn)(process.execPath, ["-e", ""], { stdio: "ignore" });
|
|
1053
1046
|
entry = {
|
|
1054
1047
|
session,
|
|
1055
1048
|
process: placeholderProc,
|
|
@@ -1070,6 +1063,7 @@ var CodexProvider = class {
|
|
|
1070
1063
|
if (!threadId) {
|
|
1071
1064
|
console.warn(`[CodexProvider] Session ${sessionId} has no thread ID, falling back to new thread`);
|
|
1072
1065
|
}
|
|
1066
|
+
this.emitUserMessage(sessionId, message);
|
|
1073
1067
|
const proc = this.spawnCodexProcess(entry.session.projectPath, message, threadId ?? void 0);
|
|
1074
1068
|
entry.session.status = "running";
|
|
1075
1069
|
entry.session.lastActiveAt = Date.now();
|
|
@@ -1093,7 +1087,26 @@ var CodexProvider = class {
|
|
|
1093
1087
|
};
|
|
1094
1088
|
}
|
|
1095
1089
|
getActiveSessions() {
|
|
1096
|
-
|
|
1090
|
+
const active = /* @__PURE__ */ new Map();
|
|
1091
|
+
for (const [id, entry] of this.activeSessions) {
|
|
1092
|
+
active.set(id, entry.session);
|
|
1093
|
+
}
|
|
1094
|
+
for (const [id, persisted] of this.persistedSessions) {
|
|
1095
|
+
if (!active.has(id) && persisted.threadId) {
|
|
1096
|
+
const projectId = persisted.projectPath.split("/").filter(Boolean).pop() ?? "unknown";
|
|
1097
|
+
active.set(id, {
|
|
1098
|
+
id,
|
|
1099
|
+
projectId,
|
|
1100
|
+
projectPath: persisted.projectPath,
|
|
1101
|
+
status: "idle",
|
|
1102
|
+
createdAt: persisted.createdAt,
|
|
1103
|
+
lastActiveAt: persisted.lastActiveAt,
|
|
1104
|
+
summary: persisted.summary,
|
|
1105
|
+
agentType: "codex"
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
return Array.from(active.values());
|
|
1097
1110
|
}
|
|
1098
1111
|
async generateSuggestion(_context) {
|
|
1099
1112
|
return "";
|
|
@@ -1218,6 +1231,15 @@ var CodexProvider = class {
|
|
|
1218
1231
|
usage: event.usage
|
|
1219
1232
|
};
|
|
1220
1233
|
this.emitter.emit(this.getEventName(sessionId), resultEvent);
|
|
1234
|
+
if (entry.threadId) {
|
|
1235
|
+
this.persistSession(sessionId, {
|
|
1236
|
+
threadId: entry.threadId,
|
|
1237
|
+
projectPath: entry.session.projectPath,
|
|
1238
|
+
summary: entry.session.summary,
|
|
1239
|
+
createdAt: entry.session.createdAt,
|
|
1240
|
+
lastActiveAt: Date.now()
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1221
1243
|
break;
|
|
1222
1244
|
}
|
|
1223
1245
|
case "turn.failed": {
|
|
@@ -1380,6 +1402,20 @@ var CodexProvider = class {
|
|
|
1380
1402
|
this.emitter.emit(this.getEventName(sessionId), syntheticResult);
|
|
1381
1403
|
});
|
|
1382
1404
|
}
|
|
1405
|
+
/**
|
|
1406
|
+
* 合成用户消息事件(Codex CLI 不会在 stdout 回显用户输入,需手动补充到事件流)
|
|
1407
|
+
*/
|
|
1408
|
+
emitUserMessage(sessionId, message) {
|
|
1409
|
+
const event = {
|
|
1410
|
+
type: "user",
|
|
1411
|
+
session_id: sessionId,
|
|
1412
|
+
message: {
|
|
1413
|
+
role: "user",
|
|
1414
|
+
content: [{ type: "text", text: message }]
|
|
1415
|
+
}
|
|
1416
|
+
};
|
|
1417
|
+
this.emitter.emit(this.getEventName(sessionId), event);
|
|
1418
|
+
}
|
|
1383
1419
|
emitError(sessionId, message) {
|
|
1384
1420
|
const event = {
|
|
1385
1421
|
type: "result",
|
|
@@ -1443,6 +1479,22 @@ var CodexProvider = class {
|
|
|
1443
1479
|
isKnownSession(sessionId) {
|
|
1444
1480
|
return this.activeSessions.has(sessionId) || this.persistedSessions.has(sessionId);
|
|
1445
1481
|
}
|
|
1482
|
+
/**
|
|
1483
|
+
* 获取指定项目路径下的 Codex 持久化会话列表(供 project_sessions 合并)
|
|
1484
|
+
*/
|
|
1485
|
+
getPersistedSessionsForProject(projectPath) {
|
|
1486
|
+
const result = [];
|
|
1487
|
+
for (const [sessionId, meta] of this.persistedSessions) {
|
|
1488
|
+
if (meta.projectPath === projectPath && meta.threadId) {
|
|
1489
|
+
result.push({
|
|
1490
|
+
sessionId,
|
|
1491
|
+
lastModified: meta.lastActiveAt,
|
|
1492
|
+
summary: meta.summary
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
return result;
|
|
1497
|
+
}
|
|
1446
1498
|
};
|
|
1447
1499
|
|
|
1448
1500
|
// src/providers/ProviderFactory.ts
|
|
@@ -1554,8 +1606,15 @@ var SessionManager = class {
|
|
|
1554
1606
|
* 调用 provider.startSession(),订阅事件流,
|
|
1555
1607
|
* 将 ClaudeStreamEvent 包装为 ServerEvent 转发。
|
|
1556
1608
|
*/
|
|
1557
|
-
async createSession(projectPath, message, resumeSessionId, newSessionId, model, permissionMode, effort, images, agentType
|
|
1558
|
-
|
|
1609
|
+
async createSession(projectPath, message, resumeSessionId, newSessionId, model, permissionMode, effort, images, agentType) {
|
|
1610
|
+
let resolvedAgentType = agentType ?? "claude-code";
|
|
1611
|
+
if (!agentType && resumeSessionId && this.providerFactory) {
|
|
1612
|
+
const codexProvider = this.providerFactory.getProvider("codex");
|
|
1613
|
+
if (codexProvider instanceof CodexProvider && codexProvider.isKnownSession(resumeSessionId)) {
|
|
1614
|
+
resolvedAgentType = "codex";
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
const provider = this.providerFactory ? this.providerFactory.getProvider(resolvedAgentType) : this.provider;
|
|
1559
1618
|
const session = await provider.startSession({
|
|
1560
1619
|
projectPath,
|
|
1561
1620
|
message,
|
|
@@ -1566,7 +1625,7 @@ var SessionManager = class {
|
|
|
1566
1625
|
effort,
|
|
1567
1626
|
images
|
|
1568
1627
|
});
|
|
1569
|
-
this.sessionAgentType.set(session.id,
|
|
1628
|
+
this.sessionAgentType.set(session.id, resolvedAgentType);
|
|
1570
1629
|
this.lastBroadcastStatus.set(session.id, session.status);
|
|
1571
1630
|
this.sessionProjectPaths.set(session.id, projectPath);
|
|
1572
1631
|
this.unsubscribeSession(session.id);
|
|
@@ -1922,8 +1981,8 @@ var SessionManager = class {
|
|
|
1922
1981
|
};
|
|
1923
1982
|
this.updateSessionStatus(sessionId, "waiting_question");
|
|
1924
1983
|
this.emit({ type: "question_request", request });
|
|
1925
|
-
const answerPromise = new Promise((
|
|
1926
|
-
this.pendingQuestions.set(requestId, { sessionId, toolUseId, question, options, createdAt: request.createdAt, resolve
|
|
1984
|
+
const answerPromise = new Promise((resolve) => {
|
|
1985
|
+
this.pendingQuestions.set(requestId, { sessionId, toolUseId, question, options, createdAt: request.createdAt, resolve });
|
|
1927
1986
|
});
|
|
1928
1987
|
answerPromise.then(async (answer) => {
|
|
1929
1988
|
try {
|
|
@@ -2165,11 +2224,11 @@ var WsBridge = class _WsBridge {
|
|
|
2165
2224
|
* 使用此方法代替 new WsBridge(),确保 EADDRINUSE 等错误能被调用方的 try-catch 捕获。
|
|
2166
2225
|
*/
|
|
2167
2226
|
static async create(options) {
|
|
2168
|
-
return new Promise((
|
|
2227
|
+
return new Promise((resolve, reject) => {
|
|
2169
2228
|
const bridge = new _WsBridge(options);
|
|
2170
2229
|
bridge.wss.once("listening", () => {
|
|
2171
2230
|
bridge.wss.on("error", (err) => console.error(`[WsBridge] ${t("ws.serverError")}:`, err));
|
|
2172
|
-
|
|
2231
|
+
resolve(bridge);
|
|
2173
2232
|
});
|
|
2174
2233
|
bridge.wss.once("error", reject);
|
|
2175
2234
|
});
|
|
@@ -2232,7 +2291,7 @@ var WsBridge = class _WsBridge {
|
|
|
2232
2291
|
}
|
|
2233
2292
|
/** 优雅关闭 WebSocket 服务 */
|
|
2234
2293
|
close() {
|
|
2235
|
-
return new Promise((
|
|
2294
|
+
return new Promise((resolve, reject) => {
|
|
2236
2295
|
if (this.heartbeatTimer) {
|
|
2237
2296
|
clearInterval(this.heartbeatTimer);
|
|
2238
2297
|
this.heartbeatTimer = null;
|
|
@@ -2245,7 +2304,7 @@ var WsBridge = class _WsBridge {
|
|
|
2245
2304
|
reject(err);
|
|
2246
2305
|
} else {
|
|
2247
2306
|
console.log("[WsBridge] WebSocket server closed");
|
|
2248
|
-
|
|
2307
|
+
resolve();
|
|
2249
2308
|
}
|
|
2250
2309
|
});
|
|
2251
2310
|
});
|
|
@@ -2385,11 +2444,11 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2385
2444
|
* 异步工厂方法:等待端口监听成功后 resolve,端口占用等错误时 reject。
|
|
2386
2445
|
*/
|
|
2387
2446
|
static async create(options) {
|
|
2388
|
-
return new Promise((
|
|
2447
|
+
return new Promise((resolve, reject) => {
|
|
2389
2448
|
const proxy = new _ApprovalProxy(options);
|
|
2390
2449
|
proxy.server.once("listening", () => {
|
|
2391
2450
|
proxy.server.on("error", (err) => console.error(`[ApprovalProxy] ${t("approval.serverError")}:`, err));
|
|
2392
|
-
|
|
2451
|
+
resolve(proxy);
|
|
2393
2452
|
});
|
|
2394
2453
|
proxy.server.once("error", reject);
|
|
2395
2454
|
});
|
|
@@ -2546,7 +2605,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2546
2605
|
}
|
|
2547
2606
|
/** 优雅关闭 HTTP 服务 */
|
|
2548
2607
|
close() {
|
|
2549
|
-
return new Promise((
|
|
2608
|
+
return new Promise((resolve, reject) => {
|
|
2550
2609
|
const pendingEntries = Array.from(this.pendingApprovals.entries());
|
|
2551
2610
|
for (const [, pending] of pendingEntries) {
|
|
2552
2611
|
clearTimeout(pending.timer);
|
|
@@ -2558,7 +2617,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2558
2617
|
reject(err);
|
|
2559
2618
|
} else {
|
|
2560
2619
|
console.log(`[ApprovalProxy] ${t("approval.httpClosed")}`);
|
|
2561
|
-
|
|
2620
|
+
resolve();
|
|
2562
2621
|
}
|
|
2563
2622
|
});
|
|
2564
2623
|
});
|
|
@@ -2629,13 +2688,13 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2629
2688
|
return;
|
|
2630
2689
|
}
|
|
2631
2690
|
this.notifyApprovalRequest(approvalRequest);
|
|
2632
|
-
const decision = await new Promise((
|
|
2691
|
+
const decision = await new Promise((resolve) => {
|
|
2633
2692
|
const timer = setTimeout(() => {
|
|
2634
2693
|
console.log(`[ApprovalProxy] ${t("approval.timeout", { id: requestId })}`);
|
|
2635
2694
|
this.pendingApprovals.delete(requestId);
|
|
2636
|
-
|
|
2695
|
+
resolve({ decision: "allow" });
|
|
2637
2696
|
}, 325e3);
|
|
2638
|
-
this.pendingApprovals.set(requestId, { resolve
|
|
2697
|
+
this.pendingApprovals.set(requestId, { resolve, timer, request: approvalRequest });
|
|
2639
2698
|
});
|
|
2640
2699
|
this.sendJson(res, 200, decision);
|
|
2641
2700
|
} catch (err) {
|
|
@@ -2696,7 +2755,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2696
2755
|
/** 手动解析请求的 JSON body(限制最大 1MB 防止滥用) */
|
|
2697
2756
|
parseJsonBody(req) {
|
|
2698
2757
|
const MAX_BODY_SIZE = 1024 * 1024;
|
|
2699
|
-
return new Promise((
|
|
2758
|
+
return new Promise((resolve, reject) => {
|
|
2700
2759
|
const chunks = [];
|
|
2701
2760
|
let totalSize = 0;
|
|
2702
2761
|
let destroyed = false;
|
|
@@ -2714,7 +2773,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2714
2773
|
try {
|
|
2715
2774
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
2716
2775
|
const parsed = JSON.parse(raw);
|
|
2717
|
-
|
|
2776
|
+
resolve(parsed);
|
|
2718
2777
|
} catch {
|
|
2719
2778
|
reject(new Error(t("approval.invalidJson")));
|
|
2720
2779
|
}
|
|
@@ -3381,12 +3440,12 @@ var DesktopNotificationChannel = class {
|
|
|
3381
3440
|
const body = payload.body.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
3382
3441
|
const sound = payload.sound ?? "Ping";
|
|
3383
3442
|
const script = `display notification "${body}" with title "${title}" sound name "${sound}"`;
|
|
3384
|
-
return new Promise((
|
|
3443
|
+
return new Promise((resolve) => {
|
|
3385
3444
|
(0, import_node_child_process6.execFile)("osascript", ["-e", script], (err) => {
|
|
3386
3445
|
if (err) {
|
|
3387
3446
|
console.warn("[DesktopNotificationChannel] Send notification failed:", err.message);
|
|
3388
3447
|
}
|
|
3389
|
-
|
|
3448
|
+
resolve();
|
|
3390
3449
|
});
|
|
3391
3450
|
});
|
|
3392
3451
|
}
|
|
@@ -3586,7 +3645,7 @@ var ActivityPushChannel = class {
|
|
|
3586
3645
|
const topic = "com.kachun.sessix.push-type.liveactivity";
|
|
3587
3646
|
const jwt = this.getJWT();
|
|
3588
3647
|
const payloadStr = JSON.stringify(payload);
|
|
3589
|
-
return new Promise((
|
|
3648
|
+
return new Promise((resolve, reject) => {
|
|
3590
3649
|
let client;
|
|
3591
3650
|
try {
|
|
3592
3651
|
client = this.getHttp2Client();
|
|
@@ -3614,7 +3673,7 @@ var ActivityPushChannel = class {
|
|
|
3614
3673
|
});
|
|
3615
3674
|
req.on("end", () => {
|
|
3616
3675
|
if (statusCode === 200) {
|
|
3617
|
-
|
|
3676
|
+
resolve();
|
|
3618
3677
|
} else {
|
|
3619
3678
|
if (statusCode === 0) {
|
|
3620
3679
|
this.http2Client?.destroy();
|
|
@@ -3797,7 +3856,7 @@ async function getSessionHistory(projectPath, sessionId) {
|
|
|
3797
3856
|
if (err.code === "ENOENT") return null;
|
|
3798
3857
|
throw err;
|
|
3799
3858
|
});
|
|
3800
|
-
if (raw === null) return { ok:
|
|
3859
|
+
if (raw === null) return { ok: true, value: [] };
|
|
3801
3860
|
const lines = raw.split("\n").filter((l) => l.trim());
|
|
3802
3861
|
const events = [];
|
|
3803
3862
|
for (const line of lines) {
|
|
@@ -4287,7 +4346,7 @@ async function killPortProcess(port) {
|
|
|
4287
4346
|
await execAsync(`kill -9 ${pids.join(" ")}`);
|
|
4288
4347
|
}
|
|
4289
4348
|
}
|
|
4290
|
-
await new Promise((
|
|
4349
|
+
await new Promise((resolve) => setTimeout(resolve, 600));
|
|
4291
4350
|
} catch {
|
|
4292
4351
|
}
|
|
4293
4352
|
}
|
|
@@ -4539,10 +4598,27 @@ async function start(opts = {}) {
|
|
|
4539
4598
|
case "list_project_sessions": {
|
|
4540
4599
|
const histResult = await getHistoricalSessions(event.projectPath);
|
|
4541
4600
|
if (histResult.ok) {
|
|
4601
|
+
const sessions = histResult.value;
|
|
4602
|
+
const codexProvider = providerFactory.getProvider("codex");
|
|
4603
|
+
if (codexProvider instanceof CodexProvider) {
|
|
4604
|
+
const codexSessions = codexProvider.getPersistedSessionsForProject(event.projectPath);
|
|
4605
|
+
const existingIds = new Set(sessions.map((s) => s.sessionId));
|
|
4606
|
+
for (const cs of codexSessions) {
|
|
4607
|
+
if (!existingIds.has(cs.sessionId)) {
|
|
4608
|
+
sessions.push({
|
|
4609
|
+
sessionId: cs.sessionId,
|
|
4610
|
+
lastModified: cs.lastModified,
|
|
4611
|
+
summary: cs.summary,
|
|
4612
|
+
agentType: "codex"
|
|
4613
|
+
});
|
|
4614
|
+
}
|
|
4615
|
+
}
|
|
4616
|
+
sessions.sort((a, b) => b.lastModified - a.lastModified);
|
|
4617
|
+
}
|
|
4542
4618
|
wsBridge.send(ws, {
|
|
4543
4619
|
type: "project_sessions",
|
|
4544
4620
|
projectPath: event.projectPath,
|
|
4545
|
-
sessions
|
|
4621
|
+
sessions
|
|
4546
4622
|
});
|
|
4547
4623
|
} else {
|
|
4548
4624
|
wsBridge.send(ws, {
|