sessix-server 0.3.3 → 0.3.5
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 +150 -47
- package/dist/server.js +150 -47
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -655,9 +655,33 @@ var ProcessProvider = class {
|
|
|
655
655
|
const isQuestion = block.name === "AskUserQuestion" || block.name === "AskFollowupQuestion";
|
|
656
656
|
if (!isQuestion) continue;
|
|
657
657
|
const input = block.input;
|
|
658
|
-
|
|
658
|
+
let question = "";
|
|
659
|
+
let options;
|
|
660
|
+
let questions;
|
|
661
|
+
if (typeof input.question === "string") {
|
|
662
|
+
question = input.question;
|
|
663
|
+
options = Array.isArray(input.options) ? input.options : void 0;
|
|
664
|
+
} else if (Array.isArray(input.questions) && input.questions.length > 0) {
|
|
665
|
+
questions = input.questions.map((q) => {
|
|
666
|
+
const item = {
|
|
667
|
+
question: typeof q.question === "string" ? q.question : "",
|
|
668
|
+
header: typeof q.header === "string" ? q.header : void 0,
|
|
669
|
+
multiSelect: typeof q.multiSelect === "boolean" ? q.multiSelect : void 0
|
|
670
|
+
};
|
|
671
|
+
if (Array.isArray(q.options)) {
|
|
672
|
+
item.options = q.options.map((o) => ({
|
|
673
|
+
label: typeof o.label === "string" ? o.label : String(o),
|
|
674
|
+
description: typeof o.description === "string" ? o.description : void 0
|
|
675
|
+
}));
|
|
676
|
+
}
|
|
677
|
+
return item;
|
|
678
|
+
});
|
|
679
|
+
const first = questions[0];
|
|
680
|
+
question = first.question;
|
|
681
|
+
options = first.options?.map((o) => o.label);
|
|
682
|
+
}
|
|
659
683
|
if (!question) continue;
|
|
660
|
-
const prevKey = `${block.id}:${question}:${JSON.stringify(
|
|
684
|
+
const prevKey = `${block.id}:${question}:${JSON.stringify(options ?? [])}`;
|
|
661
685
|
let sessionSet = this.emittedQuestionToolUseIds.get(sessionId);
|
|
662
686
|
if (!sessionSet) {
|
|
663
687
|
sessionSet = /* @__PURE__ */ new Set();
|
|
@@ -669,7 +693,8 @@ var ProcessProvider = class {
|
|
|
669
693
|
this.emitter.emit(this.getQuestionEventName(sessionId), {
|
|
670
694
|
toolUseId: block.id,
|
|
671
695
|
question,
|
|
672
|
-
options
|
|
696
|
+
options,
|
|
697
|
+
questions
|
|
673
698
|
});
|
|
674
699
|
}
|
|
675
700
|
}
|
|
@@ -944,11 +969,15 @@ function isCodexAvailable() {
|
|
|
944
969
|
// src/providers/CodexProvider.ts
|
|
945
970
|
var SESSIX_DIR = (0, import_path.join)((0, import_os.homedir)(), ".sessix");
|
|
946
971
|
var CODEX_SESSIONS_FILE = (0, import_path.join)(SESSIX_DIR, "codex-sessions.json");
|
|
972
|
+
var CODEX_EVENTS_DIR = (0, import_path.join)(SESSIX_DIR, "codex-events");
|
|
973
|
+
var SESSION_EXPIRY_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
947
974
|
var CodexProvider = class {
|
|
948
975
|
activeSessions = /* @__PURE__ */ new Map();
|
|
949
976
|
emitter = new import_events2.EventEmitter();
|
|
950
977
|
/** 持久化的会话元数据(sessionId → metadata) */
|
|
951
978
|
persistedSessions = /* @__PURE__ */ new Map();
|
|
979
|
+
/** 会话事件缓存(sessionId → events),用于历史回放 */
|
|
980
|
+
sessionEvents = /* @__PURE__ */ new Map();
|
|
952
981
|
/** 自增 ID 计数器,用于生成 ClaudeStreamEvent 中的 message/block ID */
|
|
953
982
|
idCounter = 0;
|
|
954
983
|
constructor() {
|
|
@@ -995,7 +1024,7 @@ var CodexProvider = class {
|
|
|
995
1024
|
subtype: "init",
|
|
996
1025
|
session_id: sessionId
|
|
997
1026
|
};
|
|
998
|
-
this.
|
|
1027
|
+
this.emitAndRecord(sessionId, initEvent);
|
|
999
1028
|
this.emitUserMessage(sessionId, message);
|
|
1000
1029
|
proc.on("error", (err) => {
|
|
1001
1030
|
console.error(`[CodexProvider] Session ${sessionId} process error:`, err.message);
|
|
@@ -1082,26 +1111,7 @@ var CodexProvider = class {
|
|
|
1082
1111
|
};
|
|
1083
1112
|
}
|
|
1084
1113
|
getActiveSessions() {
|
|
1085
|
-
|
|
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());
|
|
1114
|
+
return Array.from(this.activeSessions.values()).map((entry) => entry.session);
|
|
1105
1115
|
}
|
|
1106
1116
|
async generateSuggestion(_context) {
|
|
1107
1117
|
return "";
|
|
@@ -1225,7 +1235,7 @@ var CodexProvider = class {
|
|
|
1225
1235
|
num_turns: 1,
|
|
1226
1236
|
usage: event.usage
|
|
1227
1237
|
};
|
|
1228
|
-
this.
|
|
1238
|
+
this.emitAndRecord(sessionId, resultEvent);
|
|
1229
1239
|
if (entry.threadId) {
|
|
1230
1240
|
this.persistSession(sessionId, {
|
|
1231
1241
|
threadId: entry.threadId,
|
|
@@ -1235,6 +1245,7 @@ var CodexProvider = class {
|
|
|
1235
1245
|
lastActiveAt: Date.now()
|
|
1236
1246
|
});
|
|
1237
1247
|
}
|
|
1248
|
+
this.persistSessionEvents(sessionId);
|
|
1238
1249
|
break;
|
|
1239
1250
|
}
|
|
1240
1251
|
case "turn.failed": {
|
|
@@ -1248,7 +1259,8 @@ var CodexProvider = class {
|
|
|
1248
1259
|
duration_ms: entry.turnStartTime ? Date.now() - entry.turnStartTime : 0,
|
|
1249
1260
|
num_turns: 1
|
|
1250
1261
|
};
|
|
1251
|
-
this.
|
|
1262
|
+
this.emitAndRecord(sessionId, failEvent);
|
|
1263
|
+
this.persistSessionEvents(sessionId);
|
|
1252
1264
|
break;
|
|
1253
1265
|
}
|
|
1254
1266
|
}
|
|
@@ -1269,7 +1281,7 @@ var CodexProvider = class {
|
|
|
1269
1281
|
content: [{ type: "text", text: item.text ?? "" }]
|
|
1270
1282
|
}
|
|
1271
1283
|
};
|
|
1272
|
-
this.
|
|
1284
|
+
this.emitAndRecord(sessionId, msgEvent);
|
|
1273
1285
|
break;
|
|
1274
1286
|
}
|
|
1275
1287
|
case "command_execution": {
|
|
@@ -1289,7 +1301,7 @@ var CodexProvider = class {
|
|
|
1289
1301
|
}]
|
|
1290
1302
|
}
|
|
1291
1303
|
};
|
|
1292
|
-
this.
|
|
1304
|
+
this.emitAndRecord(sessionId, toolEvent);
|
|
1293
1305
|
const resultContent = item.aggregated_output ?? "";
|
|
1294
1306
|
const isError = item.exit_code != null && item.exit_code !== 0;
|
|
1295
1307
|
const toolResultEvent = {
|
|
@@ -1305,7 +1317,7 @@ var CodexProvider = class {
|
|
|
1305
1317
|
}]
|
|
1306
1318
|
}
|
|
1307
1319
|
};
|
|
1308
|
-
this.
|
|
1320
|
+
this.emitAndRecord(sessionId, toolResultEvent);
|
|
1309
1321
|
break;
|
|
1310
1322
|
}
|
|
1311
1323
|
case "file_change": {
|
|
@@ -1325,7 +1337,7 @@ var CodexProvider = class {
|
|
|
1325
1337
|
}]
|
|
1326
1338
|
}
|
|
1327
1339
|
};
|
|
1328
|
-
this.
|
|
1340
|
+
this.emitAndRecord(sessionId, toolEvent);
|
|
1329
1341
|
const toolResultEvent = {
|
|
1330
1342
|
type: "user",
|
|
1331
1343
|
session_id: sessionId,
|
|
@@ -1338,7 +1350,7 @@ var CodexProvider = class {
|
|
|
1338
1350
|
}]
|
|
1339
1351
|
}
|
|
1340
1352
|
};
|
|
1341
|
-
this.
|
|
1353
|
+
this.emitAndRecord(sessionId, toolResultEvent);
|
|
1342
1354
|
break;
|
|
1343
1355
|
}
|
|
1344
1356
|
case "reasoning": {
|
|
@@ -1352,7 +1364,7 @@ var CodexProvider = class {
|
|
|
1352
1364
|
content: [{ type: "thinking", thinking: item.text ?? "" }]
|
|
1353
1365
|
}
|
|
1354
1366
|
};
|
|
1355
|
-
this.
|
|
1367
|
+
this.emitAndRecord(sessionId, thinkEvent);
|
|
1356
1368
|
break;
|
|
1357
1369
|
}
|
|
1358
1370
|
}
|
|
@@ -1394,7 +1406,7 @@ var CodexProvider = class {
|
|
|
1394
1406
|
duration_ms: 0,
|
|
1395
1407
|
num_turns: 0
|
|
1396
1408
|
};
|
|
1397
|
-
this.
|
|
1409
|
+
this.emitAndRecord(sessionId, syntheticResult);
|
|
1398
1410
|
});
|
|
1399
1411
|
}
|
|
1400
1412
|
/**
|
|
@@ -1409,7 +1421,7 @@ var CodexProvider = class {
|
|
|
1409
1421
|
content: [{ type: "text", text: message }]
|
|
1410
1422
|
}
|
|
1411
1423
|
};
|
|
1412
|
-
this.
|
|
1424
|
+
this.emitAndRecord(sessionId, event);
|
|
1413
1425
|
}
|
|
1414
1426
|
emitError(sessionId, message) {
|
|
1415
1427
|
const event = {
|
|
@@ -1421,11 +1433,59 @@ var CodexProvider = class {
|
|
|
1421
1433
|
is_error: true,
|
|
1422
1434
|
num_turns: 0
|
|
1423
1435
|
};
|
|
1424
|
-
this.
|
|
1436
|
+
this.emitAndRecord(sessionId, event);
|
|
1425
1437
|
}
|
|
1426
1438
|
getEventName(sessionId) {
|
|
1427
1439
|
return `claude:${sessionId}`;
|
|
1428
1440
|
}
|
|
1441
|
+
/**
|
|
1442
|
+
* 发射事件并同时记录到 sessionEvents 缓存
|
|
1443
|
+
*/
|
|
1444
|
+
emitAndRecord(sessionId, event) {
|
|
1445
|
+
this.emitter.emit(this.getEventName(sessionId), event);
|
|
1446
|
+
let events = this.sessionEvents.get(sessionId);
|
|
1447
|
+
if (!events) {
|
|
1448
|
+
events = [];
|
|
1449
|
+
this.sessionEvents.set(sessionId, events);
|
|
1450
|
+
}
|
|
1451
|
+
events.push(event);
|
|
1452
|
+
}
|
|
1453
|
+
/**
|
|
1454
|
+
* 获取 Codex 会话的历史事件(供 load_session_history 调用)
|
|
1455
|
+
* 优先从内存读,miss 时从磁盘加载
|
|
1456
|
+
*/
|
|
1457
|
+
getSessionHistory(sessionId) {
|
|
1458
|
+
const cached = this.sessionEvents.get(sessionId);
|
|
1459
|
+
if (cached && cached.length > 0) return cached;
|
|
1460
|
+
const filePath = (0, import_path.join)(CODEX_EVENTS_DIR, `${sessionId}.json`);
|
|
1461
|
+
try {
|
|
1462
|
+
if (!(0, import_fs.existsSync)(filePath)) return [];
|
|
1463
|
+
const data = JSON.parse((0, import_fs.readFileSync)(filePath, "utf-8"));
|
|
1464
|
+
this.sessionEvents.set(sessionId, data);
|
|
1465
|
+
return data;
|
|
1466
|
+
} catch {
|
|
1467
|
+
return [];
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
/**
|
|
1471
|
+
* 将会话事件持久化到磁盘
|
|
1472
|
+
*/
|
|
1473
|
+
persistSessionEvents(sessionId) {
|
|
1474
|
+
const events = this.sessionEvents.get(sessionId);
|
|
1475
|
+
if (!events || events.length === 0) return;
|
|
1476
|
+
try {
|
|
1477
|
+
if (!(0, import_fs.existsSync)(CODEX_EVENTS_DIR)) {
|
|
1478
|
+
(0, import_fs.mkdirSync)(CODEX_EVENTS_DIR, { recursive: true });
|
|
1479
|
+
}
|
|
1480
|
+
(0, import_fs.writeFileSync)(
|
|
1481
|
+
(0, import_path.join)(CODEX_EVENTS_DIR, `${sessionId}.json`),
|
|
1482
|
+
JSON.stringify(events),
|
|
1483
|
+
"utf-8"
|
|
1484
|
+
);
|
|
1485
|
+
} catch (err) {
|
|
1486
|
+
console.error(`[CodexProvider] Failed to persist events for ${sessionId}:`, err);
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1429
1489
|
// ============================================
|
|
1430
1490
|
// 持久化方法
|
|
1431
1491
|
// ============================================
|
|
@@ -1436,8 +1496,24 @@ var CodexProvider = class {
|
|
|
1436
1496
|
try {
|
|
1437
1497
|
if (!(0, import_fs.existsSync)(CODEX_SESSIONS_FILE)) return;
|
|
1438
1498
|
const data = JSON.parse((0, import_fs.readFileSync)(CODEX_SESSIONS_FILE, "utf-8"));
|
|
1499
|
+
const now = Date.now();
|
|
1500
|
+
let expiredCount = 0;
|
|
1439
1501
|
for (const [sessionId, meta] of Object.entries(data)) {
|
|
1440
|
-
|
|
1502
|
+
const m = meta;
|
|
1503
|
+
if (now - m.lastActiveAt > SESSION_EXPIRY_MS) {
|
|
1504
|
+
expiredCount++;
|
|
1505
|
+
try {
|
|
1506
|
+
const eventsFile = (0, import_path.join)(CODEX_EVENTS_DIR, `${sessionId}.json`);
|
|
1507
|
+
if ((0, import_fs.existsSync)(eventsFile)) (0, import_fs.unlinkSync)(eventsFile);
|
|
1508
|
+
} catch {
|
|
1509
|
+
}
|
|
1510
|
+
continue;
|
|
1511
|
+
}
|
|
1512
|
+
this.persistedSessions.set(sessionId, m);
|
|
1513
|
+
}
|
|
1514
|
+
if (expiredCount > 0) {
|
|
1515
|
+
console.log(`[CodexProvider] Cleaned up ${expiredCount} expired sessions`);
|
|
1516
|
+
this.flushPersistedSessions();
|
|
1441
1517
|
}
|
|
1442
1518
|
console.log(`[CodexProvider] Loaded ${this.persistedSessions.size} persisted sessions`);
|
|
1443
1519
|
} catch (err) {
|
|
@@ -1685,10 +1761,13 @@ var SessionManager = class {
|
|
|
1685
1761
|
console.warn(`[SessionManager] Question request not found: ${requestId}`);
|
|
1686
1762
|
return;
|
|
1687
1763
|
}
|
|
1764
|
+
const { sessionId } = pending;
|
|
1688
1765
|
this.pendingQuestions.delete(requestId);
|
|
1689
|
-
this.updateSessionStatus(pending.sessionId, "running");
|
|
1690
1766
|
pending.resolve(answer);
|
|
1691
1767
|
console.log(`[SessionManager] Question answered: ${requestId}`);
|
|
1768
|
+
if (!this.hasPendingQuestionsForSession(sessionId)) {
|
|
1769
|
+
this.updateSessionStatus(sessionId, "running");
|
|
1770
|
+
}
|
|
1692
1771
|
}
|
|
1693
1772
|
/**
|
|
1694
1773
|
* 获取指定会话的所有待回答问题(用于重连时恢复)
|
|
@@ -1703,6 +1782,7 @@ var SessionManager = class {
|
|
|
1703
1782
|
toolUseId: pending.toolUseId,
|
|
1704
1783
|
question: pending.question,
|
|
1705
1784
|
options: pending.options,
|
|
1785
|
+
questions: pending.questions,
|
|
1706
1786
|
createdAt: pending.createdAt
|
|
1707
1787
|
});
|
|
1708
1788
|
}
|
|
@@ -1713,6 +1793,13 @@ var SessionManager = class {
|
|
|
1713
1793
|
isQuestionPending(requestId) {
|
|
1714
1794
|
return this.pendingQuestions.has(requestId);
|
|
1715
1795
|
}
|
|
1796
|
+
/** 检查指定会话是否有待回答问题 */
|
|
1797
|
+
hasPendingQuestionsForSession(sessionId) {
|
|
1798
|
+
for (const pending of this.pendingQuestions.values()) {
|
|
1799
|
+
if (pending.sessionId === sessionId) return true;
|
|
1800
|
+
}
|
|
1801
|
+
return false;
|
|
1802
|
+
}
|
|
1716
1803
|
/**
|
|
1717
1804
|
* 获取所有待回答问题(用于客户端重连时恢复状态)
|
|
1718
1805
|
*/
|
|
@@ -1725,6 +1812,7 @@ var SessionManager = class {
|
|
|
1725
1812
|
toolUseId: pending.toolUseId,
|
|
1726
1813
|
question: pending.question,
|
|
1727
1814
|
options: pending.options,
|
|
1815
|
+
questions: pending.questions,
|
|
1728
1816
|
createdAt: pending.createdAt
|
|
1729
1817
|
});
|
|
1730
1818
|
}
|
|
@@ -1792,8 +1880,8 @@ var SessionManager = class {
|
|
|
1792
1880
|
});
|
|
1793
1881
|
const unsubscribeQuestion = provider.onQuestion(
|
|
1794
1882
|
sessionId,
|
|
1795
|
-
({ toolUseId, question, options }) => {
|
|
1796
|
-
this.handleAskUserQuestion(sessionId, toolUseId, question, options);
|
|
1883
|
+
({ toolUseId, question, options, questions }) => {
|
|
1884
|
+
this.handleAskUserQuestion(sessionId, toolUseId, question, options, questions);
|
|
1797
1885
|
}
|
|
1798
1886
|
);
|
|
1799
1887
|
this.unsubscribeMap.set(sessionId, () => {
|
|
@@ -1863,7 +1951,9 @@ var SessionManager = class {
|
|
|
1863
1951
|
stats.totalCostUsd = (stats.totalCostUsd ?? 0) + event.total_cost_usd;
|
|
1864
1952
|
}
|
|
1865
1953
|
this.sessionStats.set(sessionId, stats);
|
|
1866
|
-
if (
|
|
1954
|
+
if (this.hasPendingQuestionsForSession(sessionId)) {
|
|
1955
|
+
console.log(`[SessionManager] Session ${sessionId}: result received but questions pending, keeping waiting_question`);
|
|
1956
|
+
} else if (event.is_error) {
|
|
1867
1957
|
this.updateSessionStatus(sessionId, "error");
|
|
1868
1958
|
} else {
|
|
1869
1959
|
this.updateSessionStatus(sessionId, "idle");
|
|
@@ -1947,19 +2037,24 @@ var SessionManager = class {
|
|
|
1947
2037
|
/**
|
|
1948
2038
|
* 处理 AskUserQuestion 事件:广播问题请求到手机,等待用户回答
|
|
1949
2039
|
*/
|
|
1950
|
-
handleAskUserQuestion(sessionId, toolUseId, question, options) {
|
|
2040
|
+
handleAskUserQuestion(sessionId, toolUseId, question, options, questions) {
|
|
1951
2041
|
const existingEntry = Array.from(this.pendingQuestions.entries()).find(
|
|
1952
2042
|
([, v]) => v.toolUseId === toolUseId
|
|
1953
2043
|
);
|
|
1954
2044
|
if (existingEntry) {
|
|
1955
|
-
const [existingRequestId] = existingEntry;
|
|
2045
|
+
const [existingRequestId, existingPending] = existingEntry;
|
|
2046
|
+
existingPending.question = question;
|
|
2047
|
+
existingPending.options = options;
|
|
2048
|
+
existingPending.questions = questions;
|
|
2049
|
+
existingPending.createdAt = Date.now();
|
|
1956
2050
|
const updatedRequest = {
|
|
1957
2051
|
id: existingRequestId,
|
|
1958
2052
|
sessionId,
|
|
1959
2053
|
toolUseId,
|
|
1960
2054
|
question,
|
|
1961
2055
|
options,
|
|
1962
|
-
|
|
2056
|
+
questions,
|
|
2057
|
+
createdAt: existingPending.createdAt
|
|
1963
2058
|
};
|
|
1964
2059
|
this.emit({ type: "question_request", request: updatedRequest });
|
|
1965
2060
|
console.log(`[SessionManager] Session ${sessionId}: AskUserQuestion updated (requestId=${existingRequestId})`);
|
|
@@ -1972,12 +2067,13 @@ var SessionManager = class {
|
|
|
1972
2067
|
toolUseId,
|
|
1973
2068
|
question,
|
|
1974
2069
|
options,
|
|
2070
|
+
questions,
|
|
1975
2071
|
createdAt: Date.now()
|
|
1976
2072
|
};
|
|
1977
2073
|
this.updateSessionStatus(sessionId, "waiting_question");
|
|
1978
2074
|
this.emit({ type: "question_request", request });
|
|
1979
2075
|
const answerPromise = new Promise((resolve) => {
|
|
1980
|
-
this.pendingQuestions.set(requestId, { sessionId, toolUseId, question, options, createdAt: request.createdAt, resolve });
|
|
2076
|
+
this.pendingQuestions.set(requestId, { sessionId, toolUseId, question, options, questions, createdAt: request.createdAt, resolve });
|
|
1981
2077
|
});
|
|
1982
2078
|
answerPromise.then(async (answer) => {
|
|
1983
2079
|
try {
|
|
@@ -4626,18 +4722,25 @@ async function start(opts = {}) {
|
|
|
4626
4722
|
}
|
|
4627
4723
|
case "load_session_history": {
|
|
4628
4724
|
const historyResult = await getSessionHistory(event.projectPath, event.sessionId);
|
|
4629
|
-
|
|
4725
|
+
let historyEvents = historyResult.ok ? historyResult.value : [];
|
|
4726
|
+
if (historyEvents.length === 0) {
|
|
4727
|
+
const codexProv = providerFactory.getProvider("codex");
|
|
4728
|
+
if (codexProv instanceof CodexProvider && codexProv.isKnownSession(event.sessionId)) {
|
|
4729
|
+
historyEvents = codexProv.getSessionHistory(event.sessionId);
|
|
4730
|
+
}
|
|
4731
|
+
}
|
|
4732
|
+
if (!historyResult.ok && historyEvents.length === 0) {
|
|
4630
4733
|
wsBridge.send(ws, {
|
|
4631
4734
|
type: "error",
|
|
4632
4735
|
message: t("server.readHistoryFailed", { error: historyResult.error.message }),
|
|
4633
4736
|
code: "SESSION_HISTORY_ERROR",
|
|
4634
4737
|
sessionId: event.sessionId
|
|
4635
4738
|
});
|
|
4636
|
-
} else if (
|
|
4739
|
+
} else if (historyEvents.length > 0) {
|
|
4637
4740
|
wsBridge.send(ws, {
|
|
4638
4741
|
type: "session_history",
|
|
4639
4742
|
sessionId: event.sessionId,
|
|
4640
|
-
events:
|
|
4743
|
+
events: historyEvents
|
|
4641
4744
|
});
|
|
4642
4745
|
const activeSession = sessionManager.getActiveSessions().find((s) => s.id === event.sessionId);
|
|
4643
4746
|
const isStreaming = activeSession?.status === "running" || activeSession?.status === "waiting_approval";
|
package/dist/server.js
CHANGED
|
@@ -660,9 +660,33 @@ var ProcessProvider = class {
|
|
|
660
660
|
const isQuestion = block.name === "AskUserQuestion" || block.name === "AskFollowupQuestion";
|
|
661
661
|
if (!isQuestion) continue;
|
|
662
662
|
const input = block.input;
|
|
663
|
-
|
|
663
|
+
let question = "";
|
|
664
|
+
let options;
|
|
665
|
+
let questions;
|
|
666
|
+
if (typeof input.question === "string") {
|
|
667
|
+
question = input.question;
|
|
668
|
+
options = Array.isArray(input.options) ? input.options : void 0;
|
|
669
|
+
} else if (Array.isArray(input.questions) && input.questions.length > 0) {
|
|
670
|
+
questions = input.questions.map((q) => {
|
|
671
|
+
const item = {
|
|
672
|
+
question: typeof q.question === "string" ? q.question : "",
|
|
673
|
+
header: typeof q.header === "string" ? q.header : void 0,
|
|
674
|
+
multiSelect: typeof q.multiSelect === "boolean" ? q.multiSelect : void 0
|
|
675
|
+
};
|
|
676
|
+
if (Array.isArray(q.options)) {
|
|
677
|
+
item.options = q.options.map((o) => ({
|
|
678
|
+
label: typeof o.label === "string" ? o.label : String(o),
|
|
679
|
+
description: typeof o.description === "string" ? o.description : void 0
|
|
680
|
+
}));
|
|
681
|
+
}
|
|
682
|
+
return item;
|
|
683
|
+
});
|
|
684
|
+
const first = questions[0];
|
|
685
|
+
question = first.question;
|
|
686
|
+
options = first.options?.map((o) => o.label);
|
|
687
|
+
}
|
|
664
688
|
if (!question) continue;
|
|
665
|
-
const prevKey = `${block.id}:${question}:${JSON.stringify(
|
|
689
|
+
const prevKey = `${block.id}:${question}:${JSON.stringify(options ?? [])}`;
|
|
666
690
|
let sessionSet = this.emittedQuestionToolUseIds.get(sessionId);
|
|
667
691
|
if (!sessionSet) {
|
|
668
692
|
sessionSet = /* @__PURE__ */ new Set();
|
|
@@ -674,7 +698,8 @@ var ProcessProvider = class {
|
|
|
674
698
|
this.emitter.emit(this.getQuestionEventName(sessionId), {
|
|
675
699
|
toolUseId: block.id,
|
|
676
700
|
question,
|
|
677
|
-
options
|
|
701
|
+
options,
|
|
702
|
+
questions
|
|
678
703
|
});
|
|
679
704
|
}
|
|
680
705
|
}
|
|
@@ -949,11 +974,15 @@ function isCodexAvailable() {
|
|
|
949
974
|
// src/providers/CodexProvider.ts
|
|
950
975
|
var SESSIX_DIR = (0, import_path.join)((0, import_os.homedir)(), ".sessix");
|
|
951
976
|
var CODEX_SESSIONS_FILE = (0, import_path.join)(SESSIX_DIR, "codex-sessions.json");
|
|
977
|
+
var CODEX_EVENTS_DIR = (0, import_path.join)(SESSIX_DIR, "codex-events");
|
|
978
|
+
var SESSION_EXPIRY_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
952
979
|
var CodexProvider = class {
|
|
953
980
|
activeSessions = /* @__PURE__ */ new Map();
|
|
954
981
|
emitter = new import_events2.EventEmitter();
|
|
955
982
|
/** 持久化的会话元数据(sessionId → metadata) */
|
|
956
983
|
persistedSessions = /* @__PURE__ */ new Map();
|
|
984
|
+
/** 会话事件缓存(sessionId → events),用于历史回放 */
|
|
985
|
+
sessionEvents = /* @__PURE__ */ new Map();
|
|
957
986
|
/** 自增 ID 计数器,用于生成 ClaudeStreamEvent 中的 message/block ID */
|
|
958
987
|
idCounter = 0;
|
|
959
988
|
constructor() {
|
|
@@ -1000,7 +1029,7 @@ var CodexProvider = class {
|
|
|
1000
1029
|
subtype: "init",
|
|
1001
1030
|
session_id: sessionId
|
|
1002
1031
|
};
|
|
1003
|
-
this.
|
|
1032
|
+
this.emitAndRecord(sessionId, initEvent);
|
|
1004
1033
|
this.emitUserMessage(sessionId, message);
|
|
1005
1034
|
proc.on("error", (err) => {
|
|
1006
1035
|
console.error(`[CodexProvider] Session ${sessionId} process error:`, err.message);
|
|
@@ -1087,26 +1116,7 @@ var CodexProvider = class {
|
|
|
1087
1116
|
};
|
|
1088
1117
|
}
|
|
1089
1118
|
getActiveSessions() {
|
|
1090
|
-
|
|
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());
|
|
1119
|
+
return Array.from(this.activeSessions.values()).map((entry) => entry.session);
|
|
1110
1120
|
}
|
|
1111
1121
|
async generateSuggestion(_context) {
|
|
1112
1122
|
return "";
|
|
@@ -1230,7 +1240,7 @@ var CodexProvider = class {
|
|
|
1230
1240
|
num_turns: 1,
|
|
1231
1241
|
usage: event.usage
|
|
1232
1242
|
};
|
|
1233
|
-
this.
|
|
1243
|
+
this.emitAndRecord(sessionId, resultEvent);
|
|
1234
1244
|
if (entry.threadId) {
|
|
1235
1245
|
this.persistSession(sessionId, {
|
|
1236
1246
|
threadId: entry.threadId,
|
|
@@ -1240,6 +1250,7 @@ var CodexProvider = class {
|
|
|
1240
1250
|
lastActiveAt: Date.now()
|
|
1241
1251
|
});
|
|
1242
1252
|
}
|
|
1253
|
+
this.persistSessionEvents(sessionId);
|
|
1243
1254
|
break;
|
|
1244
1255
|
}
|
|
1245
1256
|
case "turn.failed": {
|
|
@@ -1253,7 +1264,8 @@ var CodexProvider = class {
|
|
|
1253
1264
|
duration_ms: entry.turnStartTime ? Date.now() - entry.turnStartTime : 0,
|
|
1254
1265
|
num_turns: 1
|
|
1255
1266
|
};
|
|
1256
|
-
this.
|
|
1267
|
+
this.emitAndRecord(sessionId, failEvent);
|
|
1268
|
+
this.persistSessionEvents(sessionId);
|
|
1257
1269
|
break;
|
|
1258
1270
|
}
|
|
1259
1271
|
}
|
|
@@ -1274,7 +1286,7 @@ var CodexProvider = class {
|
|
|
1274
1286
|
content: [{ type: "text", text: item.text ?? "" }]
|
|
1275
1287
|
}
|
|
1276
1288
|
};
|
|
1277
|
-
this.
|
|
1289
|
+
this.emitAndRecord(sessionId, msgEvent);
|
|
1278
1290
|
break;
|
|
1279
1291
|
}
|
|
1280
1292
|
case "command_execution": {
|
|
@@ -1294,7 +1306,7 @@ var CodexProvider = class {
|
|
|
1294
1306
|
}]
|
|
1295
1307
|
}
|
|
1296
1308
|
};
|
|
1297
|
-
this.
|
|
1309
|
+
this.emitAndRecord(sessionId, toolEvent);
|
|
1298
1310
|
const resultContent = item.aggregated_output ?? "";
|
|
1299
1311
|
const isError = item.exit_code != null && item.exit_code !== 0;
|
|
1300
1312
|
const toolResultEvent = {
|
|
@@ -1310,7 +1322,7 @@ var CodexProvider = class {
|
|
|
1310
1322
|
}]
|
|
1311
1323
|
}
|
|
1312
1324
|
};
|
|
1313
|
-
this.
|
|
1325
|
+
this.emitAndRecord(sessionId, toolResultEvent);
|
|
1314
1326
|
break;
|
|
1315
1327
|
}
|
|
1316
1328
|
case "file_change": {
|
|
@@ -1330,7 +1342,7 @@ var CodexProvider = class {
|
|
|
1330
1342
|
}]
|
|
1331
1343
|
}
|
|
1332
1344
|
};
|
|
1333
|
-
this.
|
|
1345
|
+
this.emitAndRecord(sessionId, toolEvent);
|
|
1334
1346
|
const toolResultEvent = {
|
|
1335
1347
|
type: "user",
|
|
1336
1348
|
session_id: sessionId,
|
|
@@ -1343,7 +1355,7 @@ var CodexProvider = class {
|
|
|
1343
1355
|
}]
|
|
1344
1356
|
}
|
|
1345
1357
|
};
|
|
1346
|
-
this.
|
|
1358
|
+
this.emitAndRecord(sessionId, toolResultEvent);
|
|
1347
1359
|
break;
|
|
1348
1360
|
}
|
|
1349
1361
|
case "reasoning": {
|
|
@@ -1357,7 +1369,7 @@ var CodexProvider = class {
|
|
|
1357
1369
|
content: [{ type: "thinking", thinking: item.text ?? "" }]
|
|
1358
1370
|
}
|
|
1359
1371
|
};
|
|
1360
|
-
this.
|
|
1372
|
+
this.emitAndRecord(sessionId, thinkEvent);
|
|
1361
1373
|
break;
|
|
1362
1374
|
}
|
|
1363
1375
|
}
|
|
@@ -1399,7 +1411,7 @@ var CodexProvider = class {
|
|
|
1399
1411
|
duration_ms: 0,
|
|
1400
1412
|
num_turns: 0
|
|
1401
1413
|
};
|
|
1402
|
-
this.
|
|
1414
|
+
this.emitAndRecord(sessionId, syntheticResult);
|
|
1403
1415
|
});
|
|
1404
1416
|
}
|
|
1405
1417
|
/**
|
|
@@ -1414,7 +1426,7 @@ var CodexProvider = class {
|
|
|
1414
1426
|
content: [{ type: "text", text: message }]
|
|
1415
1427
|
}
|
|
1416
1428
|
};
|
|
1417
|
-
this.
|
|
1429
|
+
this.emitAndRecord(sessionId, event);
|
|
1418
1430
|
}
|
|
1419
1431
|
emitError(sessionId, message) {
|
|
1420
1432
|
const event = {
|
|
@@ -1426,11 +1438,59 @@ var CodexProvider = class {
|
|
|
1426
1438
|
is_error: true,
|
|
1427
1439
|
num_turns: 0
|
|
1428
1440
|
};
|
|
1429
|
-
this.
|
|
1441
|
+
this.emitAndRecord(sessionId, event);
|
|
1430
1442
|
}
|
|
1431
1443
|
getEventName(sessionId) {
|
|
1432
1444
|
return `claude:${sessionId}`;
|
|
1433
1445
|
}
|
|
1446
|
+
/**
|
|
1447
|
+
* 发射事件并同时记录到 sessionEvents 缓存
|
|
1448
|
+
*/
|
|
1449
|
+
emitAndRecord(sessionId, event) {
|
|
1450
|
+
this.emitter.emit(this.getEventName(sessionId), event);
|
|
1451
|
+
let events = this.sessionEvents.get(sessionId);
|
|
1452
|
+
if (!events) {
|
|
1453
|
+
events = [];
|
|
1454
|
+
this.sessionEvents.set(sessionId, events);
|
|
1455
|
+
}
|
|
1456
|
+
events.push(event);
|
|
1457
|
+
}
|
|
1458
|
+
/**
|
|
1459
|
+
* 获取 Codex 会话的历史事件(供 load_session_history 调用)
|
|
1460
|
+
* 优先从内存读,miss 时从磁盘加载
|
|
1461
|
+
*/
|
|
1462
|
+
getSessionHistory(sessionId) {
|
|
1463
|
+
const cached = this.sessionEvents.get(sessionId);
|
|
1464
|
+
if (cached && cached.length > 0) return cached;
|
|
1465
|
+
const filePath = (0, import_path.join)(CODEX_EVENTS_DIR, `${sessionId}.json`);
|
|
1466
|
+
try {
|
|
1467
|
+
if (!(0, import_fs.existsSync)(filePath)) return [];
|
|
1468
|
+
const data = JSON.parse((0, import_fs.readFileSync)(filePath, "utf-8"));
|
|
1469
|
+
this.sessionEvents.set(sessionId, data);
|
|
1470
|
+
return data;
|
|
1471
|
+
} catch {
|
|
1472
|
+
return [];
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
/**
|
|
1476
|
+
* 将会话事件持久化到磁盘
|
|
1477
|
+
*/
|
|
1478
|
+
persistSessionEvents(sessionId) {
|
|
1479
|
+
const events = this.sessionEvents.get(sessionId);
|
|
1480
|
+
if (!events || events.length === 0) return;
|
|
1481
|
+
try {
|
|
1482
|
+
if (!(0, import_fs.existsSync)(CODEX_EVENTS_DIR)) {
|
|
1483
|
+
(0, import_fs.mkdirSync)(CODEX_EVENTS_DIR, { recursive: true });
|
|
1484
|
+
}
|
|
1485
|
+
(0, import_fs.writeFileSync)(
|
|
1486
|
+
(0, import_path.join)(CODEX_EVENTS_DIR, `${sessionId}.json`),
|
|
1487
|
+
JSON.stringify(events),
|
|
1488
|
+
"utf-8"
|
|
1489
|
+
);
|
|
1490
|
+
} catch (err) {
|
|
1491
|
+
console.error(`[CodexProvider] Failed to persist events for ${sessionId}:`, err);
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1434
1494
|
// ============================================
|
|
1435
1495
|
// 持久化方法
|
|
1436
1496
|
// ============================================
|
|
@@ -1441,8 +1501,24 @@ var CodexProvider = class {
|
|
|
1441
1501
|
try {
|
|
1442
1502
|
if (!(0, import_fs.existsSync)(CODEX_SESSIONS_FILE)) return;
|
|
1443
1503
|
const data = JSON.parse((0, import_fs.readFileSync)(CODEX_SESSIONS_FILE, "utf-8"));
|
|
1504
|
+
const now = Date.now();
|
|
1505
|
+
let expiredCount = 0;
|
|
1444
1506
|
for (const [sessionId, meta] of Object.entries(data)) {
|
|
1445
|
-
|
|
1507
|
+
const m = meta;
|
|
1508
|
+
if (now - m.lastActiveAt > SESSION_EXPIRY_MS) {
|
|
1509
|
+
expiredCount++;
|
|
1510
|
+
try {
|
|
1511
|
+
const eventsFile = (0, import_path.join)(CODEX_EVENTS_DIR, `${sessionId}.json`);
|
|
1512
|
+
if ((0, import_fs.existsSync)(eventsFile)) (0, import_fs.unlinkSync)(eventsFile);
|
|
1513
|
+
} catch {
|
|
1514
|
+
}
|
|
1515
|
+
continue;
|
|
1516
|
+
}
|
|
1517
|
+
this.persistedSessions.set(sessionId, m);
|
|
1518
|
+
}
|
|
1519
|
+
if (expiredCount > 0) {
|
|
1520
|
+
console.log(`[CodexProvider] Cleaned up ${expiredCount} expired sessions`);
|
|
1521
|
+
this.flushPersistedSessions();
|
|
1446
1522
|
}
|
|
1447
1523
|
console.log(`[CodexProvider] Loaded ${this.persistedSessions.size} persisted sessions`);
|
|
1448
1524
|
} catch (err) {
|
|
@@ -1690,10 +1766,13 @@ var SessionManager = class {
|
|
|
1690
1766
|
console.warn(`[SessionManager] Question request not found: ${requestId}`);
|
|
1691
1767
|
return;
|
|
1692
1768
|
}
|
|
1769
|
+
const { sessionId } = pending;
|
|
1693
1770
|
this.pendingQuestions.delete(requestId);
|
|
1694
|
-
this.updateSessionStatus(pending.sessionId, "running");
|
|
1695
1771
|
pending.resolve(answer);
|
|
1696
1772
|
console.log(`[SessionManager] Question answered: ${requestId}`);
|
|
1773
|
+
if (!this.hasPendingQuestionsForSession(sessionId)) {
|
|
1774
|
+
this.updateSessionStatus(sessionId, "running");
|
|
1775
|
+
}
|
|
1697
1776
|
}
|
|
1698
1777
|
/**
|
|
1699
1778
|
* 获取指定会话的所有待回答问题(用于重连时恢复)
|
|
@@ -1708,6 +1787,7 @@ var SessionManager = class {
|
|
|
1708
1787
|
toolUseId: pending.toolUseId,
|
|
1709
1788
|
question: pending.question,
|
|
1710
1789
|
options: pending.options,
|
|
1790
|
+
questions: pending.questions,
|
|
1711
1791
|
createdAt: pending.createdAt
|
|
1712
1792
|
});
|
|
1713
1793
|
}
|
|
@@ -1718,6 +1798,13 @@ var SessionManager = class {
|
|
|
1718
1798
|
isQuestionPending(requestId) {
|
|
1719
1799
|
return this.pendingQuestions.has(requestId);
|
|
1720
1800
|
}
|
|
1801
|
+
/** 检查指定会话是否有待回答问题 */
|
|
1802
|
+
hasPendingQuestionsForSession(sessionId) {
|
|
1803
|
+
for (const pending of this.pendingQuestions.values()) {
|
|
1804
|
+
if (pending.sessionId === sessionId) return true;
|
|
1805
|
+
}
|
|
1806
|
+
return false;
|
|
1807
|
+
}
|
|
1721
1808
|
/**
|
|
1722
1809
|
* 获取所有待回答问题(用于客户端重连时恢复状态)
|
|
1723
1810
|
*/
|
|
@@ -1730,6 +1817,7 @@ var SessionManager = class {
|
|
|
1730
1817
|
toolUseId: pending.toolUseId,
|
|
1731
1818
|
question: pending.question,
|
|
1732
1819
|
options: pending.options,
|
|
1820
|
+
questions: pending.questions,
|
|
1733
1821
|
createdAt: pending.createdAt
|
|
1734
1822
|
});
|
|
1735
1823
|
}
|
|
@@ -1797,8 +1885,8 @@ var SessionManager = class {
|
|
|
1797
1885
|
});
|
|
1798
1886
|
const unsubscribeQuestion = provider.onQuestion(
|
|
1799
1887
|
sessionId,
|
|
1800
|
-
({ toolUseId, question, options }) => {
|
|
1801
|
-
this.handleAskUserQuestion(sessionId, toolUseId, question, options);
|
|
1888
|
+
({ toolUseId, question, options, questions }) => {
|
|
1889
|
+
this.handleAskUserQuestion(sessionId, toolUseId, question, options, questions);
|
|
1802
1890
|
}
|
|
1803
1891
|
);
|
|
1804
1892
|
this.unsubscribeMap.set(sessionId, () => {
|
|
@@ -1868,7 +1956,9 @@ var SessionManager = class {
|
|
|
1868
1956
|
stats.totalCostUsd = (stats.totalCostUsd ?? 0) + event.total_cost_usd;
|
|
1869
1957
|
}
|
|
1870
1958
|
this.sessionStats.set(sessionId, stats);
|
|
1871
|
-
if (
|
|
1959
|
+
if (this.hasPendingQuestionsForSession(sessionId)) {
|
|
1960
|
+
console.log(`[SessionManager] Session ${sessionId}: result received but questions pending, keeping waiting_question`);
|
|
1961
|
+
} else if (event.is_error) {
|
|
1872
1962
|
this.updateSessionStatus(sessionId, "error");
|
|
1873
1963
|
} else {
|
|
1874
1964
|
this.updateSessionStatus(sessionId, "idle");
|
|
@@ -1952,19 +2042,24 @@ var SessionManager = class {
|
|
|
1952
2042
|
/**
|
|
1953
2043
|
* 处理 AskUserQuestion 事件:广播问题请求到手机,等待用户回答
|
|
1954
2044
|
*/
|
|
1955
|
-
handleAskUserQuestion(sessionId, toolUseId, question, options) {
|
|
2045
|
+
handleAskUserQuestion(sessionId, toolUseId, question, options, questions) {
|
|
1956
2046
|
const existingEntry = Array.from(this.pendingQuestions.entries()).find(
|
|
1957
2047
|
([, v]) => v.toolUseId === toolUseId
|
|
1958
2048
|
);
|
|
1959
2049
|
if (existingEntry) {
|
|
1960
|
-
const [existingRequestId] = existingEntry;
|
|
2050
|
+
const [existingRequestId, existingPending] = existingEntry;
|
|
2051
|
+
existingPending.question = question;
|
|
2052
|
+
existingPending.options = options;
|
|
2053
|
+
existingPending.questions = questions;
|
|
2054
|
+
existingPending.createdAt = Date.now();
|
|
1961
2055
|
const updatedRequest = {
|
|
1962
2056
|
id: existingRequestId,
|
|
1963
2057
|
sessionId,
|
|
1964
2058
|
toolUseId,
|
|
1965
2059
|
question,
|
|
1966
2060
|
options,
|
|
1967
|
-
|
|
2061
|
+
questions,
|
|
2062
|
+
createdAt: existingPending.createdAt
|
|
1968
2063
|
};
|
|
1969
2064
|
this.emit({ type: "question_request", request: updatedRequest });
|
|
1970
2065
|
console.log(`[SessionManager] Session ${sessionId}: AskUserQuestion updated (requestId=${existingRequestId})`);
|
|
@@ -1977,12 +2072,13 @@ var SessionManager = class {
|
|
|
1977
2072
|
toolUseId,
|
|
1978
2073
|
question,
|
|
1979
2074
|
options,
|
|
2075
|
+
questions,
|
|
1980
2076
|
createdAt: Date.now()
|
|
1981
2077
|
};
|
|
1982
2078
|
this.updateSessionStatus(sessionId, "waiting_question");
|
|
1983
2079
|
this.emit({ type: "question_request", request });
|
|
1984
2080
|
const answerPromise = new Promise((resolve) => {
|
|
1985
|
-
this.pendingQuestions.set(requestId, { sessionId, toolUseId, question, options, createdAt: request.createdAt, resolve });
|
|
2081
|
+
this.pendingQuestions.set(requestId, { sessionId, toolUseId, question, options, questions, createdAt: request.createdAt, resolve });
|
|
1986
2082
|
});
|
|
1987
2083
|
answerPromise.then(async (answer) => {
|
|
1988
2084
|
try {
|
|
@@ -4631,18 +4727,25 @@ async function start(opts = {}) {
|
|
|
4631
4727
|
}
|
|
4632
4728
|
case "load_session_history": {
|
|
4633
4729
|
const historyResult = await getSessionHistory(event.projectPath, event.sessionId);
|
|
4634
|
-
|
|
4730
|
+
let historyEvents = historyResult.ok ? historyResult.value : [];
|
|
4731
|
+
if (historyEvents.length === 0) {
|
|
4732
|
+
const codexProv = providerFactory.getProvider("codex");
|
|
4733
|
+
if (codexProv instanceof CodexProvider && codexProv.isKnownSession(event.sessionId)) {
|
|
4734
|
+
historyEvents = codexProv.getSessionHistory(event.sessionId);
|
|
4735
|
+
}
|
|
4736
|
+
}
|
|
4737
|
+
if (!historyResult.ok && historyEvents.length === 0) {
|
|
4635
4738
|
wsBridge.send(ws, {
|
|
4636
4739
|
type: "error",
|
|
4637
4740
|
message: t("server.readHistoryFailed", { error: historyResult.error.message }),
|
|
4638
4741
|
code: "SESSION_HISTORY_ERROR",
|
|
4639
4742
|
sessionId: event.sessionId
|
|
4640
4743
|
});
|
|
4641
|
-
} else if (
|
|
4744
|
+
} else if (historyEvents.length > 0) {
|
|
4642
4745
|
wsBridge.send(ws, {
|
|
4643
4746
|
type: "session_history",
|
|
4644
4747
|
sessionId: event.sessionId,
|
|
4645
|
-
events:
|
|
4748
|
+
events: historyEvents
|
|
4646
4749
|
});
|
|
4647
4750
|
const activeSession = sessionManager.getActiveSessions().find((s) => s.id === event.sessionId);
|
|
4648
4751
|
const isStreaming = activeSession?.status === "running" || activeSession?.status === "waiting_approval";
|