@ccpocket/bridge 1.63.0 → 1.63.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/codex-process.d.ts +3 -0
- package/dist/codex-process.js +6 -0
- package/dist/codex-process.js.map +1 -1
- package/dist/parser.d.ts +2 -0
- package/dist/parser.js.map +1 -1
- package/dist/session.d.ts +5 -0
- package/dist/session.js +41 -0
- package/dist/session.js.map +1 -1
- package/dist/sessions-index.d.ts +2 -0
- package/dist/sessions-index.js +48 -5
- package/dist/sessions-index.js.map +1 -1
- package/dist/websocket.d.ts +19 -0
- package/dist/websocket.js +481 -131
- package/dist/websocket.js.map +1 -1
- package/package.json +1 -1
package/dist/websocket.js
CHANGED
|
@@ -50,6 +50,11 @@ const FALLBACK_CODEX_MODELS = [
|
|
|
50
50
|
"gpt-5.3-codex-spark",
|
|
51
51
|
];
|
|
52
52
|
const FALLBACK_CODEX_REASONING_EFFORTS = ["low", "medium", "high", "xhigh"];
|
|
53
|
+
const CODEX_RECENT_THREAD_SOURCE_KINDS = [
|
|
54
|
+
"cli",
|
|
55
|
+
"vscode",
|
|
56
|
+
"appServer",
|
|
57
|
+
];
|
|
53
58
|
const CODEX_USER_TURN_UUID_RE = /^codex:user-turn:(\d+)$/;
|
|
54
59
|
const OPT_IN_SERVER_MESSAGES = new Set([
|
|
55
60
|
"conversation_queue",
|
|
@@ -849,37 +854,8 @@ export class BridgeWebSocketServer {
|
|
|
849
854
|
pastMessages.push(raw);
|
|
850
855
|
continue;
|
|
851
856
|
}
|
|
852
|
-
const paths = new Set();
|
|
853
|
-
if (Array.isArray(msg.imagePaths)) {
|
|
854
|
-
for (const path of msg.imagePaths) {
|
|
855
|
-
if (typeof path === "string" && path.length > 0)
|
|
856
|
-
paths.add(path);
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
857
|
const content = typeof msg.content === "string" ? msg.content : "";
|
|
860
|
-
|
|
861
|
-
for (const path of this.imageStore.extractImagePaths(content)) {
|
|
862
|
-
paths.add(path);
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
const images = this.imageStore && paths.size > 0
|
|
866
|
-
? await this.imageStore.registerImages([...paths], session.projectPath)
|
|
867
|
-
: [];
|
|
868
|
-
if (this.imageStore && Array.isArray(msg.imageBase64)) {
|
|
869
|
-
for (const image of msg.imageBase64) {
|
|
870
|
-
const rawImage = image;
|
|
871
|
-
if (typeof rawImage.data !== "string"
|
|
872
|
-
|| typeof rawImage.mimeType !== "string") {
|
|
873
|
-
continue;
|
|
874
|
-
}
|
|
875
|
-
const ref = this.imageStore.registerFromBase64(rawImage.data, rawImage.mimeType);
|
|
876
|
-
if (ref)
|
|
877
|
-
images.push(ref);
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
const existingImages = Array.isArray(msg.images)
|
|
881
|
-
? msg.images
|
|
882
|
-
: [];
|
|
858
|
+
const images = await this.registerPastToolResultImages(session, msg);
|
|
883
859
|
pastMessages.push({
|
|
884
860
|
role: "tool_result",
|
|
885
861
|
toolUseId: typeof msg.toolUseId === "string"
|
|
@@ -887,13 +863,308 @@ export class BridgeWebSocketServer {
|
|
|
887
863
|
: `past-tool-result-${pastMessages.length}`,
|
|
888
864
|
content,
|
|
889
865
|
...(typeof msg.toolName === "string" ? { toolName: msg.toolName } : {}),
|
|
890
|
-
...(
|
|
891
|
-
? { images: [...existingImages, ...images] }
|
|
892
|
-
: {}),
|
|
866
|
+
...(images.length > 0 ? { images } : {}),
|
|
893
867
|
});
|
|
894
868
|
}
|
|
895
869
|
return { pastMessages, historyMessages };
|
|
896
870
|
}
|
|
871
|
+
codexThreadIdForSession(session) {
|
|
872
|
+
if (session.provider !== "codex")
|
|
873
|
+
return undefined;
|
|
874
|
+
if (session.claudeSessionId)
|
|
875
|
+
return session.claudeSessionId;
|
|
876
|
+
if (session.process instanceof CodexProcess) {
|
|
877
|
+
return session.process.sessionId ?? undefined;
|
|
878
|
+
}
|
|
879
|
+
return undefined;
|
|
880
|
+
}
|
|
881
|
+
async codexCanonicalHistoryEntries(session) {
|
|
882
|
+
const threadId = this.codexThreadIdForSession(session);
|
|
883
|
+
if (!threadId)
|
|
884
|
+
return null;
|
|
885
|
+
const history = await this.getCodexThreadHistory(threadId, session.projectPath);
|
|
886
|
+
session.claudeSessionId = threadId;
|
|
887
|
+
const messages = await this.codexHistoryToServerMessages(session, history);
|
|
888
|
+
const entries = messages.map((message, index) => ({
|
|
889
|
+
seq: index + 1,
|
|
890
|
+
message,
|
|
891
|
+
}));
|
|
892
|
+
this.applyCodexCanonicalHistoryBaseline(session, history, entries);
|
|
893
|
+
return [...entries, ...session.historyEntries];
|
|
894
|
+
}
|
|
895
|
+
applyCodexCanonicalHistoryBaseline(session, history, canonicalEntries) {
|
|
896
|
+
const liveEntries = session.historyEntries.map((entry) => ({
|
|
897
|
+
seq: entry.seq,
|
|
898
|
+
message: entry.message,
|
|
899
|
+
}));
|
|
900
|
+
const canonicalKeys = new Set();
|
|
901
|
+
const canonicalUserUuids = new Set();
|
|
902
|
+
for (const entry of canonicalEntries) {
|
|
903
|
+
for (const key of this.codexHistoryMessageIdentityKeys(entry.message)) {
|
|
904
|
+
canonicalKeys.add(key);
|
|
905
|
+
}
|
|
906
|
+
const message = entry.message;
|
|
907
|
+
if (message.type === "user_input" && message.userMessageUuid) {
|
|
908
|
+
canonicalUserUuids.add(message.userMessageUuid);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
this.seedCodexCanonicalUserTurnUuidMap(session, history);
|
|
912
|
+
let nextSeq = canonicalEntries.at(-1)?.seq ?? 0;
|
|
913
|
+
const retainedMessages = [];
|
|
914
|
+
const retainedEntries = [];
|
|
915
|
+
for (const entry of liveEntries) {
|
|
916
|
+
if (!this.shouldRetainCodexLiveHistoryMessage(entry.message))
|
|
917
|
+
continue;
|
|
918
|
+
const keys = this.codexHistoryMessageIdentityKeys(entry.message);
|
|
919
|
+
if (keys.some((key) => canonicalKeys.has(key)))
|
|
920
|
+
continue;
|
|
921
|
+
const seq = ++nextSeq;
|
|
922
|
+
entry.message.historySeq = seq;
|
|
923
|
+
retainedMessages.push(entry.message);
|
|
924
|
+
retainedEntries.push({ seq, message: entry.message });
|
|
925
|
+
}
|
|
926
|
+
session.pastMessages = history;
|
|
927
|
+
session.history = retainedMessages;
|
|
928
|
+
session.historyEntries = retainedEntries;
|
|
929
|
+
session.historyRevision = nextSeq;
|
|
930
|
+
session.codexCanonicalHistoryRevision = canonicalEntries.at(-1)?.seq ?? 0;
|
|
931
|
+
session.historyLowWatermark =
|
|
932
|
+
retainedEntries[0]?.seq ?? session.historyRevision + 1;
|
|
933
|
+
if (session.pendingCodexUserEchoUuids) {
|
|
934
|
+
for (const uuid of canonicalUserUuids) {
|
|
935
|
+
session.pendingCodexUserEchoUuids.delete(uuid);
|
|
936
|
+
}
|
|
937
|
+
for (const uuid of [...session.pendingCodexUserEchoUuids]) {
|
|
938
|
+
const stillLive = retainedMessages.some((message) => message.type === "user_input" && message.userMessageUuid === uuid);
|
|
939
|
+
if (!stillLive)
|
|
940
|
+
session.pendingCodexUserEchoUuids.delete(uuid);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
seedCodexCanonicalUserTurnUuidMap(session, history) {
|
|
945
|
+
if (session.provider !== "codex")
|
|
946
|
+
return;
|
|
947
|
+
for (const message of history) {
|
|
948
|
+
if (message.role !== "user" ||
|
|
949
|
+
!message.rawItemId ||
|
|
950
|
+
!message.uuid) {
|
|
951
|
+
continue;
|
|
952
|
+
}
|
|
953
|
+
session.codexUserTurnUuidByRawId ??= new Map();
|
|
954
|
+
session.codexUserTurnUuidByRawId.set(message.rawItemId, message.uuid);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
shouldRetainCodexLiveHistoryMessage(message) {
|
|
958
|
+
return !(message.type === "system" &&
|
|
959
|
+
message.subtype === "tip");
|
|
960
|
+
}
|
|
961
|
+
codexHistoryMessageIdentityKeys(message) {
|
|
962
|
+
if (message.type === "user_input") {
|
|
963
|
+
return message.userMessageUuid ? [`user:${message.userMessageUuid}`] : [];
|
|
964
|
+
}
|
|
965
|
+
if (message.type === "assistant") {
|
|
966
|
+
const assistantId = message.messageUuid ?? message.message.id;
|
|
967
|
+
if (assistantId)
|
|
968
|
+
return [`assistant:${assistantId}`];
|
|
969
|
+
return [
|
|
970
|
+
`assistant-content:${this.historyValueKey(message.message.content)}`,
|
|
971
|
+
];
|
|
972
|
+
}
|
|
973
|
+
if (message.type === "tool_result") {
|
|
974
|
+
if (message.toolUseId) {
|
|
975
|
+
return [`tool-result:${message.toolUseId}:${message.toolName ?? ""}`];
|
|
976
|
+
}
|
|
977
|
+
return [
|
|
978
|
+
`tool-result-content:${message.toolName ?? ""}:${message.content}`,
|
|
979
|
+
];
|
|
980
|
+
}
|
|
981
|
+
return [];
|
|
982
|
+
}
|
|
983
|
+
historyValueKey(value) {
|
|
984
|
+
try {
|
|
985
|
+
return JSON.stringify(value);
|
|
986
|
+
}
|
|
987
|
+
catch {
|
|
988
|
+
return String(value);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
async sendCodexCanonicalHistorySnapshot(ws, sessionId, session, options = {}) {
|
|
992
|
+
try {
|
|
993
|
+
const entries = await this.codexCanonicalHistoryEntries(session);
|
|
994
|
+
if (!entries)
|
|
995
|
+
return false;
|
|
996
|
+
this.send(ws, {
|
|
997
|
+
type: "history_snapshot",
|
|
998
|
+
sessionId,
|
|
999
|
+
fromSeq: entries[0]?.seq ?? 1,
|
|
1000
|
+
toSeq: entries.at(-1)?.seq ?? 0,
|
|
1001
|
+
messages: entries,
|
|
1002
|
+
status: session.status,
|
|
1003
|
+
reason: "reset",
|
|
1004
|
+
});
|
|
1005
|
+
this.sendCodexQueueState(ws, sessionId, session);
|
|
1006
|
+
if (options.includeCachedCommands) {
|
|
1007
|
+
this.sendCachedCommands(ws, sessionId, session);
|
|
1008
|
+
}
|
|
1009
|
+
return true;
|
|
1010
|
+
}
|
|
1011
|
+
catch (err) {
|
|
1012
|
+
this.send(ws, {
|
|
1013
|
+
type: "error",
|
|
1014
|
+
message: `Failed to read Codex thread history: ${err instanceof Error ? err.message : String(err)}`,
|
|
1015
|
+
});
|
|
1016
|
+
return true;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
async sendCodexCanonicalLegacyHistory(ws, sessionId, session) {
|
|
1020
|
+
try {
|
|
1021
|
+
const entries = await this.codexCanonicalHistoryEntries(session);
|
|
1022
|
+
if (!entries)
|
|
1023
|
+
return false;
|
|
1024
|
+
this.send(ws, {
|
|
1025
|
+
type: "history",
|
|
1026
|
+
messages: entries.map((entry) => entry.message),
|
|
1027
|
+
sessionId,
|
|
1028
|
+
});
|
|
1029
|
+
this.send(ws, {
|
|
1030
|
+
type: "status",
|
|
1031
|
+
status: session.status,
|
|
1032
|
+
sessionId,
|
|
1033
|
+
});
|
|
1034
|
+
this.sendCodexQueueState(ws, sessionId, session);
|
|
1035
|
+
this.sendCachedCommands(ws, sessionId, session);
|
|
1036
|
+
return true;
|
|
1037
|
+
}
|
|
1038
|
+
catch (err) {
|
|
1039
|
+
this.send(ws, {
|
|
1040
|
+
type: "error",
|
|
1041
|
+
message: `Failed to read Codex thread history: ${err instanceof Error ? err.message : String(err)}`,
|
|
1042
|
+
});
|
|
1043
|
+
return true;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
shouldResetCodexHistoryDelta(session, sinceSeq, resultKind) {
|
|
1047
|
+
if (!this.codexThreadIdForSession(session))
|
|
1048
|
+
return false;
|
|
1049
|
+
if (typeof session.codexCanonicalHistoryRevision !== "number")
|
|
1050
|
+
return true;
|
|
1051
|
+
if (sinceSeq < session.codexCanonicalHistoryRevision)
|
|
1052
|
+
return true;
|
|
1053
|
+
return resultKind === "snapshot";
|
|
1054
|
+
}
|
|
1055
|
+
async codexHistoryToServerMessages(session, history) {
|
|
1056
|
+
const messages = [];
|
|
1057
|
+
for (const item of history) {
|
|
1058
|
+
const converted = await this.codexHistoryMessageToServerMessage(session, item);
|
|
1059
|
+
if (converted)
|
|
1060
|
+
messages.push(converted);
|
|
1061
|
+
}
|
|
1062
|
+
return messages;
|
|
1063
|
+
}
|
|
1064
|
+
async codexHistoryMessageToServerMessage(session, item) {
|
|
1065
|
+
if (item.role === "user") {
|
|
1066
|
+
const images = await this.registerPastUserMessageImages(session, item);
|
|
1067
|
+
const text = this.sessionHistoryText(item.content);
|
|
1068
|
+
if (!text && item.imageCount == null && images.length === 0)
|
|
1069
|
+
return null;
|
|
1070
|
+
return {
|
|
1071
|
+
type: "user_input",
|
|
1072
|
+
text,
|
|
1073
|
+
...(item.uuid ? { userMessageUuid: item.uuid } : {}),
|
|
1074
|
+
...(item.isMeta ? { isMeta: true } : {}),
|
|
1075
|
+
...(item.imageCount != null || images.length > 0
|
|
1076
|
+
? { imageCount: Math.max(item.imageCount ?? 0, images.length) }
|
|
1077
|
+
: {}),
|
|
1078
|
+
...(item.timestamp ? { timestamp: item.timestamp } : {}),
|
|
1079
|
+
...(images.length > 0 ? { images } : {}),
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
if (item.role === "assistant") {
|
|
1083
|
+
const content = this.sessionHistoryAssistantContent(item.content);
|
|
1084
|
+
if (content.length === 0)
|
|
1085
|
+
return null;
|
|
1086
|
+
const messageId = item.uuid ??
|
|
1087
|
+
this.sessionHistorySingleToolUseId(item.content) ??
|
|
1088
|
+
randomUUID();
|
|
1089
|
+
return {
|
|
1090
|
+
type: "assistant",
|
|
1091
|
+
message: {
|
|
1092
|
+
id: messageId,
|
|
1093
|
+
role: "assistant",
|
|
1094
|
+
content,
|
|
1095
|
+
model: session.codexSettings?.model ?? "",
|
|
1096
|
+
},
|
|
1097
|
+
...(item.uuid ? { messageUuid: item.uuid } : {}),
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
const images = await this.registerPastToolResultImages(session, item);
|
|
1101
|
+
const content = this.sessionHistoryText(item.content);
|
|
1102
|
+
if (!content && images.length === 0)
|
|
1103
|
+
return null;
|
|
1104
|
+
return {
|
|
1105
|
+
type: "tool_result",
|
|
1106
|
+
toolUseId: item.toolUseId ?? item.uuid ?? `codex-history-tool-${randomUUID()}`,
|
|
1107
|
+
content,
|
|
1108
|
+
...(item.toolName ? { toolName: item.toolName } : {}),
|
|
1109
|
+
...(images.length > 0 ? { images } : {}),
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
sessionHistoryText(content) {
|
|
1113
|
+
if (typeof content === "string")
|
|
1114
|
+
return content;
|
|
1115
|
+
if (!Array.isArray(content))
|
|
1116
|
+
return "";
|
|
1117
|
+
return content
|
|
1118
|
+
.map((item) => {
|
|
1119
|
+
if (item.type === "text" && typeof item.text === "string") {
|
|
1120
|
+
return item.text;
|
|
1121
|
+
}
|
|
1122
|
+
if (item.type === "thinking" && typeof item.thinking === "string") {
|
|
1123
|
+
return item.thinking;
|
|
1124
|
+
}
|
|
1125
|
+
return "";
|
|
1126
|
+
})
|
|
1127
|
+
.filter((text) => text.length > 0)
|
|
1128
|
+
.join("\n");
|
|
1129
|
+
}
|
|
1130
|
+
sessionHistoryAssistantContent(content) {
|
|
1131
|
+
if (typeof content === "string") {
|
|
1132
|
+
return content.trim().length > 0
|
|
1133
|
+
? [{ type: "text", text: content }]
|
|
1134
|
+
: [];
|
|
1135
|
+
}
|
|
1136
|
+
if (!Array.isArray(content))
|
|
1137
|
+
return [];
|
|
1138
|
+
const items = [];
|
|
1139
|
+
for (const item of content) {
|
|
1140
|
+
if (item.type === "text" && typeof item.text === "string") {
|
|
1141
|
+
items.push({ type: "text", text: item.text });
|
|
1142
|
+
}
|
|
1143
|
+
else if (item.type === "thinking" &&
|
|
1144
|
+
typeof item.thinking === "string") {
|
|
1145
|
+
items.push({ type: "thinking", thinking: item.thinking });
|
|
1146
|
+
}
|
|
1147
|
+
else if (item.type === "tool_use" &&
|
|
1148
|
+
typeof item.id === "string" &&
|
|
1149
|
+
typeof item.name === "string") {
|
|
1150
|
+
items.push({
|
|
1151
|
+
type: "tool_use",
|
|
1152
|
+
id: item.id,
|
|
1153
|
+
name: item.name,
|
|
1154
|
+
input: item.input ?? {},
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
return items;
|
|
1159
|
+
}
|
|
1160
|
+
sessionHistorySingleToolUseId(content) {
|
|
1161
|
+
if (!Array.isArray(content) || content.length !== 1)
|
|
1162
|
+
return undefined;
|
|
1163
|
+
const item = content[0];
|
|
1164
|
+
return item.type === "tool_use" && typeof item.id === "string"
|
|
1165
|
+
? item.id
|
|
1166
|
+
: undefined;
|
|
1167
|
+
}
|
|
897
1168
|
async registerPastUserMessageImages(session, msg) {
|
|
898
1169
|
if (!this.imageStore)
|
|
899
1170
|
return [];
|
|
@@ -901,6 +1172,12 @@ export class BridgeWebSocketServer {
|
|
|
901
1172
|
? msg.images
|
|
902
1173
|
: [];
|
|
903
1174
|
const refs = [...existingImages];
|
|
1175
|
+
if (Array.isArray(msg.imagePaths)) {
|
|
1176
|
+
const paths = msg.imagePaths.filter((path) => typeof path === "string" && path.length > 0);
|
|
1177
|
+
if (paths.length > 0) {
|
|
1178
|
+
refs.push(...(await this.imageStore.registerImages(paths, session.projectPath)));
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
904
1181
|
if (Array.isArray(msg.imageBase64)) {
|
|
905
1182
|
for (const image of msg.imageBase64) {
|
|
906
1183
|
const rawImage = image;
|
|
@@ -932,6 +1209,43 @@ export class BridgeWebSocketServer {
|
|
|
932
1209
|
}
|
|
933
1210
|
return refs;
|
|
934
1211
|
}
|
|
1212
|
+
async registerPastToolResultImages(session, msg) {
|
|
1213
|
+
const existingImages = Array.isArray(msg.images)
|
|
1214
|
+
? msg.images
|
|
1215
|
+
: [];
|
|
1216
|
+
if (!this.imageStore)
|
|
1217
|
+
return [...existingImages];
|
|
1218
|
+
const paths = new Set();
|
|
1219
|
+
if (Array.isArray(msg.imagePaths)) {
|
|
1220
|
+
for (const path of msg.imagePaths) {
|
|
1221
|
+
if (typeof path === "string" && path.length > 0)
|
|
1222
|
+
paths.add(path);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
const content = typeof msg.content === "string" ? msg.content : "";
|
|
1226
|
+
if (content) {
|
|
1227
|
+
for (const path of this.imageStore.extractImagePaths(content)) {
|
|
1228
|
+
paths.add(path);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
const refs = [...existingImages];
|
|
1232
|
+
if (paths.size > 0) {
|
|
1233
|
+
refs.push(...(await this.imageStore.registerImages([...paths], session.projectPath)));
|
|
1234
|
+
}
|
|
1235
|
+
if (Array.isArray(msg.imageBase64)) {
|
|
1236
|
+
for (const image of msg.imageBase64) {
|
|
1237
|
+
const rawImage = image;
|
|
1238
|
+
if (typeof rawImage.data !== "string" ||
|
|
1239
|
+
typeof rawImage.mimeType !== "string") {
|
|
1240
|
+
continue;
|
|
1241
|
+
}
|
|
1242
|
+
const ref = this.imageStore.registerFromBase64(rawImage.data, rawImage.mimeType);
|
|
1243
|
+
if (ref)
|
|
1244
|
+
refs.push(ref);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
return refs;
|
|
1248
|
+
}
|
|
935
1249
|
async getCodexThreadHistoryFromRpc(threadId, projectPath) {
|
|
936
1250
|
const activeProcess = this.getActiveCodexProcess();
|
|
937
1251
|
const process = activeProcess ?? (await this.createStandaloneCodexProcess(projectPath));
|
|
@@ -940,23 +1254,27 @@ export class BridgeWebSocketServer {
|
|
|
940
1254
|
const thread = await process.readThread(threadId, true);
|
|
941
1255
|
return codexThreadToSessionHistory(thread);
|
|
942
1256
|
}
|
|
1257
|
+
catch (err) {
|
|
1258
|
+
if (this.isCodexThreadNotMaterializedError(err))
|
|
1259
|
+
return [];
|
|
1260
|
+
throw err;
|
|
1261
|
+
}
|
|
943
1262
|
finally {
|
|
944
1263
|
if (isStandalone) {
|
|
945
1264
|
process.stop();
|
|
946
1265
|
}
|
|
947
1266
|
}
|
|
948
1267
|
}
|
|
1268
|
+
isCodexThreadNotMaterializedError(err) {
|
|
1269
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1270
|
+
return (message.includes("is not materialized yet") &&
|
|
1271
|
+
message.includes("includeTurns is unavailable before first user message"));
|
|
1272
|
+
}
|
|
949
1273
|
async getCodexThreadHistory(threadId, projectPath) {
|
|
950
1274
|
if (!this.getActiveCodexProcess() && process.env.NODE_ENV === "test") {
|
|
951
1275
|
return getCodexSessionHistory(threadId);
|
|
952
1276
|
}
|
|
953
|
-
|
|
954
|
-
return await this.getCodexThreadHistoryFromRpc(threadId, projectPath);
|
|
955
|
-
}
|
|
956
|
-
catch (err) {
|
|
957
|
-
console.warn(`[ws] thread/read failed for ${threadId}; falling back to JSONL: ${err instanceof Error ? err.message : String(err)}`);
|
|
958
|
-
return getCodexSessionHistory(threadId);
|
|
959
|
-
}
|
|
1277
|
+
return this.getCodexThreadHistoryFromRpc(threadId, projectPath);
|
|
960
1278
|
}
|
|
961
1279
|
codexHistoryFromThreadOrFallback(params) {
|
|
962
1280
|
const messages = codexThreadToSessionHistory(params.thread);
|
|
@@ -1319,7 +1637,7 @@ export class BridgeWebSocketServer {
|
|
|
1319
1637
|
}
|
|
1320
1638
|
if (clientMessageId &&
|
|
1321
1639
|
baseSeq !== undefined &&
|
|
1322
|
-
this.hasInputConflictSince(session
|
|
1640
|
+
this.hasInputConflictSince(session, baseSeq)) {
|
|
1323
1641
|
this.send(ws, {
|
|
1324
1642
|
type: "input_rejected",
|
|
1325
1643
|
sessionId: session.id,
|
|
@@ -2319,6 +2637,12 @@ export class BridgeWebSocketServer {
|
|
|
2319
2637
|
case "get_history": {
|
|
2320
2638
|
const session = this.sessionManager.get(msg.sessionId);
|
|
2321
2639
|
if (session) {
|
|
2640
|
+
if (session.provider === "codex") {
|
|
2641
|
+
const handled = await this.sendCodexCanonicalLegacyHistory(ws, msg.sessionId, session);
|
|
2642
|
+
if (handled) {
|
|
2643
|
+
break;
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2322
2646
|
const splitPastHistory = session.pastMessages && session.pastMessages.length > 0
|
|
2323
2647
|
? await this.splitPastHistoryMessages(session)
|
|
2324
2648
|
: { pastMessages: [], historyMessages: [] };
|
|
@@ -2342,56 +2666,9 @@ export class BridgeWebSocketServer {
|
|
|
2342
2666
|
sessionId: msg.sessionId,
|
|
2343
2667
|
});
|
|
2344
2668
|
if (session.provider === "codex") {
|
|
2345
|
-
|
|
2346
|
-
this.sendConversationQueue(ws, {
|
|
2347
|
-
type: "conversation_queue",
|
|
2348
|
-
sessionId: msg.sessionId,
|
|
2349
|
-
limit: 1,
|
|
2350
|
-
items: item
|
|
2351
|
-
? [
|
|
2352
|
-
{
|
|
2353
|
-
itemId: item.itemId,
|
|
2354
|
-
text: item.text,
|
|
2355
|
-
createdAt: item.createdAt,
|
|
2356
|
-
...(item.updatedAt ? { updatedAt: item.updatedAt } : {}),
|
|
2357
|
-
...(item.imageCount
|
|
2358
|
-
? { imageCount: item.imageCount }
|
|
2359
|
-
: {}),
|
|
2360
|
-
...(item.skills?.length ? { skills: item.skills } : {}),
|
|
2361
|
-
...(item.mentions?.length
|
|
2362
|
-
? { mentions: item.mentions }
|
|
2363
|
-
: {}),
|
|
2364
|
-
},
|
|
2365
|
-
]
|
|
2366
|
-
: [],
|
|
2367
|
-
});
|
|
2368
|
-
}
|
|
2369
|
-
// Send cached slash commands so the client can restore them even when
|
|
2370
|
-
// the original init/supported_commands message was evicted from the
|
|
2371
|
-
// in-memory history (MAX_HISTORY_PER_SESSION overflow).
|
|
2372
|
-
const cached = this.sessionManager.getCachedCommands(session.projectPath);
|
|
2373
|
-
if (cached &&
|
|
2374
|
-
(cached.slashCommands.length > 0 ||
|
|
2375
|
-
cached.skills.length > 0 ||
|
|
2376
|
-
cached.apps.length > 0 ||
|
|
2377
|
-
cached.plugins.length > 0)) {
|
|
2378
|
-
this.send(ws, {
|
|
2379
|
-
type: "system",
|
|
2380
|
-
subtype: "supported_commands",
|
|
2381
|
-
sessionId: msg.sessionId,
|
|
2382
|
-
slashCommands: cached.slashCommands,
|
|
2383
|
-
skills: cached.skills,
|
|
2384
|
-
...(cached.skillMetadata
|
|
2385
|
-
? { skillMetadata: cached.skillMetadata }
|
|
2386
|
-
: {}),
|
|
2387
|
-
apps: cached.apps,
|
|
2388
|
-
...(cached.appMetadata ? { appMetadata: cached.appMetadata } : {}),
|
|
2389
|
-
plugins: cached.plugins,
|
|
2390
|
-
...(cached.pluginMetadata
|
|
2391
|
-
? { pluginMetadata: cached.pluginMetadata }
|
|
2392
|
-
: {}),
|
|
2393
|
-
});
|
|
2669
|
+
this.sendCodexQueueState(ws, msg.sessionId, session);
|
|
2394
2670
|
}
|
|
2671
|
+
this.sendCachedCommands(ws, msg.sessionId, session);
|
|
2395
2672
|
}
|
|
2396
2673
|
else {
|
|
2397
2674
|
this.send(ws, {
|
|
@@ -2403,8 +2680,40 @@ export class BridgeWebSocketServer {
|
|
|
2403
2680
|
}
|
|
2404
2681
|
case "get_history_delta": {
|
|
2405
2682
|
const session = this.sessionManager.get(msg.sessionId);
|
|
2406
|
-
|
|
2407
|
-
|
|
2683
|
+
if (session?.provider === "codex") {
|
|
2684
|
+
const result = this.sessionManager.getHistorySince(msg.sessionId, msg.sinceSeq);
|
|
2685
|
+
if (!result) {
|
|
2686
|
+
this.send(ws, {
|
|
2687
|
+
type: "error",
|
|
2688
|
+
message: `Session ${msg.sessionId} not found`,
|
|
2689
|
+
});
|
|
2690
|
+
break;
|
|
2691
|
+
}
|
|
2692
|
+
if (this.shouldResetCodexHistoryDelta(session, msg.sinceSeq, result.kind)) {
|
|
2693
|
+
await this.sendCodexCanonicalHistorySnapshot(ws, msg.sessionId, session);
|
|
2694
|
+
break;
|
|
2695
|
+
}
|
|
2696
|
+
this.send(ws, {
|
|
2697
|
+
type: result.kind === "snapshot" ? "history_snapshot" : "history_delta",
|
|
2698
|
+
sessionId: msg.sessionId,
|
|
2699
|
+
fromSeq: result.fromSeq,
|
|
2700
|
+
toSeq: result.toSeq,
|
|
2701
|
+
messages: result.entries,
|
|
2702
|
+
status: session.status,
|
|
2703
|
+
...(result.kind === "snapshot" ? { reason: result.reason } : {}),
|
|
2704
|
+
});
|
|
2705
|
+
this.sendCodexQueueState(ws, msg.sessionId, session);
|
|
2706
|
+
break;
|
|
2707
|
+
}
|
|
2708
|
+
if (session) {
|
|
2709
|
+
const result = this.sessionManager.getHistorySince(msg.sessionId, msg.sinceSeq);
|
|
2710
|
+
if (!result) {
|
|
2711
|
+
this.send(ws, {
|
|
2712
|
+
type: "error",
|
|
2713
|
+
message: `Session ${msg.sessionId} not found`,
|
|
2714
|
+
});
|
|
2715
|
+
break;
|
|
2716
|
+
}
|
|
2408
2717
|
if (session.pastMessages && session.pastMessages.length > 0) {
|
|
2409
2718
|
const splitPastHistory = await this.splitPastHistoryMessages(session);
|
|
2410
2719
|
if (splitPastHistory.pastMessages.length > 0) {
|
|
@@ -2427,31 +2736,6 @@ export class BridgeWebSocketServer {
|
|
|
2427
2736
|
status: session.status,
|
|
2428
2737
|
...(result.kind === "snapshot" ? { reason: result.reason } : {}),
|
|
2429
2738
|
});
|
|
2430
|
-
if (session.provider === "codex") {
|
|
2431
|
-
const item = session.codexQueuedInput;
|
|
2432
|
-
this.sendConversationQueue(ws, {
|
|
2433
|
-
type: "conversation_queue",
|
|
2434
|
-
sessionId: msg.sessionId,
|
|
2435
|
-
limit: 1,
|
|
2436
|
-
items: item
|
|
2437
|
-
? [
|
|
2438
|
-
{
|
|
2439
|
-
itemId: item.itemId,
|
|
2440
|
-
text: item.text,
|
|
2441
|
-
createdAt: item.createdAt,
|
|
2442
|
-
...(item.updatedAt ? { updatedAt: item.updatedAt } : {}),
|
|
2443
|
-
...(item.imageCount
|
|
2444
|
-
? { imageCount: item.imageCount }
|
|
2445
|
-
: {}),
|
|
2446
|
-
...(item.skills?.length ? { skills: item.skills } : {}),
|
|
2447
|
-
...(item.mentions?.length
|
|
2448
|
-
? { mentions: item.mentions }
|
|
2449
|
-
: {}),
|
|
2450
|
-
},
|
|
2451
|
-
]
|
|
2452
|
-
: [],
|
|
2453
|
-
});
|
|
2454
|
-
}
|
|
2455
2739
|
}
|
|
2456
2740
|
else {
|
|
2457
2741
|
this.send(ws, {
|
|
@@ -4528,21 +4812,37 @@ export class BridgeWebSocketServer {
|
|
|
4528
4812
|
(await this.createStandaloneCodexProcess(msg.projectPath));
|
|
4529
4813
|
const isStandalone = process !== this.getActiveCodexProcess();
|
|
4530
4814
|
try {
|
|
4531
|
-
const result = await process.listThreads({
|
|
4532
|
-
limit: limit + offset,
|
|
4533
|
-
cwd: msg.projectPath,
|
|
4534
|
-
searchTerm: msg.searchQuery,
|
|
4535
|
-
});
|
|
4536
4815
|
const archivedIds = this.archiveStore.archivedIds();
|
|
4537
|
-
const visibleThreads =
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4816
|
+
const visibleThreads = [];
|
|
4817
|
+
let cursor;
|
|
4818
|
+
let hasServerMore = false;
|
|
4819
|
+
const targetCount = offset + limit;
|
|
4820
|
+
do {
|
|
4821
|
+
const request = {
|
|
4822
|
+
limit: Math.max(limit, 1),
|
|
4823
|
+
cwd: msg.projectPath,
|
|
4824
|
+
searchTerm: msg.searchQuery,
|
|
4825
|
+
sourceKinds: CODEX_RECENT_THREAD_SOURCE_KINDS,
|
|
4826
|
+
};
|
|
4827
|
+
if (cursor != null)
|
|
4828
|
+
request.cursor = cursor;
|
|
4829
|
+
const result = await process.listThreads(request);
|
|
4830
|
+
for (const thread of result.data) {
|
|
4831
|
+
if (archivedIds.has(thread.id))
|
|
4832
|
+
continue;
|
|
4833
|
+
if (msg.namedOnly && !thread.name)
|
|
4834
|
+
continue;
|
|
4835
|
+
visibleThreads.push(thread);
|
|
4836
|
+
}
|
|
4837
|
+
cursor = result.nextCursor;
|
|
4838
|
+
hasServerMore = cursor != null;
|
|
4839
|
+
} while (visibleThreads.length < targetCount && cursor != null);
|
|
4840
|
+
const pageThreads = visibleThreads.slice(offset, offset + limit);
|
|
4841
|
+
const indexedById = await getCodexSessionIndexMetadata(pageThreads.map((thread) => thread.id));
|
|
4842
|
+
const sessions = pageThreads.map((thread) => codexThreadToRecentSession(thread, indexedById.get(thread.id)));
|
|
4543
4843
|
return {
|
|
4544
4844
|
sessions,
|
|
4545
|
-
hasMore:
|
|
4845
|
+
hasMore: hasServerMore || visibleThreads.length > offset + limit,
|
|
4546
4846
|
};
|
|
4547
4847
|
}
|
|
4548
4848
|
finally {
|
|
@@ -4750,8 +5050,13 @@ export class BridgeWebSocketServer {
|
|
|
4750
5050
|
return true;
|
|
4751
5051
|
return (this.clientSupportedServerMessages.get(ws)?.has(type) ?? false);
|
|
4752
5052
|
}
|
|
4753
|
-
hasInputConflictSince(
|
|
4754
|
-
|
|
5053
|
+
hasInputConflictSince(session, baseSeq) {
|
|
5054
|
+
if (session.provider === "codex" &&
|
|
5055
|
+
typeof session.codexCanonicalHistoryRevision === "number" &&
|
|
5056
|
+
baseSeq < session.codexCanonicalHistoryRevision) {
|
|
5057
|
+
return true;
|
|
5058
|
+
}
|
|
5059
|
+
const delta = this.sessionManager.getHistorySince(session.id, baseSeq);
|
|
4755
5060
|
if (!delta)
|
|
4756
5061
|
return true;
|
|
4757
5062
|
if (delta.kind === "snapshot")
|
|
@@ -4770,6 +5075,51 @@ export class BridgeWebSocketServer {
|
|
|
4770
5075
|
return false;
|
|
4771
5076
|
});
|
|
4772
5077
|
}
|
|
5078
|
+
sendCodexQueueState(ws, sessionId, session) {
|
|
5079
|
+
const item = session.codexQueuedInput;
|
|
5080
|
+
this.sendConversationQueue(ws, {
|
|
5081
|
+
type: "conversation_queue",
|
|
5082
|
+
sessionId,
|
|
5083
|
+
limit: 1,
|
|
5084
|
+
items: item
|
|
5085
|
+
? [
|
|
5086
|
+
{
|
|
5087
|
+
itemId: item.itemId,
|
|
5088
|
+
text: item.text,
|
|
5089
|
+
createdAt: item.createdAt,
|
|
5090
|
+
...(item.updatedAt ? { updatedAt: item.updatedAt } : {}),
|
|
5091
|
+
...(item.imageCount ? { imageCount: item.imageCount } : {}),
|
|
5092
|
+
...(item.skills?.length ? { skills: item.skills } : {}),
|
|
5093
|
+
...(item.mentions?.length ? { mentions: item.mentions } : {}),
|
|
5094
|
+
},
|
|
5095
|
+
]
|
|
5096
|
+
: [],
|
|
5097
|
+
});
|
|
5098
|
+
}
|
|
5099
|
+
sendCachedCommands(ws, sessionId, session) {
|
|
5100
|
+
// Restore command metadata when the original init/supported_commands event
|
|
5101
|
+
// has fallen out of the bounded in-memory history.
|
|
5102
|
+
const cached = this.sessionManager.getCachedCommands(session.projectPath);
|
|
5103
|
+
if (!cached ||
|
|
5104
|
+
(cached.slashCommands.length === 0 &&
|
|
5105
|
+
cached.skills.length === 0 &&
|
|
5106
|
+
cached.apps.length === 0 &&
|
|
5107
|
+
cached.plugins.length === 0)) {
|
|
5108
|
+
return;
|
|
5109
|
+
}
|
|
5110
|
+
this.send(ws, {
|
|
5111
|
+
type: "system",
|
|
5112
|
+
subtype: "supported_commands",
|
|
5113
|
+
sessionId,
|
|
5114
|
+
slashCommands: cached.slashCommands,
|
|
5115
|
+
skills: cached.skills,
|
|
5116
|
+
...(cached.skillMetadata ? { skillMetadata: cached.skillMetadata } : {}),
|
|
5117
|
+
apps: cached.apps,
|
|
5118
|
+
...(cached.appMetadata ? { appMetadata: cached.appMetadata } : {}),
|
|
5119
|
+
plugins: cached.plugins,
|
|
5120
|
+
...(cached.pluginMetadata ? { pluginMetadata: cached.pluginMetadata } : {}),
|
|
5121
|
+
});
|
|
5122
|
+
}
|
|
4773
5123
|
sendConversationQueue(ws, msg) {
|
|
4774
5124
|
if (!this.shouldSendToClient(ws, msg))
|
|
4775
5125
|
return;
|