codexapp 0.1.51 → 0.1.52
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/README.md +17 -0
- package/dist/assets/index-B6t8lUoD.css +1 -0
- package/dist/assets/index-CGldz3Gt.js +1428 -0
- package/dist/index.html +2 -2
- package/dist-cli/index.js +521 -118
- package/dist-cli/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/assets/index-8tP34EwU.js +0 -1429
- package/dist/assets/index-B8I4NE8D.css +0 -1
package/dist-cli/index.js
CHANGED
|
@@ -30,7 +30,7 @@ import { request as httpRequest } from "http";
|
|
|
30
30
|
import { request as httpsRequest } from "https";
|
|
31
31
|
import { homedir as homedir3 } from "os";
|
|
32
32
|
import { tmpdir as tmpdir2 } from "os";
|
|
33
|
-
import { basename as
|
|
33
|
+
import { basename as basename3, isAbsolute, join as join3, resolve } from "path";
|
|
34
34
|
import { createInterface } from "readline";
|
|
35
35
|
import { writeFile as writeFile2 } from "fs/promises";
|
|
36
36
|
|
|
@@ -70,6 +70,17 @@ function getCodexHomeDir() {
|
|
|
70
70
|
function getSkillsInstallDir() {
|
|
71
71
|
return join(getCodexHomeDir(), "skills");
|
|
72
72
|
}
|
|
73
|
+
function resolveSkillInstallerScriptPath() {
|
|
74
|
+
const candidates = [
|
|
75
|
+
join(getCodexHomeDir(), "skills", ".system", "skill-installer", "scripts", "install-skill-from-github.py"),
|
|
76
|
+
join(homedir(), ".codex", "skills", ".system", "skill-installer", "scripts", "install-skill-from-github.py"),
|
|
77
|
+
join(homedir(), ".cursor", "skills", ".system", "skill-installer", "scripts", "install-skill-from-github.py")
|
|
78
|
+
];
|
|
79
|
+
for (const candidate of candidates) {
|
|
80
|
+
if (existsSync(candidate)) return candidate;
|
|
81
|
+
}
|
|
82
|
+
throw new Error(`Skill installer script not found. Checked: ${candidates.join(", ")}`);
|
|
83
|
+
}
|
|
73
84
|
async function runCommand(command, args, options = {}) {
|
|
74
85
|
await new Promise((resolve3, reject) => {
|
|
75
86
|
const proc = spawn(command, args, {
|
|
@@ -269,6 +280,23 @@ async function scanInstalledSkillsFromDisk() {
|
|
|
269
280
|
}
|
|
270
281
|
return map;
|
|
271
282
|
}
|
|
283
|
+
function extractSkillDescriptionFromMarkdown(markdown) {
|
|
284
|
+
const lines = markdown.split(/\r?\n/);
|
|
285
|
+
let inCodeFence = false;
|
|
286
|
+
for (const rawLine of lines) {
|
|
287
|
+
const line = rawLine.trim();
|
|
288
|
+
if (line.startsWith("```")) {
|
|
289
|
+
inCodeFence = !inCodeFence;
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (inCodeFence || line.length === 0) continue;
|
|
293
|
+
if (line.startsWith("#")) continue;
|
|
294
|
+
if (line.startsWith(">")) continue;
|
|
295
|
+
if (line.startsWith("- ") || line.startsWith("* ")) continue;
|
|
296
|
+
return line;
|
|
297
|
+
}
|
|
298
|
+
return "";
|
|
299
|
+
}
|
|
272
300
|
function getSkillsSyncStatePath() {
|
|
273
301
|
return join(getCodexHomeDir(), "skills-sync.json");
|
|
274
302
|
}
|
|
@@ -994,7 +1022,7 @@ async function handleSkillsRoutes(req, res, url, context) {
|
|
|
994
1022
|
}
|
|
995
1023
|
const localDir = await detectUserSkillsDir(appServer);
|
|
996
1024
|
await pullInstalledSkillsFolderFromRepo(state.githubToken, state.repoOwner, state.repoName);
|
|
997
|
-
const installerScript =
|
|
1025
|
+
const installerScript = resolveSkillInstallerScriptPath();
|
|
998
1026
|
const localSkills = await scanInstalledSkillsFromDisk();
|
|
999
1027
|
for (const skill of remote) {
|
|
1000
1028
|
const owner = skill.owner || uniqueOwnerByName.get(skill.name) || "";
|
|
@@ -1041,15 +1069,29 @@ async function handleSkillsRoutes(req, res, url, context) {
|
|
|
1041
1069
|
try {
|
|
1042
1070
|
const owner = url.searchParams.get("owner") || "";
|
|
1043
1071
|
const name = url.searchParams.get("name") || "";
|
|
1072
|
+
const installed = url.searchParams.get("installed") === "true";
|
|
1073
|
+
const skillPath = url.searchParams.get("path") || "";
|
|
1044
1074
|
if (!owner || !name) {
|
|
1045
1075
|
setJson(res, 400, { error: "Missing owner or name" });
|
|
1046
1076
|
return true;
|
|
1047
1077
|
}
|
|
1078
|
+
if (installed) {
|
|
1079
|
+
const installedMap = await scanInstalledSkillsFromDisk();
|
|
1080
|
+
const installedInfo = installedMap.get(name);
|
|
1081
|
+
const localSkillPath = installedInfo?.path || (skillPath ? skillPath.endsWith("/SKILL.md") ? skillPath : `${skillPath}/SKILL.md` : "");
|
|
1082
|
+
if (localSkillPath) {
|
|
1083
|
+
const content2 = await readFile(localSkillPath, "utf8");
|
|
1084
|
+
const description2 = extractSkillDescriptionFromMarkdown(content2);
|
|
1085
|
+
setJson(res, 200, { content: content2, description: description2, source: "local" });
|
|
1086
|
+
return true;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1048
1089
|
const rawUrl = `https://raw.githubusercontent.com/${HUB_SKILLS_OWNER}/${HUB_SKILLS_REPO}/main/skills/${owner}/${name}/SKILL.md`;
|
|
1049
1090
|
const resp = await fetch(rawUrl);
|
|
1050
1091
|
if (!resp.ok) throw new Error(`Failed to fetch SKILL.md: ${resp.status}`);
|
|
1051
1092
|
const content = await resp.text();
|
|
1052
|
-
|
|
1093
|
+
const description = extractSkillDescriptionFromMarkdown(content);
|
|
1094
|
+
setJson(res, 200, { content, description, source: "remote" });
|
|
1053
1095
|
} catch (error) {
|
|
1054
1096
|
setJson(res, 502, { error: getErrorMessage(error, "Failed to fetch SKILL.md") });
|
|
1055
1097
|
}
|
|
@@ -1064,7 +1106,7 @@ async function handleSkillsRoutes(req, res, url, context) {
|
|
|
1064
1106
|
setJson(res, 400, { error: "Missing owner or name" });
|
|
1065
1107
|
return true;
|
|
1066
1108
|
}
|
|
1067
|
-
const installerScript =
|
|
1109
|
+
const installerScript = resolveSkillInstallerScriptPath();
|
|
1068
1110
|
const installDest = await detectUserSkillsDir(appServer);
|
|
1069
1111
|
await runCommand("python3", [
|
|
1070
1112
|
installerScript,
|
|
@@ -1120,11 +1162,377 @@ async function handleSkillsRoutes(req, res, url, context) {
|
|
|
1120
1162
|
return false;
|
|
1121
1163
|
}
|
|
1122
1164
|
|
|
1165
|
+
// src/server/telegramThreadBridge.ts
|
|
1166
|
+
import { basename } from "path";
|
|
1167
|
+
function asRecord2(value) {
|
|
1168
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1169
|
+
}
|
|
1170
|
+
function getErrorMessage2(payload, fallback) {
|
|
1171
|
+
if (payload instanceof Error && payload.message.trim().length > 0) {
|
|
1172
|
+
return payload.message;
|
|
1173
|
+
}
|
|
1174
|
+
const record = asRecord2(payload);
|
|
1175
|
+
if (!record) return fallback;
|
|
1176
|
+
const error = record.error;
|
|
1177
|
+
if (typeof error === "string" && error.length > 0) return error;
|
|
1178
|
+
const nestedError = asRecord2(error);
|
|
1179
|
+
if (nestedError && typeof nestedError.message === "string" && nestedError.message.length > 0) {
|
|
1180
|
+
return nestedError.message;
|
|
1181
|
+
}
|
|
1182
|
+
return fallback;
|
|
1183
|
+
}
|
|
1184
|
+
var TelegramThreadBridge = class {
|
|
1185
|
+
constructor(appServer) {
|
|
1186
|
+
this.threadIdByChatId = /* @__PURE__ */ new Map();
|
|
1187
|
+
this.chatIdsByThreadId = /* @__PURE__ */ new Map();
|
|
1188
|
+
this.lastForwardedTurnByThreadId = /* @__PURE__ */ new Map();
|
|
1189
|
+
this.active = false;
|
|
1190
|
+
this.pollingTask = null;
|
|
1191
|
+
this.nextUpdateOffset = 0;
|
|
1192
|
+
this.lastError = "";
|
|
1193
|
+
this.appServer = appServer;
|
|
1194
|
+
this.token = process.env.TELEGRAM_BOT_TOKEN?.trim() ?? "";
|
|
1195
|
+
this.defaultCwd = process.env.TELEGRAM_DEFAULT_CWD?.trim() ?? process.cwd();
|
|
1196
|
+
}
|
|
1197
|
+
start() {
|
|
1198
|
+
if (!this.token || this.active) return;
|
|
1199
|
+
this.active = true;
|
|
1200
|
+
void this.notifyOnlineForKnownChats().catch(() => {
|
|
1201
|
+
});
|
|
1202
|
+
this.pollingTask = this.pollLoop();
|
|
1203
|
+
this.appServer.onNotification((notification) => {
|
|
1204
|
+
void this.handleNotification(notification).catch(() => {
|
|
1205
|
+
});
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
stop() {
|
|
1209
|
+
this.active = false;
|
|
1210
|
+
}
|
|
1211
|
+
async pollLoop() {
|
|
1212
|
+
while (this.active) {
|
|
1213
|
+
try {
|
|
1214
|
+
const updates = await this.getUpdates();
|
|
1215
|
+
this.lastError = "";
|
|
1216
|
+
for (const update of updates) {
|
|
1217
|
+
const updateId = typeof update.update_id === "number" ? update.update_id : -1;
|
|
1218
|
+
if (updateId >= 0) {
|
|
1219
|
+
this.nextUpdateOffset = Math.max(this.nextUpdateOffset, updateId + 1);
|
|
1220
|
+
}
|
|
1221
|
+
await this.handleIncomingUpdate(update);
|
|
1222
|
+
}
|
|
1223
|
+
} catch (error) {
|
|
1224
|
+
this.lastError = getErrorMessage2(error, "Telegram polling failed");
|
|
1225
|
+
await new Promise((resolve3) => setTimeout(resolve3, 1500));
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
async getUpdates() {
|
|
1230
|
+
if (!this.token) {
|
|
1231
|
+
throw new Error("Telegram bot token is not configured");
|
|
1232
|
+
}
|
|
1233
|
+
const response = await fetch(this.apiUrl("getUpdates"), {
|
|
1234
|
+
method: "POST",
|
|
1235
|
+
headers: { "Content-Type": "application/json" },
|
|
1236
|
+
body: JSON.stringify({
|
|
1237
|
+
timeout: 45,
|
|
1238
|
+
offset: this.nextUpdateOffset,
|
|
1239
|
+
allowed_updates: ["message", "callback_query"]
|
|
1240
|
+
})
|
|
1241
|
+
});
|
|
1242
|
+
const payload = asRecord2(await response.json());
|
|
1243
|
+
const result = Array.isArray(payload?.result) ? payload.result : [];
|
|
1244
|
+
return result;
|
|
1245
|
+
}
|
|
1246
|
+
apiUrl(method) {
|
|
1247
|
+
return `https://api.telegram.org/bot${this.token}/${method}`;
|
|
1248
|
+
}
|
|
1249
|
+
configureToken(token) {
|
|
1250
|
+
const normalizedToken = token.trim();
|
|
1251
|
+
if (!normalizedToken) {
|
|
1252
|
+
throw new Error("Telegram bot token is required");
|
|
1253
|
+
}
|
|
1254
|
+
this.token = normalizedToken;
|
|
1255
|
+
}
|
|
1256
|
+
getStatus() {
|
|
1257
|
+
return {
|
|
1258
|
+
configured: this.token.length > 0,
|
|
1259
|
+
active: this.active,
|
|
1260
|
+
mappedChats: this.threadIdByChatId.size,
|
|
1261
|
+
mappedThreads: this.chatIdsByThreadId.size,
|
|
1262
|
+
lastError: this.lastError
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
connectThread(threadId, chatId, token) {
|
|
1266
|
+
const normalizedThreadId = threadId.trim();
|
|
1267
|
+
if (!normalizedThreadId) {
|
|
1268
|
+
throw new Error("threadId is required");
|
|
1269
|
+
}
|
|
1270
|
+
if (!Number.isFinite(chatId)) {
|
|
1271
|
+
throw new Error("chatId must be a number");
|
|
1272
|
+
}
|
|
1273
|
+
if (typeof token === "string" && token.trim().length > 0) {
|
|
1274
|
+
this.configureToken(token);
|
|
1275
|
+
}
|
|
1276
|
+
if (!this.token) {
|
|
1277
|
+
throw new Error("Telegram bot token is not configured");
|
|
1278
|
+
}
|
|
1279
|
+
this.bindChatToThread(chatId, normalizedThreadId);
|
|
1280
|
+
this.start();
|
|
1281
|
+
void this.sendOnlineMessage(chatId).catch(() => {
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
async sendTelegramMessage(chatId, text, options = {}) {
|
|
1285
|
+
const message = text.trim();
|
|
1286
|
+
if (!message) return;
|
|
1287
|
+
const payload = { chat_id: chatId, text: message };
|
|
1288
|
+
if (options.replyMarkup) {
|
|
1289
|
+
payload.reply_markup = options.replyMarkup;
|
|
1290
|
+
}
|
|
1291
|
+
await fetch(this.apiUrl("sendMessage"), {
|
|
1292
|
+
method: "POST",
|
|
1293
|
+
headers: { "Content-Type": "application/json" },
|
|
1294
|
+
body: JSON.stringify(payload)
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
async sendOnlineMessage(chatId) {
|
|
1298
|
+
await this.sendTelegramMessage(chatId, "Codex thread bridge went online.");
|
|
1299
|
+
}
|
|
1300
|
+
async notifyOnlineForKnownChats() {
|
|
1301
|
+
const knownChatIds = Array.from(this.threadIdByChatId.keys());
|
|
1302
|
+
for (const chatId of knownChatIds) {
|
|
1303
|
+
await this.sendOnlineMessage(chatId);
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
async handleIncomingUpdate(update) {
|
|
1307
|
+
if (update.callback_query) {
|
|
1308
|
+
await this.handleCallbackQuery(update.callback_query);
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
const message = update.message;
|
|
1312
|
+
const chatId = message?.chat?.id;
|
|
1313
|
+
const text = message?.text?.trim();
|
|
1314
|
+
if (typeof chatId !== "number" || !text) return;
|
|
1315
|
+
if (text === "/start") {
|
|
1316
|
+
await this.sendThreadPicker(chatId);
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
if (text === "/newthread") {
|
|
1320
|
+
const threadId2 = await this.createThreadForChat(chatId);
|
|
1321
|
+
await this.sendTelegramMessage(chatId, `Mapped to new thread: ${threadId2}`);
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
const threadCommand = text.match(/^\/thread\s+(\S+)$/);
|
|
1325
|
+
if (threadCommand) {
|
|
1326
|
+
const threadId2 = threadCommand[1];
|
|
1327
|
+
this.bindChatToThread(chatId, threadId2);
|
|
1328
|
+
await this.sendTelegramMessage(chatId, `Mapped to thread: ${threadId2}`);
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
const threadId = await this.ensureThreadForChat(chatId);
|
|
1332
|
+
try {
|
|
1333
|
+
await this.appServer.rpc("turn/start", {
|
|
1334
|
+
threadId,
|
|
1335
|
+
input: [{ type: "text", text }]
|
|
1336
|
+
});
|
|
1337
|
+
} catch (error) {
|
|
1338
|
+
const message2 = getErrorMessage2(error, "Failed to forward message to thread");
|
|
1339
|
+
await this.sendTelegramMessage(chatId, `Forward failed: ${message2}`);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
async handleCallbackQuery(callbackQuery) {
|
|
1343
|
+
const callbackId = typeof callbackQuery.id === "string" ? callbackQuery.id : "";
|
|
1344
|
+
const data = typeof callbackQuery.data === "string" ? callbackQuery.data : "";
|
|
1345
|
+
const chatId = callbackQuery.message?.chat?.id;
|
|
1346
|
+
if (!callbackId) return;
|
|
1347
|
+
if (!data.startsWith("thread:") || typeof chatId !== "number") {
|
|
1348
|
+
await this.answerCallbackQuery(callbackId, "Invalid selection");
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
const threadId = data.slice("thread:".length).trim();
|
|
1352
|
+
if (!threadId) {
|
|
1353
|
+
await this.answerCallbackQuery(callbackId, "Invalid thread id");
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
this.bindChatToThread(chatId, threadId);
|
|
1357
|
+
await this.answerCallbackQuery(callbackId, "Thread connected");
|
|
1358
|
+
await this.sendTelegramMessage(chatId, `Connected to thread: ${threadId}`);
|
|
1359
|
+
const history = await this.readThreadHistorySummary(threadId);
|
|
1360
|
+
if (history) {
|
|
1361
|
+
await this.sendTelegramMessage(chatId, history);
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
async answerCallbackQuery(callbackQueryId, text) {
|
|
1365
|
+
await fetch(this.apiUrl("answerCallbackQuery"), {
|
|
1366
|
+
method: "POST",
|
|
1367
|
+
headers: { "Content-Type": "application/json" },
|
|
1368
|
+
body: JSON.stringify({
|
|
1369
|
+
callback_query_id: callbackQueryId,
|
|
1370
|
+
text
|
|
1371
|
+
})
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
async sendThreadPicker(chatId) {
|
|
1375
|
+
const threads = await this.listRecentThreads();
|
|
1376
|
+
if (threads.length === 0) {
|
|
1377
|
+
await this.sendTelegramMessage(chatId, "No threads found. Send /newthread to create one.");
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
const inlineKeyboard = threads.map((thread) => [
|
|
1381
|
+
{
|
|
1382
|
+
text: thread.title,
|
|
1383
|
+
callback_data: `thread:${thread.id}`
|
|
1384
|
+
}
|
|
1385
|
+
]);
|
|
1386
|
+
await this.sendTelegramMessage(chatId, "Select a thread to connect:", {
|
|
1387
|
+
replyMarkup: { inline_keyboard: inlineKeyboard }
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
async listRecentThreads() {
|
|
1391
|
+
const payload = asRecord2(await this.appServer.rpc("thread/list", {
|
|
1392
|
+
archived: false,
|
|
1393
|
+
limit: 20,
|
|
1394
|
+
sortKey: "updated_at"
|
|
1395
|
+
}));
|
|
1396
|
+
const rows = Array.isArray(payload?.data) ? payload.data : [];
|
|
1397
|
+
const threads = [];
|
|
1398
|
+
for (const row of rows) {
|
|
1399
|
+
const record = asRecord2(row);
|
|
1400
|
+
const id = typeof record?.id === "string" ? record.id.trim() : "";
|
|
1401
|
+
if (!id) continue;
|
|
1402
|
+
const name = typeof record?.name === "string" ? record.name.trim() : "";
|
|
1403
|
+
const preview = typeof record?.preview === "string" ? record.preview.trim() : "";
|
|
1404
|
+
const cwd = typeof record?.cwd === "string" ? record.cwd.trim() : "";
|
|
1405
|
+
const projectName = cwd ? basename(cwd) : "project";
|
|
1406
|
+
const threadTitle = (name || preview || id).replace(/\s+/g, " ").trim();
|
|
1407
|
+
const title = `${projectName}/${threadTitle}`.slice(0, 64);
|
|
1408
|
+
threads.push({ id, title });
|
|
1409
|
+
}
|
|
1410
|
+
return threads;
|
|
1411
|
+
}
|
|
1412
|
+
async createThreadForChat(chatId) {
|
|
1413
|
+
const response = asRecord2(await this.appServer.rpc("thread/start", { cwd: this.defaultCwd }));
|
|
1414
|
+
const thread = asRecord2(response?.thread);
|
|
1415
|
+
const threadId = typeof thread?.id === "string" ? thread.id : "";
|
|
1416
|
+
if (!threadId) {
|
|
1417
|
+
throw new Error("thread/start did not return thread id");
|
|
1418
|
+
}
|
|
1419
|
+
this.bindChatToThread(chatId, threadId);
|
|
1420
|
+
return threadId;
|
|
1421
|
+
}
|
|
1422
|
+
async ensureThreadForChat(chatId) {
|
|
1423
|
+
const existing = this.threadIdByChatId.get(chatId);
|
|
1424
|
+
if (existing) return existing;
|
|
1425
|
+
return this.createThreadForChat(chatId);
|
|
1426
|
+
}
|
|
1427
|
+
bindChatToThread(chatId, threadId) {
|
|
1428
|
+
const previousThreadId = this.threadIdByChatId.get(chatId);
|
|
1429
|
+
if (previousThreadId && previousThreadId !== threadId) {
|
|
1430
|
+
const previousSet = this.chatIdsByThreadId.get(previousThreadId);
|
|
1431
|
+
previousSet?.delete(chatId);
|
|
1432
|
+
if (previousSet && previousSet.size === 0) {
|
|
1433
|
+
this.chatIdsByThreadId.delete(previousThreadId);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
this.threadIdByChatId.set(chatId, threadId);
|
|
1437
|
+
const chatIds = this.chatIdsByThreadId.get(threadId) ?? /* @__PURE__ */ new Set();
|
|
1438
|
+
chatIds.add(chatId);
|
|
1439
|
+
this.chatIdsByThreadId.set(threadId, chatIds);
|
|
1440
|
+
}
|
|
1441
|
+
extractThreadId(notification) {
|
|
1442
|
+
const params = asRecord2(notification.params);
|
|
1443
|
+
if (!params) return "";
|
|
1444
|
+
const directThreadId = typeof params.threadId === "string" ? params.threadId : "";
|
|
1445
|
+
if (directThreadId) return directThreadId;
|
|
1446
|
+
const turn = asRecord2(params.turn);
|
|
1447
|
+
const turnThreadId = typeof turn?.threadId === "string" ? turn.threadId : "";
|
|
1448
|
+
return turnThreadId;
|
|
1449
|
+
}
|
|
1450
|
+
extractTurnId(notification) {
|
|
1451
|
+
const params = asRecord2(notification.params);
|
|
1452
|
+
if (!params) return "";
|
|
1453
|
+
const directTurnId = typeof params.turnId === "string" ? params.turnId : "";
|
|
1454
|
+
if (directTurnId) return directTurnId;
|
|
1455
|
+
const turn = asRecord2(params.turn);
|
|
1456
|
+
const turnId = typeof turn?.id === "string" ? turn.id : "";
|
|
1457
|
+
return turnId;
|
|
1458
|
+
}
|
|
1459
|
+
async handleNotification(notification) {
|
|
1460
|
+
if (notification.method !== "turn/completed") return;
|
|
1461
|
+
const threadId = this.extractThreadId(notification);
|
|
1462
|
+
if (!threadId) return;
|
|
1463
|
+
const chatIds = this.chatIdsByThreadId.get(threadId);
|
|
1464
|
+
if (!chatIds || chatIds.size === 0) return;
|
|
1465
|
+
const turnId = this.extractTurnId(notification);
|
|
1466
|
+
const lastForwardedTurnId = this.lastForwardedTurnByThreadId.get(threadId);
|
|
1467
|
+
if (turnId && lastForwardedTurnId === turnId) return;
|
|
1468
|
+
const assistantReply = await this.readLatestAssistantMessage(threadId);
|
|
1469
|
+
if (!assistantReply) return;
|
|
1470
|
+
for (const chatId of chatIds) {
|
|
1471
|
+
await this.sendTelegramMessage(chatId, assistantReply);
|
|
1472
|
+
}
|
|
1473
|
+
if (turnId) {
|
|
1474
|
+
this.lastForwardedTurnByThreadId.set(threadId, turnId);
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
async readLatestAssistantMessage(threadId) {
|
|
1478
|
+
const response = asRecord2(await this.appServer.rpc("thread/read", { threadId, includeTurns: true }));
|
|
1479
|
+
const thread = asRecord2(response?.thread);
|
|
1480
|
+
const turns = Array.isArray(thread?.turns) ? thread.turns : [];
|
|
1481
|
+
for (let turnIndex = turns.length - 1; turnIndex >= 0; turnIndex -= 1) {
|
|
1482
|
+
const turn = asRecord2(turns[turnIndex]);
|
|
1483
|
+
const items = Array.isArray(turn?.items) ? turn.items : [];
|
|
1484
|
+
for (let itemIndex = items.length - 1; itemIndex >= 0; itemIndex -= 1) {
|
|
1485
|
+
const item = asRecord2(items[itemIndex]);
|
|
1486
|
+
if (item?.type === "agentMessage") {
|
|
1487
|
+
const text = typeof item.text === "string" ? item.text.trim() : "";
|
|
1488
|
+
if (text) return text;
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
return "";
|
|
1493
|
+
}
|
|
1494
|
+
async readThreadHistorySummary(threadId) {
|
|
1495
|
+
const response = asRecord2(await this.appServer.rpc("thread/read", { threadId, includeTurns: true }));
|
|
1496
|
+
const thread = asRecord2(response?.thread);
|
|
1497
|
+
const turns = Array.isArray(thread?.turns) ? thread.turns : [];
|
|
1498
|
+
const historyRows = [];
|
|
1499
|
+
for (const turn of turns) {
|
|
1500
|
+
const turnRecord = asRecord2(turn);
|
|
1501
|
+
const items = Array.isArray(turnRecord?.items) ? turnRecord.items : [];
|
|
1502
|
+
for (const item of items) {
|
|
1503
|
+
const itemRecord = asRecord2(item);
|
|
1504
|
+
const type = typeof itemRecord?.type === "string" ? itemRecord.type : "";
|
|
1505
|
+
if (type === "userMessage") {
|
|
1506
|
+
const content = Array.isArray(itemRecord?.content) ? itemRecord.content : [];
|
|
1507
|
+
for (const block of content) {
|
|
1508
|
+
const blockRecord = asRecord2(block);
|
|
1509
|
+
if (blockRecord?.type === "text" && typeof blockRecord.text === "string" && blockRecord.text.trim()) {
|
|
1510
|
+
historyRows.push(`User: ${blockRecord.text.trim()}`);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
if (type === "agentMessage" && typeof itemRecord?.text === "string" && itemRecord.text.trim()) {
|
|
1515
|
+
historyRows.push(`Assistant: ${itemRecord.text.trim()}`);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
if (historyRows.length === 0) {
|
|
1520
|
+
return "Thread has no message history yet.";
|
|
1521
|
+
}
|
|
1522
|
+
const tail = historyRows.slice(-12).join("\n\n");
|
|
1523
|
+
const maxLen = 3800;
|
|
1524
|
+
const summary = tail.length > maxLen ? tail.slice(tail.length - maxLen) : tail;
|
|
1525
|
+
return `Recent history:
|
|
1526
|
+
|
|
1527
|
+
${summary}`;
|
|
1528
|
+
}
|
|
1529
|
+
};
|
|
1530
|
+
|
|
1123
1531
|
// src/utils/commandInvocation.ts
|
|
1124
1532
|
import { spawnSync } from "child_process";
|
|
1125
1533
|
import { existsSync as existsSync2 } from "fs";
|
|
1126
1534
|
import { homedir as homedir2 } from "os";
|
|
1127
|
-
import { basename, extname, join as join2 } from "path";
|
|
1535
|
+
import { basename as basename2, extname, join as join2 } from "path";
|
|
1128
1536
|
var WINDOWS_CMD_NAMES = /* @__PURE__ */ new Set(["codex", "npm", "npx"]);
|
|
1129
1537
|
function quoteCmdExeArg(value) {
|
|
1130
1538
|
const normalized = value.replace(/"/g, '""');
|
|
@@ -1138,7 +1546,7 @@ function needsCmdExeWrapper(command) {
|
|
|
1138
1546
|
return false;
|
|
1139
1547
|
}
|
|
1140
1548
|
const lowerCommand = command.toLowerCase();
|
|
1141
|
-
const baseName =
|
|
1549
|
+
const baseName = basename2(lowerCommand);
|
|
1142
1550
|
if (/\.(cmd|bat)$/i.test(baseName)) {
|
|
1143
1551
|
return true;
|
|
1144
1552
|
}
|
|
@@ -1199,18 +1607,18 @@ function resolveCodexCommand() {
|
|
|
1199
1607
|
}
|
|
1200
1608
|
|
|
1201
1609
|
// src/server/codexAppServerBridge.ts
|
|
1202
|
-
function
|
|
1610
|
+
function asRecord3(value) {
|
|
1203
1611
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1204
1612
|
}
|
|
1205
|
-
function
|
|
1613
|
+
function getErrorMessage3(payload, fallback) {
|
|
1206
1614
|
if (payload instanceof Error && payload.message.trim().length > 0) {
|
|
1207
1615
|
return payload.message;
|
|
1208
1616
|
}
|
|
1209
|
-
const record =
|
|
1617
|
+
const record = asRecord3(payload);
|
|
1210
1618
|
if (!record) return fallback;
|
|
1211
1619
|
const error = record.error;
|
|
1212
1620
|
if (typeof error === "string" && error.length > 0) return error;
|
|
1213
|
-
const nestedError =
|
|
1621
|
+
const nestedError = asRecord3(error);
|
|
1214
1622
|
if (nestedError && typeof nestedError.message === "string" && nestedError.message.length > 0) {
|
|
1215
1623
|
return nestedError.message;
|
|
1216
1624
|
}
|
|
@@ -1222,15 +1630,15 @@ function setJson2(res, statusCode, payload) {
|
|
|
1222
1630
|
res.end(JSON.stringify(payload));
|
|
1223
1631
|
}
|
|
1224
1632
|
function extractThreadMessageText(threadReadPayload) {
|
|
1225
|
-
const payload =
|
|
1226
|
-
const thread =
|
|
1633
|
+
const payload = asRecord3(threadReadPayload);
|
|
1634
|
+
const thread = asRecord3(payload?.thread);
|
|
1227
1635
|
const turns = Array.isArray(thread?.turns) ? thread.turns : [];
|
|
1228
1636
|
const parts = [];
|
|
1229
1637
|
for (const turn of turns) {
|
|
1230
|
-
const turnRecord =
|
|
1638
|
+
const turnRecord = asRecord3(turn);
|
|
1231
1639
|
const items = Array.isArray(turnRecord?.items) ? turnRecord.items : [];
|
|
1232
1640
|
for (const item of items) {
|
|
1233
|
-
const itemRecord =
|
|
1641
|
+
const itemRecord = asRecord3(item);
|
|
1234
1642
|
const type = typeof itemRecord?.type === "string" ? itemRecord.type : "";
|
|
1235
1643
|
if (type === "agentMessage" && typeof itemRecord?.text === "string" && itemRecord.text.trim().length > 0) {
|
|
1236
1644
|
parts.push(itemRecord.text.trim());
|
|
@@ -1239,7 +1647,7 @@ function extractThreadMessageText(threadReadPayload) {
|
|
|
1239
1647
|
if (type === "userMessage") {
|
|
1240
1648
|
const content = Array.isArray(itemRecord?.content) ? itemRecord.content : [];
|
|
1241
1649
|
for (const block of content) {
|
|
1242
|
-
const blockRecord =
|
|
1650
|
+
const blockRecord = asRecord3(block);
|
|
1243
1651
|
if (blockRecord?.type === "text" && typeof blockRecord.text === "string" && blockRecord.text.trim().length > 0) {
|
|
1244
1652
|
parts.push(blockRecord.text.trim());
|
|
1245
1653
|
}
|
|
@@ -1273,54 +1681,6 @@ function scoreFileCandidate(path, query) {
|
|
|
1273
1681
|
if (lowerPath.includes(lowerQuery)) return 4;
|
|
1274
1682
|
return 10;
|
|
1275
1683
|
}
|
|
1276
|
-
function decodeHtmlEntities(value) {
|
|
1277
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(///gi, "/");
|
|
1278
|
-
}
|
|
1279
|
-
function stripHtml(value) {
|
|
1280
|
-
return decodeHtmlEntities(value.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim());
|
|
1281
|
-
}
|
|
1282
|
-
function parseGithubTrendingHtml(html, limit) {
|
|
1283
|
-
const rows = html.match(/<article[\s\S]*?<\/article>/g) ?? [];
|
|
1284
|
-
const items = [];
|
|
1285
|
-
let seq = Date.now();
|
|
1286
|
-
for (const row of rows) {
|
|
1287
|
-
const repoBlockMatch = row.match(/<h2[\s\S]*?<\/h2>/);
|
|
1288
|
-
const hrefMatch = repoBlockMatch?.[0]?.match(/href="\/([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)"/);
|
|
1289
|
-
if (!hrefMatch) continue;
|
|
1290
|
-
const fullName = hrefMatch[1] ?? "";
|
|
1291
|
-
if (!fullName || items.some((item) => item.fullName === fullName)) continue;
|
|
1292
|
-
const descriptionMatch = row.match(/<p[^>]*class="[^"]*col-9[^"]*"[^>]*>([\s\S]*?)<\/p>/) ?? row.match(/<p[^>]*class="[^"]*color-fg-muted[^"]*"[^>]*>([\s\S]*?)<\/p>/) ?? row.match(/<p[^>]*>([\s\S]*?)<\/p>/);
|
|
1293
|
-
const languageMatch = row.match(/programmingLanguage[^>]*>\s*([\s\S]*?)\s*<\/span>/);
|
|
1294
|
-
const starsMatch = row.match(/href="\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/stargazers"[\s\S]*?>([\s\S]*?)<\/a>/);
|
|
1295
|
-
const starsText = stripHtml(starsMatch?.[1] ?? "").replace(/,/g, "");
|
|
1296
|
-
const stars = Number.parseInt(starsText, 10);
|
|
1297
|
-
items.push({
|
|
1298
|
-
id: seq,
|
|
1299
|
-
fullName,
|
|
1300
|
-
url: `https://github.com/${fullName}`,
|
|
1301
|
-
description: stripHtml(descriptionMatch?.[1] ?? ""),
|
|
1302
|
-
language: stripHtml(languageMatch?.[1] ?? ""),
|
|
1303
|
-
stars: Number.isFinite(stars) ? stars : 0
|
|
1304
|
-
});
|
|
1305
|
-
seq += 1;
|
|
1306
|
-
if (items.length >= limit) break;
|
|
1307
|
-
}
|
|
1308
|
-
return items;
|
|
1309
|
-
}
|
|
1310
|
-
async function fetchGithubTrending(since, limit) {
|
|
1311
|
-
const endpoint = `https://github.com/trending?since=${since}`;
|
|
1312
|
-
const response = await fetch(endpoint, {
|
|
1313
|
-
headers: {
|
|
1314
|
-
"User-Agent": "codex-web-local",
|
|
1315
|
-
Accept: "text/html"
|
|
1316
|
-
}
|
|
1317
|
-
});
|
|
1318
|
-
if (!response.ok) {
|
|
1319
|
-
throw new Error(`GitHub trending fetch failed (${response.status})`);
|
|
1320
|
-
}
|
|
1321
|
-
const html = await response.text();
|
|
1322
|
-
return parseGithubTrendingHtml(html, limit);
|
|
1323
|
-
}
|
|
1324
1684
|
async function listFilesWithRipgrep(cwd) {
|
|
1325
1685
|
return await new Promise((resolve3, reject) => {
|
|
1326
1686
|
const proc = spawn2("rg", ["--files", "--hidden", "-g", "!.git", "-g", "!node_modules"], {
|
|
@@ -1380,11 +1740,11 @@ async function runCommand2(command, args, options = {}) {
|
|
|
1380
1740
|
});
|
|
1381
1741
|
}
|
|
1382
1742
|
function isMissingHeadError(error) {
|
|
1383
|
-
const message =
|
|
1743
|
+
const message = getErrorMessage3(error, "").toLowerCase();
|
|
1384
1744
|
return message.includes("not a valid object name: 'head'") || message.includes("not a valid object name: head") || message.includes("invalid reference: head");
|
|
1385
1745
|
}
|
|
1386
1746
|
function isNotGitRepositoryError(error) {
|
|
1387
|
-
const message =
|
|
1747
|
+
const message = getErrorMessage3(error, "").toLowerCase();
|
|
1388
1748
|
return message.includes("not a git repository") || message.includes("fatal: not a git repository");
|
|
1389
1749
|
}
|
|
1390
1750
|
async function ensureRepoHasInitialCommit(repoRoot) {
|
|
@@ -1526,9 +1886,9 @@ var sessionIndexThreadTitleCacheState = {
|
|
|
1526
1886
|
cache: EMPTY_THREAD_TITLE_CACHE
|
|
1527
1887
|
};
|
|
1528
1888
|
function normalizeThreadTitleCache(value) {
|
|
1529
|
-
const record =
|
|
1889
|
+
const record = asRecord3(value);
|
|
1530
1890
|
if (!record) return EMPTY_THREAD_TITLE_CACHE;
|
|
1531
|
-
const rawTitles =
|
|
1891
|
+
const rawTitles = asRecord3(record.titles);
|
|
1532
1892
|
const titles = {};
|
|
1533
1893
|
if (rawTitles) {
|
|
1534
1894
|
for (const [k, v] of Object.entries(rawTitles)) {
|
|
@@ -1552,7 +1912,7 @@ function removeFromThreadTitleCache(cache, id) {
|
|
|
1552
1912
|
return { titles, order: cache.order.filter((o) => o !== id) };
|
|
1553
1913
|
}
|
|
1554
1914
|
function normalizeSessionIndexThreadTitle(value) {
|
|
1555
|
-
const record =
|
|
1915
|
+
const record = asRecord3(value);
|
|
1556
1916
|
if (!record) return null;
|
|
1557
1917
|
const id = typeof record.id === "string" ? record.id.trim() : "";
|
|
1558
1918
|
const title = typeof record.thread_name === "string" ? record.thread_name.trim() : "";
|
|
@@ -1596,7 +1956,7 @@ async function readThreadTitleCache() {
|
|
|
1596
1956
|
const statePath = getCodexGlobalStatePath();
|
|
1597
1957
|
try {
|
|
1598
1958
|
const raw = await readFile2(statePath, "utf8");
|
|
1599
|
-
const payload =
|
|
1959
|
+
const payload = asRecord3(JSON.parse(raw)) ?? {};
|
|
1600
1960
|
return normalizeThreadTitleCache(payload["thread-titles"]);
|
|
1601
1961
|
} catch {
|
|
1602
1962
|
return EMPTY_THREAD_TITLE_CACHE;
|
|
@@ -1607,7 +1967,7 @@ async function writeThreadTitleCache(cache) {
|
|
|
1607
1967
|
let payload = {};
|
|
1608
1968
|
try {
|
|
1609
1969
|
const raw = await readFile2(statePath, "utf8");
|
|
1610
|
-
payload =
|
|
1970
|
+
payload = asRecord3(JSON.parse(raw)) ?? {};
|
|
1611
1971
|
} catch {
|
|
1612
1972
|
payload = {};
|
|
1613
1973
|
}
|
|
@@ -1683,7 +2043,7 @@ async function readWorkspaceRootsState() {
|
|
|
1683
2043
|
try {
|
|
1684
2044
|
const raw = await readFile2(statePath, "utf8");
|
|
1685
2045
|
const parsed = JSON.parse(raw);
|
|
1686
|
-
payload =
|
|
2046
|
+
payload = asRecord3(parsed) ?? {};
|
|
1687
2047
|
} catch {
|
|
1688
2048
|
payload = {};
|
|
1689
2049
|
}
|
|
@@ -1698,7 +2058,7 @@ async function writeWorkspaceRootsState(nextState) {
|
|
|
1698
2058
|
let payload = {};
|
|
1699
2059
|
try {
|
|
1700
2060
|
const raw = await readFile2(statePath, "utf8");
|
|
1701
|
-
payload =
|
|
2061
|
+
payload = asRecord3(JSON.parse(raw)) ?? {};
|
|
1702
2062
|
} catch {
|
|
1703
2063
|
payload = {};
|
|
1704
2064
|
}
|
|
@@ -1707,6 +2067,36 @@ async function writeWorkspaceRootsState(nextState) {
|
|
|
1707
2067
|
payload["active-workspace-roots"] = normalizeStringArray(nextState.active);
|
|
1708
2068
|
await writeFile2(statePath, JSON.stringify(payload), "utf8");
|
|
1709
2069
|
}
|
|
2070
|
+
function normalizeTelegramBridgeConfig(value) {
|
|
2071
|
+
const record = asRecord3(value);
|
|
2072
|
+
if (!record) return { botToken: "" };
|
|
2073
|
+
const botToken = typeof record.botToken === "string" ? record.botToken.trim() : "";
|
|
2074
|
+
return { botToken };
|
|
2075
|
+
}
|
|
2076
|
+
async function readTelegramBridgeConfig() {
|
|
2077
|
+
const statePath = getCodexGlobalStatePath();
|
|
2078
|
+
try {
|
|
2079
|
+
const raw = await readFile2(statePath, "utf8");
|
|
2080
|
+
const payload = asRecord3(JSON.parse(raw)) ?? {};
|
|
2081
|
+
return normalizeTelegramBridgeConfig(payload["telegram-bridge"]);
|
|
2082
|
+
} catch {
|
|
2083
|
+
return { botToken: "" };
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
async function writeTelegramBridgeConfig(nextState) {
|
|
2087
|
+
const statePath = getCodexGlobalStatePath();
|
|
2088
|
+
let payload = {};
|
|
2089
|
+
try {
|
|
2090
|
+
const raw = await readFile2(statePath, "utf8");
|
|
2091
|
+
payload = asRecord3(JSON.parse(raw)) ?? {};
|
|
2092
|
+
} catch {
|
|
2093
|
+
payload = {};
|
|
2094
|
+
}
|
|
2095
|
+
payload["telegram-bridge"] = {
|
|
2096
|
+
botToken: nextState.botToken.trim()
|
|
2097
|
+
};
|
|
2098
|
+
await writeFile2(statePath, JSON.stringify(payload), "utf8");
|
|
2099
|
+
}
|
|
1710
2100
|
async function readJsonBody(req) {
|
|
1711
2101
|
const raw = await readRawBody(req);
|
|
1712
2102
|
if (raw.length === 0) return null;
|
|
@@ -1783,11 +2173,11 @@ function handleFileUpload(req, res) {
|
|
|
1783
2173
|
await writeFile2(destPath, fileData);
|
|
1784
2174
|
setJson2(res, 200, { path: destPath });
|
|
1785
2175
|
} catch (err) {
|
|
1786
|
-
setJson2(res, 500, { error:
|
|
2176
|
+
setJson2(res, 500, { error: getErrorMessage3(err, "Upload failed") });
|
|
1787
2177
|
}
|
|
1788
2178
|
});
|
|
1789
2179
|
req.on("error", (err) => {
|
|
1790
|
-
setJson2(res, 500, { error:
|
|
2180
|
+
setJson2(res, 500, { error: getErrorMessage3(err, "Upload stream error") });
|
|
1791
2181
|
});
|
|
1792
2182
|
}
|
|
1793
2183
|
function httpPost(url, headers, body) {
|
|
@@ -1981,7 +2371,7 @@ var AppServerProcess = class {
|
|
|
1981
2371
|
}
|
|
1982
2372
|
this.pendingServerRequests.delete(requestId);
|
|
1983
2373
|
this.sendServerRequestReply(requestId, reply);
|
|
1984
|
-
const requestParams =
|
|
2374
|
+
const requestParams = asRecord3(pendingRequest.params);
|
|
1985
2375
|
const threadId = typeof requestParams?.threadId === "string" && requestParams.threadId.length > 0 ? requestParams.threadId : "";
|
|
1986
2376
|
this.emitNotification({
|
|
1987
2377
|
method: "server/request/resolved",
|
|
@@ -2050,7 +2440,7 @@ var AppServerProcess = class {
|
|
|
2050
2440
|
}
|
|
2051
2441
|
async respondToServerRequest(payload) {
|
|
2052
2442
|
await this.ensureInitialized();
|
|
2053
|
-
const body =
|
|
2443
|
+
const body = asRecord3(payload);
|
|
2054
2444
|
if (!body) {
|
|
2055
2445
|
throw new Error("Invalid response payload: expected object");
|
|
2056
2446
|
}
|
|
@@ -2058,7 +2448,7 @@ var AppServerProcess = class {
|
|
|
2058
2448
|
if (typeof id !== "number" || !Number.isInteger(id)) {
|
|
2059
2449
|
throw new Error('Invalid response payload: "id" must be an integer');
|
|
2060
2450
|
}
|
|
2061
|
-
const rawError =
|
|
2451
|
+
const rawError = asRecord3(body.error);
|
|
2062
2452
|
if (rawError) {
|
|
2063
2453
|
const message = typeof rawError.message === "string" && rawError.message.trim().length > 0 ? rawError.message.trim() : "Server request rejected by client";
|
|
2064
2454
|
const code = typeof rawError.code === "number" && Number.isFinite(rawError.code) ? Math.trunc(rawError.code) : -32e3;
|
|
@@ -2133,13 +2523,13 @@ var MethodCatalog = class {
|
|
|
2133
2523
|
});
|
|
2134
2524
|
}
|
|
2135
2525
|
extractMethodsFromClientRequest(payload) {
|
|
2136
|
-
const root =
|
|
2526
|
+
const root = asRecord3(payload);
|
|
2137
2527
|
const oneOf = Array.isArray(root?.oneOf) ? root.oneOf : [];
|
|
2138
2528
|
const methods = /* @__PURE__ */ new Set();
|
|
2139
2529
|
for (const entry of oneOf) {
|
|
2140
|
-
const row =
|
|
2141
|
-
const properties =
|
|
2142
|
-
const methodDef =
|
|
2530
|
+
const row = asRecord3(entry);
|
|
2531
|
+
const properties = asRecord3(row?.properties);
|
|
2532
|
+
const methodDef = asRecord3(properties?.method);
|
|
2143
2533
|
const methodEnum = Array.isArray(methodDef?.enum) ? methodDef.enum : [];
|
|
2144
2534
|
for (const item of methodEnum) {
|
|
2145
2535
|
if (typeof item === "string" && item.length > 0) {
|
|
@@ -2150,13 +2540,13 @@ var MethodCatalog = class {
|
|
|
2150
2540
|
return Array.from(methods).sort((a, b) => a.localeCompare(b));
|
|
2151
2541
|
}
|
|
2152
2542
|
extractMethodsFromServerNotification(payload) {
|
|
2153
|
-
const root =
|
|
2543
|
+
const root = asRecord3(payload);
|
|
2154
2544
|
const oneOf = Array.isArray(root?.oneOf) ? root.oneOf : [];
|
|
2155
2545
|
const methods = /* @__PURE__ */ new Set();
|
|
2156
2546
|
for (const entry of oneOf) {
|
|
2157
|
-
const row =
|
|
2158
|
-
const properties =
|
|
2159
|
-
const methodDef =
|
|
2547
|
+
const row = asRecord3(entry);
|
|
2548
|
+
const properties = asRecord3(row?.properties);
|
|
2549
|
+
const methodDef = asRecord3(properties?.method);
|
|
2160
2550
|
const methodEnum = Array.isArray(methodDef?.enum) ? methodDef.enum : [];
|
|
2161
2551
|
for (const item of methodEnum) {
|
|
2162
2552
|
if (typeof item === "string" && item.length > 0) {
|
|
@@ -2198,9 +2588,11 @@ function getSharedBridgeState() {
|
|
|
2198
2588
|
const globalScope = globalThis;
|
|
2199
2589
|
const existing = globalScope[SHARED_BRIDGE_KEY];
|
|
2200
2590
|
if (existing) return existing;
|
|
2591
|
+
const appServer = new AppServerProcess();
|
|
2201
2592
|
const created = {
|
|
2202
|
-
appServer
|
|
2203
|
-
methodCatalog: new MethodCatalog()
|
|
2593
|
+
appServer,
|
|
2594
|
+
methodCatalog: new MethodCatalog(),
|
|
2595
|
+
telegramBridge: new TelegramThreadBridge(appServer)
|
|
2204
2596
|
};
|
|
2205
2597
|
globalScope[SHARED_BRIDGE_KEY] = created;
|
|
2206
2598
|
return created;
|
|
@@ -2209,7 +2601,7 @@ async function loadAllThreadsForSearch(appServer) {
|
|
|
2209
2601
|
const threads = [];
|
|
2210
2602
|
let cursor = null;
|
|
2211
2603
|
do {
|
|
2212
|
-
const response =
|
|
2604
|
+
const response = asRecord3(await appServer.rpc("thread/list", {
|
|
2213
2605
|
archived: false,
|
|
2214
2606
|
limit: 100,
|
|
2215
2607
|
sortKey: "updated_at",
|
|
@@ -2217,7 +2609,7 @@ async function loadAllThreadsForSearch(appServer) {
|
|
|
2217
2609
|
}));
|
|
2218
2610
|
const data = Array.isArray(response?.data) ? response.data : [];
|
|
2219
2611
|
for (const row of data) {
|
|
2220
|
-
const record =
|
|
2612
|
+
const record = asRecord3(row);
|
|
2221
2613
|
const id = typeof record?.id === "string" ? record.id : "";
|
|
2222
2614
|
if (!id) continue;
|
|
2223
2615
|
const title = typeof record?.name === "string" && record.name.trim().length > 0 ? record.name.trim() : typeof record?.preview === "string" && record.preview.trim().length > 0 ? record.preview.trim() : "Untitled thread";
|
|
@@ -2266,7 +2658,7 @@ async function buildThreadSearchIndex(appServer) {
|
|
|
2266
2658
|
return { docsById };
|
|
2267
2659
|
}
|
|
2268
2660
|
function createCodexBridgeMiddleware() {
|
|
2269
|
-
const { appServer, methodCatalog } = getSharedBridgeState();
|
|
2661
|
+
const { appServer, methodCatalog, telegramBridge } = getSharedBridgeState();
|
|
2270
2662
|
let threadSearchIndex = null;
|
|
2271
2663
|
let threadSearchIndexPromise = null;
|
|
2272
2664
|
async function getThreadSearchIndex() {
|
|
@@ -2282,6 +2674,12 @@ function createCodexBridgeMiddleware() {
|
|
|
2282
2674
|
return threadSearchIndexPromise;
|
|
2283
2675
|
}
|
|
2284
2676
|
void initializeSkillsSyncOnStartup(appServer);
|
|
2677
|
+
void readTelegramBridgeConfig().then((config) => {
|
|
2678
|
+
if (!config.botToken) return;
|
|
2679
|
+
telegramBridge.configureToken(config.botToken);
|
|
2680
|
+
telegramBridge.start();
|
|
2681
|
+
}).catch(() => {
|
|
2682
|
+
});
|
|
2285
2683
|
const middleware = async (req, res, next) => {
|
|
2286
2684
|
try {
|
|
2287
2685
|
if (!req.url) {
|
|
@@ -2298,7 +2696,7 @@ function createCodexBridgeMiddleware() {
|
|
|
2298
2696
|
}
|
|
2299
2697
|
if (req.method === "POST" && url.pathname === "/codex-api/rpc") {
|
|
2300
2698
|
const payload = await readJsonBody(req);
|
|
2301
|
-
const body =
|
|
2699
|
+
const body = asRecord3(payload);
|
|
2302
2700
|
if (!body || typeof body.method !== "string" || body.method.length === 0) {
|
|
2303
2701
|
setJson2(res, 400, { error: "Invalid body: expected { method, params? }" });
|
|
2304
2702
|
return;
|
|
@@ -2350,21 +2748,8 @@ function createCodexBridgeMiddleware() {
|
|
|
2350
2748
|
setJson2(res, 200, { data: { path: homedir3() } });
|
|
2351
2749
|
return;
|
|
2352
2750
|
}
|
|
2353
|
-
if (req.method === "GET" && url.pathname === "/codex-api/github-trending") {
|
|
2354
|
-
const sinceRaw = (url.searchParams.get("since") ?? "").trim().toLowerCase();
|
|
2355
|
-
const since = sinceRaw === "weekly" ? "weekly" : sinceRaw === "monthly" ? "monthly" : "daily";
|
|
2356
|
-
const limitRaw = Number.parseInt((url.searchParams.get("limit") ?? "6").trim(), 10);
|
|
2357
|
-
const limit = Number.isFinite(limitRaw) ? Math.max(1, Math.min(10, limitRaw)) : 6;
|
|
2358
|
-
try {
|
|
2359
|
-
const data = await fetchGithubTrending(since, limit);
|
|
2360
|
-
setJson2(res, 200, { data });
|
|
2361
|
-
} catch (error) {
|
|
2362
|
-
setJson2(res, 502, { error: getErrorMessage2(error, "Failed to fetch GitHub trending") });
|
|
2363
|
-
}
|
|
2364
|
-
return;
|
|
2365
|
-
}
|
|
2366
2751
|
if (req.method === "POST" && url.pathname === "/codex-api/worktree/create") {
|
|
2367
|
-
const payload =
|
|
2752
|
+
const payload = asRecord3(await readJsonBody(req));
|
|
2368
2753
|
const rawSourceCwd = typeof payload?.sourceCwd === "string" ? payload.sourceCwd.trim() : "";
|
|
2369
2754
|
if (!rawSourceCwd) {
|
|
2370
2755
|
setJson2(res, 400, { error: "Missing sourceCwd" });
|
|
@@ -2390,7 +2775,7 @@ function createCodexBridgeMiddleware() {
|
|
|
2390
2775
|
await runCommand2("git", ["init"], { cwd: sourceCwd });
|
|
2391
2776
|
gitRoot = await runCommandCapture("git", ["rev-parse", "--show-toplevel"], { cwd: sourceCwd });
|
|
2392
2777
|
}
|
|
2393
|
-
const repoName =
|
|
2778
|
+
const repoName = basename3(gitRoot) || "repo";
|
|
2394
2779
|
const worktreesRoot = join3(getCodexHomeDir2(), "worktrees");
|
|
2395
2780
|
await mkdir2(worktreesRoot, { recursive: true });
|
|
2396
2781
|
let worktreeId = "";
|
|
@@ -2429,12 +2814,12 @@ function createCodexBridgeMiddleware() {
|
|
|
2429
2814
|
}
|
|
2430
2815
|
});
|
|
2431
2816
|
} catch (error) {
|
|
2432
|
-
setJson2(res, 500, { error:
|
|
2817
|
+
setJson2(res, 500, { error: getErrorMessage3(error, "Failed to create worktree") });
|
|
2433
2818
|
}
|
|
2434
2819
|
return;
|
|
2435
2820
|
}
|
|
2436
2821
|
if (req.method === "POST" && url.pathname === "/codex-api/worktree/auto-commit") {
|
|
2437
|
-
const payload =
|
|
2822
|
+
const payload = asRecord3(await readJsonBody(req));
|
|
2438
2823
|
const rawCwd = typeof payload?.cwd === "string" ? payload.cwd.trim() : "";
|
|
2439
2824
|
const commitMessage = normalizeCommitMessage(payload?.message);
|
|
2440
2825
|
if (!rawCwd) {
|
|
@@ -2472,12 +2857,12 @@ function createCodexBridgeMiddleware() {
|
|
|
2472
2857
|
await runCommand2("git", ["commit", "-m", commitMessage], { cwd });
|
|
2473
2858
|
setJson2(res, 200, { data: { committed: true } });
|
|
2474
2859
|
} catch (error) {
|
|
2475
|
-
setJson2(res, 500, { error:
|
|
2860
|
+
setJson2(res, 500, { error: getErrorMessage3(error, "Failed to auto-commit worktree changes") });
|
|
2476
2861
|
}
|
|
2477
2862
|
return;
|
|
2478
2863
|
}
|
|
2479
2864
|
if (req.method === "POST" && url.pathname === "/codex-api/worktree/rollback-to-message") {
|
|
2480
|
-
const payload =
|
|
2865
|
+
const payload = asRecord3(await readJsonBody(req));
|
|
2481
2866
|
const rawCwd = typeof payload?.cwd === "string" ? payload.cwd.trim() : "";
|
|
2482
2867
|
const commitMessage = normalizeCommitMessage(payload?.message);
|
|
2483
2868
|
if (!rawCwd) {
|
|
@@ -2522,13 +2907,13 @@ function createCodexBridgeMiddleware() {
|
|
|
2522
2907
|
await runCommand2("git", ["reset", "--hard", resetTargetSha], { cwd });
|
|
2523
2908
|
setJson2(res, 200, { data: { reset: true, commitSha, resetTargetSha, stashed } });
|
|
2524
2909
|
} catch (error) {
|
|
2525
|
-
setJson2(res, 500, { error:
|
|
2910
|
+
setJson2(res, 500, { error: getErrorMessage3(error, "Failed to rollback worktree to user message commit") });
|
|
2526
2911
|
}
|
|
2527
2912
|
return;
|
|
2528
2913
|
}
|
|
2529
2914
|
if (req.method === "PUT" && url.pathname === "/codex-api/workspace-roots-state") {
|
|
2530
2915
|
const payload = await readJsonBody(req);
|
|
2531
|
-
const record =
|
|
2916
|
+
const record = asRecord3(payload);
|
|
2532
2917
|
if (!record) {
|
|
2533
2918
|
setJson2(res, 400, { error: "Invalid body: expected object" });
|
|
2534
2919
|
return;
|
|
@@ -2543,7 +2928,7 @@ function createCodexBridgeMiddleware() {
|
|
|
2543
2928
|
return;
|
|
2544
2929
|
}
|
|
2545
2930
|
if (req.method === "POST" && url.pathname === "/codex-api/project-root") {
|
|
2546
|
-
const payload =
|
|
2931
|
+
const payload = asRecord3(await readJsonBody(req));
|
|
2547
2932
|
const rawPath = typeof payload?.path === "string" ? payload.path.trim() : "";
|
|
2548
2933
|
const createIfMissing = payload?.createIfMissing === true;
|
|
2549
2934
|
const label = typeof payload?.label === "string" ? payload.label : "";
|
|
@@ -2617,7 +3002,7 @@ function createCodexBridgeMiddleware() {
|
|
|
2617
3002
|
return;
|
|
2618
3003
|
}
|
|
2619
3004
|
if (req.method === "POST" && url.pathname === "/codex-api/composer-file-search") {
|
|
2620
|
-
const payload =
|
|
3005
|
+
const payload = asRecord3(await readJsonBody(req));
|
|
2621
3006
|
const rawCwd = typeof payload?.cwd === "string" ? payload.cwd.trim() : "";
|
|
2622
3007
|
const query = typeof payload?.query === "string" ? payload.query.trim() : "";
|
|
2623
3008
|
const limitRaw = typeof payload?.limit === "number" ? payload.limit : 20;
|
|
@@ -2642,7 +3027,7 @@ function createCodexBridgeMiddleware() {
|
|
|
2642
3027
|
const scored = files.map((path) => ({ path, score: scoreFileCandidate(path, query) })).filter((row) => query.length === 0 || row.score < 10).sort((a, b) => a.score - b.score || a.path.localeCompare(b.path)).slice(0, limit).map((row) => ({ path: row.path }));
|
|
2643
3028
|
setJson2(res, 200, { data: scored });
|
|
2644
3029
|
} catch (error) {
|
|
2645
|
-
setJson2(res, 500, { error:
|
|
3030
|
+
setJson2(res, 500, { error: getErrorMessage3(error, "Failed to search files") });
|
|
2646
3031
|
}
|
|
2647
3032
|
return;
|
|
2648
3033
|
}
|
|
@@ -2652,7 +3037,7 @@ function createCodexBridgeMiddleware() {
|
|
|
2652
3037
|
return;
|
|
2653
3038
|
}
|
|
2654
3039
|
if (req.method === "POST" && url.pathname === "/codex-api/thread-search") {
|
|
2655
|
-
const payload =
|
|
3040
|
+
const payload = asRecord3(await readJsonBody(req));
|
|
2656
3041
|
const query = typeof payload?.query === "string" ? payload.query.trim() : "";
|
|
2657
3042
|
const limitRaw = typeof payload?.limit === "number" ? payload.limit : 200;
|
|
2658
3043
|
const limit = Math.max(1, Math.min(1e3, Math.floor(limitRaw)));
|
|
@@ -2666,7 +3051,7 @@ function createCodexBridgeMiddleware() {
|
|
|
2666
3051
|
return;
|
|
2667
3052
|
}
|
|
2668
3053
|
if (req.method === "PUT" && url.pathname === "/codex-api/thread-titles") {
|
|
2669
|
-
const payload =
|
|
3054
|
+
const payload = asRecord3(await readJsonBody(req));
|
|
2670
3055
|
const id = typeof payload?.id === "string" ? payload.id : "";
|
|
2671
3056
|
const title = typeof payload?.title === "string" ? payload.title : "";
|
|
2672
3057
|
if (!id) {
|
|
@@ -2679,6 +3064,23 @@ function createCodexBridgeMiddleware() {
|
|
|
2679
3064
|
setJson2(res, 200, { ok: true });
|
|
2680
3065
|
return;
|
|
2681
3066
|
}
|
|
3067
|
+
if (req.method === "POST" && url.pathname === "/codex-api/telegram/configure-bot") {
|
|
3068
|
+
const payload = asRecord3(await readJsonBody(req));
|
|
3069
|
+
const botToken = typeof payload?.botToken === "string" ? payload.botToken.trim() : "";
|
|
3070
|
+
if (!botToken) {
|
|
3071
|
+
setJson2(res, 400, { error: "Missing botToken" });
|
|
3072
|
+
return;
|
|
3073
|
+
}
|
|
3074
|
+
telegramBridge.configureToken(botToken);
|
|
3075
|
+
telegramBridge.start();
|
|
3076
|
+
await writeTelegramBridgeConfig({ botToken });
|
|
3077
|
+
setJson2(res, 200, { ok: true });
|
|
3078
|
+
return;
|
|
3079
|
+
}
|
|
3080
|
+
if (req.method === "GET" && url.pathname === "/codex-api/telegram/status") {
|
|
3081
|
+
setJson2(res, 200, { data: telegramBridge.getStatus() });
|
|
3082
|
+
return;
|
|
3083
|
+
}
|
|
2682
3084
|
if (req.method === "GET" && url.pathname === "/codex-api/events") {
|
|
2683
3085
|
res.statusCode = 200;
|
|
2684
3086
|
res.setHeader("Content-Type", "text/event-stream; charset=utf-8");
|
|
@@ -2711,12 +3113,13 @@ data: ${JSON.stringify({ ok: true })}
|
|
|
2711
3113
|
}
|
|
2712
3114
|
next();
|
|
2713
3115
|
} catch (error) {
|
|
2714
|
-
const message =
|
|
3116
|
+
const message = getErrorMessage3(error, "Unknown bridge error");
|
|
2715
3117
|
setJson2(res, 502, { error: message });
|
|
2716
3118
|
}
|
|
2717
3119
|
};
|
|
2718
3120
|
middleware.dispose = () => {
|
|
2719
3121
|
threadSearchIndex = null;
|
|
3122
|
+
telegramBridge.stop();
|
|
2720
3123
|
appServer.dispose();
|
|
2721
3124
|
};
|
|
2722
3125
|
middleware.subscribeNotifications = (listener) => {
|