@ccpocket/bridge 1.62.1 → 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 +12 -0
- package/dist/codex-process.js +41 -4
- package/dist/codex-process.js.map +1 -1
- package/dist/parser.d.ts +7 -0
- package/dist/parser.js +9 -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 +548 -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",
|
|
@@ -213,6 +218,14 @@ function normalizeCodexPermissionsMode(value) {
|
|
|
213
218
|
return undefined;
|
|
214
219
|
}
|
|
215
220
|
}
|
|
221
|
+
function sanitizeCodexModel(model) {
|
|
222
|
+
if (typeof model !== "string")
|
|
223
|
+
return undefined;
|
|
224
|
+
const normalized = model.trim();
|
|
225
|
+
if (!normalized || normalized === "codex")
|
|
226
|
+
return undefined;
|
|
227
|
+
return normalized;
|
|
228
|
+
}
|
|
216
229
|
function codexSettingsFromPermissionsMode(mode) {
|
|
217
230
|
switch (mode) {
|
|
218
231
|
case "default":
|
|
@@ -841,37 +854,8 @@ export class BridgeWebSocketServer {
|
|
|
841
854
|
pastMessages.push(raw);
|
|
842
855
|
continue;
|
|
843
856
|
}
|
|
844
|
-
const paths = new Set();
|
|
845
|
-
if (Array.isArray(msg.imagePaths)) {
|
|
846
|
-
for (const path of msg.imagePaths) {
|
|
847
|
-
if (typeof path === "string" && path.length > 0)
|
|
848
|
-
paths.add(path);
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
857
|
const content = typeof msg.content === "string" ? msg.content : "";
|
|
852
|
-
|
|
853
|
-
for (const path of this.imageStore.extractImagePaths(content)) {
|
|
854
|
-
paths.add(path);
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
const images = this.imageStore && paths.size > 0
|
|
858
|
-
? await this.imageStore.registerImages([...paths], session.projectPath)
|
|
859
|
-
: [];
|
|
860
|
-
if (this.imageStore && Array.isArray(msg.imageBase64)) {
|
|
861
|
-
for (const image of msg.imageBase64) {
|
|
862
|
-
const rawImage = image;
|
|
863
|
-
if (typeof rawImage.data !== "string"
|
|
864
|
-
|| typeof rawImage.mimeType !== "string") {
|
|
865
|
-
continue;
|
|
866
|
-
}
|
|
867
|
-
const ref = this.imageStore.registerFromBase64(rawImage.data, rawImage.mimeType);
|
|
868
|
-
if (ref)
|
|
869
|
-
images.push(ref);
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
const existingImages = Array.isArray(msg.images)
|
|
873
|
-
? msg.images
|
|
874
|
-
: [];
|
|
858
|
+
const images = await this.registerPastToolResultImages(session, msg);
|
|
875
859
|
pastMessages.push({
|
|
876
860
|
role: "tool_result",
|
|
877
861
|
toolUseId: typeof msg.toolUseId === "string"
|
|
@@ -879,13 +863,308 @@ export class BridgeWebSocketServer {
|
|
|
879
863
|
: `past-tool-result-${pastMessages.length}`,
|
|
880
864
|
content,
|
|
881
865
|
...(typeof msg.toolName === "string" ? { toolName: msg.toolName } : {}),
|
|
882
|
-
...(
|
|
883
|
-
? { images: [...existingImages, ...images] }
|
|
884
|
-
: {}),
|
|
866
|
+
...(images.length > 0 ? { images } : {}),
|
|
885
867
|
});
|
|
886
868
|
}
|
|
887
869
|
return { pastMessages, historyMessages };
|
|
888
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
|
+
}
|
|
889
1168
|
async registerPastUserMessageImages(session, msg) {
|
|
890
1169
|
if (!this.imageStore)
|
|
891
1170
|
return [];
|
|
@@ -893,6 +1172,12 @@ export class BridgeWebSocketServer {
|
|
|
893
1172
|
? msg.images
|
|
894
1173
|
: [];
|
|
895
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
|
+
}
|
|
896
1181
|
if (Array.isArray(msg.imageBase64)) {
|
|
897
1182
|
for (const image of msg.imageBase64) {
|
|
898
1183
|
const rawImage = image;
|
|
@@ -924,6 +1209,43 @@ export class BridgeWebSocketServer {
|
|
|
924
1209
|
}
|
|
925
1210
|
return refs;
|
|
926
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
|
+
}
|
|
927
1249
|
async getCodexThreadHistoryFromRpc(threadId, projectPath) {
|
|
928
1250
|
const activeProcess = this.getActiveCodexProcess();
|
|
929
1251
|
const process = activeProcess ?? (await this.createStandaloneCodexProcess(projectPath));
|
|
@@ -932,23 +1254,27 @@ export class BridgeWebSocketServer {
|
|
|
932
1254
|
const thread = await process.readThread(threadId, true);
|
|
933
1255
|
return codexThreadToSessionHistory(thread);
|
|
934
1256
|
}
|
|
1257
|
+
catch (err) {
|
|
1258
|
+
if (this.isCodexThreadNotMaterializedError(err))
|
|
1259
|
+
return [];
|
|
1260
|
+
throw err;
|
|
1261
|
+
}
|
|
935
1262
|
finally {
|
|
936
1263
|
if (isStandalone) {
|
|
937
1264
|
process.stop();
|
|
938
1265
|
}
|
|
939
1266
|
}
|
|
940
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
|
+
}
|
|
941
1273
|
async getCodexThreadHistory(threadId, projectPath) {
|
|
942
1274
|
if (!this.getActiveCodexProcess() && process.env.NODE_ENV === "test") {
|
|
943
1275
|
return getCodexSessionHistory(threadId);
|
|
944
1276
|
}
|
|
945
|
-
|
|
946
|
-
return await this.getCodexThreadHistoryFromRpc(threadId, projectPath);
|
|
947
|
-
}
|
|
948
|
-
catch (err) {
|
|
949
|
-
console.warn(`[ws] thread/read failed for ${threadId}; falling back to JSONL: ${err instanceof Error ? err.message : String(err)}`);
|
|
950
|
-
return getCodexSessionHistory(threadId);
|
|
951
|
-
}
|
|
1277
|
+
return this.getCodexThreadHistoryFromRpc(threadId, projectPath);
|
|
952
1278
|
}
|
|
953
1279
|
codexHistoryFromThreadOrFallback(params) {
|
|
954
1280
|
const messages = codexThreadToSessionHistory(params.thread);
|
|
@@ -1311,7 +1637,7 @@ export class BridgeWebSocketServer {
|
|
|
1311
1637
|
}
|
|
1312
1638
|
if (clientMessageId &&
|
|
1313
1639
|
baseSeq !== undefined &&
|
|
1314
|
-
this.hasInputConflictSince(session
|
|
1640
|
+
this.hasInputConflictSince(session, baseSeq)) {
|
|
1315
1641
|
this.send(ws, {
|
|
1316
1642
|
type: "input_rejected",
|
|
1317
1643
|
sessionId: session.id,
|
|
@@ -1917,6 +2243,65 @@ export class BridgeWebSocketServer {
|
|
|
1917
2243
|
});
|
|
1918
2244
|
break;
|
|
1919
2245
|
}
|
|
2246
|
+
case "set_codex_model": {
|
|
2247
|
+
const session = this.resolveSession(msg.sessionId);
|
|
2248
|
+
if (!session) {
|
|
2249
|
+
this.send(ws, { type: "error", message: "No active session." });
|
|
2250
|
+
return;
|
|
2251
|
+
}
|
|
2252
|
+
if (session.provider !== "codex") {
|
|
2253
|
+
this.send(ws, {
|
|
2254
|
+
type: "error",
|
|
2255
|
+
message: "Model switching is only supported for Codex sessions.",
|
|
2256
|
+
errorCode: "set_codex_model_unsupported",
|
|
2257
|
+
});
|
|
2258
|
+
break;
|
|
2259
|
+
}
|
|
2260
|
+
const model = sanitizeCodexModel(msg.model);
|
|
2261
|
+
if (!model) {
|
|
2262
|
+
this.send(ws, {
|
|
2263
|
+
type: "error",
|
|
2264
|
+
message: `Invalid Codex model: ${msg.model}`,
|
|
2265
|
+
errorCode: "set_codex_model_rejected",
|
|
2266
|
+
});
|
|
2267
|
+
break;
|
|
2268
|
+
}
|
|
2269
|
+
const modelReasoningEffort = msg.modelReasoningEffort;
|
|
2270
|
+
const currentModel = sanitizeCodexModel(session.codexSettings?.model);
|
|
2271
|
+
const currentEffort = session.codexSettings?.modelReasoningEffort;
|
|
2272
|
+
if (model === currentModel && modelReasoningEffort === currentEffort) {
|
|
2273
|
+
break;
|
|
2274
|
+
}
|
|
2275
|
+
const process = session.process;
|
|
2276
|
+
process.setModel(model, modelReasoningEffort);
|
|
2277
|
+
session.codexSettings = {
|
|
2278
|
+
...(session.codexSettings ?? {}),
|
|
2279
|
+
model,
|
|
2280
|
+
...(modelReasoningEffort !== undefined
|
|
2281
|
+
? { modelReasoningEffort }
|
|
2282
|
+
: {}),
|
|
2283
|
+
};
|
|
2284
|
+
session.lastActivityAt = new Date();
|
|
2285
|
+
this.broadcast({
|
|
2286
|
+
type: "system",
|
|
2287
|
+
subtype: "set_codex_model",
|
|
2288
|
+
sessionId: session.id,
|
|
2289
|
+
provider: "codex",
|
|
2290
|
+
model,
|
|
2291
|
+
...(modelReasoningEffort !== undefined
|
|
2292
|
+
? { modelReasoningEffort }
|
|
2293
|
+
: {}),
|
|
2294
|
+
});
|
|
2295
|
+
this.broadcastSessionList();
|
|
2296
|
+
this.recordDebugEvent(session.id, {
|
|
2297
|
+
direction: "internal",
|
|
2298
|
+
channel: "bridge",
|
|
2299
|
+
type: "codex_model_changed",
|
|
2300
|
+
detail: `model=${model} effort=${modelReasoningEffort ?? ""}`,
|
|
2301
|
+
});
|
|
2302
|
+
console.log(`[ws] set_codex_model(codex): model=${model} effort=${modelReasoningEffort ?? ""}`);
|
|
2303
|
+
break;
|
|
2304
|
+
}
|
|
1920
2305
|
case "set_sandbox_mode": {
|
|
1921
2306
|
if (this.failSetSandboxMode) {
|
|
1922
2307
|
this.send(ws, {
|
|
@@ -2252,6 +2637,12 @@ export class BridgeWebSocketServer {
|
|
|
2252
2637
|
case "get_history": {
|
|
2253
2638
|
const session = this.sessionManager.get(msg.sessionId);
|
|
2254
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
|
+
}
|
|
2255
2646
|
const splitPastHistory = session.pastMessages && session.pastMessages.length > 0
|
|
2256
2647
|
? await this.splitPastHistoryMessages(session)
|
|
2257
2648
|
: { pastMessages: [], historyMessages: [] };
|
|
@@ -2275,56 +2666,9 @@ export class BridgeWebSocketServer {
|
|
|
2275
2666
|
sessionId: msg.sessionId,
|
|
2276
2667
|
});
|
|
2277
2668
|
if (session.provider === "codex") {
|
|
2278
|
-
|
|
2279
|
-
this.sendConversationQueue(ws, {
|
|
2280
|
-
type: "conversation_queue",
|
|
2281
|
-
sessionId: msg.sessionId,
|
|
2282
|
-
limit: 1,
|
|
2283
|
-
items: item
|
|
2284
|
-
? [
|
|
2285
|
-
{
|
|
2286
|
-
itemId: item.itemId,
|
|
2287
|
-
text: item.text,
|
|
2288
|
-
createdAt: item.createdAt,
|
|
2289
|
-
...(item.updatedAt ? { updatedAt: item.updatedAt } : {}),
|
|
2290
|
-
...(item.imageCount
|
|
2291
|
-
? { imageCount: item.imageCount }
|
|
2292
|
-
: {}),
|
|
2293
|
-
...(item.skills?.length ? { skills: item.skills } : {}),
|
|
2294
|
-
...(item.mentions?.length
|
|
2295
|
-
? { mentions: item.mentions }
|
|
2296
|
-
: {}),
|
|
2297
|
-
},
|
|
2298
|
-
]
|
|
2299
|
-
: [],
|
|
2300
|
-
});
|
|
2301
|
-
}
|
|
2302
|
-
// Send cached slash commands so the client can restore them even when
|
|
2303
|
-
// the original init/supported_commands message was evicted from the
|
|
2304
|
-
// in-memory history (MAX_HISTORY_PER_SESSION overflow).
|
|
2305
|
-
const cached = this.sessionManager.getCachedCommands(session.projectPath);
|
|
2306
|
-
if (cached &&
|
|
2307
|
-
(cached.slashCommands.length > 0 ||
|
|
2308
|
-
cached.skills.length > 0 ||
|
|
2309
|
-
cached.apps.length > 0 ||
|
|
2310
|
-
cached.plugins.length > 0)) {
|
|
2311
|
-
this.send(ws, {
|
|
2312
|
-
type: "system",
|
|
2313
|
-
subtype: "supported_commands",
|
|
2314
|
-
sessionId: msg.sessionId,
|
|
2315
|
-
slashCommands: cached.slashCommands,
|
|
2316
|
-
skills: cached.skills,
|
|
2317
|
-
...(cached.skillMetadata
|
|
2318
|
-
? { skillMetadata: cached.skillMetadata }
|
|
2319
|
-
: {}),
|
|
2320
|
-
apps: cached.apps,
|
|
2321
|
-
...(cached.appMetadata ? { appMetadata: cached.appMetadata } : {}),
|
|
2322
|
-
plugins: cached.plugins,
|
|
2323
|
-
...(cached.pluginMetadata
|
|
2324
|
-
? { pluginMetadata: cached.pluginMetadata }
|
|
2325
|
-
: {}),
|
|
2326
|
-
});
|
|
2669
|
+
this.sendCodexQueueState(ws, msg.sessionId, session);
|
|
2327
2670
|
}
|
|
2671
|
+
this.sendCachedCommands(ws, msg.sessionId, session);
|
|
2328
2672
|
}
|
|
2329
2673
|
else {
|
|
2330
2674
|
this.send(ws, {
|
|
@@ -2336,8 +2680,40 @@ export class BridgeWebSocketServer {
|
|
|
2336
2680
|
}
|
|
2337
2681
|
case "get_history_delta": {
|
|
2338
2682
|
const session = this.sessionManager.get(msg.sessionId);
|
|
2339
|
-
|
|
2340
|
-
|
|
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
|
+
}
|
|
2341
2717
|
if (session.pastMessages && session.pastMessages.length > 0) {
|
|
2342
2718
|
const splitPastHistory = await this.splitPastHistoryMessages(session);
|
|
2343
2719
|
if (splitPastHistory.pastMessages.length > 0) {
|
|
@@ -2360,31 +2736,6 @@ export class BridgeWebSocketServer {
|
|
|
2360
2736
|
status: session.status,
|
|
2361
2737
|
...(result.kind === "snapshot" ? { reason: result.reason } : {}),
|
|
2362
2738
|
});
|
|
2363
|
-
if (session.provider === "codex") {
|
|
2364
|
-
const item = session.codexQueuedInput;
|
|
2365
|
-
this.sendConversationQueue(ws, {
|
|
2366
|
-
type: "conversation_queue",
|
|
2367
|
-
sessionId: msg.sessionId,
|
|
2368
|
-
limit: 1,
|
|
2369
|
-
items: item
|
|
2370
|
-
? [
|
|
2371
|
-
{
|
|
2372
|
-
itemId: item.itemId,
|
|
2373
|
-
text: item.text,
|
|
2374
|
-
createdAt: item.createdAt,
|
|
2375
|
-
...(item.updatedAt ? { updatedAt: item.updatedAt } : {}),
|
|
2376
|
-
...(item.imageCount
|
|
2377
|
-
? { imageCount: item.imageCount }
|
|
2378
|
-
: {}),
|
|
2379
|
-
...(item.skills?.length ? { skills: item.skills } : {}),
|
|
2380
|
-
...(item.mentions?.length
|
|
2381
|
-
? { mentions: item.mentions }
|
|
2382
|
-
: {}),
|
|
2383
|
-
},
|
|
2384
|
-
]
|
|
2385
|
-
: [],
|
|
2386
|
-
});
|
|
2387
|
-
}
|
|
2388
2739
|
}
|
|
2389
2740
|
else {
|
|
2390
2741
|
this.send(ws, {
|
|
@@ -4461,21 +4812,37 @@ export class BridgeWebSocketServer {
|
|
|
4461
4812
|
(await this.createStandaloneCodexProcess(msg.projectPath));
|
|
4462
4813
|
const isStandalone = process !== this.getActiveCodexProcess();
|
|
4463
4814
|
try {
|
|
4464
|
-
const result = await process.listThreads({
|
|
4465
|
-
limit: limit + offset,
|
|
4466
|
-
cwd: msg.projectPath,
|
|
4467
|
-
searchTerm: msg.searchQuery,
|
|
4468
|
-
});
|
|
4469
4815
|
const archivedIds = this.archiveStore.archivedIds();
|
|
4470
|
-
const visibleThreads =
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
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)));
|
|
4476
4843
|
return {
|
|
4477
4844
|
sessions,
|
|
4478
|
-
hasMore:
|
|
4845
|
+
hasMore: hasServerMore || visibleThreads.length > offset + limit,
|
|
4479
4846
|
};
|
|
4480
4847
|
}
|
|
4481
4848
|
finally {
|
|
@@ -4683,8 +5050,13 @@ export class BridgeWebSocketServer {
|
|
|
4683
5050
|
return true;
|
|
4684
5051
|
return (this.clientSupportedServerMessages.get(ws)?.has(type) ?? false);
|
|
4685
5052
|
}
|
|
4686
|
-
hasInputConflictSince(
|
|
4687
|
-
|
|
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);
|
|
4688
5060
|
if (!delta)
|
|
4689
5061
|
return true;
|
|
4690
5062
|
if (delta.kind === "snapshot")
|
|
@@ -4703,6 +5075,51 @@ export class BridgeWebSocketServer {
|
|
|
4703
5075
|
return false;
|
|
4704
5076
|
});
|
|
4705
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
|
+
}
|
|
4706
5123
|
sendConversationQueue(ws, msg) {
|
|
4707
5124
|
if (!this.shouldSendToClient(ws, msg))
|
|
4708
5125
|
return;
|