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/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 basename2, isAbsolute, join as join3, resolve } from "path";
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 = "/Users/igor/.cursor/skills/.system/skill-installer/scripts/install-skill-from-github.py";
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
- setJson(res, 200, { content });
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 = "/Users/igor/.cursor/skills/.system/skill-installer/scripts/install-skill-from-github.py";
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 = basename(lowerCommand);
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 asRecord2(value) {
1610
+ function asRecord3(value) {
1203
1611
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
1204
1612
  }
1205
- function getErrorMessage2(payload, fallback) {
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 = asRecord2(payload);
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 = asRecord2(error);
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 = asRecord2(threadReadPayload);
1226
- const thread = asRecord2(payload?.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 = asRecord2(turn);
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 = asRecord2(item);
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 = asRecord2(block);
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(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&#x2F;/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 = getErrorMessage2(error, "").toLowerCase();
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 = getErrorMessage2(error, "").toLowerCase();
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 = asRecord2(value);
1889
+ const record = asRecord3(value);
1530
1890
  if (!record) return EMPTY_THREAD_TITLE_CACHE;
1531
- const rawTitles = asRecord2(record.titles);
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 = asRecord2(value);
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 = asRecord2(JSON.parse(raw)) ?? {};
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 = asRecord2(JSON.parse(raw)) ?? {};
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 = asRecord2(parsed) ?? {};
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 = asRecord2(JSON.parse(raw)) ?? {};
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: getErrorMessage2(err, "Upload failed") });
2176
+ setJson2(res, 500, { error: getErrorMessage3(err, "Upload failed") });
1787
2177
  }
1788
2178
  });
1789
2179
  req.on("error", (err) => {
1790
- setJson2(res, 500, { error: getErrorMessage2(err, "Upload stream 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 = asRecord2(pendingRequest.params);
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 = asRecord2(payload);
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 = asRecord2(body.error);
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 = asRecord2(payload);
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 = asRecord2(entry);
2141
- const properties = asRecord2(row?.properties);
2142
- const methodDef = asRecord2(properties?.method);
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 = asRecord2(payload);
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 = asRecord2(entry);
2158
- const properties = asRecord2(row?.properties);
2159
- const methodDef = asRecord2(properties?.method);
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: new AppServerProcess(),
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 = asRecord2(await appServer.rpc("thread/list", {
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 = asRecord2(row);
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 = asRecord2(payload);
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 = asRecord2(await readJsonBody(req));
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 = basename2(gitRoot) || "repo";
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: getErrorMessage2(error, "Failed to create worktree") });
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 = asRecord2(await readJsonBody(req));
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: getErrorMessage2(error, "Failed to auto-commit worktree changes") });
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 = asRecord2(await readJsonBody(req));
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: getErrorMessage2(error, "Failed to rollback worktree to user message commit") });
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 = asRecord2(payload);
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 = asRecord2(await readJsonBody(req));
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 = asRecord2(await readJsonBody(req));
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: getErrorMessage2(error, "Failed to search files") });
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 = asRecord2(await readJsonBody(req));
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 = asRecord2(await readJsonBody(req));
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 = getErrorMessage2(error, "Unknown bridge error");
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) => {