sessix-server 0.3.1 → 0.3.2
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 +109 -40
- package/dist/server.js +109 -40
- 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
|
|
@@ -1917,8 +1969,8 @@ var SessionManager = class {
|
|
|
1917
1969
|
};
|
|
1918
1970
|
this.updateSessionStatus(sessionId, "waiting_question");
|
|
1919
1971
|
this.emit({ type: "question_request", request });
|
|
1920
|
-
const answerPromise = new Promise((
|
|
1921
|
-
this.pendingQuestions.set(requestId, { sessionId, toolUseId, question, options, createdAt: request.createdAt, resolve
|
|
1972
|
+
const answerPromise = new Promise((resolve) => {
|
|
1973
|
+
this.pendingQuestions.set(requestId, { sessionId, toolUseId, question, options, createdAt: request.createdAt, resolve });
|
|
1922
1974
|
});
|
|
1923
1975
|
answerPromise.then(async (answer) => {
|
|
1924
1976
|
try {
|
|
@@ -2160,11 +2212,11 @@ var WsBridge = class _WsBridge {
|
|
|
2160
2212
|
* 使用此方法代替 new WsBridge(),确保 EADDRINUSE 等错误能被调用方的 try-catch 捕获。
|
|
2161
2213
|
*/
|
|
2162
2214
|
static async create(options) {
|
|
2163
|
-
return new Promise((
|
|
2215
|
+
return new Promise((resolve, reject) => {
|
|
2164
2216
|
const bridge = new _WsBridge(options);
|
|
2165
2217
|
bridge.wss.once("listening", () => {
|
|
2166
2218
|
bridge.wss.on("error", (err) => console.error(`[WsBridge] ${t("ws.serverError")}:`, err));
|
|
2167
|
-
|
|
2219
|
+
resolve(bridge);
|
|
2168
2220
|
});
|
|
2169
2221
|
bridge.wss.once("error", reject);
|
|
2170
2222
|
});
|
|
@@ -2227,7 +2279,7 @@ var WsBridge = class _WsBridge {
|
|
|
2227
2279
|
}
|
|
2228
2280
|
/** 优雅关闭 WebSocket 服务 */
|
|
2229
2281
|
close() {
|
|
2230
|
-
return new Promise((
|
|
2282
|
+
return new Promise((resolve, reject) => {
|
|
2231
2283
|
if (this.heartbeatTimer) {
|
|
2232
2284
|
clearInterval(this.heartbeatTimer);
|
|
2233
2285
|
this.heartbeatTimer = null;
|
|
@@ -2240,7 +2292,7 @@ var WsBridge = class _WsBridge {
|
|
|
2240
2292
|
reject(err);
|
|
2241
2293
|
} else {
|
|
2242
2294
|
console.log("[WsBridge] WebSocket server closed");
|
|
2243
|
-
|
|
2295
|
+
resolve();
|
|
2244
2296
|
}
|
|
2245
2297
|
});
|
|
2246
2298
|
});
|
|
@@ -2380,11 +2432,11 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2380
2432
|
* 异步工厂方法:等待端口监听成功后 resolve,端口占用等错误时 reject。
|
|
2381
2433
|
*/
|
|
2382
2434
|
static async create(options) {
|
|
2383
|
-
return new Promise((
|
|
2435
|
+
return new Promise((resolve, reject) => {
|
|
2384
2436
|
const proxy = new _ApprovalProxy(options);
|
|
2385
2437
|
proxy.server.once("listening", () => {
|
|
2386
2438
|
proxy.server.on("error", (err) => console.error(`[ApprovalProxy] ${t("approval.serverError")}:`, err));
|
|
2387
|
-
|
|
2439
|
+
resolve(proxy);
|
|
2388
2440
|
});
|
|
2389
2441
|
proxy.server.once("error", reject);
|
|
2390
2442
|
});
|
|
@@ -2541,7 +2593,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2541
2593
|
}
|
|
2542
2594
|
/** 优雅关闭 HTTP 服务 */
|
|
2543
2595
|
close() {
|
|
2544
|
-
return new Promise((
|
|
2596
|
+
return new Promise((resolve, reject) => {
|
|
2545
2597
|
const pendingEntries = Array.from(this.pendingApprovals.entries());
|
|
2546
2598
|
for (const [, pending] of pendingEntries) {
|
|
2547
2599
|
clearTimeout(pending.timer);
|
|
@@ -2553,7 +2605,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2553
2605
|
reject(err);
|
|
2554
2606
|
} else {
|
|
2555
2607
|
console.log(`[ApprovalProxy] ${t("approval.httpClosed")}`);
|
|
2556
|
-
|
|
2608
|
+
resolve();
|
|
2557
2609
|
}
|
|
2558
2610
|
});
|
|
2559
2611
|
});
|
|
@@ -2624,13 +2676,13 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2624
2676
|
return;
|
|
2625
2677
|
}
|
|
2626
2678
|
this.notifyApprovalRequest(approvalRequest);
|
|
2627
|
-
const decision = await new Promise((
|
|
2679
|
+
const decision = await new Promise((resolve) => {
|
|
2628
2680
|
const timer = setTimeout(() => {
|
|
2629
2681
|
console.log(`[ApprovalProxy] ${t("approval.timeout", { id: requestId })}`);
|
|
2630
2682
|
this.pendingApprovals.delete(requestId);
|
|
2631
|
-
|
|
2683
|
+
resolve({ decision: "allow" });
|
|
2632
2684
|
}, 325e3);
|
|
2633
|
-
this.pendingApprovals.set(requestId, { resolve
|
|
2685
|
+
this.pendingApprovals.set(requestId, { resolve, timer, request: approvalRequest });
|
|
2634
2686
|
});
|
|
2635
2687
|
this.sendJson(res, 200, decision);
|
|
2636
2688
|
} catch (err) {
|
|
@@ -2691,7 +2743,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2691
2743
|
/** 手动解析请求的 JSON body(限制最大 1MB 防止滥用) */
|
|
2692
2744
|
parseJsonBody(req) {
|
|
2693
2745
|
const MAX_BODY_SIZE = 1024 * 1024;
|
|
2694
|
-
return new Promise((
|
|
2746
|
+
return new Promise((resolve, reject) => {
|
|
2695
2747
|
const chunks = [];
|
|
2696
2748
|
let totalSize = 0;
|
|
2697
2749
|
let destroyed = false;
|
|
@@ -2709,7 +2761,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2709
2761
|
try {
|
|
2710
2762
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
2711
2763
|
const parsed = JSON.parse(raw);
|
|
2712
|
-
|
|
2764
|
+
resolve(parsed);
|
|
2713
2765
|
} catch {
|
|
2714
2766
|
reject(new Error(t("approval.invalidJson")));
|
|
2715
2767
|
}
|
|
@@ -3376,12 +3428,12 @@ var DesktopNotificationChannel = class {
|
|
|
3376
3428
|
const body = payload.body.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
3377
3429
|
const sound = payload.sound ?? "Ping";
|
|
3378
3430
|
const script = `display notification "${body}" with title "${title}" sound name "${sound}"`;
|
|
3379
|
-
return new Promise((
|
|
3431
|
+
return new Promise((resolve) => {
|
|
3380
3432
|
(0, import_node_child_process6.execFile)("osascript", ["-e", script], (err) => {
|
|
3381
3433
|
if (err) {
|
|
3382
3434
|
console.warn("[DesktopNotificationChannel] Send notification failed:", err.message);
|
|
3383
3435
|
}
|
|
3384
|
-
|
|
3436
|
+
resolve();
|
|
3385
3437
|
});
|
|
3386
3438
|
});
|
|
3387
3439
|
}
|
|
@@ -3581,7 +3633,7 @@ var ActivityPushChannel = class {
|
|
|
3581
3633
|
const topic = "com.kachun.sessix.push-type.liveactivity";
|
|
3582
3634
|
const jwt = this.getJWT();
|
|
3583
3635
|
const payloadStr = JSON.stringify(payload);
|
|
3584
|
-
return new Promise((
|
|
3636
|
+
return new Promise((resolve, reject) => {
|
|
3585
3637
|
let client;
|
|
3586
3638
|
try {
|
|
3587
3639
|
client = this.getHttp2Client();
|
|
@@ -3609,7 +3661,7 @@ var ActivityPushChannel = class {
|
|
|
3609
3661
|
});
|
|
3610
3662
|
req.on("end", () => {
|
|
3611
3663
|
if (statusCode === 200) {
|
|
3612
|
-
|
|
3664
|
+
resolve();
|
|
3613
3665
|
} else {
|
|
3614
3666
|
if (statusCode === 0) {
|
|
3615
3667
|
this.http2Client?.destroy();
|
|
@@ -4282,7 +4334,7 @@ async function killPortProcess(port) {
|
|
|
4282
4334
|
await execAsync(`kill -9 ${pids.join(" ")}`);
|
|
4283
4335
|
}
|
|
4284
4336
|
}
|
|
4285
|
-
await new Promise((
|
|
4337
|
+
await new Promise((resolve) => setTimeout(resolve, 600));
|
|
4286
4338
|
} catch {
|
|
4287
4339
|
}
|
|
4288
4340
|
}
|
|
@@ -4534,10 +4586,27 @@ async function start(opts = {}) {
|
|
|
4534
4586
|
case "list_project_sessions": {
|
|
4535
4587
|
const histResult = await getHistoricalSessions(event.projectPath);
|
|
4536
4588
|
if (histResult.ok) {
|
|
4589
|
+
const sessions = histResult.value;
|
|
4590
|
+
const codexProvider = providerFactory.getProvider("codex");
|
|
4591
|
+
if (codexProvider instanceof CodexProvider) {
|
|
4592
|
+
const codexSessions = codexProvider.getPersistedSessionsForProject(event.projectPath);
|
|
4593
|
+
const existingIds = new Set(sessions.map((s) => s.sessionId));
|
|
4594
|
+
for (const cs of codexSessions) {
|
|
4595
|
+
if (!existingIds.has(cs.sessionId)) {
|
|
4596
|
+
sessions.push({
|
|
4597
|
+
sessionId: cs.sessionId,
|
|
4598
|
+
lastModified: cs.lastModified,
|
|
4599
|
+
summary: cs.summary,
|
|
4600
|
+
agentType: "codex"
|
|
4601
|
+
});
|
|
4602
|
+
}
|
|
4603
|
+
}
|
|
4604
|
+
sessions.sort((a, b) => b.lastModified - a.lastModified);
|
|
4605
|
+
}
|
|
4537
4606
|
wsBridge.send(ws, {
|
|
4538
4607
|
type: "project_sessions",
|
|
4539
4608
|
projectPath: event.projectPath,
|
|
4540
|
-
sessions
|
|
4609
|
+
sessions
|
|
4541
4610
|
});
|
|
4542
4611
|
} else {
|
|
4543
4612
|
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
|
|
@@ -1922,8 +1974,8 @@ var SessionManager = class {
|
|
|
1922
1974
|
};
|
|
1923
1975
|
this.updateSessionStatus(sessionId, "waiting_question");
|
|
1924
1976
|
this.emit({ type: "question_request", request });
|
|
1925
|
-
const answerPromise = new Promise((
|
|
1926
|
-
this.pendingQuestions.set(requestId, { sessionId, toolUseId, question, options, createdAt: request.createdAt, resolve
|
|
1977
|
+
const answerPromise = new Promise((resolve) => {
|
|
1978
|
+
this.pendingQuestions.set(requestId, { sessionId, toolUseId, question, options, createdAt: request.createdAt, resolve });
|
|
1927
1979
|
});
|
|
1928
1980
|
answerPromise.then(async (answer) => {
|
|
1929
1981
|
try {
|
|
@@ -2165,11 +2217,11 @@ var WsBridge = class _WsBridge {
|
|
|
2165
2217
|
* 使用此方法代替 new WsBridge(),确保 EADDRINUSE 等错误能被调用方的 try-catch 捕获。
|
|
2166
2218
|
*/
|
|
2167
2219
|
static async create(options) {
|
|
2168
|
-
return new Promise((
|
|
2220
|
+
return new Promise((resolve, reject) => {
|
|
2169
2221
|
const bridge = new _WsBridge(options);
|
|
2170
2222
|
bridge.wss.once("listening", () => {
|
|
2171
2223
|
bridge.wss.on("error", (err) => console.error(`[WsBridge] ${t("ws.serverError")}:`, err));
|
|
2172
|
-
|
|
2224
|
+
resolve(bridge);
|
|
2173
2225
|
});
|
|
2174
2226
|
bridge.wss.once("error", reject);
|
|
2175
2227
|
});
|
|
@@ -2232,7 +2284,7 @@ var WsBridge = class _WsBridge {
|
|
|
2232
2284
|
}
|
|
2233
2285
|
/** 优雅关闭 WebSocket 服务 */
|
|
2234
2286
|
close() {
|
|
2235
|
-
return new Promise((
|
|
2287
|
+
return new Promise((resolve, reject) => {
|
|
2236
2288
|
if (this.heartbeatTimer) {
|
|
2237
2289
|
clearInterval(this.heartbeatTimer);
|
|
2238
2290
|
this.heartbeatTimer = null;
|
|
@@ -2245,7 +2297,7 @@ var WsBridge = class _WsBridge {
|
|
|
2245
2297
|
reject(err);
|
|
2246
2298
|
} else {
|
|
2247
2299
|
console.log("[WsBridge] WebSocket server closed");
|
|
2248
|
-
|
|
2300
|
+
resolve();
|
|
2249
2301
|
}
|
|
2250
2302
|
});
|
|
2251
2303
|
});
|
|
@@ -2385,11 +2437,11 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2385
2437
|
* 异步工厂方法:等待端口监听成功后 resolve,端口占用等错误时 reject。
|
|
2386
2438
|
*/
|
|
2387
2439
|
static async create(options) {
|
|
2388
|
-
return new Promise((
|
|
2440
|
+
return new Promise((resolve, reject) => {
|
|
2389
2441
|
const proxy = new _ApprovalProxy(options);
|
|
2390
2442
|
proxy.server.once("listening", () => {
|
|
2391
2443
|
proxy.server.on("error", (err) => console.error(`[ApprovalProxy] ${t("approval.serverError")}:`, err));
|
|
2392
|
-
|
|
2444
|
+
resolve(proxy);
|
|
2393
2445
|
});
|
|
2394
2446
|
proxy.server.once("error", reject);
|
|
2395
2447
|
});
|
|
@@ -2546,7 +2598,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2546
2598
|
}
|
|
2547
2599
|
/** 优雅关闭 HTTP 服务 */
|
|
2548
2600
|
close() {
|
|
2549
|
-
return new Promise((
|
|
2601
|
+
return new Promise((resolve, reject) => {
|
|
2550
2602
|
const pendingEntries = Array.from(this.pendingApprovals.entries());
|
|
2551
2603
|
for (const [, pending] of pendingEntries) {
|
|
2552
2604
|
clearTimeout(pending.timer);
|
|
@@ -2558,7 +2610,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2558
2610
|
reject(err);
|
|
2559
2611
|
} else {
|
|
2560
2612
|
console.log(`[ApprovalProxy] ${t("approval.httpClosed")}`);
|
|
2561
|
-
|
|
2613
|
+
resolve();
|
|
2562
2614
|
}
|
|
2563
2615
|
});
|
|
2564
2616
|
});
|
|
@@ -2629,13 +2681,13 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2629
2681
|
return;
|
|
2630
2682
|
}
|
|
2631
2683
|
this.notifyApprovalRequest(approvalRequest);
|
|
2632
|
-
const decision = await new Promise((
|
|
2684
|
+
const decision = await new Promise((resolve) => {
|
|
2633
2685
|
const timer = setTimeout(() => {
|
|
2634
2686
|
console.log(`[ApprovalProxy] ${t("approval.timeout", { id: requestId })}`);
|
|
2635
2687
|
this.pendingApprovals.delete(requestId);
|
|
2636
|
-
|
|
2688
|
+
resolve({ decision: "allow" });
|
|
2637
2689
|
}, 325e3);
|
|
2638
|
-
this.pendingApprovals.set(requestId, { resolve
|
|
2690
|
+
this.pendingApprovals.set(requestId, { resolve, timer, request: approvalRequest });
|
|
2639
2691
|
});
|
|
2640
2692
|
this.sendJson(res, 200, decision);
|
|
2641
2693
|
} catch (err) {
|
|
@@ -2696,7 +2748,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2696
2748
|
/** 手动解析请求的 JSON body(限制最大 1MB 防止滥用) */
|
|
2697
2749
|
parseJsonBody(req) {
|
|
2698
2750
|
const MAX_BODY_SIZE = 1024 * 1024;
|
|
2699
|
-
return new Promise((
|
|
2751
|
+
return new Promise((resolve, reject) => {
|
|
2700
2752
|
const chunks = [];
|
|
2701
2753
|
let totalSize = 0;
|
|
2702
2754
|
let destroyed = false;
|
|
@@ -2714,7 +2766,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2714
2766
|
try {
|
|
2715
2767
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
2716
2768
|
const parsed = JSON.parse(raw);
|
|
2717
|
-
|
|
2769
|
+
resolve(parsed);
|
|
2718
2770
|
} catch {
|
|
2719
2771
|
reject(new Error(t("approval.invalidJson")));
|
|
2720
2772
|
}
|
|
@@ -3381,12 +3433,12 @@ var DesktopNotificationChannel = class {
|
|
|
3381
3433
|
const body = payload.body.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
3382
3434
|
const sound = payload.sound ?? "Ping";
|
|
3383
3435
|
const script = `display notification "${body}" with title "${title}" sound name "${sound}"`;
|
|
3384
|
-
return new Promise((
|
|
3436
|
+
return new Promise((resolve) => {
|
|
3385
3437
|
(0, import_node_child_process6.execFile)("osascript", ["-e", script], (err) => {
|
|
3386
3438
|
if (err) {
|
|
3387
3439
|
console.warn("[DesktopNotificationChannel] Send notification failed:", err.message);
|
|
3388
3440
|
}
|
|
3389
|
-
|
|
3441
|
+
resolve();
|
|
3390
3442
|
});
|
|
3391
3443
|
});
|
|
3392
3444
|
}
|
|
@@ -3586,7 +3638,7 @@ var ActivityPushChannel = class {
|
|
|
3586
3638
|
const topic = "com.kachun.sessix.push-type.liveactivity";
|
|
3587
3639
|
const jwt = this.getJWT();
|
|
3588
3640
|
const payloadStr = JSON.stringify(payload);
|
|
3589
|
-
return new Promise((
|
|
3641
|
+
return new Promise((resolve, reject) => {
|
|
3590
3642
|
let client;
|
|
3591
3643
|
try {
|
|
3592
3644
|
client = this.getHttp2Client();
|
|
@@ -3614,7 +3666,7 @@ var ActivityPushChannel = class {
|
|
|
3614
3666
|
});
|
|
3615
3667
|
req.on("end", () => {
|
|
3616
3668
|
if (statusCode === 200) {
|
|
3617
|
-
|
|
3669
|
+
resolve();
|
|
3618
3670
|
} else {
|
|
3619
3671
|
if (statusCode === 0) {
|
|
3620
3672
|
this.http2Client?.destroy();
|
|
@@ -4287,7 +4339,7 @@ async function killPortProcess(port) {
|
|
|
4287
4339
|
await execAsync(`kill -9 ${pids.join(" ")}`);
|
|
4288
4340
|
}
|
|
4289
4341
|
}
|
|
4290
|
-
await new Promise((
|
|
4342
|
+
await new Promise((resolve) => setTimeout(resolve, 600));
|
|
4291
4343
|
} catch {
|
|
4292
4344
|
}
|
|
4293
4345
|
}
|
|
@@ -4539,10 +4591,27 @@ async function start(opts = {}) {
|
|
|
4539
4591
|
case "list_project_sessions": {
|
|
4540
4592
|
const histResult = await getHistoricalSessions(event.projectPath);
|
|
4541
4593
|
if (histResult.ok) {
|
|
4594
|
+
const sessions = histResult.value;
|
|
4595
|
+
const codexProvider = providerFactory.getProvider("codex");
|
|
4596
|
+
if (codexProvider instanceof CodexProvider) {
|
|
4597
|
+
const codexSessions = codexProvider.getPersistedSessionsForProject(event.projectPath);
|
|
4598
|
+
const existingIds = new Set(sessions.map((s) => s.sessionId));
|
|
4599
|
+
for (const cs of codexSessions) {
|
|
4600
|
+
if (!existingIds.has(cs.sessionId)) {
|
|
4601
|
+
sessions.push({
|
|
4602
|
+
sessionId: cs.sessionId,
|
|
4603
|
+
lastModified: cs.lastModified,
|
|
4604
|
+
summary: cs.summary,
|
|
4605
|
+
agentType: "codex"
|
|
4606
|
+
});
|
|
4607
|
+
}
|
|
4608
|
+
}
|
|
4609
|
+
sessions.sort((a, b) => b.lastModified - a.lastModified);
|
|
4610
|
+
}
|
|
4542
4611
|
wsBridge.send(ws, {
|
|
4543
4612
|
type: "project_sessions",
|
|
4544
4613
|
projectPath: event.projectPath,
|
|
4545
|
-
sessions
|
|
4614
|
+
sessions
|
|
4546
4615
|
});
|
|
4547
4616
|
} else {
|
|
4548
4617
|
wsBridge.send(ws, {
|