@debbl/relay 0.0.3 → 0.0.4

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 CHANGED
@@ -4,6 +4,9 @@
4
4
 
5
5
  A Feishu bot that forwards chat messages to Codex and returns results in chat.
6
6
 
7
+ > [!WARNING]
8
+ > This project is currently in beta, and breaking changes may happen at any time.
9
+
7
10
  ## Install
8
11
 
9
12
  ```bash
@@ -56,8 +59,8 @@ Config fields:
56
59
  ## Run
57
60
 
58
61
  ```bash
59
- pnpm install
60
- pnpm dev
62
+ cd <your_project>
63
+ relay
61
64
  ```
62
65
 
63
66
  ## How To Use In Feishu
@@ -66,7 +69,7 @@ pnpm dev
66
69
 
67
70
  1. Send a text message to the bot.
68
71
  2. Bot replies immediately with a processing echo:
69
- - `已收到,正在处理任务: <task preview>`
72
+ - `Received. Processing task: <task preview>`
70
73
  3. Bot sends final Codex result when done.
71
74
  4. After `/new`, the first normal prompt is used directly as the session title (with normalization and truncation).
72
75
 
@@ -93,6 +96,8 @@ pnpm dev
93
96
  - Relay stores session index at `~/.relay/sessions.json` and restores active sessions after restart.
94
97
  - The index stores thread ids and basic metadata only; full session transcripts are in `~/.codex/sessions`.
95
98
  - Relay fixes workspace root to the process startup directory (`process.cwd()`).
99
+ - Relay checks npm for newer `@debbl/relay` versions at startup and prints a warning when an update is available.
100
+ - Set `RELAY_SKIP_UPDATE_CHECK=1` to disable the startup update check.
96
101
  - Ensure process user can read/write `~/.codex/sessions`.
97
102
  - Ensure process user can read/write `~/.relay/sessions.json`.
98
103
  - `.env.local` is no longer used for runtime config.
@@ -101,7 +106,6 @@ pnpm dev
101
106
 
102
107
  ```bash
103
108
  pnpm i18n:extract
104
- pnpm i18n:compile
105
109
  pnpm lint
106
110
  pnpm typecheck
107
111
  pnpm test
package/README.zh.md CHANGED
@@ -4,6 +4,9 @@
4
4
 
5
5
  一个将飞书聊天消息转发给 Codex,并将结果回传到聊天中的机器人。
6
6
 
7
+ > [!WARNING]
8
+ > 当前项目处于 Beta 阶段,可能随时发生破坏性更新。
9
+
7
10
  ## 安装
8
11
 
9
12
  ```bash
@@ -56,8 +59,8 @@ Relay 仅从 `~/.relay/config.json` 读取配置。
56
59
  ## 运行
57
60
 
58
61
  ```bash
59
- pnpm install
60
- pnpm dev
62
+ cd <your_project>
63
+ relay
61
64
  ```
62
65
 
63
66
  ## 在飞书中使用
@@ -93,6 +96,8 @@ pnpm dev
93
96
  - Relay 会把会话索引保存到 `~/.relay/sessions.json`,重启后可恢复活跃会话。
94
97
  - 索引仅保存 thread id 和基础元数据;完整会话内容仍在 `~/.codex/sessions`。
95
98
  - Relay 将工作区根目录固定为进程启动目录(`process.cwd()`)。
99
+ - Relay 启动时会到 npm 检查 `@debbl/relay` 是否有新版本,有更新会输出告警提示。
100
+ - 设置 `RELAY_SKIP_UPDATE_CHECK=1` 可禁用启动时版本检查。
96
101
  - 请确保进程用户对 `~/.codex/sessions` 有读写权限。
97
102
  - 请确保进程用户对 `~/.relay/sessions.json` 有读写权限。
98
103
  - 运行时不再使用 `.env.local`。
@@ -101,7 +106,6 @@ pnpm dev
101
106
 
102
107
  ```bash
103
108
  pnpm i18n:extract
104
- pnpm i18n:compile
105
109
  pnpm lint
106
110
  pnpm typecheck
107
111
  pnpm test
package/dist/index.mjs CHANGED
@@ -6,7 +6,9 @@ import fs from "node:fs";
6
6
  import path from "node:path";
7
7
  import { spawn } from "node:child_process";
8
8
  import { createInterface } from "node:readline";
9
+ import { fileURLToPath } from "node:url";
9
10
  import os from "node:os";
11
+ import https from "node:https";
10
12
 
11
13
  //#region src/bot/commands.ts
12
14
  const COMMAND_HELP = "/help";
@@ -1123,6 +1125,33 @@ async function listOpenProjects() {
1123
1125
  return { roots: [process.cwd()] };
1124
1126
  }
1125
1127
 
1128
+ //#endregion
1129
+ //#region src/core/package-meta.ts
1130
+ const DEFAULT_PACKAGE_NAME = "@debbl/relay";
1131
+ const DEFAULT_PACKAGE_VERSION = "0.0.0";
1132
+ function readRelayPackageMetadata() {
1133
+ const packageJsonPath = fileURLToPath(new URL("../../package.json", import.meta.url));
1134
+ try {
1135
+ const raw = fs.readFileSync(packageJsonPath, "utf-8");
1136
+ const parsed = JSON.parse(raw);
1137
+ return {
1138
+ name: readPackageField(parsed.name, DEFAULT_PACKAGE_NAME),
1139
+ version: readPackageField(parsed.version, DEFAULT_PACKAGE_VERSION)
1140
+ };
1141
+ } catch {
1142
+ return {
1143
+ name: DEFAULT_PACKAGE_NAME,
1144
+ version: DEFAULT_PACKAGE_VERSION
1145
+ };
1146
+ }
1147
+ }
1148
+ function readPackageField(value, fallback) {
1149
+ if (typeof value !== "string") return fallback;
1150
+ const normalized = value.trim();
1151
+ if (normalized.length === 0) return fallback;
1152
+ return normalized;
1153
+ }
1154
+
1126
1155
  //#endregion
1127
1156
  //#region src/locales/en/messages.po
1128
1157
  const messages$1 = JSON.parse("{\"1xKjU/\":[\"Failed to read open projects. Please try again later.\"],\"36QmnO\":[\"thread: \",[\"0\"]],\"4IDydv\":[\"Invalid relay config at \",[\"configPath\"],\": root must be a JSON object.\"],\"4Vrm3r\":[\"cwd: \",[\"0\"]],\"6tcMXX\":[\"Invalid relay config: \",[\"field\"],\" is required and must be a non-empty string.\"],\"AWIU5i\":[\"Relay config is missing. Template created at \",[\"configPath\"],\". Please edit this file and restart.\"],\"Bb/dZi\":[\"Available commands:\"],\"CfFOzJ\":[\"Invalid relay config at \",[\"configPath\"],\": env must be a JSON object.\"],\"CgiJb7\":[\"Invalid relay config: locale \\\"\",[\"0\"],\"\\\" is not supported. Falling back to \",[\"systemLocale\"],\".\"],\"Ck6rmi\":[\"Current session has been cleared.\"],\"FZcpfm\":[\"Failed to process message. Please try again later.\"],\"G1WYAl\":[\"Unknown command \\\"\",[\"0\"],\"\\\".\\n\\n\",[\"helpText\"]],\"H7VlDR\":[\"Currently busy. Please try again later.\"],\"HEx9te\":[\"New Session\"],\"Hrkm8q\":[\"Invalid relay config: CODEX_TIMEOUT_MS must be a positive integer.\"],\"JvAWQ5\":[\"No working directories are currently open.\"],\"K8IUjY\":[\"mode: \",[\"0\"]],\"Ks6r4a\":[\"Invalid relay config: \",[\"field\"],\" must be a string.\"],\"MZ28Ys\":[\"Failed to read open projects: \",[\"0\"]],\"NX40Ku\":[\"/new [default|plan] - Create a new session\"],\"OZZfXh\":[\"Current session status:\"],\"Oeb7jH\":[\"model: \",[\"0\"]],\"Qq8U1p\":[\"/status does not accept arguments.\\n\\n\",[\"helpText\"]],\"RLcmLa\":[\"/status - Show current session status\"],\"Rn/EAE\":[\"You are a session title generator.\\nGenerate a short English title based on the user message.\\nStrict requirements:\\n1. Output title text only, with no explanation.\\n2. Output a single line with no line breaks.\\n3. Do not use quotes.\\n4. Keep the title within 24 characters.\"],\"T/qXZl\":[\"/new accepts at most one optional argument: default or plan.\\n\\n\",[\"helpText\"]],\"Xg0HiS\":[\"/mode <default|plan> - Switch current session mode\"],\"YwjZm9\":[\"No active session. Send a normal message or use /new to create one.\"],\"aQiwam\":[\"Failed to start relay: \",[\"message\"]],\"dV7HiV\":[\"Invalid relay config: LOCALE \\\"\",[\"0\"],\"\\\" is not supported. Falling back to en.\"],\"dXeZCW\":[\"Switched to \",[\"0\"],\" mode.\"],\"dZhhUX\":[\"Created a new session.\"],\"doQwEN\":[\"Codex execution failed: \",[\"0\"]],\"eie2Mj\":[\"Command cannot be empty.\\n\\n\",[\"helpText\"]],\"gHcqpZ\":[\"Invalid mode \\\"\",[\"modeToken\"],\"\\\", only default or plan are supported.\\n\\n\",[\"helpText\"]],\"gf810l\":[\"/mode requires one argument: default or plan.\\n\\n\",[\"helpText\"]],\"j3bsc1\":[[\"0\"],\". \",[\"root\"]],\"ksj3fj\":[\"Invalid relay config: LOCALE \\\"\",[\"normalized\"],\"\\\" is not supported. Falling back to en.\"],\"lksJXf\":[\"/projects - Show current working directories\"],\"mehbut\":[\"No active session. Send a normal message or use /new to create one first.\"],\"miupLy\":[\"Current working directories:\"],\"o4F8ck\":[\"/reset does not accept arguments.\\n\\n\",[\"helpText\"]],\"oEMyJf\":[\"Please send a text message.\"],\"oKQkYZ\":[\"/projects does not accept arguments.\\n\\n\",[\"helpText\"]],\"orhkAj\":[\"Codex execution failed. Please try again later.\"],\"qN3BkN\":[\"/reset - Clear current session\"],\"rHDhWM\":[\"Failed to parse message. Please send a text message.\"],\"rdodSw\":[\"You are a session title generator.\\nGenerate a short Chinese title based on the user message.\\nStrict requirements:\\n1. Output title text only, with no explanation.\\n2. Output a single line with no line breaks.\\n3. Do not use quotes or title marks.\\n4. Keep the title within 24 characters.\"],\"smfLBQ\":[\"Cannot identify sender. Please try again later.\"],\"tHEFWw\":[\"title: \",[\"title\"]],\"tZQUtS\":[\"Failed to read relay config at \",[\"configPath\"],\": \",[\"0\"]],\"uAkDSp\":[\"User message: \",[\"0\"]],\"wBrugH\":[\"/help does not accept arguments.\\n\\n\",[\"helpText\"]],\"wSNjFy\":[\"/help - Show help\"],\"xBlpjC\":[\"Invalid relay config: locale \\\"\",[\"normalized\"],\"\\\" is not supported. Falling back to \",[\"systemLocale\"],\".\"],\"z+7ZYe\":[\"Invalid JSON in relay config at \",[\"configPath\"],\": \",[\"0\"]]}");
@@ -1391,6 +1420,127 @@ function formatStartupError(error) {
1391
1420
  });
1392
1421
  }
1393
1422
 
1423
+ //#endregion
1424
+ //#region src/core/update-check.ts
1425
+ const DEFAULT_REGISTRY_BASE_URL = "https://registry.npmjs.org";
1426
+ const DEFAULT_TIMEOUT_MS = 2500;
1427
+ async function checkRelayPackageUpdate(options) {
1428
+ const currentVersion = options.currentVersion.trim();
1429
+ if (currentVersion.length === 0) return;
1430
+ const requestJson = options.requestJson ?? requestRegistryJson;
1431
+ const logger = options.logger ?? console;
1432
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
1433
+ const registryBaseUrl = normalizeRegistryBaseUrl(options.registryBaseUrl ?? DEFAULT_REGISTRY_BASE_URL);
1434
+ const packagePath = encodeURIComponent(options.packageName);
1435
+ const requestUrl = new URL(`${registryBaseUrl}/${packagePath}`);
1436
+ let payload;
1437
+ try {
1438
+ payload = await requestJson(requestUrl, timeoutMs);
1439
+ } catch {
1440
+ return;
1441
+ }
1442
+ const latestVersion = readLatestVersion(payload);
1443
+ if (latestVersion === void 0) return;
1444
+ if (compareSemverVersions(latestVersion, currentVersion) <= 0) return;
1445
+ logger.warn(`[relay] A new version of ${options.packageName} is available: ${currentVersion} -> ${latestVersion}. Run "npm i -g ${options.packageName}@latest".`);
1446
+ }
1447
+ function compareSemverVersions(left, right) {
1448
+ const leftSemver = parseSemver(left);
1449
+ const rightSemver = parseSemver(right);
1450
+ if (leftSemver === null || rightSemver === null) return 0;
1451
+ const coreDiff = compareNumber(leftSemver.major, rightSemver.major) || compareNumber(leftSemver.minor, rightSemver.minor) || compareNumber(leftSemver.patch, rightSemver.patch);
1452
+ if (coreDiff !== 0) return coreDiff;
1453
+ return comparePrerelease(leftSemver.prerelease, rightSemver.prerelease);
1454
+ }
1455
+ function parseSemver(value) {
1456
+ const normalized = value.trim();
1457
+ const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(normalized);
1458
+ if (!match) return null;
1459
+ return {
1460
+ major: Number.parseInt(match[1], 10),
1461
+ minor: Number.parseInt(match[2], 10),
1462
+ patch: Number.parseInt(match[3], 10),
1463
+ prerelease: match[4] === void 0 ? [] : match[4].split(".")
1464
+ };
1465
+ }
1466
+ function comparePrerelease(left, right) {
1467
+ if (left.length === 0 && right.length === 0) return 0;
1468
+ if (left.length === 0) return 1;
1469
+ if (right.length === 0) return -1;
1470
+ const maxLength = Math.max(left.length, right.length);
1471
+ for (let index = 0; index < maxLength; index += 1) {
1472
+ const leftPart = left[index];
1473
+ const rightPart = right[index];
1474
+ if (leftPart === void 0) return -1;
1475
+ if (rightPart === void 0) return 1;
1476
+ const compared = comparePrereleasePart(leftPart, rightPart);
1477
+ if (compared !== 0) return compared;
1478
+ }
1479
+ return 0;
1480
+ }
1481
+ function comparePrereleasePart(left, right) {
1482
+ const numericPattern = /^\d+$/;
1483
+ const leftIsNumeric = numericPattern.test(left);
1484
+ const rightIsNumeric = numericPattern.test(right);
1485
+ if (leftIsNumeric && rightIsNumeric) return compareNumber(Number.parseInt(left, 10), Number.parseInt(right, 10));
1486
+ if (leftIsNumeric) return -1;
1487
+ if (rightIsNumeric) return 1;
1488
+ return left.localeCompare(right);
1489
+ }
1490
+ function compareNumber(left, right) {
1491
+ if (left === right) return 0;
1492
+ return left > right ? 1 : -1;
1493
+ }
1494
+ function readLatestVersion(payload) {
1495
+ if (!isRecord(payload)) return;
1496
+ const distTags = payload["dist-tags"];
1497
+ if (!isRecord(distTags)) return;
1498
+ const latest = distTags.latest;
1499
+ if (typeof latest !== "string") return;
1500
+ const normalized = latest.trim();
1501
+ if (normalized.length === 0) return;
1502
+ return normalized;
1503
+ }
1504
+ function isRecord(value) {
1505
+ return typeof value === "object" && value !== null;
1506
+ }
1507
+ function normalizeRegistryBaseUrl(value) {
1508
+ return value.endsWith("/") ? value.slice(0, -1) : value;
1509
+ }
1510
+ async function requestRegistryJson(url, timeoutMs) {
1511
+ return await new Promise((resolve, reject) => {
1512
+ const request = https.get(url, { headers: { accept: "application/vnd.npm.install-v1+json" } }, (response) => {
1513
+ const statusCode = response.statusCode ?? 0;
1514
+ if (statusCode < 200 || statusCode >= 300) {
1515
+ response.resume();
1516
+ reject(/* @__PURE__ */ new Error(`Unexpected npm registry response: ${statusCode}`));
1517
+ return;
1518
+ }
1519
+ let body = "";
1520
+ response.setEncoding("utf-8");
1521
+ response.on("data", (chunk) => {
1522
+ body += chunk;
1523
+ });
1524
+ response.on("end", () => {
1525
+ try {
1526
+ resolve(JSON.parse(body));
1527
+ } catch (error) {
1528
+ reject(error);
1529
+ }
1530
+ });
1531
+ response.on("error", (error) => {
1532
+ reject(error);
1533
+ });
1534
+ });
1535
+ request.setTimeout(timeoutMs, () => {
1536
+ request.destroy(/* @__PURE__ */ new Error("Update check timed out."));
1537
+ });
1538
+ request.on("error", (error) => {
1539
+ reject(error);
1540
+ });
1541
+ });
1542
+ }
1543
+
1394
1544
  //#endregion
1395
1545
  //#region src/feishu/reply.ts
1396
1546
  const FALLBACK_REPLY_TAG = "no-thread";
@@ -1452,6 +1602,13 @@ try {
1452
1602
  }));
1453
1603
  process.exit(1);
1454
1604
  }
1605
+ if (process.env.RELAY_SKIP_UPDATE_CHECK !== "1") {
1606
+ const packageMetadata = readRelayPackageMetadata();
1607
+ checkRelayPackageUpdate({
1608
+ packageName: packageMetadata.name,
1609
+ currentVersion: packageMetadata.version
1610
+ });
1611
+ }
1455
1612
  const BUSY_MESSAGE = i18n._({
1456
1613
  id: "H7VlDR",
1457
1614
  message: "Currently busy. Please try again later."
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["COMMAND_HELP","COMMAND_NEW","COMMAND_MODE","COMMAND_STATUS","COMMAND_PROJECTS","COMMAND_RESET","getHelpText","t","join","parseCommand","input","normalized","trim","helpText","length","type","message","startsWith","prompt","parts","split","command","toLowerCase","modeToken","mode","parseMode","fs","path","sessionStore","Map","sessionQueue","SESSION_FILE_NAME","SESSION_FILE_VERSION","persistenceState","initializeSessionStore","input","relayDir","join","homeDir","filePath","mkdirSync","recursive","ensureSessionFileExists","persisted","readPersistedSessionsFile","workspaceSessions","workspaces","workspaceCwd","clear","activeBySessionKey","sessionRef","set","sessionKey","hydrateSession","data","getSessionKey","chatType","chatId","userId","getSession","get","setSession","session","persistSetSession","clearSession","delete","persistClearSession","withSessionLock","run","previous","Promise","resolve","running","then","queueItem","undefined","resetSessionStore","state","savedAt","Date","toISOString","activeSession","toPersistedActiveSession","historySession","toPersistedSessionSnapshot","getOrCreateWorkspaceSessions","history","historyBySessionKey","threadId","push","updatedAt","writePersistedSessionsFile","existsSync","initialContent","JSON","stringify","createEmptyPersistedSessionsFile","writeFileSync","encoding","flag","version","raw","readFileSync","error","Error","formatError","parsed","parse","parsePersistedSessionsFile","value","isObject","TypeError","workspaceValue","Object","entries","parseWorkspaceSessions","parseWorkspaceActiveSession","parseWorkspaceHistorySessions","parsePersistedActiveSession","entryKey","historyValue","trim","length","Array","isArray","map","item","index","parsePersistedSessionSnapshot","location","parseNonEmptyString","snapshot","model","mode","title","normalizeOptionalTitle","cwd","normalized","existing","created","tempPath","now","Math","random","toString","slice","content","renameSync","rmSync","force","message","String","getSessionKey","getHelpText","parseCommand","MAX_SESSION_TITLE_LENGTH","handleIncomingText","input","deps","sessionKey","chatType","chatId","userId","senderId","withSessionLock","parsed","text","currentSession","getSession","type","message","t","title","normalizeSessionTitle","threadId","mode","model","join","result","listOpenProjects","roots","length","lines","map","root","index","error","formatProjectsError","clearSession","setSession","created","createThread","cwd","formatCodexError","runTurn","prompt","session","resolveSessionTitle","Error","trim","currentTitle","undefined","buildFallbackSessionTitle","normalizedPrompt","normalizePrompt","truncateTitle","normalized","chars","Array","from","slice","replace","shouldProcessMessage","event","botOpenId","isMessageFromBot","message","chat_type","shouldHandleGroupMessage","mentions","length","some","mention","id","open_id","resolveSenderId","senderId","user_id","union_id","senderType","sender","sender_type","toLowerCase","sender_id","isPlainObject","isMessageFromBot","resolveSenderId","shouldHandleGroupMessage","buildReplyForMessageEvent","event","deps","botOpenId","message","message_type","t","text","parseTextContent","content","chat_type","mentions","senderId","sender","sender_id","normalizedText","stripMentionTags","trim","length","handleIncomingText","chatType","chatId","chat_id","replace","parsed","JSON","parse","isPlainObject","parseRpcLine","line","parsed","JSON","parse","method","isRpcRequestId","id","params","isRpcErrorObject","error","result","formatRpcError","code","message","value","isRpcErrorResponse","isRpcSuccessResponse","isRpcServerRequest","getServerRequestResult","decision","acceptSettings","forSession","endsWith","answers","success","contentItems","type","text","spawn","createInterface","formatRpcError","getServerRequestResult","isRpcErrorResponse","isRpcServerRequest","isRpcSuccessResponse","parseRpcLine","CodexAppServerClient","setNotificationHandler","handler","notificationHandler","request","method","params","exited","Error","buildExitMessage","requestId","nextId","responsePromise","Promise","resolve","reject","pending","set","value","payload","JSON","stringify","jsonrpc","id","child","stdin","write","error","delete","dispose","lineReader","close","killed","kill","handleStdoutLine","line","parsed","respondToServerRequest","catch","stderrBuffer","push","String","get","result","sendRpcResult","sendRpcError","writeRpcPayload","code","message","serialized","signal","suffix","length","at","options","Map","commandArgs","codexBin","cwd","stdio","input","stdout","crlfDelay","Infinity","on","stderr","chunk","text","trim","values","clear","isPlainObject","initializeClient","client","request","clientInfo","name","title","version","capabilities","experimentalApi","getCollaborationModes","raw","isCollaborationModeListResponse","Error","data","openThread","session","cwd","startThread","resumed","resumeThread","threadId","error","isThreadMissingError","approvalPolicy","sandbox","experimentalRawEvents","parseThreadResult","selectCollaborationModePayload","masks","mode","model","selected","find","mask","toLowerCase","settings","reasoning_effort","developer_instructions","isThreadResult","thread","id","message","includes","isCollaborationModeMask","value","modeIsValid","Array","isArray","every","isPlainObject","createTurnAccumulator","turnCompleted","turnError","lastAgentMessageByItem","lastAgentMessageByTask","applyTurnNotification","accumulator","notification","method","params","message","item","type","text","msg","last_agent_message","turn","error","status","resolveTurnMessage","CodexAppServerClient","getCollaborationModes","initializeClient","openThread","selectCollaborationModePayload","startThread","applyTurnNotification","createTurnAccumulator","resolveTurnMessage","formatRpcError","parseRpcLine","DEFAULT_CODEX_BIN","createCodexThread","input","client","cwd","codexBin","runWithOptionalTimeout","opened","threadId","model","mode","timeoutMs","dispose","runCodexTurn","accumulator","turnDone","createDeferred","turnDoneResolved","setNotificationHandler","notification","turnCompleted","resolve","modeMasks","session","collaborationMode","request","type","text","prompt","text_elements","promise","turnError","Error","message","trim","length","reject","run","onTimeout","withTimeout","Promise","innerResolve","innerReject","timeoutHandle","timeoutPromise","_","setTimeout","race","clearTimeout","process","listOpenProjects","roots","cwd","i18n","messages","enMessages","zhMessages","DEFAULT_LOCALE","detectDefaultLocale","CATALOGS","en","zh","activeLocale","initializeI18n","locale","resolved","resolveLocale","loadAndActivate","getCurrentLocale","ensureI18nInitialized","isSupportedLocale","getDefaultLocale","mappedLocale","mapToAppLocale","systemLocale","readSystemLocale","Intl","DateTimeFormat","resolvedOptions","undefined","normalized","trim","length","toLowerCase","replaceAll","startsWith","fs","os","path","process","getDefaultLocale","initializeI18n","isSupportedLocale","DEFAULT_CODEX_BIN","TEMPLATE_ENV_CONFIG","BASE_DOMAIN","APP_ID","APP_SECRET","BOT_OPEN_ID","CODEX_BIN","CODEX_TIMEOUT_MS","TEMPLATE_CONFIG","env","loadRelayConfig","options","homeDir","homedir","workspaceCwd","cwd","configDir","join","configPath","existsSync","ensureConfigTemplate","Error","t","parsed","parseConfigFile","locale","readLocale","localeValue","domain","readRequiredString","appId","appSecret","baseConfig","botOpenId","readOptionalString","codexBin","codexTimeoutMs","readTimeoutMs","mkdirSync","recursive","writeFileSync","JSON","stringify","encoding","flag","raw","readFileSync","error","formatError","parse","isObject","configObject","undefined","value","field","normalized","TypeError","trim","length","Number","isInteger","trimmed","test","parseInt","systemLocale","console","warn","formatInvalidLocale","mapped","mapLocaleToAppLocale","String","Array","isArray","toLowerCase","replaceAll","startsWith","message","process","loadRelayConfig","loadConfigOrExit","error","console","formatStartupError","exit","message","Error","String","t","resolveSenderId","getSession","getSessionKey","FALLBACK_REPLY_TAG","sendReply","larkClient","data","text","options","content","JSON","stringify","formatReplyTextWithThreadId","message","chat_type","im","v1","create","params","receive_id_type","receive_id","chat_id","msg_type","reply","path","message_id","includeThreadTag","trim","replyTag","resolveReplyTag","normalizedText","length","senderId","sender","sender_id","sessionKey","chatType","chatId","userId","session","threadId","process","Lark","isPlainObject","parseCommand","handleIncomingText","shouldProcessMessage","buildReplyForMessageEvent","stripMentionTags","createCodexThread","runCodexTurn","listOpenProjects","loadConfigOrExit","sendReply","initializeI18n","clearSession","getSession","initializeSessionStore","setSession","withSessionLock","relayConfig","locale","homeDir","workspaceCwd","error","message","Error","String","console","t","exit","BUSY_MESSAGE","client","Client","baseConfig","wsClient","WSClient","isTaskRunning","processIncomingEvent","data","reply","botOpenId","input","createThread","mode","cwd","codexBin","timeoutMs","codexTimeoutMs","runTurn","params","includeThreadTag","shouldAttachThreadTag","replyError","rawText","parseEventText","content","normalizedText","trim","length","type","parsed","JSON","parse","text","eventDispatcher","EventDispatcher","register","info","stringify","finally","start"],"sources":["../src/bot/commands.ts","../src/session/store.ts","../src/bot/handler.ts","../src/bot/message-filter.ts","../src/bot/relay.ts","../src/codex/rpc.ts","../src/codex/app-server-client.ts","../src/codex/thread.ts","../src/codex/turn-state.ts","../src/codex/app-server.ts","../src/codex/state.ts","../src/locales/en/messages.po","../src/locales/zh/messages.po","../src/i18n/runtime.ts","../src/core/config.ts","../src/core/startup.ts","../src/feishu/reply.ts","../src/index.ts"],"sourcesContent":["import { t } from '@lingui/core/macro'\nimport type { ChatMode, ParsedCommand } from '../core/types'\n\nconst COMMAND_HELP = '/help'\nconst COMMAND_NEW = '/new'\nconst COMMAND_MODE = '/mode'\nconst COMMAND_STATUS = '/status'\nconst COMMAND_PROJECTS = '/projects'\nconst COMMAND_RESET = '/reset'\n\nexport function getHelpText(): string {\n return [\n t`Available commands:`,\n t`/help - Show help`,\n t`/new [default|plan] - Create a new session`,\n t`/mode <default|plan> - Switch current session mode`,\n t`/status - Show current session status`,\n t`/projects - Show current working directories`,\n t`/reset - Clear current session`,\n ].join('\\n')\n}\n\nexport function parseCommand(input: string): ParsedCommand {\n const normalized = input.trim()\n const helpText = getHelpText()\n\n if (normalized.length === 0) {\n return {\n type: 'invalid',\n message: t`Command cannot be empty.\\n\\n${helpText}`,\n }\n }\n\n if (!normalized.startsWith('/')) {\n return { type: 'prompt', prompt: normalized }\n }\n\n const parts = normalized.split(/\\s+/)\n const command = parts[0]?.toLowerCase()\n\n if (command === COMMAND_HELP) {\n if (parts.length > 1) {\n return {\n type: 'invalid',\n message: t`/help does not accept arguments.\\n\\n${helpText}`,\n }\n }\n\n return { type: 'help' }\n }\n\n if (command === COMMAND_NEW) {\n if (parts.length > 2) {\n return {\n type: 'invalid',\n message: t`/new accepts at most one optional argument: default or plan.\\n\\n${helpText}`,\n }\n }\n\n const modeToken = parts[1]\n if (!modeToken) {\n return { type: 'new', mode: 'default' }\n }\n\n const mode = parseMode(modeToken)\n if (!mode) {\n return {\n type: 'invalid',\n message: t`Invalid mode \"${modeToken}\", only default or plan are supported.\\n\\n${helpText}`,\n }\n }\n\n return { type: 'new', mode }\n }\n\n if (command === COMMAND_MODE) {\n const modeToken = parts[1]\n if (!modeToken || parts.length > 2) {\n return {\n type: 'invalid',\n message: t`/mode requires one argument: default or plan.\\n\\n${helpText}`,\n }\n }\n\n const mode = parseMode(modeToken)\n if (!mode) {\n return {\n type: 'invalid',\n message: t`Invalid mode \"${modeToken}\", only default or plan are supported.\\n\\n${helpText}`,\n }\n }\n\n return { type: 'mode', mode }\n }\n\n if (command === COMMAND_STATUS) {\n if (parts.length > 1) {\n return {\n type: 'invalid',\n message: t`/status does not accept arguments.\\n\\n${helpText}`,\n }\n }\n\n return { type: 'status' }\n }\n\n if (command === COMMAND_PROJECTS) {\n if (parts.length > 1) {\n return {\n type: 'invalid',\n message: t`/projects does not accept arguments.\\n\\n${helpText}`,\n }\n }\n\n return { type: 'projects' }\n }\n\n if (command === COMMAND_RESET) {\n if (parts.length > 1) {\n return {\n type: 'invalid',\n message: t`/reset does not accept arguments.\\n\\n${helpText}`,\n }\n }\n\n return { type: 'reset' }\n }\n\n return {\n type: 'invalid',\n message: t`Unknown command \"${command ?? normalized}\".\\n\\n${helpText}`,\n }\n}\n\nfunction parseMode(input: string): ChatMode | null {\n const normalized = input.toLowerCase()\n if (normalized === 'default' || normalized === 'plan') {\n return normalized\n }\n\n return null\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport type { BotSession, ChatMode, SessionKeyInput } from '../core/types'\n\nconst sessionStore = new Map<string, BotSession>()\nconst sessionQueue = new Map<string, Promise<void>>()\nconst SESSION_FILE_NAME = 'sessions.json'\nconst SESSION_FILE_VERSION = 1 as const\n\ninterface PersistedSessionSnapshot {\n mode: ChatMode\n model: string\n title?: string\n savedAt: string\n}\n\ninterface PersistedActiveSession extends PersistedSessionSnapshot {\n sessionKey: string\n threadId: string\n}\n\ninterface PersistedWorkspaceSessions {\n activeBySessionKey: PersistedActiveSession | null\n historyBySessionKey: Record<string, PersistedSessionSnapshot[]>\n}\n\ninterface PersistedSessionsFile {\n version: typeof SESSION_FILE_VERSION\n updatedAt: string\n workspaces: Record<string, PersistedWorkspaceSessions>\n}\n\ninterface SessionStorePersistenceState {\n filePath: string\n workspaceCwd: string\n data: PersistedSessionsFile\n}\n\nlet persistenceState: SessionStorePersistenceState | null = null\n\nexport function initializeSessionStore(input: {\n homeDir: string\n workspaceCwd: string\n}): void {\n const relayDir = path.join(input.homeDir, '.relay')\n const filePath = path.join(relayDir, SESSION_FILE_NAME)\n\n fs.mkdirSync(relayDir, { recursive: true })\n ensureSessionFileExists(filePath)\n\n const persisted = readPersistedSessionsFile(filePath)\n const workspaceSessions = persisted.workspaces[input.workspaceCwd]\n\n sessionStore.clear()\n sessionQueue.clear()\n\n if (workspaceSessions) {\n if (workspaceSessions.activeBySessionKey) {\n const sessionRef = workspaceSessions.activeBySessionKey\n sessionStore.set(\n sessionRef.sessionKey,\n hydrateSession(sessionRef, input.workspaceCwd),\n )\n }\n }\n\n persistenceState = {\n filePath,\n workspaceCwd: input.workspaceCwd,\n data: persisted,\n }\n}\n\nexport function getSessionKey(input: SessionKeyInput): string {\n if (input.chatType === 'p2p') {\n return `p2p:${input.chatId}`\n }\n\n return `group:${input.chatId}:${input.userId}`\n}\n\nexport function getSession(sessionKey: string): BotSession | undefined {\n return sessionStore.get(sessionKey)\n}\n\nexport function setSession(sessionKey: string, session: BotSession): void {\n sessionStore.set(sessionKey, session)\n persistSetSession(sessionKey, session)\n}\n\nexport function clearSession(sessionKey: string): void {\n sessionStore.delete(sessionKey)\n persistClearSession(sessionKey)\n}\n\nexport async function withSessionLock<T>(\n sessionKey: string,\n run: () => Promise<T>,\n): Promise<T> {\n const previous = sessionQueue.get(sessionKey) ?? Promise.resolve()\n const running = previous.then(\n () => run(),\n () => run(),\n )\n const queueItem = running.then(\n () => undefined,\n () => undefined,\n )\n\n sessionQueue.set(sessionKey, queueItem)\n\n try {\n return await running\n } finally {\n if (sessionQueue.get(sessionKey) === queueItem) {\n sessionQueue.delete(sessionKey)\n }\n }\n}\n\nexport function resetSessionStore(): void {\n sessionStore.clear()\n sessionQueue.clear()\n persistenceState = null\n}\n\nfunction persistSetSession(sessionKey: string, session: BotSession): void {\n const state = persistenceState\n if (!state) {\n return\n }\n\n const savedAt = new Date().toISOString()\n const activeSession = toPersistedActiveSession(sessionKey, session, savedAt)\n const historySession = toPersistedSessionSnapshot(session, savedAt)\n const workspaceSessions = getOrCreateWorkspaceSessions(\n state.data,\n state.workspaceCwd,\n )\n\n workspaceSessions.activeBySessionKey = activeSession\n const history = workspaceSessions.historyBySessionKey[session.threadId] ?? []\n history.push(historySession)\n workspaceSessions.historyBySessionKey[session.threadId] = history\n\n state.data.updatedAt = savedAt\n writePersistedSessionsFile(state.filePath, state.data)\n}\n\nfunction persistClearSession(sessionKey: string): void {\n const state = persistenceState\n if (!state) {\n return\n }\n\n const workspaceSessions = state.data.workspaces[state.workspaceCwd]\n if (!workspaceSessions) {\n return\n }\n\n if (\n !workspaceSessions.activeBySessionKey ||\n workspaceSessions.activeBySessionKey.sessionKey !== sessionKey\n ) {\n return\n }\n\n workspaceSessions.activeBySessionKey = null\n\n state.data.updatedAt = new Date().toISOString()\n writePersistedSessionsFile(state.filePath, state.data)\n}\n\nfunction ensureSessionFileExists(filePath: string): void {\n if (fs.existsSync(filePath)) {\n return\n }\n\n const initialContent = `${JSON.stringify(createEmptyPersistedSessionsFile(), null, 2)}\\n`\n fs.writeFileSync(filePath, initialContent, { encoding: 'utf-8', flag: 'wx' })\n}\n\nfunction createEmptyPersistedSessionsFile(): PersistedSessionsFile {\n return {\n version: SESSION_FILE_VERSION,\n updatedAt: new Date().toISOString(),\n workspaces: {},\n }\n}\n\nfunction readPersistedSessionsFile(filePath: string): PersistedSessionsFile {\n let raw: string\n try {\n raw = fs.readFileSync(filePath, 'utf-8')\n } catch (error) {\n throw new Error(\n `Failed to read relay session index at ${filePath}: ${formatError(error)}`,\n )\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch (error) {\n throw new Error(\n `Invalid JSON in relay session index at ${filePath}: ${formatError(error)}`,\n )\n }\n\n return parsePersistedSessionsFile(parsed, filePath)\n}\n\nfunction parsePersistedSessionsFile(\n value: unknown,\n filePath: string,\n): PersistedSessionsFile {\n if (!isObject(value)) {\n throw new Error(\n `Invalid relay session index at ${filePath}: root must be a JSON object.`,\n )\n }\n\n if (value.version !== SESSION_FILE_VERSION) {\n throw new Error(\n `Invalid relay session index at ${filePath}: version must be ${SESSION_FILE_VERSION}.`,\n )\n }\n\n if (typeof value.updatedAt !== 'string') {\n throw new TypeError(\n `Invalid relay session index at ${filePath}: updatedAt must be a string.`,\n )\n }\n\n if (!isObject(value.workspaces)) {\n throw new Error(\n `Invalid relay session index at ${filePath}: workspaces must be a JSON object.`,\n )\n }\n\n const workspaces: Record<string, PersistedWorkspaceSessions> = {}\n for (const [workspaceCwd, workspaceValue] of Object.entries(\n value.workspaces,\n )) {\n workspaces[workspaceCwd] = parseWorkspaceSessions(\n workspaceValue,\n filePath,\n workspaceCwd,\n )\n }\n\n return {\n version: SESSION_FILE_VERSION,\n updatedAt: value.updatedAt,\n workspaces,\n }\n}\n\nfunction parseWorkspaceSessions(\n value: unknown,\n filePath: string,\n workspaceCwd: string,\n): PersistedWorkspaceSessions {\n if (!isObject(value)) {\n throw new Error(\n `Invalid relay session index at ${filePath}: workspace \"${workspaceCwd}\" must be a JSON object.`,\n )\n }\n\n const activeBySessionKey = parseWorkspaceActiveSession(\n value.activeBySessionKey,\n filePath,\n workspaceCwd,\n )\n const historyBySessionKey = parseWorkspaceHistorySessions(\n value.historyBySessionKey,\n filePath,\n workspaceCwd,\n )\n\n return {\n activeBySessionKey,\n historyBySessionKey,\n }\n}\n\nfunction parseWorkspaceActiveSession(\n value: unknown,\n filePath: string,\n workspaceCwd: string,\n): PersistedActiveSession | null {\n if (value === null || value === undefined) {\n return null\n }\n\n if (!isObject(value)) {\n throw new Error(\n `Invalid relay session index at ${filePath}: activeBySessionKey for workspace \"${workspaceCwd}\" must be a JSON object or null.`,\n )\n }\n\n return parsePersistedActiveSession(\n value,\n filePath,\n `activeBySessionKey for workspace \"${workspaceCwd}\"`,\n )\n}\n\nfunction parseWorkspaceHistorySessions(\n value: unknown,\n filePath: string,\n workspaceCwd: string,\n): Record<string, PersistedSessionSnapshot[]> {\n if (!isObject(value)) {\n throw new Error(\n `Invalid relay session index at ${filePath}: historyBySessionKey for workspace \"${workspaceCwd}\" must be a JSON object.`,\n )\n }\n\n const historyBySessionKey: Record<string, PersistedSessionSnapshot[]> = {}\n for (const [entryKey, historyValue] of Object.entries(value)) {\n if (entryKey.trim().length === 0) {\n throw new Error(\n `Invalid relay session index at ${filePath}: historyBySessionKey key in workspace \"${workspaceCwd}\" must be a non-empty threadId.`,\n )\n }\n\n if (!Array.isArray(historyValue)) {\n throw new TypeError(\n `Invalid relay session index at ${filePath}: historyBySessionKey.${entryKey} must be an array.`,\n )\n }\n\n historyBySessionKey[entryKey] = historyValue.map((item, index) =>\n parsePersistedSessionSnapshot(\n item,\n filePath,\n `historyBySessionKey.${entryKey}[${index}]`,\n ),\n )\n }\n\n return historyBySessionKey\n}\n\nfunction parsePersistedActiveSession(\n value: unknown,\n filePath: string,\n location: string,\n): PersistedActiveSession {\n if (!isObject(value)) {\n throw new Error(\n `Invalid relay session index at ${filePath}: ${location} must be a JSON object.`,\n )\n }\n\n const sessionKey = parseNonEmptyString(\n value.sessionKey,\n filePath,\n `${location}.sessionKey`,\n )\n const threadId = parseNonEmptyString(\n value.threadId,\n filePath,\n `${location}.threadId`,\n )\n const snapshot = parsePersistedSessionSnapshot(value, filePath, location)\n return {\n sessionKey,\n threadId,\n ...snapshot,\n }\n}\n\nfunction parsePersistedSessionSnapshot(\n value: unknown,\n filePath: string,\n location: string,\n): PersistedSessionSnapshot {\n if (!isObject(value)) {\n throw new Error(\n `Invalid relay session index at ${filePath}: ${location} must be a JSON object.`,\n )\n }\n\n const model = parseNonEmptyString(value.model, filePath, `${location}.model`)\n if (value.mode !== 'default' && value.mode !== 'plan') {\n throw new Error(\n `Invalid relay session index at ${filePath}: ${location}.mode must be \"default\" or \"plan\".`,\n )\n }\n\n if (typeof value.savedAt !== 'string') {\n throw new TypeError(\n `Invalid relay session index at ${filePath}: ${location}.savedAt must be a string.`,\n )\n }\n\n const title = normalizeOptionalTitle(value.title)\n return {\n mode: value.mode,\n model,\n title,\n savedAt: value.savedAt,\n }\n}\n\nfunction parseNonEmptyString(\n value: unknown,\n filePath: string,\n location: string,\n): string {\n if (typeof value !== 'string' || value.trim().length === 0) {\n throw new Error(\n `Invalid relay session index at ${filePath}: ${location} must be a non-empty string.`,\n )\n }\n\n return value\n}\n\nfunction hydrateSession(\n sessionRef: PersistedActiveSession,\n cwd: string,\n): BotSession {\n return {\n threadId: sessionRef.threadId,\n mode: sessionRef.mode,\n model: sessionRef.model,\n cwd,\n title: normalizeOptionalTitle(sessionRef.title),\n }\n}\n\nfunction toPersistedActiveSession(\n sessionKey: string,\n session: BotSession,\n savedAt: string,\n): PersistedActiveSession {\n return {\n sessionKey,\n threadId: session.threadId,\n ...toPersistedSessionSnapshot(session, savedAt),\n }\n}\n\nfunction toPersistedSessionSnapshot(\n session: BotSession,\n savedAt: string,\n): PersistedSessionSnapshot {\n const title = normalizeOptionalTitle(session.title)\n\n return {\n mode: session.mode,\n model: session.model,\n title,\n savedAt,\n }\n}\n\nfunction normalizeOptionalTitle(title: unknown): string | undefined {\n if (typeof title !== 'string') {\n return undefined\n }\n\n const normalized = title.trim()\n if (normalized.length === 0) {\n return undefined\n }\n\n return normalized\n}\n\nfunction getOrCreateWorkspaceSessions(\n data: PersistedSessionsFile,\n workspaceCwd: string,\n): PersistedWorkspaceSessions {\n const existing = data.workspaces[workspaceCwd]\n if (existing) {\n return existing\n }\n\n const created: PersistedWorkspaceSessions = {\n activeBySessionKey: null,\n historyBySessionKey: {},\n }\n data.workspaces[workspaceCwd] = created\n return created\n}\n\nfunction writePersistedSessionsFile(\n filePath: string,\n data: PersistedSessionsFile,\n): void {\n const tempPath = `${filePath}.tmp-${Date.now()}-${Math.random().toString(16).slice(2)}`\n const content = `${JSON.stringify(data, null, 2)}\\n`\n\n try {\n fs.writeFileSync(tempPath, content, 'utf-8')\n fs.renameSync(tempPath, filePath)\n } catch (error) {\n try {\n if (fs.existsSync(tempPath)) {\n fs.rmSync(tempPath, { force: true })\n }\n } catch {\n // Best-effort cleanup for temporary file.\n }\n\n throw new Error(\n `Failed to write relay session index at ${filePath}: ${formatError(error)}`,\n )\n }\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction formatError(error: unknown): string {\n if (error instanceof Error) {\n return error.message\n }\n\n return String(error)\n}\n","import { t } from '@lingui/core/macro'\nimport { getSessionKey } from '../session/store'\nimport { getHelpText, parseCommand } from './commands'\nimport type {\n BotSession,\n ChatMode,\n CodexTurnResult,\n HandleIncomingTextInput,\n OpenProjectsResult,\n} from '../core/types'\n\nconst MAX_SESSION_TITLE_LENGTH = 24\n\nexport interface HandleIncomingTextDeps {\n createThread: (mode: ChatMode) => Promise<BotSession>\n runTurn: (params: {\n prompt: string\n mode: ChatMode\n session: BotSession | null\n }) => Promise<CodexTurnResult>\n getSession: (sessionKey: string) => BotSession | undefined\n setSession: (sessionKey: string, session: BotSession) => void\n clearSession: (sessionKey: string) => void\n withSessionLock: <T>(sessionKey: string, run: () => Promise<T>) => Promise<T>\n listOpenProjects: () => Promise<OpenProjectsResult>\n}\n\nexport async function handleIncomingText(\n input: HandleIncomingTextInput,\n deps: HandleIncomingTextDeps,\n): Promise<string> {\n const sessionKey = getSessionKey({\n chatType: input.chatType,\n chatId: input.chatId,\n userId: input.senderId,\n })\n\n return deps.withSessionLock(sessionKey, async () => {\n const parsed = parseCommand(input.text)\n const currentSession = deps.getSession(sessionKey)\n\n if (parsed.type === 'invalid') {\n return parsed.message\n }\n\n if (parsed.type === 'help') {\n return getHelpText()\n }\n\n if (parsed.type === 'status') {\n if (!currentSession) {\n return t`No active session. Send a normal message or use /new to create one.`\n }\n\n const title =\n normalizeSessionTitle(currentSession.title) ?? t`New Session`\n\n return [\n t`Current session status:`,\n t`thread: ${currentSession.threadId}`,\n t`title: ${title}`,\n t`mode: ${currentSession.mode}`,\n t`model: ${currentSession.model}`,\n ].join('\\n')\n }\n\n if (parsed.type === 'projects') {\n try {\n const result = await deps.listOpenProjects()\n if (result.roots.length === 0) {\n return t`No working directories are currently open.`\n }\n\n const lines = result.roots.map(\n (root, index) => t`${index + 1}. ${root}`,\n )\n return [t`Current working directories:`, ...lines].join('\\n')\n } catch (error) {\n return formatProjectsError(error)\n }\n }\n\n if (parsed.type === 'reset') {\n deps.clearSession(sessionKey)\n return t`Current session has been cleared.`\n }\n\n if (parsed.type === 'mode') {\n if (!currentSession) {\n return t`No active session. Send a normal message or use /new to create one first.`\n }\n\n deps.setSession(sessionKey, {\n ...currentSession,\n mode: parsed.mode,\n })\n\n return t`Switched to ${parsed.mode} mode.`\n }\n\n if (parsed.type === 'new') {\n try {\n const created = await deps.createThread(parsed.mode)\n deps.setSession(sessionKey, created)\n return [\n t`Created a new session.`,\n t`thread: ${created.threadId}`,\n t`cwd: ${created.cwd}`,\n t`mode: ${created.mode}`,\n t`model: ${created.model}`,\n ].join('\\n')\n } catch (error) {\n return formatCodexError(error)\n }\n }\n\n try {\n const mode = currentSession?.mode ?? 'default'\n const result = await deps.runTurn({\n prompt: parsed.prompt,\n mode,\n session: currentSession ?? null,\n })\n\n const title = await resolveSessionTitle({\n currentSession,\n prompt: parsed.prompt,\n })\n\n deps.setSession(sessionKey, {\n threadId: result.threadId,\n model: result.model,\n mode: result.mode,\n cwd: result.cwd,\n title,\n })\n return result.message\n } catch (error) {\n return formatCodexError(error)\n }\n })\n}\n\nfunction formatCodexError(error: unknown): string {\n if (error instanceof Error && error.message.trim().length > 0) {\n return t`Codex execution failed: ${error.message}`\n }\n\n return t`Codex execution failed. Please try again later.`\n}\n\nfunction formatProjectsError(error: unknown): string {\n if (error instanceof Error && error.message.trim().length > 0) {\n return t`Failed to read open projects: ${error.message}`\n }\n\n return t`Failed to read open projects. Please try again later.`\n}\n\nasync function resolveSessionTitle(input: {\n currentSession: BotSession | undefined\n prompt: string\n}): Promise<string | undefined> {\n const currentTitle = normalizeSessionTitle(input.currentSession?.title)\n if (currentTitle) {\n return currentTitle\n }\n\n if (!input.currentSession) {\n return undefined\n }\n\n return buildFallbackSessionTitle(input.prompt)\n}\n\nfunction buildFallbackSessionTitle(prompt: string): string {\n const normalizedPrompt = normalizePrompt(prompt)\n if (normalizedPrompt.length === 0) {\n return t`New Session`\n }\n\n return truncateTitle(normalizedPrompt)\n}\n\nfunction normalizeSessionTitle(title: string | undefined): string | null {\n if (!title) {\n return null\n }\n\n const normalized = title.trim()\n if (normalized.length === 0) {\n return null\n }\n\n return normalized\n}\n\nfunction truncateTitle(input: string): string {\n const chars = Array.from(input)\n if (chars.length <= MAX_SESSION_TITLE_LENGTH) {\n return input\n }\n\n if (MAX_SESSION_TITLE_LENGTH <= 3) {\n return chars.slice(0, MAX_SESSION_TITLE_LENGTH).join('')\n }\n\n return `${chars.slice(0, MAX_SESSION_TITLE_LENGTH - 3).join('')}...`\n}\n\nfunction normalizePrompt(input: string): string {\n return input.replace(/\\s+/g, ' ').trim()\n}\n","import type { FeishuMention } from '../core/types'\n\ninterface EventSenderId {\n open_id?: string\n user_id?: string\n union_id?: string\n}\n\ninterface MessageFilterEvent {\n sender: {\n sender_type?: string\n sender_id?: EventSenderId\n }\n message: {\n chat_type: string\n mentions?: FeishuMention[]\n }\n}\n\nexport function shouldProcessMessage(\n event: MessageFilterEvent,\n botOpenId?: string,\n): boolean {\n if (isMessageFromBot(event, botOpenId)) {\n return false\n }\n\n if (event.message.chat_type === 'p2p') {\n return true\n }\n\n return shouldHandleGroupMessage(event.message.mentions, botOpenId)\n}\n\nexport function shouldHandleGroupMessage(\n mentions: FeishuMention[] | undefined,\n botOpenId?: string,\n): boolean {\n if (!mentions || mentions.length === 0) {\n return false\n }\n\n if (!botOpenId) {\n return true\n }\n\n return mentions.some((mention) => mention.id?.open_id === botOpenId)\n}\n\nexport function resolveSenderId(\n senderId: EventSenderId | undefined,\n): string | null {\n if (!senderId) {\n return null\n }\n\n return senderId.open_id ?? senderId.user_id ?? senderId.union_id ?? null\n}\n\nexport function isMessageFromBot(\n event: MessageFilterEvent,\n botOpenId?: string,\n): boolean {\n const senderType = event.sender.sender_type?.toLowerCase()\n if (senderType === 'app') {\n return true\n }\n\n if (!botOpenId) {\n return false\n }\n\n const senderId = resolveSenderId(event.sender.sender_id)\n return senderId === botOpenId\n}\n","import { t } from '@lingui/core/macro'\nimport { isPlainObject } from 'es-toolkit/predicate'\nimport {\n isMessageFromBot,\n resolveSenderId,\n shouldHandleGroupMessage,\n} from './message-filter'\nimport type { FeishuMention, HandleIncomingTextInput } from '../core/types'\n\nexport interface ReceiveMessageEvent {\n sender: {\n sender_type?: string\n sender_id?: {\n open_id?: string\n user_id?: string\n union_id?: string\n }\n }\n message: {\n chat_id: string\n chat_type: string\n message_type: string\n content: string\n mentions?: FeishuMention[]\n }\n}\n\nexport interface RelayBotDeps {\n botOpenId?: string\n handleIncomingText: (input: HandleIncomingTextInput) => Promise<string>\n}\n\nexport { shouldHandleGroupMessage } from './message-filter'\n\nexport async function buildReplyForMessageEvent(\n event: ReceiveMessageEvent,\n deps: RelayBotDeps,\n): Promise<string | null> {\n if (isMessageFromBot(event, deps.botOpenId)) {\n return null\n }\n\n if (event.message.message_type !== 'text') {\n return t`Failed to parse message. Please send a text message.`\n }\n\n const text = parseTextContent(event.message.content)\n if (!text) {\n return t`Failed to parse message. Please send a text message.`\n }\n\n if (\n event.message.chat_type !== 'p2p' &&\n !shouldHandleGroupMessage(event.message.mentions, deps.botOpenId)\n ) {\n return null\n }\n\n const senderId = resolveSenderId(event.sender.sender_id)\n if (!senderId) {\n return t`Cannot identify sender. Please try again later.`\n }\n\n const normalizedText = stripMentionTags(text).trim()\n if (normalizedText.length === 0) {\n return t`Please send a text message.`\n }\n\n return deps.handleIncomingText({\n chatType: event.message.chat_type,\n chatId: event.message.chat_id,\n senderId,\n text: normalizedText,\n })\n}\n\nexport function stripMentionTags(text: string): string {\n return text.replace(/<at\\b[^>]*>.*?<\\/at>/g, '').trim()\n}\n\nfunction parseTextContent(content: string): string | null {\n try {\n const parsed: unknown = JSON.parse(content)\n if (!isPlainObject(parsed)) {\n return null\n }\n\n return typeof parsed.text === 'string' ? parsed.text : null\n } catch {\n return null\n }\n}\n","import { isPlainObject } from 'es-toolkit/predicate'\nimport type {\n RpcErrorObject,\n RpcErrorResponse,\n RpcIncomingLine,\n RpcRequestId,\n RpcServerRequest,\n RpcSuccessResponse,\n} from '../core/types'\n\nexport function parseRpcLine(line: string): RpcIncomingLine | null {\n let parsed: unknown\n try {\n parsed = JSON.parse(line)\n } catch {\n return null\n }\n\n if (!isPlainObject(parsed)) {\n return null\n }\n\n if (typeof parsed.method === 'string') {\n if (isRpcRequestId(parsed.id)) {\n return {\n id: parsed.id,\n method: parsed.method,\n params: parsed.params,\n }\n }\n\n return {\n method: parsed.method,\n params: parsed.params,\n }\n }\n\n if (!isRpcRequestId(parsed.id)) {\n return null\n }\n\n if ('error' in parsed && isRpcErrorObject(parsed.error)) {\n return {\n id: parsed.id,\n error: parsed.error,\n }\n }\n\n if ('result' in parsed) {\n return {\n id: parsed.id,\n result: parsed.result,\n }\n }\n\n return null\n}\n\nexport function formatRpcError(error: RpcErrorObject): string {\n return `Codex RPC error (${error.code}): ${error.message}`\n}\n\nexport function isRpcRequestId(value: unknown): value is RpcRequestId {\n return typeof value === 'number' || typeof value === 'string'\n}\n\nexport function isRpcErrorObject(value: unknown): value is RpcErrorObject {\n if (!isPlainObject(value)) {\n return false\n }\n\n return typeof value.code === 'number' && typeof value.message === 'string'\n}\n\nexport function isRpcErrorResponse(\n value: RpcIncomingLine,\n): value is RpcErrorResponse {\n return 'error' in value\n}\n\nexport function isRpcSuccessResponse(\n value: RpcIncomingLine,\n): value is RpcSuccessResponse<unknown> {\n return 'result' in value\n}\n\nexport function isRpcServerRequest(\n value: RpcIncomingLine,\n): value is RpcServerRequest<unknown> {\n return 'method' in value && 'id' in value\n}\n\nexport function getServerRequestResult(method: string): unknown | null {\n if (method === 'item/commandExecution/requestApproval') {\n return {\n decision: 'accept',\n acceptSettings: {\n forSession: true,\n },\n }\n }\n\n if (method === 'item/fileChange/requestApproval') {\n return { decision: 'accept' }\n }\n\n if (method.endsWith('/requestApproval')) {\n return { decision: 'accept' }\n }\n\n if (method === 'execCommandApproval') {\n return { decision: 'allow' }\n }\n\n if (method === 'applyPatchApproval') {\n return { decision: 'allow' }\n }\n\n if (method.endsWith('Approval')) {\n return { decision: 'allow' }\n }\n\n if (method === 'item/tool/requestUserInput') {\n return { answers: {} }\n }\n\n if (method === 'item/tool/call') {\n return {\n success: false,\n contentItems: [\n {\n type: 'inputText',\n text: 'Dynamic tool calls are unavailable in relay-bot.',\n },\n ],\n }\n }\n\n return null\n}\n","import { spawn } from 'node:child_process'\nimport { createInterface } from 'node:readline'\nimport {\n formatRpcError,\n getServerRequestResult,\n isRpcErrorResponse,\n isRpcServerRequest,\n isRpcSuccessResponse,\n parseRpcLine,\n} from './rpc'\nimport type { ChildProcessWithoutNullStreams } from 'node:child_process'\nimport type { Interface } from 'node:readline'\nimport type {\n RpcNotification,\n RpcRequestId,\n RpcServerRequest,\n} from '../core/types'\n\ninterface PendingRequest {\n resolve: (value: unknown) => void\n reject: (reason: Error) => void\n}\n\nexport class CodexAppServerClient {\n private readonly options: {\n cwd: string\n codexBin: string\n }\n\n private readonly child: ChildProcessWithoutNullStreams\n\n private readonly pending = new Map<RpcRequestId, PendingRequest>()\n\n private readonly stderrBuffer: string[] = []\n\n private readonly lineReader: Interface\n\n private nextId = 1\n\n private notificationHandler:\n | ((notification: RpcNotification<unknown>) => void)\n | null = null\n\n private exited = false\n\n constructor(options: { cwd: string; codexBin: string }) {\n this.options = options\n const commandArgs = ['app-server']\n\n this.child = spawn(this.options.codexBin, commandArgs, {\n cwd: this.options.cwd,\n stdio: ['pipe', 'pipe', 'pipe'],\n })\n this.lineReader = createInterface({\n input: this.child.stdout,\n crlfDelay: Infinity,\n })\n\n this.lineReader.on('line', (line) => {\n this.handleStdoutLine(line)\n })\n\n this.child.stderr.on('data', (chunk) => {\n const text = String(chunk).trim()\n if (text.length > 0) {\n this.stderrBuffer.push(text)\n }\n })\n\n this.child.on('exit', (code, signal) => {\n this.exited = true\n const error = new Error(this.buildExitMessage(code, signal))\n for (const pending of this.pending.values()) {\n pending.reject(error)\n }\n this.pending.clear()\n })\n }\n\n setNotificationHandler(\n handler: (notification: RpcNotification<unknown>) => void,\n ): void {\n this.notificationHandler = handler\n }\n\n async request<T>(method: string, params: unknown): Promise<T> {\n if (this.exited) {\n throw new Error(this.buildExitMessage(null, null))\n }\n\n const requestId = this.nextId\n this.nextId += 1\n\n const responsePromise = new Promise<T>((resolve, reject) => {\n this.pending.set(requestId, {\n resolve: (value: unknown) => resolve(value as T),\n reject,\n })\n })\n\n const payload = JSON.stringify({\n jsonrpc: '2.0',\n id: requestId,\n method,\n params,\n })\n\n await new Promise<void>((resolve, reject) => {\n this.child.stdin.write(`${payload}\\n`, (error) => {\n if (error) {\n this.pending.delete(requestId)\n reject(error)\n return\n }\n resolve()\n })\n })\n\n return responsePromise\n }\n\n dispose(): void {\n this.lineReader.close()\n if (!this.child.killed) {\n this.child.kill('SIGTERM')\n }\n }\n\n private handleStdoutLine(line: string): void {\n const parsed = parseRpcLine(line)\n if (!parsed) {\n return\n }\n\n if ('method' in parsed) {\n if (isRpcServerRequest(parsed)) {\n void this.respondToServerRequest(parsed).catch((error) => {\n this.stderrBuffer.push(\n `failed to respond to server request \"${parsed.method}\": ${String(\n error,\n )}`,\n )\n })\n return\n }\n\n this.notificationHandler?.(parsed)\n return\n }\n\n const pending = this.pending.get(parsed.id)\n if (!pending) {\n return\n }\n\n this.pending.delete(parsed.id)\n if (isRpcErrorResponse(parsed)) {\n pending.reject(new Error(formatRpcError(parsed.error)))\n return\n }\n\n if (isRpcSuccessResponse(parsed)) {\n pending.resolve(parsed.result)\n }\n }\n\n private async respondToServerRequest(\n request: RpcServerRequest<unknown>,\n ): Promise<void> {\n const result = getServerRequestResult(request.method)\n if (result !== null) {\n await this.sendRpcResult(request.id, result)\n return\n }\n\n await this.sendRpcError(\n request.id,\n -32601,\n `Unsupported server request method: ${request.method}`,\n )\n }\n\n private async sendRpcResult(\n id: RpcRequestId,\n result: unknown,\n ): Promise<void> {\n await this.writeRpcPayload({\n jsonrpc: '2.0',\n id,\n result,\n })\n }\n\n private async sendRpcError(\n id: RpcRequestId,\n code: number,\n message: string,\n ): Promise<void> {\n await this.writeRpcPayload({\n jsonrpc: '2.0',\n id,\n error: {\n code,\n message,\n },\n })\n }\n\n private async writeRpcPayload(payload: {\n jsonrpc: '2.0'\n id: RpcRequestId\n result?: unknown\n error?: {\n code: number\n message: string\n }\n }): Promise<void> {\n if (this.exited) {\n return\n }\n\n const serialized = JSON.stringify(payload)\n await new Promise<void>((resolve, reject) => {\n this.child.stdin.write(`${serialized}\\n`, (error) => {\n if (error) {\n reject(error)\n return\n }\n resolve()\n })\n })\n }\n\n private buildExitMessage(\n code: number | null,\n signal: NodeJS.Signals | null,\n ): string {\n const suffix =\n this.stderrBuffer.length > 0\n ? `; stderr: ${this.stderrBuffer.at(-1)}`\n : ''\n return `Codex app-server exited (code=${code ?? 'null'}, signal=${\n signal ?? 'null'\n })${suffix}`\n }\n}\n","import { isPlainObject } from 'es-toolkit/predicate'\nimport type {\n BotSession,\n ChatMode,\n CollaborationModeListResponse,\n CollaborationModeMask,\n ThreadResult,\n} from '../core/types'\nimport type { CodexAppServerClient } from './app-server-client'\n\nexport interface OpenThreadResult {\n threadId: string\n cwd: string\n model: string\n}\n\nexport interface CollaborationModePayload {\n mode: ChatMode\n settings: {\n model: string\n reasoning_effort: string | null\n developer_instructions: string | null\n }\n}\n\nexport async function initializeClient(\n client: CodexAppServerClient,\n): Promise<void> {\n await client.request('initialize', {\n clientInfo: {\n name: 'relay-bot',\n title: 'Relay Bot',\n version: '0.0.0',\n },\n capabilities: {\n experimentalApi: true,\n },\n })\n}\n\nexport async function getCollaborationModes(\n client: CodexAppServerClient,\n): Promise<CollaborationModeMask[]> {\n const raw = await client.request('collaborationMode/list', {})\n if (!isCollaborationModeListResponse(raw)) {\n throw new Error('Invalid collaboration mode response from Codex')\n }\n\n return raw.data\n}\n\nexport async function openThread(\n client: CodexAppServerClient,\n session: BotSession | null,\n cwd: string,\n): Promise<OpenThreadResult> {\n if (!session) {\n return startThread(client, cwd)\n }\n\n if (session.cwd !== cwd) {\n return startThread(client, cwd)\n }\n\n try {\n const resumed = await resumeThread(client, session.threadId)\n if (resumed.cwd !== cwd) {\n return startThread(client, cwd)\n }\n\n return resumed\n } catch (error) {\n if (isThreadMissingError(error)) {\n return startThread(client, cwd)\n }\n throw error\n }\n}\n\nexport async function startThread(\n client: CodexAppServerClient,\n cwd: string,\n): Promise<OpenThreadResult> {\n const raw = await client.request('thread/start', {\n cwd,\n approvalPolicy: 'on-request',\n sandbox: 'workspace-write',\n experimentalRawEvents: false,\n })\n\n return parseThreadResult(raw)\n}\n\nexport function selectCollaborationModePayload(\n masks: CollaborationModeMask[],\n mode: ChatMode,\n model: string,\n): CollaborationModePayload {\n const selected = masks.find((mask) => {\n if (mask.mode === mode) {\n return true\n }\n\n return mask.name.toLowerCase() === mode\n })\n\n if (!selected) {\n throw new Error(`Collaboration mode \"${mode}\" is unavailable`)\n }\n\n return {\n mode,\n settings: {\n model,\n reasoning_effort: selected.reasoning_effort,\n developer_instructions: selected.developer_instructions,\n },\n }\n}\n\nasync function resumeThread(\n client: CodexAppServerClient,\n threadId: string,\n): Promise<OpenThreadResult> {\n const raw = await client.request('thread/resume', {\n threadId,\n })\n\n return parseThreadResult(raw)\n}\n\nfunction parseThreadResult(raw: unknown): OpenThreadResult {\n if (!isThreadResult(raw)) {\n throw new Error('Invalid thread response from Codex')\n }\n\n return {\n threadId: raw.thread.id,\n model: raw.model,\n cwd: raw.cwd,\n }\n}\n\nfunction isThreadMissingError(error: unknown): boolean {\n if (!(error instanceof Error)) {\n return false\n }\n\n return error.message.includes('thread not found')\n}\n\nfunction isCollaborationModeMask(\n value: unknown,\n): value is CollaborationModeMask {\n if (!isPlainObject(value)) {\n return false\n }\n\n const modeIsValid =\n value.mode === null || value.mode === 'default' || value.mode === 'plan'\n\n return (\n typeof value.name === 'string' &&\n modeIsValid &&\n (typeof value.model === 'string' || value.model === null) &&\n (typeof value.reasoning_effort === 'string' ||\n value.reasoning_effort === null) &&\n (typeof value.developer_instructions === 'string' ||\n value.developer_instructions === null)\n )\n}\n\nfunction isCollaborationModeListResponse(\n value: unknown,\n): value is CollaborationModeListResponse {\n if (!isPlainObject(value) || !Array.isArray(value.data)) {\n return false\n }\n\n return value.data.every(isCollaborationModeMask)\n}\n\nfunction isThreadResult(value: unknown): value is ThreadResult {\n if (!isPlainObject(value) || !isPlainObject(value.thread)) {\n return false\n }\n\n return typeof value.thread.id === 'string' && typeof value.model === 'string'\n}\n","import { isPlainObject } from 'es-toolkit/predicate'\nimport type {\n RpcItemCompletedParams,\n RpcNotification,\n RpcTaskCompleteParams,\n RpcTurnCompletedParams,\n TurnAccumulator,\n} from '../core/types'\n\nexport function createTurnAccumulator(): TurnAccumulator {\n return {\n turnCompleted: false,\n turnError: null,\n lastAgentMessageByItem: null,\n lastAgentMessageByTask: null,\n }\n}\n\nexport function applyTurnNotification(\n accumulator: TurnAccumulator,\n notification: RpcNotification<unknown>,\n): void {\n if (notification.method === 'error') {\n if (\n isPlainObject(notification.params) &&\n typeof notification.params.message === 'string'\n ) {\n accumulator.turnError = notification.params.message\n } else {\n accumulator.turnError = 'Codex returned an unknown error event'\n }\n accumulator.turnCompleted = true\n return\n }\n\n if (notification.method === 'item/completed') {\n const params = notification.params as RpcItemCompletedParams\n const item = params.item\n if (item?.type === 'agentMessage' && typeof item.text === 'string') {\n accumulator.lastAgentMessageByItem = item.text\n }\n return\n }\n\n if (notification.method === 'codex/event/task_complete') {\n const params = notification.params as RpcTaskCompleteParams\n const message = params.msg?.last_agent_message\n if (typeof message === 'string') {\n accumulator.lastAgentMessageByTask = message\n }\n return\n }\n\n if (notification.method === 'turn/completed') {\n const params = notification.params as RpcTurnCompletedParams\n accumulator.turnCompleted = true\n if (params.turn?.error?.message) {\n accumulator.turnError = params.turn.error.message\n return\n }\n\n if (params.turn?.status === 'failed') {\n accumulator.turnError = 'Codex turn failed'\n }\n }\n}\n\nexport function resolveTurnMessage(\n accumulator: TurnAccumulator,\n): string | null {\n return (\n accumulator.lastAgentMessageByTask ?? accumulator.lastAgentMessageByItem\n )\n}\n","import { CodexAppServerClient } from './app-server-client'\nimport {\n getCollaborationModes,\n initializeClient,\n openThread,\n selectCollaborationModePayload,\n startThread,\n} from './thread'\nimport {\n applyTurnNotification,\n createTurnAccumulator,\n resolveTurnMessage,\n} from './turn-state'\nimport type { BotSession, ChatMode, CodexTurnResult } from '../core/types'\n\nexport { formatRpcError, parseRpcLine } from './rpc'\nexport {\n applyTurnNotification,\n createTurnAccumulator,\n resolveTurnMessage,\n} from './turn-state'\n\nconst DEFAULT_CODEX_BIN = 'codex'\n\ninterface Deferred<T> {\n promise: Promise<T>\n resolve: (value: T | PromiseLike<T>) => void\n reject: (reason?: unknown) => void\n}\n\nexport interface RunCodexTurnInput {\n prompt: string\n mode: ChatMode\n session: BotSession | null\n cwd: string\n codexBin?: string\n timeoutMs?: number\n}\n\nexport interface CreateCodexThreadInput {\n mode: ChatMode\n cwd: string\n codexBin?: string\n timeoutMs?: number\n}\n\nexport async function createCodexThread(\n input: CreateCodexThreadInput,\n): Promise<BotSession> {\n const client = new CodexAppServerClient({\n cwd: input.cwd,\n codexBin: input.codexBin ?? DEFAULT_CODEX_BIN,\n })\n\n try {\n return await runWithOptionalTimeout(\n async () => {\n await initializeClient(client)\n const opened = await startThread(client, input.cwd)\n return {\n threadId: opened.threadId,\n model: opened.model,\n mode: input.mode,\n cwd: opened.cwd,\n }\n },\n input.timeoutMs,\n () => client.dispose(),\n )\n } finally {\n client.dispose()\n }\n}\n\nexport async function runCodexTurn(\n input: RunCodexTurnInput,\n): Promise<CodexTurnResult> {\n const client = new CodexAppServerClient({\n cwd: input.cwd,\n codexBin: input.codexBin ?? DEFAULT_CODEX_BIN,\n })\n\n const accumulator = createTurnAccumulator()\n const turnDone = createDeferred<void>()\n let turnDoneResolved = false\n\n client.setNotificationHandler((notification) => {\n applyTurnNotification(accumulator, notification)\n if (accumulator.turnCompleted && !turnDoneResolved) {\n turnDoneResolved = true\n turnDone.resolve()\n }\n })\n\n try {\n return await runWithOptionalTimeout(\n async () => {\n await initializeClient(client)\n const modeMasks = await getCollaborationModes(client)\n const opened = await openThread(client, input.session, input.cwd)\n const collaborationMode = selectCollaborationModePayload(\n modeMasks,\n input.mode,\n opened.model,\n )\n\n await client.request('turn/start', {\n threadId: opened.threadId,\n input: [\n {\n type: 'text',\n text: input.prompt,\n text_elements: [],\n },\n ],\n collaborationMode,\n })\n\n await turnDone.promise\n\n if (accumulator.turnError) {\n throw new Error(accumulator.turnError)\n }\n\n const message = resolveTurnMessage(accumulator)\n if (!message || message.trim().length === 0) {\n throw new Error('Codex did not return a message')\n }\n\n return {\n threadId: opened.threadId,\n model: opened.model,\n mode: input.mode,\n message,\n cwd: opened.cwd,\n }\n },\n input.timeoutMs,\n () => {\n if (!turnDoneResolved) {\n turnDoneResolved = true\n turnDone.reject(new Error('Codex execution timed out'))\n }\n client.dispose()\n },\n )\n } finally {\n client.dispose()\n }\n}\n\nasync function runWithOptionalTimeout<T>(\n run: () => Promise<T>,\n timeoutMs: number | undefined,\n onTimeout: () => void,\n): Promise<T> {\n if (typeof timeoutMs !== 'number' || timeoutMs <= 0) {\n return run()\n }\n\n return withTimeout(run, timeoutMs, onTimeout)\n}\n\nfunction createDeferred<T>(): Deferred<T> {\n let resolve!: (value: T | PromiseLike<T>) => void\n let reject!: (reason?: unknown) => void\n const promise = new Promise<T>((innerResolve, innerReject) => {\n resolve = innerResolve\n reject = innerReject\n })\n\n return { promise, resolve, reject }\n}\n\nasync function withTimeout<T>(\n run: () => Promise<T>,\n timeoutMs: number,\n onTimeout: () => void,\n): Promise<T> {\n let timeoutHandle: NodeJS.Timeout | undefined\n const timeoutPromise = new Promise<T>((_, reject) => {\n timeoutHandle = setTimeout(() => {\n onTimeout()\n reject(new Error(`Codex request timed out after ${timeoutMs}ms`))\n }, timeoutMs)\n })\n\n try {\n return await Promise.race([run(), timeoutPromise])\n } finally {\n if (timeoutHandle) {\n clearTimeout(timeoutHandle)\n }\n }\n}\n","import process from 'node:process'\nimport type { OpenProjectsResult } from '../core/types'\n\nexport async function listOpenProjects(): Promise<OpenProjectsResult> {\n return {\n roots: [process.cwd()],\n }\n}\n","msgid \"\"\nmsgstr \"\"\n\"POT-Creation-Date: 2026-02-20 17:04+0800\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=utf-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"X-Generator: @lingui/cli\\n\"\n\"Language: en\\n\"\n\"Project-Id-Version: \\n\"\n\"Report-Msgid-Bugs-To: \\n\"\n\"PO-Revision-Date: \\n\"\n\"Last-Translator: \\n\"\n\"Language-Team: \\n\"\n\"Plural-Forms: \\n\"\n\n#. placeholder {0}: index + 1\n#: src/bot/handler.ts:75\nmsgid \"{0}. {root}\"\nmsgstr \"{0}. {root}\"\n\n#: src/bot/commands.ts:14\nmsgid \"/help - Show help\"\nmsgstr \"/help - Show help\"\n\n#: src/bot/commands.ts:45\nmsgid \"\"\n\"/help does not accept arguments.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"/help does not accept arguments.\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/commands.ts:16\nmsgid \"/mode <default|plan> - Switch current session mode\"\nmsgstr \"/mode <default|plan> - Switch current session mode\"\n\n#: src/bot/commands.ts:81\nmsgid \"\"\n\"/mode requires one argument: default or plan.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"/mode requires one argument: default or plan.\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/commands.ts:15\nmsgid \"/new [default|plan] - Create a new session\"\nmsgstr \"/new [default|plan] - Create a new session\"\n\n#: src/bot/commands.ts:56\nmsgid \"\"\n\"/new accepts at most one optional argument: default or plan.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"/new accepts at most one optional argument: default or plan.\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/commands.ts:18\nmsgid \"/projects - Show current working directories\"\nmsgstr \"/projects - Show current working directories\"\n\n#: src/bot/commands.ts:111\nmsgid \"\"\n\"/projects does not accept arguments.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"/projects does not accept arguments.\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/commands.ts:19\nmsgid \"/reset - Clear current session\"\nmsgstr \"/reset - Clear current session\"\n\n#: src/bot/commands.ts:122\nmsgid \"\"\n\"/reset does not accept arguments.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"/reset does not accept arguments.\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/commands.ts:17\nmsgid \"/status - Show current session status\"\nmsgstr \"/status - Show current session status\"\n\n#: src/bot/commands.ts:100\nmsgid \"\"\n\"/status does not accept arguments.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"/status does not accept arguments.\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/commands.ts:13\nmsgid \"Available commands:\"\nmsgstr \"Available commands:\"\n\n#: src/bot/relay.ts:61\nmsgid \"Cannot identify sender. Please try again later.\"\nmsgstr \"Cannot identify sender. Please try again later.\"\n\n#. placeholder {0}: error.message\n#: src/bot/handler.ts:146\nmsgid \"Codex execution failed: {0}\"\nmsgstr \"Codex execution failed: {0}\"\n\n#: src/bot/handler.ts:149\nmsgid \"Codex execution failed. Please try again later.\"\nmsgstr \"Codex execution failed. Please try again later.\"\n\n#: src/bot/commands.ts:30\nmsgid \"\"\n\"Command cannot be empty.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"Command cannot be empty.\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/handler.ts:106\nmsgid \"Created a new session.\"\nmsgstr \"Created a new session.\"\n\n#: src/bot/handler.ts:85\nmsgid \"Current session has been cleared.\"\nmsgstr \"Current session has been cleared.\"\n\n#: src/bot/handler.ts:59\nmsgid \"Current session status:\"\nmsgstr \"Current session status:\"\n\n#: src/bot/handler.ts:77\nmsgid \"Current working directories:\"\nmsgstr \"Current working directories:\"\n\n#: src/index.ts:37\nmsgid \"Currently busy. Please try again later.\"\nmsgstr \"Currently busy. Please try again later.\"\n\n#. placeholder {0}: created.cwd\n#: src/bot/handler.ts:108\nmsgid \"cwd: {0}\"\nmsgstr \"cwd: {0}\"\n\n#: src/bot/relay.ts:44\n#: src/bot/relay.ts:49\nmsgid \"Failed to parse message. Please send a text message.\"\nmsgstr \"Failed to parse message. Please send a text message.\"\n\n#: src/index.ts:86\nmsgid \"Failed to process message. Please try again later.\"\nmsgstr \"Failed to process message. Please try again later.\"\n\n#. placeholder {0}: error.message\n#: src/bot/handler.ts:154\nmsgid \"Failed to read open projects: {0}\"\nmsgstr \"Failed to read open projects: {0}\"\n\n#: src/bot/handler.ts:157\nmsgid \"Failed to read open projects. Please try again later.\"\nmsgstr \"Failed to read open projects. Please try again later.\"\n\n#. placeholder {0}: formatError(error)\n#: src/core/config.ts:131\nmsgid \"Failed to read relay config at {configPath}: {0}\"\nmsgstr \"Failed to read relay config at {configPath}: {0}\"\n\n#: src/core/startup.ts:17\n#: src/index.ts:33\nmsgid \"Failed to start relay: {message}\"\nmsgstr \"Failed to start relay: {message}\"\n\n#. placeholder {0}: formatError(error)\n#: src/core/config.ts:140\nmsgid \"Invalid JSON in relay config at {configPath}: {0}\"\nmsgstr \"Invalid JSON in relay config at {configPath}: {0}\"\n\n#: src/bot/commands.ts:69\n#: src/bot/commands.ts:89\nmsgid \"\"\n\"Invalid mode \\\"{modeToken}\\\", only default or plan are supported.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"Invalid mode \\\"{modeToken}\\\", only default or plan are supported.\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/core/config.ts:160\nmsgid \"Invalid relay config at {configPath}: env must be a JSON object.\"\nmsgstr \"Invalid relay config at {configPath}: env must be a JSON object.\"\n\n#: src/core/config.ts:146\nmsgid \"Invalid relay config at {configPath}: root must be a JSON object.\"\nmsgstr \"Invalid relay config at {configPath}: root must be a JSON object.\"\n\n#: src/core/config.ts:174\nmsgid \"Invalid relay config: {field} is required and must be a non-empty string.\"\nmsgstr \"Invalid relay config: {field} is required and must be a non-empty string.\"\n\n#: src/core/config.ts:187\nmsgid \"Invalid relay config: {field} must be a string.\"\nmsgstr \"Invalid relay config: {field} must be a string.\"\n\n#: src/core/config.ts:208\n#: src/core/config.ts:219\n#: src/core/config.ts:227\nmsgid \"Invalid relay config: CODEX_TIMEOUT_MS must be a positive integer.\"\nmsgstr \"Invalid relay config: CODEX_TIMEOUT_MS must be a positive integer.\"\n\n#. placeholder {0}: formatInvalidLocale(value)\n#: src/core/config.ts:241\nmsgid \"Invalid relay config: locale \\\"{0}\\\" is not supported. Falling back to {systemLocale}.\"\nmsgstr \"Invalid relay config: locale \\\"{0}\\\" is not supported. Falling back to {systemLocale}.\"\n\n#: src/core/config.ts:226\n#~ msgid \"Invalid relay config: LOCALE \\\"{0}\\\" is not supported. Falling back to en.\"\n#~ msgstr \"Invalid relay config: LOCALE \\\"{0}\\\" is not supported. Falling back to en.\"\n\n#: src/core/config.ts:257\nmsgid \"Invalid relay config: locale \\\"{normalized}\\\" is not supported. Falling back to {systemLocale}.\"\nmsgstr \"Invalid relay config: locale \\\"{normalized}\\\" is not supported. Falling back to {systemLocale}.\"\n\n#: src/core/config.ts:241\n#~ msgid \"Invalid relay config: LOCALE \\\"{normalized}\\\" is not supported. Falling back to en.\"\n#~ msgstr \"Invalid relay config: LOCALE \\\"{normalized}\\\" is not supported. Falling back to en.\"\n\n#. placeholder {0}: created.mode\n#. placeholder {0}: currentSession.mode\n#: src/bot/handler.ts:62\n#: src/bot/handler.ts:109\nmsgid \"mode: {0}\"\nmsgstr \"mode: {0}\"\n\n#. placeholder {0}: created.model\n#. placeholder {0}: currentSession.model\n#: src/bot/handler.ts:63\n#: src/bot/handler.ts:110\nmsgid \"model: {0}\"\nmsgstr \"model: {0}\"\n\n#: src/bot/handler.ts:56\n#: src/bot/handler.ts:179\nmsgid \"New Session\"\nmsgstr \"New Session\"\n\n#: src/bot/handler.ts:90\nmsgid \"No active session. Send a normal message or use /new to create one first.\"\nmsgstr \"No active session. Send a normal message or use /new to create one first.\"\n\n#: src/bot/handler.ts:52\nmsgid \"No active session. Send a normal message or use /new to create one.\"\nmsgstr \"No active session. Send a normal message or use /new to create one.\"\n\n#: src/bot/handler.ts:71\nmsgid \"No working directories are currently open.\"\nmsgstr \"No working directories are currently open.\"\n\n#: src/bot/relay.ts:66\nmsgid \"Please send a text message.\"\nmsgstr \"Please send a text message.\"\n\n#: src/core/config.ts:80\nmsgid \"Relay config is missing. Template created at {configPath}. Please edit this file and restart.\"\nmsgstr \"Relay config is missing. Template created at {configPath}. Please edit this file and restart.\"\n\n#. placeholder {0}: parsed.mode\n#: src/bot/handler.ts:98\nmsgid \"Switched to {0} mode.\"\nmsgstr \"Switched to {0} mode.\"\n\n#. placeholder {0}: created.threadId\n#. placeholder {0}: currentSession.threadId\n#: src/bot/handler.ts:60\n#: src/bot/handler.ts:107\nmsgid \"thread: {0}\"\nmsgstr \"thread: {0}\"\n\n#: src/bot/handler.ts:61\nmsgid \"title: {title}\"\nmsgstr \"title: {title}\"\n\n#. placeholder {0}: command ?? normalized\n#: src/bot/commands.ts:131\nmsgid \"\"\n\"Unknown command \\\"{0}\\\".\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"Unknown command \\\"{0}\\\".\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/handler.ts:263\n#~ msgid \"User message: {0}\"\n#~ msgstr \"User message: {0}\"\n\n#: src/bot/handler.ts:248\n#~ msgid \"\"\n#~ \"You are a session title generator.\\n\"\n#~ \"Generate a short Chinese title based on the user message.\\n\"\n#~ \"Strict requirements:\\n\"\n#~ \"1. Output title text only, with no explanation.\\n\"\n#~ \"2. Output a single line with no line breaks.\\n\"\n#~ \"3. Do not use quotes or title marks.\\n\"\n#~ \"4. Keep the title within 24 characters.\"\n#~ msgstr \"\"\n#~ \"You are a session title generator.\\n\"\n#~ \"Generate a short Chinese title based on the user message.\\n\"\n#~ \"Strict requirements:\\n\"\n#~ \"1. Output title text only, with no explanation.\\n\"\n#~ \"2. Output a single line with no line breaks.\\n\"\n#~ \"3. Do not use quotes or title marks.\\n\"\n#~ \"4. Keep the title within 24 characters.\"\n\n#: src/bot/handler.ts:255\n#~ msgid \"\"\n#~ \"You are a session title generator.\\n\"\n#~ \"Generate a short English title based on the user message.\\n\"\n#~ \"Strict requirements:\\n\"\n#~ \"1. Output title text only, with no explanation.\\n\"\n#~ \"2. Output a single line with no line breaks.\\n\"\n#~ \"3. Do not use quotes.\\n\"\n#~ \"4. Keep the title within 24 characters.\"\n#~ msgstr \"\"\n#~ \"You are a session title generator.\\n\"\n#~ \"Generate a short English title based on the user message.\\n\"\n#~ \"Strict requirements:\\n\"\n#~ \"1. Output title text only, with no explanation.\\n\"\n#~ \"2. Output a single line with no line breaks.\\n\"\n#~ \"3. Do not use quotes.\\n\"\n#~ \"4. Keep the title within 24 characters.\"\n","msgid \"\"\nmsgstr \"\"\n\"POT-Creation-Date: 2026-02-20 17:04+0800\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=utf-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"X-Generator: @lingui/cli\\n\"\n\"Language: zh\\n\"\n\"Project-Id-Version: \\n\"\n\"Report-Msgid-Bugs-To: \\n\"\n\"PO-Revision-Date: \\n\"\n\"Last-Translator: \\n\"\n\"Language-Team: \\n\"\n\"Plural-Forms: \\n\"\n\n#. placeholder {0}: index + 1\n#: src/bot/handler.ts:75\nmsgid \"{0}. {root}\"\nmsgstr \"{0}. {root}\"\n\n#: src/bot/commands.ts:14\nmsgid \"/help - Show help\"\nmsgstr \"/help - 显示帮助\"\n\n#: src/bot/commands.ts:45\nmsgid \"\"\n\"/help does not accept arguments.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"/help 不接受参数。\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/commands.ts:16\nmsgid \"/mode <default|plan> - Switch current session mode\"\nmsgstr \"/mode <default|plan> - 切换当前会话模式\"\n\n#: src/bot/commands.ts:81\nmsgid \"\"\n\"/mode requires one argument: default or plan.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"/mode 需要一个参数:default 或 plan。\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/commands.ts:15\nmsgid \"/new [default|plan] - Create a new session\"\nmsgstr \"/new [default|plan] - 创建新会话\"\n\n#: src/bot/commands.ts:56\nmsgid \"\"\n\"/new accepts at most one optional argument: default or plan.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"/new 最多接受一个可选参数:default 或 plan。\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/commands.ts:18\nmsgid \"/projects - Show current working directories\"\nmsgstr \"/projects - 显示当前工作目录\"\n\n#: src/bot/commands.ts:111\nmsgid \"\"\n\"/projects does not accept arguments.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"/projects 不接受参数。\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/commands.ts:19\nmsgid \"/reset - Clear current session\"\nmsgstr \"/reset - 清空当前会话\"\n\n#: src/bot/commands.ts:122\nmsgid \"\"\n\"/reset does not accept arguments.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"/reset 不接受参数。\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/commands.ts:17\nmsgid \"/status - Show current session status\"\nmsgstr \"/status - 显示当前会话状态\"\n\n#: src/bot/commands.ts:100\nmsgid \"\"\n\"/status does not accept arguments.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"/status 不接受参数。\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/commands.ts:13\nmsgid \"Available commands:\"\nmsgstr \"可用命令:\"\n\n#: src/bot/relay.ts:61\nmsgid \"Cannot identify sender. Please try again later.\"\nmsgstr \"无法识别发送者,请稍后重试。\"\n\n#. placeholder {0}: error.message\n#: src/bot/handler.ts:146\nmsgid \"Codex execution failed: {0}\"\nmsgstr \"Codex 执行失败:{0}\"\n\n#: src/bot/handler.ts:149\nmsgid \"Codex execution failed. Please try again later.\"\nmsgstr \"Codex 执行失败,请稍后重试。\"\n\n#: src/bot/commands.ts:30\nmsgid \"\"\n\"Command cannot be empty.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"命令不能为空。\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/handler.ts:106\nmsgid \"Created a new session.\"\nmsgstr \"已创建新会话。\"\n\n#: src/bot/handler.ts:85\nmsgid \"Current session has been cleared.\"\nmsgstr \"当前会话已清空。\"\n\n#: src/bot/handler.ts:59\nmsgid \"Current session status:\"\nmsgstr \"当前会话状态:\"\n\n#: src/bot/handler.ts:77\nmsgid \"Current working directories:\"\nmsgstr \"当前工作目录:\"\n\n#: src/index.ts:37\nmsgid \"Currently busy. Please try again later.\"\nmsgstr \"当前忙碌,请稍后重试。\"\n\n#. placeholder {0}: created.cwd\n#: src/bot/handler.ts:108\nmsgid \"cwd: {0}\"\nmsgstr \"cwd: {0}\"\n\n#: src/bot/relay.ts:44\n#: src/bot/relay.ts:49\nmsgid \"Failed to parse message. Please send a text message.\"\nmsgstr \"解析消息失败,请发送文本消息。\"\n\n#: src/index.ts:86\nmsgid \"Failed to process message. Please try again later.\"\nmsgstr \"处理消息失败,请稍后重试。\"\n\n#. placeholder {0}: error.message\n#: src/bot/handler.ts:154\nmsgid \"Failed to read open projects: {0}\"\nmsgstr \"读取已打开项目失败:{0}\"\n\n#: src/bot/handler.ts:157\nmsgid \"Failed to read open projects. Please try again later.\"\nmsgstr \"读取已打开项目失败,请稍后重试。\"\n\n#. placeholder {0}: formatError(error)\n#: src/core/config.ts:131\nmsgid \"Failed to read relay config at {configPath}: {0}\"\nmsgstr \"读取relay 配置失败 {configPath}:{0}\"\n\n#: src/core/startup.ts:17\n#: src/index.ts:33\nmsgid \"Failed to start relay: {message}\"\nmsgstr \"启动 relay 失败:{message}\"\n\n#. placeholder {0}: formatError(error)\n#: src/core/config.ts:140\nmsgid \"Invalid JSON in relay config at {configPath}: {0}\"\nmsgstr \"relay 配置 {configPath} 中的 JSON 无效:{0}\"\n\n#: src/bot/commands.ts:69\n#: src/bot/commands.ts:89\nmsgid \"\"\n\"Invalid mode \\\"{modeToken}\\\", only default or plan are supported.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"无效的模式 \\\"{modeToken}\\\",仅支持 default 或 plan。\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/core/config.ts:160\nmsgid \"Invalid relay config at {configPath}: env must be a JSON object.\"\nmsgstr \"relay 配置 {configPath} 无效:env 必须是 JSON 对象。\"\n\n#: src/core/config.ts:146\nmsgid \"Invalid relay config at {configPath}: root must be a JSON object.\"\nmsgstr \"relay 配置 {configPath} 无效:root 必须是 JSON 对象。\"\n\n#: src/core/config.ts:174\nmsgid \"Invalid relay config: {field} is required and must be a non-empty string.\"\nmsgstr \"relay 配置无效:{field} 为必填项且必须为非空字符串。\"\n\n#: src/core/config.ts:187\nmsgid \"Invalid relay config: {field} must be a string.\"\nmsgstr \"relay 配置无效:{field} 必须是字符串。\"\n\n#: src/core/config.ts:208\n#: src/core/config.ts:219\n#: src/core/config.ts:227\nmsgid \"Invalid relay config: CODEX_TIMEOUT_MS must be a positive integer.\"\nmsgstr \"relay 配置无效:CODEX_TIMEOUT_MS 必须是正整数。\"\n\n#. placeholder {0}: formatInvalidLocale(value)\n#: src/core/config.ts:241\nmsgid \"Invalid relay config: locale \\\"{0}\\\" is not supported. Falling back to {systemLocale}.\"\nmsgstr \"relay 配置无效:不支持的 locale \\\"{0}\\\",已回退为 {systemLocale}。\"\n\n#: src/core/config.ts:226\n#~ msgid \"Invalid relay config: LOCALE \\\"{0}\\\" is not supported. Falling back to en.\"\n#~ msgstr \"relay 配置无效:不支持的 LOCALE \\\"{0}\\\",已回退为 en。\"\n\n#: src/core/config.ts:257\nmsgid \"Invalid relay config: locale \\\"{normalized}\\\" is not supported. Falling back to {systemLocale}.\"\nmsgstr \"relay 配置无效:不支持的 locale \\\"{normalized}\\\",已回退为 {systemLocale}。\"\n\n#: src/core/config.ts:241\n#~ msgid \"Invalid relay config: LOCALE \\\"{normalized}\\\" is not supported. Falling back to en.\"\n#~ msgstr \"relay 配置无效:不支持的 LOCALE \\\"{normalized}\\\",已回退为 en。\"\n\n#. placeholder {0}: created.mode\n#. placeholder {0}: currentSession.mode\n#: src/bot/handler.ts:62\n#: src/bot/handler.ts:109\nmsgid \"mode: {0}\"\nmsgstr \"mode: {0}\"\n\n#. placeholder {0}: created.model\n#. placeholder {0}: currentSession.model\n#: src/bot/handler.ts:63\n#: src/bot/handler.ts:110\nmsgid \"model: {0}\"\nmsgstr \"model: {0}\"\n\n#: src/bot/handler.ts:56\n#: src/bot/handler.ts:179\nmsgid \"New Session\"\nmsgstr \"新会话\"\n\n#: src/bot/handler.ts:90\nmsgid \"No active session. Send a normal message or use /new to create one first.\"\nmsgstr \"没有活跃会话。请先发送普通消息或使用 /new 创建会话。\"\n\n#: src/bot/handler.ts:52\nmsgid \"No active session. Send a normal message or use /new to create one.\"\nmsgstr \"没有活跃会话。请发送普通消息或使用 /new 创建会话。\"\n\n#: src/bot/handler.ts:71\nmsgid \"No working directories are currently open.\"\nmsgstr \"当前没有打开的工作目录。\"\n\n#: src/bot/relay.ts:66\nmsgid \"Please send a text message.\"\nmsgstr \"请发送文本消息。\"\n\n#: src/core/config.ts:80\nmsgid \"Relay config is missing. Template created at {configPath}. Please edit this file and restart.\"\nmsgstr \"relay 配置缺失,已在 {configPath} 创建模板。请编辑该文件后重启。\"\n\n#. placeholder {0}: parsed.mode\n#: src/bot/handler.ts:98\nmsgid \"Switched to {0} mode.\"\nmsgstr \"已切换到 {0} 模式。\"\n\n#. placeholder {0}: created.threadId\n#. placeholder {0}: currentSession.threadId\n#: src/bot/handler.ts:60\n#: src/bot/handler.ts:107\nmsgid \"thread: {0}\"\nmsgstr \"thread: {0}\"\n\n#: src/bot/handler.ts:61\nmsgid \"title: {title}\"\nmsgstr \"title: {title}\"\n\n#. placeholder {0}: command ?? normalized\n#: src/bot/commands.ts:131\nmsgid \"\"\n\"Unknown command \\\"{0}\\\".\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"未知命令 \\\"{0}\\\"。\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/handler.ts:263\n#~ msgid \"User message: {0}\"\n#~ msgstr \"用户消息:{0}\"\n\n#: src/bot/handler.ts:248\n#~ msgid \"\"\n#~ \"You are a session title generator.\\n\"\n#~ \"Generate a short Chinese title based on the user message.\\n\"\n#~ \"Strict requirements:\\n\"\n#~ \"1. Output title text only, with no explanation.\\n\"\n#~ \"2. Output a single line with no line breaks.\\n\"\n#~ \"3. Do not use quotes or title marks.\\n\"\n#~ \"4. Keep the title within 24 characters.\"\n#~ msgstr \"\"\n#~ \"你是会话标题生成器。\\n\"\n#~ \"根据用户消息生成简短中文标题。\\n\"\n#~ \"严格要求:\\n\"\n#~ \"1. 仅输出标题文字,不要解释。\\n\"\n#~ \"2. 单行输出,不要换行。\\n\"\n#~ \"3. 不要使用引号或书名号。\\n\"\n#~ \"4. 标题不超过 24 个字符。\"\n\n#: src/bot/handler.ts:255\n#~ msgid \"\"\n#~ \"You are a session title generator.\\n\"\n#~ \"Generate a short English title based on the user message.\\n\"\n#~ \"Strict requirements:\\n\"\n#~ \"1. Output title text only, with no explanation.\\n\"\n#~ \"2. Output a single line with no line breaks.\\n\"\n#~ \"3. Do not use quotes.\\n\"\n#~ \"4. Keep the title within 24 characters.\"\n#~ msgstr \"\"\n#~ \"你是会话标题生成器。\\n\"\n#~ \"根据用户消息生成简短英文标题。\\n\"\n#~ \"严格要求:\\n\"\n#~ \"1. 仅输出标题文字,不要解释。\\n\"\n#~ \"2. 单行输出,不要换行。\\n\"\n#~ \"3. 不要使用引号。\\n\"\n#~ \"4. 标题不超过 24 个字符。\"\n","import { i18n } from '@lingui/core'\nimport { messages as enMessages } from '../locales/en/messages.po'\nimport { messages as zhMessages } from '../locales/zh/messages.po'\nimport type { Messages } from '@lingui/core'\n\nexport type AppLocale = 'en' | 'zh'\n\nconst DEFAULT_LOCALE: AppLocale = detectDefaultLocale()\n\nconst CATALOGS: Record<AppLocale, Messages> = {\n en: enMessages as Messages,\n zh: zhMessages as Messages,\n}\n\nlet activeLocale: AppLocale | null = null\n\n// Activate a default locale eagerly so top-level `t` calls in imported modules\n// never run before Lingui has an active locale.\ninitializeI18n(DEFAULT_LOCALE)\n\nexport function initializeI18n(locale?: string): AppLocale {\n const resolved = resolveLocale(locale)\n i18n.loadAndActivate({\n locale: resolved,\n messages: CATALOGS[resolved],\n })\n activeLocale = resolved\n return resolved\n}\n\nexport function getCurrentLocale(): AppLocale {\n ensureI18nInitialized()\n return activeLocale ?? DEFAULT_LOCALE\n}\n\nexport function isSupportedLocale(locale: string): locale is AppLocale {\n return locale === 'en' || locale === 'zh'\n}\n\nexport function getDefaultLocale(): AppLocale {\n return DEFAULT_LOCALE\n}\n\nfunction resolveLocale(locale?: string): AppLocale {\n if (!locale) {\n return DEFAULT_LOCALE\n }\n\n const mappedLocale = mapToAppLocale(locale)\n if (mappedLocale) {\n return mappedLocale\n }\n\n return DEFAULT_LOCALE\n}\n\nfunction ensureI18nInitialized(): void {\n if (!activeLocale) {\n initializeI18n(DEFAULT_LOCALE)\n }\n}\n\nfunction detectDefaultLocale(): AppLocale {\n const systemLocale = readSystemLocale()\n if (!systemLocale) {\n return 'en'\n }\n\n return mapToAppLocale(systemLocale) ?? 'en'\n}\n\nfunction readSystemLocale(): string | undefined {\n const locale = Intl.DateTimeFormat().resolvedOptions().locale\n if (typeof locale !== 'string') {\n return undefined\n }\n\n const normalized = locale.trim()\n if (normalized.length === 0) {\n return undefined\n }\n\n return normalized\n}\n\nfunction mapToAppLocale(locale: string): AppLocale | null {\n const normalized = locale.trim().toLowerCase().replaceAll('_', '-')\n\n if (normalized === 'zh' || normalized.startsWith('zh-')) {\n return 'zh'\n }\n\n if (normalized === 'en' || normalized.startsWith('en-')) {\n return 'en'\n }\n\n return null\n}\n","import fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { t } from '@lingui/core/macro'\nimport {\n getDefaultLocale,\n initializeI18n,\n isSupportedLocale,\n} from '../i18n/runtime'\nimport type { AppLocale } from '../i18n/runtime'\n\nconst DEFAULT_CODEX_BIN = 'codex'\n\nconst TEMPLATE_ENV_CONFIG: Required<RelayConfigEnv> = {\n BASE_DOMAIN: 'https://open.feishu.cn',\n APP_ID: 'your_app_id',\n APP_SECRET: 'your_app_secret',\n BOT_OPEN_ID: 'ou_xxx',\n CODEX_BIN: DEFAULT_CODEX_BIN,\n CODEX_TIMEOUT_MS: null,\n}\n\nconst TEMPLATE_CONFIG: {\n locale?: AppLocale\n env: Required<RelayConfigEnv>\n} = {\n env: TEMPLATE_ENV_CONFIG,\n}\n\nexport interface RelayConfigEnv {\n BASE_DOMAIN?: string\n APP_ID?: string\n APP_SECRET?: string\n BOT_OPEN_ID?: string\n CODEX_BIN?: string\n CODEX_TIMEOUT_MS?: number | string | null\n}\n\ninterface RelayConfigFile extends RelayConfigEnv {\n locale?: string\n env?: RelayConfigEnv\n}\n\ninterface ParsedRelayConfig {\n env: RelayConfigEnv\n localeValue: unknown\n}\n\nexport interface RelayConfig {\n baseConfig: {\n appId: string\n appSecret: string\n domain: string\n }\n homeDir: string\n botOpenId?: string\n codexBin: string\n codexTimeoutMs?: number\n workspaceCwd: string\n locale: AppLocale\n}\n\nexport interface LoadRelayConfigOptions {\n homeDir?: string\n workspaceCwd?: string\n}\n\nexport function loadRelayConfig(\n options: LoadRelayConfigOptions = {},\n): RelayConfig {\n const homeDir = options.homeDir ?? os.homedir()\n const workspaceCwd = options.workspaceCwd ?? process.cwd()\n const configDir = path.join(homeDir, '.relay')\n const configPath = path.join(configDir, 'config.json')\n\n if (!fs.existsSync(configPath)) {\n ensureConfigTemplate(configDir, configPath)\n throw new Error(\n t`Relay config is missing. Template created at ${configPath}. Please edit this file and restart.`,\n )\n }\n\n const parsed = parseConfigFile(configPath)\n const locale = readLocale(parsed.localeValue)\n initializeI18n(locale)\n\n const domain = readRequiredString(parsed.env.BASE_DOMAIN, 'BASE_DOMAIN')\n const appId = readRequiredString(parsed.env.APP_ID, 'APP_ID')\n const appSecret = readRequiredString(parsed.env.APP_SECRET, 'APP_SECRET')\n\n return {\n baseConfig: {\n appId,\n appSecret,\n domain,\n },\n homeDir,\n botOpenId: readOptionalString(parsed.env.BOT_OPEN_ID, 'BOT_OPEN_ID'),\n codexBin:\n readOptionalString(parsed.env.CODEX_BIN, 'CODEX_BIN') ??\n DEFAULT_CODEX_BIN,\n codexTimeoutMs: readTimeoutMs(parsed.env.CODEX_TIMEOUT_MS),\n workspaceCwd,\n locale,\n }\n}\n\nfunction ensureConfigTemplate(configDir: string, configPath: string): void {\n fs.mkdirSync(configDir, { recursive: true })\n if (fs.existsSync(configPath)) {\n return\n }\n\n fs.writeFileSync(\n configPath,\n `${JSON.stringify(TEMPLATE_CONFIG, null, 2)}\\n`,\n {\n encoding: 'utf-8',\n flag: 'wx',\n },\n )\n}\n\nfunction parseConfigFile(configPath: string): ParsedRelayConfig {\n let raw: string\n try {\n raw = fs.readFileSync(configPath, 'utf-8')\n } catch (error) {\n throw new Error(\n t`Failed to read relay config at ${configPath}: ${formatError(error)}`,\n )\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch (error) {\n throw new Error(\n t`Invalid JSON in relay config at ${configPath}: ${formatError(error)}`,\n )\n }\n\n if (!isObject(parsed)) {\n throw new Error(\n t`Invalid relay config at ${configPath}: root must be a JSON object.`,\n )\n }\n\n const configObject = parsed as RelayConfigFile\n if (configObject.env === undefined) {\n return {\n env: configObject,\n localeValue: configObject.locale,\n }\n }\n\n if (!isObject(configObject.env)) {\n throw new Error(\n t`Invalid relay config at ${configPath}: env must be a JSON object.`,\n )\n }\n\n return {\n env: configObject.env,\n localeValue: configObject.locale,\n }\n}\n\nfunction readRequiredString(value: unknown, field: string): string {\n const normalized = readOptionalString(value, field)\n if (!normalized) {\n throw new Error(\n t`Invalid relay config: ${field} is required and must be a non-empty string.`,\n )\n }\n\n return normalized\n}\n\nfunction readOptionalString(value: unknown, field: string): string | undefined {\n if (value === undefined) {\n return undefined\n }\n\n if (typeof value !== 'string') {\n throw new TypeError(t`Invalid relay config: ${field} must be a string.`)\n }\n\n const normalized = value.trim()\n if (normalized.length === 0) {\n return undefined\n }\n\n return normalized\n}\n\nfunction readTimeoutMs(value: unknown): number | undefined {\n if (value === undefined || value === null) {\n return undefined\n }\n\n if (typeof value === 'number') {\n if (Number.isInteger(value) && value > 0) {\n return value\n }\n throw new Error(\n t`Invalid relay config: CODEX_TIMEOUT_MS must be a positive integer.`,\n )\n }\n\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (trimmed.length === 0) {\n return undefined\n }\n if (!/^[1-9]\\d*$/.test(trimmed)) {\n throw new Error(\n t`Invalid relay config: CODEX_TIMEOUT_MS must be a positive integer.`,\n )\n }\n\n return Number.parseInt(trimmed, 10)\n }\n\n throw new Error(\n t`Invalid relay config: CODEX_TIMEOUT_MS must be a positive integer.`,\n )\n}\n\nfunction readLocale(value: unknown): AppLocale {\n // When locale is missing in config, use the system-detected default locale.\n const systemLocale = getDefaultLocale()\n\n if (value === undefined || value === null) {\n return systemLocale\n }\n\n if (typeof value !== 'string') {\n console.warn(\n t`Invalid relay config: locale \"${formatInvalidLocale(value)}\" is not supported. Falling back to ${systemLocale}.`,\n )\n return systemLocale\n }\n\n const normalized = value.trim()\n if (normalized.length === 0) {\n return systemLocale\n }\n\n const mapped = mapLocaleToAppLocale(normalized)\n if (mapped) {\n return mapped\n }\n\n console.warn(\n t`Invalid relay config: locale \"${normalized}\" is not supported. Falling back to ${systemLocale}.`,\n )\n\n return systemLocale\n}\n\nfunction formatInvalidLocale(value: unknown): string {\n if (typeof value === 'string') {\n return value\n }\n\n if (typeof value === 'number' || typeof value === 'boolean') {\n return String(value)\n }\n\n try {\n return JSON.stringify(value)\n } catch {\n return String(value)\n }\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction mapLocaleToAppLocale(value: string): AppLocale | null {\n if (isSupportedLocale(value)) {\n return value\n }\n\n const normalized = value.toLowerCase().replaceAll('_', '-')\n if (normalized === 'zh' || normalized.startsWith('zh-')) {\n return 'zh'\n }\n\n if (normalized === 'en' || normalized.startsWith('en-')) {\n return 'en'\n }\n\n return null\n}\n\nfunction formatError(error: unknown): string {\n if (error instanceof Error) {\n return error.message\n }\n\n return String(error)\n}\n","import process from 'node:process'\nimport { t } from '@lingui/core/macro'\nimport { loadRelayConfig } from './config'\nimport type { RelayConfig } from './config'\n\nexport function loadConfigOrExit(): RelayConfig {\n try {\n return loadRelayConfig()\n } catch (error) {\n console.error(formatStartupError(error))\n process.exit(1)\n }\n}\n\nfunction formatStartupError(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error)\n return t`Failed to start relay: ${message}`\n}\n","import { resolveSenderId } from '../bot/message-filter'\nimport { getSession, getSessionKey } from '../session/store'\nimport type * as Lark from '@larksuiteoapi/node-sdk'\nimport type { ReceiveMessageEvent } from '../bot/relay'\n\nconst FALLBACK_REPLY_TAG = 'no-thread'\n\nexport interface SendReplyOptions {\n includeThreadTag?: boolean\n}\n\nexport interface FeishuReceiveMessageEvent extends ReceiveMessageEvent {\n event_id?: string\n message: ReceiveMessageEvent['message'] & {\n message_id: string\n }\n}\n\nexport async function sendReply(\n larkClient: Lark.Client,\n data: FeishuReceiveMessageEvent,\n text: string,\n options?: SendReplyOptions,\n): Promise<void> {\n const content = JSON.stringify({\n text: formatReplyTextWithThreadId(data, text, options),\n })\n\n if (data.message.chat_type === 'p2p') {\n await larkClient.im.v1.message.create({\n params: {\n receive_id_type: 'chat_id',\n },\n data: {\n receive_id: data.message.chat_id,\n msg_type: 'text',\n content,\n },\n })\n return\n }\n\n await larkClient.im.v1.message.reply({\n path: {\n message_id: data.message.message_id,\n },\n data: {\n msg_type: 'text',\n content,\n },\n })\n}\n\nfunction formatReplyTextWithThreadId(\n data: FeishuReceiveMessageEvent,\n text: string,\n options?: SendReplyOptions,\n): string {\n if (!options?.includeThreadTag) {\n return text.trim()\n }\n\n const replyTag = resolveReplyTag(data)\n const normalizedText = text.trim()\n if (normalizedText.length === 0) {\n return `${replyTag}\\n`\n }\n\n return `${replyTag}\\n\\n${normalizedText}`\n}\n\nfunction resolveReplyTag(data: FeishuReceiveMessageEvent): string {\n const senderId = resolveSenderId(data.sender.sender_id)\n if (!senderId) {\n return FALLBACK_REPLY_TAG\n }\n\n const sessionKey = getSessionKey({\n chatType: data.message.chat_type,\n chatId: data.message.chat_id,\n userId: senderId,\n })\n const session = getSession(sessionKey)\n if (!session || session.threadId.trim().length === 0) {\n return FALLBACK_REPLY_TAG\n }\n\n return session.threadId\n}\n","import process from 'node:process'\nimport * as Lark from '@larksuiteoapi/node-sdk'\nimport { t } from '@lingui/core/macro'\nimport { isPlainObject } from 'es-toolkit/predicate'\nimport { parseCommand } from './bot/commands'\nimport { handleIncomingText } from './bot/handler'\nimport { shouldProcessMessage } from './bot/message-filter'\nimport { buildReplyForMessageEvent, stripMentionTags } from './bot/relay'\nimport { createCodexThread, runCodexTurn } from './codex/app-server'\nimport { listOpenProjects } from './codex/state'\nimport { loadConfigOrExit } from './core/startup'\nimport { sendReply } from './feishu/reply'\nimport { initializeI18n } from './i18n/runtime'\nimport {\n clearSession,\n getSession,\n initializeSessionStore,\n setSession,\n withSessionLock,\n} from './session/store'\nimport type { FeishuReceiveMessageEvent } from './feishu/reply'\n\nconst relayConfig = loadConfigOrExit()\ninitializeI18n(relayConfig.locale)\n\ntry {\n initializeSessionStore({\n homeDir: relayConfig.homeDir,\n workspaceCwd: relayConfig.workspaceCwd,\n })\n} catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n console.error(t`Failed to start relay: ${message}`)\n process.exit(1)\n}\n\nconst BUSY_MESSAGE = t`Currently busy. Please try again later.`\n\nconst client = new Lark.Client(relayConfig.baseConfig)\nconst wsClient = new Lark.WSClient(relayConfig.baseConfig)\nlet isTaskRunning = false\n\nasync function processIncomingEvent(\n data: FeishuReceiveMessageEvent,\n): Promise<void> {\n try {\n const reply = await buildReplyForMessageEvent(data, {\n botOpenId: relayConfig.botOpenId,\n handleIncomingText: (input) =>\n handleIncomingText(input, {\n createThread: (mode) =>\n createCodexThread({\n mode,\n cwd: relayConfig.workspaceCwd,\n codexBin: relayConfig.codexBin,\n timeoutMs: relayConfig.codexTimeoutMs,\n }),\n runTurn: (params) =>\n runCodexTurn({\n ...params,\n cwd: relayConfig.workspaceCwd,\n codexBin: relayConfig.codexBin,\n timeoutMs: relayConfig.codexTimeoutMs,\n }),\n getSession,\n setSession,\n clearSession,\n withSessionLock,\n listOpenProjects,\n }),\n })\n\n if (reply === null) {\n return\n }\n\n await sendReply(client, data, reply, {\n includeThreadTag: shouldAttachThreadTag(data),\n })\n } catch (error) {\n console.error('failed to handle Feishu message', error)\n try {\n await sendReply(\n client,\n data,\n t`Failed to process message. Please try again later.`,\n )\n } catch (replyError) {\n console.error('failed to send failure message', replyError)\n }\n }\n}\n\nfunction shouldAttachThreadTag(data: FeishuReceiveMessageEvent): boolean {\n const rawText = parseEventText(data.message.content)\n if (rawText === null) {\n return false\n }\n\n const normalizedText = stripMentionTags(rawText).trim()\n if (normalizedText.length === 0) {\n return false\n }\n\n return parseCommand(normalizedText).type === 'prompt'\n}\n\nfunction parseEventText(content: string): string | null {\n try {\n const parsed: unknown = JSON.parse(content)\n if (!isPlainObject(parsed)) {\n return null\n }\n\n return typeof parsed.text === 'string' ? parsed.text : null\n } catch {\n return null\n }\n}\n\nconst eventDispatcher = new Lark.EventDispatcher({}).register({\n 'im.message.receive_v1': async (data: FeishuReceiveMessageEvent) => {\n // eslint-disable-next-line no-console\n console.info(\n 'feishu message received\\n',\n JSON.stringify(data, null, 2),\n '\\n',\n )\n\n if (!shouldProcessMessage(data, relayConfig.botOpenId)) {\n return\n }\n\n if (isTaskRunning) {\n void sendReply(client, data, BUSY_MESSAGE)\n return\n }\n\n isTaskRunning = true\n void processIncomingEvent(data).finally(() => {\n isTaskRunning = false\n })\n },\n})\n\nwsClient.start({ eventDispatcher })\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["COMMAND_HELP","COMMAND_NEW","COMMAND_MODE","COMMAND_STATUS","COMMAND_PROJECTS","COMMAND_RESET","getHelpText","t","join","parseCommand","input","normalized","trim","helpText","length","type","message","startsWith","prompt","parts","split","command","toLowerCase","modeToken","mode","parseMode","fs","path","sessionStore","Map","sessionQueue","SESSION_FILE_NAME","SESSION_FILE_VERSION","persistenceState","initializeSessionStore","input","relayDir","join","homeDir","filePath","mkdirSync","recursive","ensureSessionFileExists","persisted","readPersistedSessionsFile","workspaceSessions","workspaces","workspaceCwd","clear","activeBySessionKey","sessionRef","set","sessionKey","hydrateSession","data","getSessionKey","chatType","chatId","userId","getSession","get","setSession","session","persistSetSession","clearSession","delete","persistClearSession","withSessionLock","run","previous","Promise","resolve","running","then","queueItem","undefined","resetSessionStore","state","savedAt","Date","toISOString","activeSession","toPersistedActiveSession","historySession","toPersistedSessionSnapshot","getOrCreateWorkspaceSessions","history","historyBySessionKey","threadId","push","updatedAt","writePersistedSessionsFile","existsSync","initialContent","JSON","stringify","createEmptyPersistedSessionsFile","writeFileSync","encoding","flag","version","raw","readFileSync","error","Error","formatError","parsed","parse","parsePersistedSessionsFile","value","isObject","TypeError","workspaceValue","Object","entries","parseWorkspaceSessions","parseWorkspaceActiveSession","parseWorkspaceHistorySessions","parsePersistedActiveSession","entryKey","historyValue","trim","length","Array","isArray","map","item","index","parsePersistedSessionSnapshot","location","parseNonEmptyString","snapshot","model","mode","title","normalizeOptionalTitle","cwd","normalized","existing","created","tempPath","now","Math","random","toString","slice","content","renameSync","rmSync","force","message","String","getSessionKey","getHelpText","parseCommand","MAX_SESSION_TITLE_LENGTH","handleIncomingText","input","deps","sessionKey","chatType","chatId","userId","senderId","withSessionLock","parsed","text","currentSession","getSession","type","message","t","title","normalizeSessionTitle","threadId","mode","model","join","result","listOpenProjects","roots","length","lines","map","root","index","error","formatProjectsError","clearSession","setSession","created","createThread","cwd","formatCodexError","runTurn","prompt","session","resolveSessionTitle","Error","trim","currentTitle","undefined","buildFallbackSessionTitle","normalizedPrompt","normalizePrompt","truncateTitle","normalized","chars","Array","from","slice","replace","shouldProcessMessage","event","botOpenId","isMessageFromBot","message","chat_type","shouldHandleGroupMessage","mentions","length","some","mention","id","open_id","resolveSenderId","senderId","user_id","union_id","senderType","sender","sender_type","toLowerCase","sender_id","isPlainObject","isMessageFromBot","resolveSenderId","shouldHandleGroupMessage","buildReplyForMessageEvent","event","deps","botOpenId","message","message_type","t","text","parseTextContent","content","chat_type","mentions","senderId","sender","sender_id","normalizedText","stripMentionTags","trim","length","handleIncomingText","chatType","chatId","chat_id","replace","parsed","JSON","parse","isPlainObject","parseRpcLine","line","parsed","JSON","parse","method","isRpcRequestId","id","params","isRpcErrorObject","error","result","formatRpcError","code","message","value","isRpcErrorResponse","isRpcSuccessResponse","isRpcServerRequest","getServerRequestResult","decision","acceptSettings","forSession","endsWith","answers","success","contentItems","type","text","spawn","createInterface","formatRpcError","getServerRequestResult","isRpcErrorResponse","isRpcServerRequest","isRpcSuccessResponse","parseRpcLine","CodexAppServerClient","setNotificationHandler","handler","notificationHandler","request","method","params","exited","Error","buildExitMessage","requestId","nextId","responsePromise","Promise","resolve","reject","pending","set","value","payload","JSON","stringify","jsonrpc","id","child","stdin","write","error","delete","dispose","lineReader","close","killed","kill","handleStdoutLine","line","parsed","respondToServerRequest","catch","stderrBuffer","push","String","get","result","sendRpcResult","sendRpcError","writeRpcPayload","code","message","serialized","signal","suffix","length","at","options","Map","commandArgs","codexBin","cwd","stdio","input","stdout","crlfDelay","Infinity","on","stderr","chunk","text","trim","values","clear","isPlainObject","initializeClient","client","request","clientInfo","name","title","version","capabilities","experimentalApi","getCollaborationModes","raw","isCollaborationModeListResponse","Error","data","openThread","session","cwd","startThread","resumed","resumeThread","threadId","error","isThreadMissingError","approvalPolicy","sandbox","experimentalRawEvents","parseThreadResult","selectCollaborationModePayload","masks","mode","model","selected","find","mask","toLowerCase","settings","reasoning_effort","developer_instructions","isThreadResult","thread","id","message","includes","isCollaborationModeMask","value","modeIsValid","Array","isArray","every","isPlainObject","createTurnAccumulator","turnCompleted","turnError","lastAgentMessageByItem","lastAgentMessageByTask","applyTurnNotification","accumulator","notification","method","params","message","item","type","text","msg","last_agent_message","turn","error","status","resolveTurnMessage","CodexAppServerClient","getCollaborationModes","initializeClient","openThread","selectCollaborationModePayload","startThread","applyTurnNotification","createTurnAccumulator","resolveTurnMessage","formatRpcError","parseRpcLine","DEFAULT_CODEX_BIN","createCodexThread","input","client","cwd","codexBin","runWithOptionalTimeout","opened","threadId","model","mode","timeoutMs","dispose","runCodexTurn","accumulator","turnDone","createDeferred","turnDoneResolved","setNotificationHandler","notification","turnCompleted","resolve","modeMasks","session","collaborationMode","request","type","text","prompt","text_elements","promise","turnError","Error","message","trim","length","reject","run","onTimeout","withTimeout","Promise","innerResolve","innerReject","timeoutHandle","timeoutPromise","_","setTimeout","race","clearTimeout","process","listOpenProjects","roots","cwd","fs","fileURLToPath","DEFAULT_PACKAGE_NAME","DEFAULT_PACKAGE_VERSION","readRelayPackageMetadata","packageJsonPath","URL","url","raw","readFileSync","parsed","JSON","parse","name","readPackageField","version","value","fallback","normalized","trim","length","i18n","messages","enMessages","zhMessages","DEFAULT_LOCALE","detectDefaultLocale","CATALOGS","en","zh","activeLocale","initializeI18n","locale","resolved","resolveLocale","loadAndActivate","getCurrentLocale","ensureI18nInitialized","isSupportedLocale","getDefaultLocale","mappedLocale","mapToAppLocale","systemLocale","readSystemLocale","Intl","DateTimeFormat","resolvedOptions","undefined","normalized","trim","length","toLowerCase","replaceAll","startsWith","fs","os","path","process","getDefaultLocale","initializeI18n","isSupportedLocale","DEFAULT_CODEX_BIN","TEMPLATE_ENV_CONFIG","BASE_DOMAIN","APP_ID","APP_SECRET","BOT_OPEN_ID","CODEX_BIN","CODEX_TIMEOUT_MS","TEMPLATE_CONFIG","env","loadRelayConfig","options","homeDir","homedir","workspaceCwd","cwd","configDir","join","configPath","existsSync","ensureConfigTemplate","Error","t","parsed","parseConfigFile","locale","readLocale","localeValue","domain","readRequiredString","appId","appSecret","baseConfig","botOpenId","readOptionalString","codexBin","codexTimeoutMs","readTimeoutMs","mkdirSync","recursive","writeFileSync","JSON","stringify","encoding","flag","raw","readFileSync","error","formatError","parse","isObject","configObject","undefined","value","field","normalized","TypeError","trim","length","Number","isInteger","trimmed","test","parseInt","systemLocale","console","warn","formatInvalidLocale","mapped","mapLocaleToAppLocale","String","Array","isArray","toLowerCase","replaceAll","startsWith","message","process","loadRelayConfig","loadConfigOrExit","error","console","formatStartupError","exit","message","Error","String","t","https","DEFAULT_REGISTRY_BASE_URL","DEFAULT_TIMEOUT_MS","checkRelayPackageUpdate","options","currentVersion","trim","length","requestJson","requestRegistryJson","logger","console","timeoutMs","registryBaseUrl","normalizeRegistryBaseUrl","packagePath","encodeURIComponent","packageName","requestUrl","URL","payload","latestVersion","readLatestVersion","undefined","compareSemverVersions","warn","left","right","leftSemver","parseSemver","rightSemver","coreDiff","compareNumber","major","minor","patch","comparePrerelease","prerelease","value","normalized","semverPattern","match","exec","Number","parseInt","split","maxLength","Math","max","index","leftPart","rightPart","compared","comparePrereleasePart","numericPattern","leftIsNumeric","test","rightIsNumeric","localeCompare","isRecord","distTags","latest","endsWith","slice","url","Promise","resolve","reject","request","get","headers","accept","response","statusCode","resume","Error","body","setEncoding","on","chunk","JSON","parse","error","setTimeout","destroy","resolveSenderId","getSession","getSessionKey","FALLBACK_REPLY_TAG","sendReply","larkClient","data","text","options","content","JSON","stringify","formatReplyTextWithThreadId","message","chat_type","im","v1","create","params","receive_id_type","receive_id","chat_id","msg_type","reply","path","message_id","includeThreadTag","trim","replyTag","resolveReplyTag","normalizedText","length","senderId","sender","sender_id","sessionKey","chatType","chatId","userId","session","threadId","process","Lark","isPlainObject","parseCommand","handleIncomingText","shouldProcessMessage","buildReplyForMessageEvent","stripMentionTags","createCodexThread","runCodexTurn","listOpenProjects","readRelayPackageMetadata","loadConfigOrExit","checkRelayPackageUpdate","sendReply","initializeI18n","clearSession","getSession","initializeSessionStore","setSession","withSessionLock","relayConfig","locale","homeDir","workspaceCwd","error","message","Error","String","console","t","exit","env","RELAY_SKIP_UPDATE_CHECK","packageMetadata","packageName","name","currentVersion","version","BUSY_MESSAGE","client","Client","baseConfig","wsClient","WSClient","isTaskRunning","processIncomingEvent","data","reply","botOpenId","input","createThread","mode","cwd","codexBin","timeoutMs","codexTimeoutMs","runTurn","params","includeThreadTag","shouldAttachThreadTag","replyError","rawText","parseEventText","content","normalizedText","trim","length","type","parsed","JSON","parse","text","eventDispatcher","EventDispatcher","register","info","stringify","finally","start"],"sources":["../src/bot/commands.ts","../src/session/store.ts","../src/bot/handler.ts","../src/bot/message-filter.ts","../src/bot/relay.ts","../src/codex/rpc.ts","../src/codex/app-server-client.ts","../src/codex/thread.ts","../src/codex/turn-state.ts","../src/codex/app-server.ts","../src/codex/state.ts","../src/core/package-meta.ts","../src/locales/en/messages.po","../src/locales/zh/messages.po","../src/i18n/runtime.ts","../src/core/config.ts","../src/core/startup.ts","../src/core/update-check.ts","../src/feishu/reply.ts","../src/index.ts"],"sourcesContent":["import { t } from '@lingui/core/macro'\nimport type { ChatMode, ParsedCommand } from '../core/types'\n\nconst COMMAND_HELP = '/help'\nconst COMMAND_NEW = '/new'\nconst COMMAND_MODE = '/mode'\nconst COMMAND_STATUS = '/status'\nconst COMMAND_PROJECTS = '/projects'\nconst COMMAND_RESET = '/reset'\n\nexport function getHelpText(): string {\n return [\n t`Available commands:`,\n t`/help - Show help`,\n t`/new [default|plan] - Create a new session`,\n t`/mode <default|plan> - Switch current session mode`,\n t`/status - Show current session status`,\n t`/projects - Show current working directories`,\n t`/reset - Clear current session`,\n ].join('\\n')\n}\n\nexport function parseCommand(input: string): ParsedCommand {\n const normalized = input.trim()\n const helpText = getHelpText()\n\n if (normalized.length === 0) {\n return {\n type: 'invalid',\n message: t`Command cannot be empty.\\n\\n${helpText}`,\n }\n }\n\n if (!normalized.startsWith('/')) {\n return { type: 'prompt', prompt: normalized }\n }\n\n const parts = normalized.split(/\\s+/)\n const command = parts[0]?.toLowerCase()\n\n if (command === COMMAND_HELP) {\n if (parts.length > 1) {\n return {\n type: 'invalid',\n message: t`/help does not accept arguments.\\n\\n${helpText}`,\n }\n }\n\n return { type: 'help' }\n }\n\n if (command === COMMAND_NEW) {\n if (parts.length > 2) {\n return {\n type: 'invalid',\n message: t`/new accepts at most one optional argument: default or plan.\\n\\n${helpText}`,\n }\n }\n\n const modeToken = parts[1]\n if (!modeToken) {\n return { type: 'new', mode: 'default' }\n }\n\n const mode = parseMode(modeToken)\n if (!mode) {\n return {\n type: 'invalid',\n message: t`Invalid mode \"${modeToken}\", only default or plan are supported.\\n\\n${helpText}`,\n }\n }\n\n return { type: 'new', mode }\n }\n\n if (command === COMMAND_MODE) {\n const modeToken = parts[1]\n if (!modeToken || parts.length > 2) {\n return {\n type: 'invalid',\n message: t`/mode requires one argument: default or plan.\\n\\n${helpText}`,\n }\n }\n\n const mode = parseMode(modeToken)\n if (!mode) {\n return {\n type: 'invalid',\n message: t`Invalid mode \"${modeToken}\", only default or plan are supported.\\n\\n${helpText}`,\n }\n }\n\n return { type: 'mode', mode }\n }\n\n if (command === COMMAND_STATUS) {\n if (parts.length > 1) {\n return {\n type: 'invalid',\n message: t`/status does not accept arguments.\\n\\n${helpText}`,\n }\n }\n\n return { type: 'status' }\n }\n\n if (command === COMMAND_PROJECTS) {\n if (parts.length > 1) {\n return {\n type: 'invalid',\n message: t`/projects does not accept arguments.\\n\\n${helpText}`,\n }\n }\n\n return { type: 'projects' }\n }\n\n if (command === COMMAND_RESET) {\n if (parts.length > 1) {\n return {\n type: 'invalid',\n message: t`/reset does not accept arguments.\\n\\n${helpText}`,\n }\n }\n\n return { type: 'reset' }\n }\n\n return {\n type: 'invalid',\n message: t`Unknown command \"${command ?? normalized}\".\\n\\n${helpText}`,\n }\n}\n\nfunction parseMode(input: string): ChatMode | null {\n const normalized = input.toLowerCase()\n if (normalized === 'default' || normalized === 'plan') {\n return normalized\n }\n\n return null\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport type { BotSession, ChatMode, SessionKeyInput } from '../core/types'\n\nconst sessionStore = new Map<string, BotSession>()\nconst sessionQueue = new Map<string, Promise<void>>()\nconst SESSION_FILE_NAME = 'sessions.json'\nconst SESSION_FILE_VERSION = 1 as const\n\ninterface PersistedSessionSnapshot {\n mode: ChatMode\n model: string\n title?: string\n savedAt: string\n}\n\ninterface PersistedActiveSession extends PersistedSessionSnapshot {\n sessionKey: string\n threadId: string\n}\n\ninterface PersistedWorkspaceSessions {\n activeBySessionKey: PersistedActiveSession | null\n historyBySessionKey: Record<string, PersistedSessionSnapshot[]>\n}\n\ninterface PersistedSessionsFile {\n version: typeof SESSION_FILE_VERSION\n updatedAt: string\n workspaces: Record<string, PersistedWorkspaceSessions>\n}\n\ninterface SessionStorePersistenceState {\n filePath: string\n workspaceCwd: string\n data: PersistedSessionsFile\n}\n\nlet persistenceState: SessionStorePersistenceState | null = null\n\nexport function initializeSessionStore(input: {\n homeDir: string\n workspaceCwd: string\n}): void {\n const relayDir = path.join(input.homeDir, '.relay')\n const filePath = path.join(relayDir, SESSION_FILE_NAME)\n\n fs.mkdirSync(relayDir, { recursive: true })\n ensureSessionFileExists(filePath)\n\n const persisted = readPersistedSessionsFile(filePath)\n const workspaceSessions = persisted.workspaces[input.workspaceCwd]\n\n sessionStore.clear()\n sessionQueue.clear()\n\n if (workspaceSessions) {\n if (workspaceSessions.activeBySessionKey) {\n const sessionRef = workspaceSessions.activeBySessionKey\n sessionStore.set(\n sessionRef.sessionKey,\n hydrateSession(sessionRef, input.workspaceCwd),\n )\n }\n }\n\n persistenceState = {\n filePath,\n workspaceCwd: input.workspaceCwd,\n data: persisted,\n }\n}\n\nexport function getSessionKey(input: SessionKeyInput): string {\n if (input.chatType === 'p2p') {\n return `p2p:${input.chatId}`\n }\n\n return `group:${input.chatId}:${input.userId}`\n}\n\nexport function getSession(sessionKey: string): BotSession | undefined {\n return sessionStore.get(sessionKey)\n}\n\nexport function setSession(sessionKey: string, session: BotSession): void {\n sessionStore.set(sessionKey, session)\n persistSetSession(sessionKey, session)\n}\n\nexport function clearSession(sessionKey: string): void {\n sessionStore.delete(sessionKey)\n persistClearSession(sessionKey)\n}\n\nexport async function withSessionLock<T>(\n sessionKey: string,\n run: () => Promise<T>,\n): Promise<T> {\n const previous = sessionQueue.get(sessionKey) ?? Promise.resolve()\n const running = previous.then(\n () => run(),\n () => run(),\n )\n const queueItem = running.then(\n () => undefined,\n () => undefined,\n )\n\n sessionQueue.set(sessionKey, queueItem)\n\n try {\n return await running\n } finally {\n if (sessionQueue.get(sessionKey) === queueItem) {\n sessionQueue.delete(sessionKey)\n }\n }\n}\n\nexport function resetSessionStore(): void {\n sessionStore.clear()\n sessionQueue.clear()\n persistenceState = null\n}\n\nfunction persistSetSession(sessionKey: string, session: BotSession): void {\n const state = persistenceState\n if (!state) {\n return\n }\n\n const savedAt = new Date().toISOString()\n const activeSession = toPersistedActiveSession(sessionKey, session, savedAt)\n const historySession = toPersistedSessionSnapshot(session, savedAt)\n const workspaceSessions = getOrCreateWorkspaceSessions(\n state.data,\n state.workspaceCwd,\n )\n\n workspaceSessions.activeBySessionKey = activeSession\n const history = workspaceSessions.historyBySessionKey[session.threadId] ?? []\n history.push(historySession)\n workspaceSessions.historyBySessionKey[session.threadId] = history\n\n state.data.updatedAt = savedAt\n writePersistedSessionsFile(state.filePath, state.data)\n}\n\nfunction persistClearSession(sessionKey: string): void {\n const state = persistenceState\n if (!state) {\n return\n }\n\n const workspaceSessions = state.data.workspaces[state.workspaceCwd]\n if (!workspaceSessions) {\n return\n }\n\n if (\n !workspaceSessions.activeBySessionKey ||\n workspaceSessions.activeBySessionKey.sessionKey !== sessionKey\n ) {\n return\n }\n\n workspaceSessions.activeBySessionKey = null\n\n state.data.updatedAt = new Date().toISOString()\n writePersistedSessionsFile(state.filePath, state.data)\n}\n\nfunction ensureSessionFileExists(filePath: string): void {\n if (fs.existsSync(filePath)) {\n return\n }\n\n const initialContent = `${JSON.stringify(createEmptyPersistedSessionsFile(), null, 2)}\\n`\n fs.writeFileSync(filePath, initialContent, { encoding: 'utf-8', flag: 'wx' })\n}\n\nfunction createEmptyPersistedSessionsFile(): PersistedSessionsFile {\n return {\n version: SESSION_FILE_VERSION,\n updatedAt: new Date().toISOString(),\n workspaces: {},\n }\n}\n\nfunction readPersistedSessionsFile(filePath: string): PersistedSessionsFile {\n let raw: string\n try {\n raw = fs.readFileSync(filePath, 'utf-8')\n } catch (error) {\n throw new Error(\n `Failed to read relay session index at ${filePath}: ${formatError(error)}`,\n )\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch (error) {\n throw new Error(\n `Invalid JSON in relay session index at ${filePath}: ${formatError(error)}`,\n )\n }\n\n return parsePersistedSessionsFile(parsed, filePath)\n}\n\nfunction parsePersistedSessionsFile(\n value: unknown,\n filePath: string,\n): PersistedSessionsFile {\n if (!isObject(value)) {\n throw new Error(\n `Invalid relay session index at ${filePath}: root must be a JSON object.`,\n )\n }\n\n if (value.version !== SESSION_FILE_VERSION) {\n throw new Error(\n `Invalid relay session index at ${filePath}: version must be ${SESSION_FILE_VERSION}.`,\n )\n }\n\n if (typeof value.updatedAt !== 'string') {\n throw new TypeError(\n `Invalid relay session index at ${filePath}: updatedAt must be a string.`,\n )\n }\n\n if (!isObject(value.workspaces)) {\n throw new Error(\n `Invalid relay session index at ${filePath}: workspaces must be a JSON object.`,\n )\n }\n\n const workspaces: Record<string, PersistedWorkspaceSessions> = {}\n for (const [workspaceCwd, workspaceValue] of Object.entries(\n value.workspaces,\n )) {\n workspaces[workspaceCwd] = parseWorkspaceSessions(\n workspaceValue,\n filePath,\n workspaceCwd,\n )\n }\n\n return {\n version: SESSION_FILE_VERSION,\n updatedAt: value.updatedAt,\n workspaces,\n }\n}\n\nfunction parseWorkspaceSessions(\n value: unknown,\n filePath: string,\n workspaceCwd: string,\n): PersistedWorkspaceSessions {\n if (!isObject(value)) {\n throw new Error(\n `Invalid relay session index at ${filePath}: workspace \"${workspaceCwd}\" must be a JSON object.`,\n )\n }\n\n const activeBySessionKey = parseWorkspaceActiveSession(\n value.activeBySessionKey,\n filePath,\n workspaceCwd,\n )\n const historyBySessionKey = parseWorkspaceHistorySessions(\n value.historyBySessionKey,\n filePath,\n workspaceCwd,\n )\n\n return {\n activeBySessionKey,\n historyBySessionKey,\n }\n}\n\nfunction parseWorkspaceActiveSession(\n value: unknown,\n filePath: string,\n workspaceCwd: string,\n): PersistedActiveSession | null {\n if (value === null || value === undefined) {\n return null\n }\n\n if (!isObject(value)) {\n throw new Error(\n `Invalid relay session index at ${filePath}: activeBySessionKey for workspace \"${workspaceCwd}\" must be a JSON object or null.`,\n )\n }\n\n return parsePersistedActiveSession(\n value,\n filePath,\n `activeBySessionKey for workspace \"${workspaceCwd}\"`,\n )\n}\n\nfunction parseWorkspaceHistorySessions(\n value: unknown,\n filePath: string,\n workspaceCwd: string,\n): Record<string, PersistedSessionSnapshot[]> {\n if (!isObject(value)) {\n throw new Error(\n `Invalid relay session index at ${filePath}: historyBySessionKey for workspace \"${workspaceCwd}\" must be a JSON object.`,\n )\n }\n\n const historyBySessionKey: Record<string, PersistedSessionSnapshot[]> = {}\n for (const [entryKey, historyValue] of Object.entries(value)) {\n if (entryKey.trim().length === 0) {\n throw new Error(\n `Invalid relay session index at ${filePath}: historyBySessionKey key in workspace \"${workspaceCwd}\" must be a non-empty threadId.`,\n )\n }\n\n if (!Array.isArray(historyValue)) {\n throw new TypeError(\n `Invalid relay session index at ${filePath}: historyBySessionKey.${entryKey} must be an array.`,\n )\n }\n\n historyBySessionKey[entryKey] = historyValue.map((item, index) =>\n parsePersistedSessionSnapshot(\n item,\n filePath,\n `historyBySessionKey.${entryKey}[${index}]`,\n ),\n )\n }\n\n return historyBySessionKey\n}\n\nfunction parsePersistedActiveSession(\n value: unknown,\n filePath: string,\n location: string,\n): PersistedActiveSession {\n if (!isObject(value)) {\n throw new Error(\n `Invalid relay session index at ${filePath}: ${location} must be a JSON object.`,\n )\n }\n\n const sessionKey = parseNonEmptyString(\n value.sessionKey,\n filePath,\n `${location}.sessionKey`,\n )\n const threadId = parseNonEmptyString(\n value.threadId,\n filePath,\n `${location}.threadId`,\n )\n const snapshot = parsePersistedSessionSnapshot(value, filePath, location)\n return {\n sessionKey,\n threadId,\n ...snapshot,\n }\n}\n\nfunction parsePersistedSessionSnapshot(\n value: unknown,\n filePath: string,\n location: string,\n): PersistedSessionSnapshot {\n if (!isObject(value)) {\n throw new Error(\n `Invalid relay session index at ${filePath}: ${location} must be a JSON object.`,\n )\n }\n\n const model = parseNonEmptyString(value.model, filePath, `${location}.model`)\n if (value.mode !== 'default' && value.mode !== 'plan') {\n throw new Error(\n `Invalid relay session index at ${filePath}: ${location}.mode must be \"default\" or \"plan\".`,\n )\n }\n\n if (typeof value.savedAt !== 'string') {\n throw new TypeError(\n `Invalid relay session index at ${filePath}: ${location}.savedAt must be a string.`,\n )\n }\n\n const title = normalizeOptionalTitle(value.title)\n return {\n mode: value.mode,\n model,\n title,\n savedAt: value.savedAt,\n }\n}\n\nfunction parseNonEmptyString(\n value: unknown,\n filePath: string,\n location: string,\n): string {\n if (typeof value !== 'string' || value.trim().length === 0) {\n throw new Error(\n `Invalid relay session index at ${filePath}: ${location} must be a non-empty string.`,\n )\n }\n\n return value\n}\n\nfunction hydrateSession(\n sessionRef: PersistedActiveSession,\n cwd: string,\n): BotSession {\n return {\n threadId: sessionRef.threadId,\n mode: sessionRef.mode,\n model: sessionRef.model,\n cwd,\n title: normalizeOptionalTitle(sessionRef.title),\n }\n}\n\nfunction toPersistedActiveSession(\n sessionKey: string,\n session: BotSession,\n savedAt: string,\n): PersistedActiveSession {\n return {\n sessionKey,\n threadId: session.threadId,\n ...toPersistedSessionSnapshot(session, savedAt),\n }\n}\n\nfunction toPersistedSessionSnapshot(\n session: BotSession,\n savedAt: string,\n): PersistedSessionSnapshot {\n const title = normalizeOptionalTitle(session.title)\n\n return {\n mode: session.mode,\n model: session.model,\n title,\n savedAt,\n }\n}\n\nfunction normalizeOptionalTitle(title: unknown): string | undefined {\n if (typeof title !== 'string') {\n return undefined\n }\n\n const normalized = title.trim()\n if (normalized.length === 0) {\n return undefined\n }\n\n return normalized\n}\n\nfunction getOrCreateWorkspaceSessions(\n data: PersistedSessionsFile,\n workspaceCwd: string,\n): PersistedWorkspaceSessions {\n const existing = data.workspaces[workspaceCwd]\n if (existing) {\n return existing\n }\n\n const created: PersistedWorkspaceSessions = {\n activeBySessionKey: null,\n historyBySessionKey: {},\n }\n data.workspaces[workspaceCwd] = created\n return created\n}\n\nfunction writePersistedSessionsFile(\n filePath: string,\n data: PersistedSessionsFile,\n): void {\n const tempPath = `${filePath}.tmp-${Date.now()}-${Math.random().toString(16).slice(2)}`\n const content = `${JSON.stringify(data, null, 2)}\\n`\n\n try {\n fs.writeFileSync(tempPath, content, 'utf-8')\n fs.renameSync(tempPath, filePath)\n } catch (error) {\n try {\n if (fs.existsSync(tempPath)) {\n fs.rmSync(tempPath, { force: true })\n }\n } catch {\n // Best-effort cleanup for temporary file.\n }\n\n throw new Error(\n `Failed to write relay session index at ${filePath}: ${formatError(error)}`,\n )\n }\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction formatError(error: unknown): string {\n if (error instanceof Error) {\n return error.message\n }\n\n return String(error)\n}\n","import { t } from '@lingui/core/macro'\nimport { getSessionKey } from '../session/store'\nimport { getHelpText, parseCommand } from './commands'\nimport type {\n BotSession,\n ChatMode,\n CodexTurnResult,\n HandleIncomingTextInput,\n OpenProjectsResult,\n} from '../core/types'\n\nconst MAX_SESSION_TITLE_LENGTH = 24\n\nexport interface HandleIncomingTextDeps {\n createThread: (mode: ChatMode) => Promise<BotSession>\n runTurn: (params: {\n prompt: string\n mode: ChatMode\n session: BotSession | null\n }) => Promise<CodexTurnResult>\n getSession: (sessionKey: string) => BotSession | undefined\n setSession: (sessionKey: string, session: BotSession) => void\n clearSession: (sessionKey: string) => void\n withSessionLock: <T>(sessionKey: string, run: () => Promise<T>) => Promise<T>\n listOpenProjects: () => Promise<OpenProjectsResult>\n}\n\nexport async function handleIncomingText(\n input: HandleIncomingTextInput,\n deps: HandleIncomingTextDeps,\n): Promise<string> {\n const sessionKey = getSessionKey({\n chatType: input.chatType,\n chatId: input.chatId,\n userId: input.senderId,\n })\n\n return deps.withSessionLock(sessionKey, async () => {\n const parsed = parseCommand(input.text)\n const currentSession = deps.getSession(sessionKey)\n\n if (parsed.type === 'invalid') {\n return parsed.message\n }\n\n if (parsed.type === 'help') {\n return getHelpText()\n }\n\n if (parsed.type === 'status') {\n if (!currentSession) {\n return t`No active session. Send a normal message or use /new to create one.`\n }\n\n const title =\n normalizeSessionTitle(currentSession.title) ?? t`New Session`\n\n return [\n t`Current session status:`,\n t`thread: ${currentSession.threadId}`,\n t`title: ${title}`,\n t`mode: ${currentSession.mode}`,\n t`model: ${currentSession.model}`,\n ].join('\\n')\n }\n\n if (parsed.type === 'projects') {\n try {\n const result = await deps.listOpenProjects()\n if (result.roots.length === 0) {\n return t`No working directories are currently open.`\n }\n\n const lines = result.roots.map(\n (root, index) => t`${index + 1}. ${root}`,\n )\n return [t`Current working directories:`, ...lines].join('\\n')\n } catch (error) {\n return formatProjectsError(error)\n }\n }\n\n if (parsed.type === 'reset') {\n deps.clearSession(sessionKey)\n return t`Current session has been cleared.`\n }\n\n if (parsed.type === 'mode') {\n if (!currentSession) {\n return t`No active session. Send a normal message or use /new to create one first.`\n }\n\n deps.setSession(sessionKey, {\n ...currentSession,\n mode: parsed.mode,\n })\n\n return t`Switched to ${parsed.mode} mode.`\n }\n\n if (parsed.type === 'new') {\n try {\n const created = await deps.createThread(parsed.mode)\n deps.setSession(sessionKey, created)\n return [\n t`Created a new session.`,\n t`thread: ${created.threadId}`,\n t`cwd: ${created.cwd}`,\n t`mode: ${created.mode}`,\n t`model: ${created.model}`,\n ].join('\\n')\n } catch (error) {\n return formatCodexError(error)\n }\n }\n\n try {\n const mode = currentSession?.mode ?? 'default'\n const result = await deps.runTurn({\n prompt: parsed.prompt,\n mode,\n session: currentSession ?? null,\n })\n\n const title = await resolveSessionTitle({\n currentSession,\n prompt: parsed.prompt,\n })\n\n deps.setSession(sessionKey, {\n threadId: result.threadId,\n model: result.model,\n mode: result.mode,\n cwd: result.cwd,\n title,\n })\n return result.message\n } catch (error) {\n return formatCodexError(error)\n }\n })\n}\n\nfunction formatCodexError(error: unknown): string {\n if (error instanceof Error && error.message.trim().length > 0) {\n return t`Codex execution failed: ${error.message}`\n }\n\n return t`Codex execution failed. Please try again later.`\n}\n\nfunction formatProjectsError(error: unknown): string {\n if (error instanceof Error && error.message.trim().length > 0) {\n return t`Failed to read open projects: ${error.message}`\n }\n\n return t`Failed to read open projects. Please try again later.`\n}\n\nasync function resolveSessionTitle(input: {\n currentSession: BotSession | undefined\n prompt: string\n}): Promise<string | undefined> {\n const currentTitle = normalizeSessionTitle(input.currentSession?.title)\n if (currentTitle) {\n return currentTitle\n }\n\n if (!input.currentSession) {\n return undefined\n }\n\n return buildFallbackSessionTitle(input.prompt)\n}\n\nfunction buildFallbackSessionTitle(prompt: string): string {\n const normalizedPrompt = normalizePrompt(prompt)\n if (normalizedPrompt.length === 0) {\n return t`New Session`\n }\n\n return truncateTitle(normalizedPrompt)\n}\n\nfunction normalizeSessionTitle(title: string | undefined): string | null {\n if (!title) {\n return null\n }\n\n const normalized = title.trim()\n if (normalized.length === 0) {\n return null\n }\n\n return normalized\n}\n\nfunction truncateTitle(input: string): string {\n const chars = Array.from(input)\n if (chars.length <= MAX_SESSION_TITLE_LENGTH) {\n return input\n }\n\n if (MAX_SESSION_TITLE_LENGTH <= 3) {\n return chars.slice(0, MAX_SESSION_TITLE_LENGTH).join('')\n }\n\n return `${chars.slice(0, MAX_SESSION_TITLE_LENGTH - 3).join('')}...`\n}\n\nfunction normalizePrompt(input: string): string {\n return input.replace(/\\s+/g, ' ').trim()\n}\n","import type { FeishuMention } from '../core/types'\n\ninterface EventSenderId {\n open_id?: string\n user_id?: string\n union_id?: string\n}\n\ninterface MessageFilterEvent {\n sender: {\n sender_type?: string\n sender_id?: EventSenderId\n }\n message: {\n chat_type: string\n mentions?: FeishuMention[]\n }\n}\n\nexport function shouldProcessMessage(\n event: MessageFilterEvent,\n botOpenId?: string,\n): boolean {\n if (isMessageFromBot(event, botOpenId)) {\n return false\n }\n\n if (event.message.chat_type === 'p2p') {\n return true\n }\n\n return shouldHandleGroupMessage(event.message.mentions, botOpenId)\n}\n\nexport function shouldHandleGroupMessage(\n mentions: FeishuMention[] | undefined,\n botOpenId?: string,\n): boolean {\n if (!mentions || mentions.length === 0) {\n return false\n }\n\n if (!botOpenId) {\n return true\n }\n\n return mentions.some((mention) => mention.id?.open_id === botOpenId)\n}\n\nexport function resolveSenderId(\n senderId: EventSenderId | undefined,\n): string | null {\n if (!senderId) {\n return null\n }\n\n return senderId.open_id ?? senderId.user_id ?? senderId.union_id ?? null\n}\n\nexport function isMessageFromBot(\n event: MessageFilterEvent,\n botOpenId?: string,\n): boolean {\n const senderType = event.sender.sender_type?.toLowerCase()\n if (senderType === 'app') {\n return true\n }\n\n if (!botOpenId) {\n return false\n }\n\n const senderId = resolveSenderId(event.sender.sender_id)\n return senderId === botOpenId\n}\n","import { t } from '@lingui/core/macro'\nimport { isPlainObject } from 'es-toolkit/predicate'\nimport {\n isMessageFromBot,\n resolveSenderId,\n shouldHandleGroupMessage,\n} from './message-filter'\nimport type { FeishuMention, HandleIncomingTextInput } from '../core/types'\n\nexport interface ReceiveMessageEvent {\n sender: {\n sender_type?: string\n sender_id?: {\n open_id?: string\n user_id?: string\n union_id?: string\n }\n }\n message: {\n chat_id: string\n chat_type: string\n message_type: string\n content: string\n mentions?: FeishuMention[]\n }\n}\n\nexport interface RelayBotDeps {\n botOpenId?: string\n handleIncomingText: (input: HandleIncomingTextInput) => Promise<string>\n}\n\nexport { shouldHandleGroupMessage } from './message-filter'\n\nexport async function buildReplyForMessageEvent(\n event: ReceiveMessageEvent,\n deps: RelayBotDeps,\n): Promise<string | null> {\n if (isMessageFromBot(event, deps.botOpenId)) {\n return null\n }\n\n if (event.message.message_type !== 'text') {\n return t`Failed to parse message. Please send a text message.`\n }\n\n const text = parseTextContent(event.message.content)\n if (!text) {\n return t`Failed to parse message. Please send a text message.`\n }\n\n if (\n event.message.chat_type !== 'p2p' &&\n !shouldHandleGroupMessage(event.message.mentions, deps.botOpenId)\n ) {\n return null\n }\n\n const senderId = resolveSenderId(event.sender.sender_id)\n if (!senderId) {\n return t`Cannot identify sender. Please try again later.`\n }\n\n const normalizedText = stripMentionTags(text).trim()\n if (normalizedText.length === 0) {\n return t`Please send a text message.`\n }\n\n return deps.handleIncomingText({\n chatType: event.message.chat_type,\n chatId: event.message.chat_id,\n senderId,\n text: normalizedText,\n })\n}\n\nexport function stripMentionTags(text: string): string {\n return text.replace(/<at\\b[^>]*>.*?<\\/at>/g, '').trim()\n}\n\nfunction parseTextContent(content: string): string | null {\n try {\n const parsed: unknown = JSON.parse(content)\n if (!isPlainObject(parsed)) {\n return null\n }\n\n return typeof parsed.text === 'string' ? parsed.text : null\n } catch {\n return null\n }\n}\n","import { isPlainObject } from 'es-toolkit/predicate'\nimport type {\n RpcErrorObject,\n RpcErrorResponse,\n RpcIncomingLine,\n RpcRequestId,\n RpcServerRequest,\n RpcSuccessResponse,\n} from '../core/types'\n\nexport function parseRpcLine(line: string): RpcIncomingLine | null {\n let parsed: unknown\n try {\n parsed = JSON.parse(line)\n } catch {\n return null\n }\n\n if (!isPlainObject(parsed)) {\n return null\n }\n\n if (typeof parsed.method === 'string') {\n if (isRpcRequestId(parsed.id)) {\n return {\n id: parsed.id,\n method: parsed.method,\n params: parsed.params,\n }\n }\n\n return {\n method: parsed.method,\n params: parsed.params,\n }\n }\n\n if (!isRpcRequestId(parsed.id)) {\n return null\n }\n\n if ('error' in parsed && isRpcErrorObject(parsed.error)) {\n return {\n id: parsed.id,\n error: parsed.error,\n }\n }\n\n if ('result' in parsed) {\n return {\n id: parsed.id,\n result: parsed.result,\n }\n }\n\n return null\n}\n\nexport function formatRpcError(error: RpcErrorObject): string {\n return `Codex RPC error (${error.code}): ${error.message}`\n}\n\nexport function isRpcRequestId(value: unknown): value is RpcRequestId {\n return typeof value === 'number' || typeof value === 'string'\n}\n\nexport function isRpcErrorObject(value: unknown): value is RpcErrorObject {\n if (!isPlainObject(value)) {\n return false\n }\n\n return typeof value.code === 'number' && typeof value.message === 'string'\n}\n\nexport function isRpcErrorResponse(\n value: RpcIncomingLine,\n): value is RpcErrorResponse {\n return 'error' in value\n}\n\nexport function isRpcSuccessResponse(\n value: RpcIncomingLine,\n): value is RpcSuccessResponse<unknown> {\n return 'result' in value\n}\n\nexport function isRpcServerRequest(\n value: RpcIncomingLine,\n): value is RpcServerRequest<unknown> {\n return 'method' in value && 'id' in value\n}\n\nexport function getServerRequestResult(method: string): unknown | null {\n if (method === 'item/commandExecution/requestApproval') {\n return {\n decision: 'accept',\n acceptSettings: {\n forSession: true,\n },\n }\n }\n\n if (method === 'item/fileChange/requestApproval') {\n return { decision: 'accept' }\n }\n\n if (method.endsWith('/requestApproval')) {\n return { decision: 'accept' }\n }\n\n if (method === 'execCommandApproval') {\n return { decision: 'allow' }\n }\n\n if (method === 'applyPatchApproval') {\n return { decision: 'allow' }\n }\n\n if (method.endsWith('Approval')) {\n return { decision: 'allow' }\n }\n\n if (method === 'item/tool/requestUserInput') {\n return { answers: {} }\n }\n\n if (method === 'item/tool/call') {\n return {\n success: false,\n contentItems: [\n {\n type: 'inputText',\n text: 'Dynamic tool calls are unavailable in relay-bot.',\n },\n ],\n }\n }\n\n return null\n}\n","import { spawn } from 'node:child_process'\nimport { createInterface } from 'node:readline'\nimport {\n formatRpcError,\n getServerRequestResult,\n isRpcErrorResponse,\n isRpcServerRequest,\n isRpcSuccessResponse,\n parseRpcLine,\n} from './rpc'\nimport type { ChildProcessWithoutNullStreams } from 'node:child_process'\nimport type { Interface } from 'node:readline'\nimport type {\n RpcNotification,\n RpcRequestId,\n RpcServerRequest,\n} from '../core/types'\n\ninterface PendingRequest {\n resolve: (value: unknown) => void\n reject: (reason: Error) => void\n}\n\nexport class CodexAppServerClient {\n private readonly options: {\n cwd: string\n codexBin: string\n }\n\n private readonly child: ChildProcessWithoutNullStreams\n\n private readonly pending = new Map<RpcRequestId, PendingRequest>()\n\n private readonly stderrBuffer: string[] = []\n\n private readonly lineReader: Interface\n\n private nextId = 1\n\n private notificationHandler:\n | ((notification: RpcNotification<unknown>) => void)\n | null = null\n\n private exited = false\n\n constructor(options: { cwd: string; codexBin: string }) {\n this.options = options\n const commandArgs = ['app-server']\n\n this.child = spawn(this.options.codexBin, commandArgs, {\n cwd: this.options.cwd,\n stdio: ['pipe', 'pipe', 'pipe'],\n })\n this.lineReader = createInterface({\n input: this.child.stdout,\n crlfDelay: Infinity,\n })\n\n this.lineReader.on('line', (line) => {\n this.handleStdoutLine(line)\n })\n\n this.child.stderr.on('data', (chunk) => {\n const text = String(chunk).trim()\n if (text.length > 0) {\n this.stderrBuffer.push(text)\n }\n })\n\n this.child.on('exit', (code, signal) => {\n this.exited = true\n const error = new Error(this.buildExitMessage(code, signal))\n for (const pending of this.pending.values()) {\n pending.reject(error)\n }\n this.pending.clear()\n })\n }\n\n setNotificationHandler(\n handler: (notification: RpcNotification<unknown>) => void,\n ): void {\n this.notificationHandler = handler\n }\n\n async request<T>(method: string, params: unknown): Promise<T> {\n if (this.exited) {\n throw new Error(this.buildExitMessage(null, null))\n }\n\n const requestId = this.nextId\n this.nextId += 1\n\n const responsePromise = new Promise<T>((resolve, reject) => {\n this.pending.set(requestId, {\n resolve: (value: unknown) => resolve(value as T),\n reject,\n })\n })\n\n const payload = JSON.stringify({\n jsonrpc: '2.0',\n id: requestId,\n method,\n params,\n })\n\n await new Promise<void>((resolve, reject) => {\n this.child.stdin.write(`${payload}\\n`, (error) => {\n if (error) {\n this.pending.delete(requestId)\n reject(error)\n return\n }\n resolve()\n })\n })\n\n return responsePromise\n }\n\n dispose(): void {\n this.lineReader.close()\n if (!this.child.killed) {\n this.child.kill('SIGTERM')\n }\n }\n\n private handleStdoutLine(line: string): void {\n const parsed = parseRpcLine(line)\n if (!parsed) {\n return\n }\n\n if ('method' in parsed) {\n if (isRpcServerRequest(parsed)) {\n void this.respondToServerRequest(parsed).catch((error) => {\n this.stderrBuffer.push(\n `failed to respond to server request \"${parsed.method}\": ${String(\n error,\n )}`,\n )\n })\n return\n }\n\n this.notificationHandler?.(parsed)\n return\n }\n\n const pending = this.pending.get(parsed.id)\n if (!pending) {\n return\n }\n\n this.pending.delete(parsed.id)\n if (isRpcErrorResponse(parsed)) {\n pending.reject(new Error(formatRpcError(parsed.error)))\n return\n }\n\n if (isRpcSuccessResponse(parsed)) {\n pending.resolve(parsed.result)\n }\n }\n\n private async respondToServerRequest(\n request: RpcServerRequest<unknown>,\n ): Promise<void> {\n const result = getServerRequestResult(request.method)\n if (result !== null) {\n await this.sendRpcResult(request.id, result)\n return\n }\n\n await this.sendRpcError(\n request.id,\n -32601,\n `Unsupported server request method: ${request.method}`,\n )\n }\n\n private async sendRpcResult(\n id: RpcRequestId,\n result: unknown,\n ): Promise<void> {\n await this.writeRpcPayload({\n jsonrpc: '2.0',\n id,\n result,\n })\n }\n\n private async sendRpcError(\n id: RpcRequestId,\n code: number,\n message: string,\n ): Promise<void> {\n await this.writeRpcPayload({\n jsonrpc: '2.0',\n id,\n error: {\n code,\n message,\n },\n })\n }\n\n private async writeRpcPayload(payload: {\n jsonrpc: '2.0'\n id: RpcRequestId\n result?: unknown\n error?: {\n code: number\n message: string\n }\n }): Promise<void> {\n if (this.exited) {\n return\n }\n\n const serialized = JSON.stringify(payload)\n await new Promise<void>((resolve, reject) => {\n this.child.stdin.write(`${serialized}\\n`, (error) => {\n if (error) {\n reject(error)\n return\n }\n resolve()\n })\n })\n }\n\n private buildExitMessage(\n code: number | null,\n signal: NodeJS.Signals | null,\n ): string {\n const suffix =\n this.stderrBuffer.length > 0\n ? `; stderr: ${this.stderrBuffer.at(-1)}`\n : ''\n return `Codex app-server exited (code=${code ?? 'null'}, signal=${\n signal ?? 'null'\n })${suffix}`\n }\n}\n","import { isPlainObject } from 'es-toolkit/predicate'\nimport type {\n BotSession,\n ChatMode,\n CollaborationModeListResponse,\n CollaborationModeMask,\n ThreadResult,\n} from '../core/types'\nimport type { CodexAppServerClient } from './app-server-client'\n\nexport interface OpenThreadResult {\n threadId: string\n cwd: string\n model: string\n}\n\nexport interface CollaborationModePayload {\n mode: ChatMode\n settings: {\n model: string\n reasoning_effort: string | null\n developer_instructions: string | null\n }\n}\n\nexport async function initializeClient(\n client: CodexAppServerClient,\n): Promise<void> {\n await client.request('initialize', {\n clientInfo: {\n name: 'relay-bot',\n title: 'Relay Bot',\n version: '0.0.0',\n },\n capabilities: {\n experimentalApi: true,\n },\n })\n}\n\nexport async function getCollaborationModes(\n client: CodexAppServerClient,\n): Promise<CollaborationModeMask[]> {\n const raw = await client.request('collaborationMode/list', {})\n if (!isCollaborationModeListResponse(raw)) {\n throw new Error('Invalid collaboration mode response from Codex')\n }\n\n return raw.data\n}\n\nexport async function openThread(\n client: CodexAppServerClient,\n session: BotSession | null,\n cwd: string,\n): Promise<OpenThreadResult> {\n if (!session) {\n return startThread(client, cwd)\n }\n\n if (session.cwd !== cwd) {\n return startThread(client, cwd)\n }\n\n try {\n const resumed = await resumeThread(client, session.threadId)\n if (resumed.cwd !== cwd) {\n return startThread(client, cwd)\n }\n\n return resumed\n } catch (error) {\n if (isThreadMissingError(error)) {\n return startThread(client, cwd)\n }\n throw error\n }\n}\n\nexport async function startThread(\n client: CodexAppServerClient,\n cwd: string,\n): Promise<OpenThreadResult> {\n const raw = await client.request('thread/start', {\n cwd,\n approvalPolicy: 'on-request',\n sandbox: 'workspace-write',\n experimentalRawEvents: false,\n })\n\n return parseThreadResult(raw)\n}\n\nexport function selectCollaborationModePayload(\n masks: CollaborationModeMask[],\n mode: ChatMode,\n model: string,\n): CollaborationModePayload {\n const selected = masks.find((mask) => {\n if (mask.mode === mode) {\n return true\n }\n\n return mask.name.toLowerCase() === mode\n })\n\n if (!selected) {\n throw new Error(`Collaboration mode \"${mode}\" is unavailable`)\n }\n\n return {\n mode,\n settings: {\n model,\n reasoning_effort: selected.reasoning_effort,\n developer_instructions: selected.developer_instructions,\n },\n }\n}\n\nasync function resumeThread(\n client: CodexAppServerClient,\n threadId: string,\n): Promise<OpenThreadResult> {\n const raw = await client.request('thread/resume', {\n threadId,\n })\n\n return parseThreadResult(raw)\n}\n\nfunction parseThreadResult(raw: unknown): OpenThreadResult {\n if (!isThreadResult(raw)) {\n throw new Error('Invalid thread response from Codex')\n }\n\n return {\n threadId: raw.thread.id,\n model: raw.model,\n cwd: raw.cwd,\n }\n}\n\nfunction isThreadMissingError(error: unknown): boolean {\n if (!(error instanceof Error)) {\n return false\n }\n\n return error.message.includes('thread not found')\n}\n\nfunction isCollaborationModeMask(\n value: unknown,\n): value is CollaborationModeMask {\n if (!isPlainObject(value)) {\n return false\n }\n\n const modeIsValid =\n value.mode === null || value.mode === 'default' || value.mode === 'plan'\n\n return (\n typeof value.name === 'string' &&\n modeIsValid &&\n (typeof value.model === 'string' || value.model === null) &&\n (typeof value.reasoning_effort === 'string' ||\n value.reasoning_effort === null) &&\n (typeof value.developer_instructions === 'string' ||\n value.developer_instructions === null)\n )\n}\n\nfunction isCollaborationModeListResponse(\n value: unknown,\n): value is CollaborationModeListResponse {\n if (!isPlainObject(value) || !Array.isArray(value.data)) {\n return false\n }\n\n return value.data.every(isCollaborationModeMask)\n}\n\nfunction isThreadResult(value: unknown): value is ThreadResult {\n if (!isPlainObject(value) || !isPlainObject(value.thread)) {\n return false\n }\n\n return typeof value.thread.id === 'string' && typeof value.model === 'string'\n}\n","import { isPlainObject } from 'es-toolkit/predicate'\nimport type {\n RpcItemCompletedParams,\n RpcNotification,\n RpcTaskCompleteParams,\n RpcTurnCompletedParams,\n TurnAccumulator,\n} from '../core/types'\n\nexport function createTurnAccumulator(): TurnAccumulator {\n return {\n turnCompleted: false,\n turnError: null,\n lastAgentMessageByItem: null,\n lastAgentMessageByTask: null,\n }\n}\n\nexport function applyTurnNotification(\n accumulator: TurnAccumulator,\n notification: RpcNotification<unknown>,\n): void {\n if (notification.method === 'error') {\n if (\n isPlainObject(notification.params) &&\n typeof notification.params.message === 'string'\n ) {\n accumulator.turnError = notification.params.message\n } else {\n accumulator.turnError = 'Codex returned an unknown error event'\n }\n accumulator.turnCompleted = true\n return\n }\n\n if (notification.method === 'item/completed') {\n const params = notification.params as RpcItemCompletedParams\n const item = params.item\n if (item?.type === 'agentMessage' && typeof item.text === 'string') {\n accumulator.lastAgentMessageByItem = item.text\n }\n return\n }\n\n if (notification.method === 'codex/event/task_complete') {\n const params = notification.params as RpcTaskCompleteParams\n const message = params.msg?.last_agent_message\n if (typeof message === 'string') {\n accumulator.lastAgentMessageByTask = message\n }\n return\n }\n\n if (notification.method === 'turn/completed') {\n const params = notification.params as RpcTurnCompletedParams\n accumulator.turnCompleted = true\n if (params.turn?.error?.message) {\n accumulator.turnError = params.turn.error.message\n return\n }\n\n if (params.turn?.status === 'failed') {\n accumulator.turnError = 'Codex turn failed'\n }\n }\n}\n\nexport function resolveTurnMessage(\n accumulator: TurnAccumulator,\n): string | null {\n return (\n accumulator.lastAgentMessageByTask ?? accumulator.lastAgentMessageByItem\n )\n}\n","import { CodexAppServerClient } from './app-server-client'\nimport {\n getCollaborationModes,\n initializeClient,\n openThread,\n selectCollaborationModePayload,\n startThread,\n} from './thread'\nimport {\n applyTurnNotification,\n createTurnAccumulator,\n resolveTurnMessage,\n} from './turn-state'\nimport type { BotSession, ChatMode, CodexTurnResult } from '../core/types'\n\nexport { formatRpcError, parseRpcLine } from './rpc'\nexport {\n applyTurnNotification,\n createTurnAccumulator,\n resolveTurnMessage,\n} from './turn-state'\n\nconst DEFAULT_CODEX_BIN = 'codex'\n\ninterface Deferred<T> {\n promise: Promise<T>\n resolve: (value: T | PromiseLike<T>) => void\n reject: (reason?: unknown) => void\n}\n\nexport interface RunCodexTurnInput {\n prompt: string\n mode: ChatMode\n session: BotSession | null\n cwd: string\n codexBin?: string\n timeoutMs?: number\n}\n\nexport interface CreateCodexThreadInput {\n mode: ChatMode\n cwd: string\n codexBin?: string\n timeoutMs?: number\n}\n\nexport async function createCodexThread(\n input: CreateCodexThreadInput,\n): Promise<BotSession> {\n const client = new CodexAppServerClient({\n cwd: input.cwd,\n codexBin: input.codexBin ?? DEFAULT_CODEX_BIN,\n })\n\n try {\n return await runWithOptionalTimeout(\n async () => {\n await initializeClient(client)\n const opened = await startThread(client, input.cwd)\n return {\n threadId: opened.threadId,\n model: opened.model,\n mode: input.mode,\n cwd: opened.cwd,\n }\n },\n input.timeoutMs,\n () => client.dispose(),\n )\n } finally {\n client.dispose()\n }\n}\n\nexport async function runCodexTurn(\n input: RunCodexTurnInput,\n): Promise<CodexTurnResult> {\n const client = new CodexAppServerClient({\n cwd: input.cwd,\n codexBin: input.codexBin ?? DEFAULT_CODEX_BIN,\n })\n\n const accumulator = createTurnAccumulator()\n const turnDone = createDeferred<void>()\n let turnDoneResolved = false\n\n client.setNotificationHandler((notification) => {\n applyTurnNotification(accumulator, notification)\n if (accumulator.turnCompleted && !turnDoneResolved) {\n turnDoneResolved = true\n turnDone.resolve()\n }\n })\n\n try {\n return await runWithOptionalTimeout(\n async () => {\n await initializeClient(client)\n const modeMasks = await getCollaborationModes(client)\n const opened = await openThread(client, input.session, input.cwd)\n const collaborationMode = selectCollaborationModePayload(\n modeMasks,\n input.mode,\n opened.model,\n )\n\n await client.request('turn/start', {\n threadId: opened.threadId,\n input: [\n {\n type: 'text',\n text: input.prompt,\n text_elements: [],\n },\n ],\n collaborationMode,\n })\n\n await turnDone.promise\n\n if (accumulator.turnError) {\n throw new Error(accumulator.turnError)\n }\n\n const message = resolveTurnMessage(accumulator)\n if (!message || message.trim().length === 0) {\n throw new Error('Codex did not return a message')\n }\n\n return {\n threadId: opened.threadId,\n model: opened.model,\n mode: input.mode,\n message,\n cwd: opened.cwd,\n }\n },\n input.timeoutMs,\n () => {\n if (!turnDoneResolved) {\n turnDoneResolved = true\n turnDone.reject(new Error('Codex execution timed out'))\n }\n client.dispose()\n },\n )\n } finally {\n client.dispose()\n }\n}\n\nasync function runWithOptionalTimeout<T>(\n run: () => Promise<T>,\n timeoutMs: number | undefined,\n onTimeout: () => void,\n): Promise<T> {\n if (typeof timeoutMs !== 'number' || timeoutMs <= 0) {\n return run()\n }\n\n return withTimeout(run, timeoutMs, onTimeout)\n}\n\nfunction createDeferred<T>(): Deferred<T> {\n let resolve!: (value: T | PromiseLike<T>) => void\n let reject!: (reason?: unknown) => void\n const promise = new Promise<T>((innerResolve, innerReject) => {\n resolve = innerResolve\n reject = innerReject\n })\n\n return { promise, resolve, reject }\n}\n\nasync function withTimeout<T>(\n run: () => Promise<T>,\n timeoutMs: number,\n onTimeout: () => void,\n): Promise<T> {\n let timeoutHandle: NodeJS.Timeout | undefined\n const timeoutPromise = new Promise<T>((_, reject) => {\n timeoutHandle = setTimeout(() => {\n onTimeout()\n reject(new Error(`Codex request timed out after ${timeoutMs}ms`))\n }, timeoutMs)\n })\n\n try {\n return await Promise.race([run(), timeoutPromise])\n } finally {\n if (timeoutHandle) {\n clearTimeout(timeoutHandle)\n }\n }\n}\n","import process from 'node:process'\nimport type { OpenProjectsResult } from '../core/types'\n\nexport async function listOpenProjects(): Promise<OpenProjectsResult> {\n return {\n roots: [process.cwd()],\n }\n}\n","import fs from 'node:fs'\nimport { fileURLToPath } from 'node:url'\n\nconst DEFAULT_PACKAGE_NAME = '@debbl/relay'\nconst DEFAULT_PACKAGE_VERSION = '0.0.0'\n\ninterface PackageJsonShape {\n name?: unknown\n version?: unknown\n}\n\nexport interface RelayPackageMetadata {\n name: string\n version: string\n}\n\nexport function readRelayPackageMetadata(): RelayPackageMetadata {\n const packageJsonPath = fileURLToPath(\n new URL('../../package.json', import.meta.url),\n )\n\n try {\n const raw = fs.readFileSync(packageJsonPath, 'utf-8')\n const parsed = JSON.parse(raw) as PackageJsonShape\n\n return {\n name: readPackageField(parsed.name, DEFAULT_PACKAGE_NAME),\n version: readPackageField(parsed.version, DEFAULT_PACKAGE_VERSION),\n }\n } catch {\n return {\n name: DEFAULT_PACKAGE_NAME,\n version: DEFAULT_PACKAGE_VERSION,\n }\n }\n}\n\nfunction readPackageField(value: unknown, fallback: string): string {\n if (typeof value !== 'string') {\n return fallback\n }\n\n const normalized = value.trim()\n if (normalized.length === 0) {\n return fallback\n }\n\n return normalized\n}\n","msgid \"\"\nmsgstr \"\"\n\"POT-Creation-Date: 2026-02-20 17:04+0800\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=utf-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"X-Generator: @lingui/cli\\n\"\n\"Language: en\\n\"\n\"Project-Id-Version: \\n\"\n\"Report-Msgid-Bugs-To: \\n\"\n\"PO-Revision-Date: \\n\"\n\"Last-Translator: \\n\"\n\"Language-Team: \\n\"\n\"Plural-Forms: \\n\"\n\n#. placeholder {0}: index + 1\n#: src/bot/handler.ts:75\nmsgid \"{0}. {root}\"\nmsgstr \"{0}. {root}\"\n\n#: src/bot/commands.ts:14\nmsgid \"/help - Show help\"\nmsgstr \"/help - Show help\"\n\n#: src/bot/commands.ts:45\nmsgid \"\"\n\"/help does not accept arguments.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"/help does not accept arguments.\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/commands.ts:16\nmsgid \"/mode <default|plan> - Switch current session mode\"\nmsgstr \"/mode <default|plan> - Switch current session mode\"\n\n#: src/bot/commands.ts:81\nmsgid \"\"\n\"/mode requires one argument: default or plan.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"/mode requires one argument: default or plan.\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/commands.ts:15\nmsgid \"/new [default|plan] - Create a new session\"\nmsgstr \"/new [default|plan] - Create a new session\"\n\n#: src/bot/commands.ts:56\nmsgid \"\"\n\"/new accepts at most one optional argument: default or plan.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"/new accepts at most one optional argument: default or plan.\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/commands.ts:18\nmsgid \"/projects - Show current working directories\"\nmsgstr \"/projects - Show current working directories\"\n\n#: src/bot/commands.ts:111\nmsgid \"\"\n\"/projects does not accept arguments.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"/projects does not accept arguments.\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/commands.ts:19\nmsgid \"/reset - Clear current session\"\nmsgstr \"/reset - Clear current session\"\n\n#: src/bot/commands.ts:122\nmsgid \"\"\n\"/reset does not accept arguments.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"/reset does not accept arguments.\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/commands.ts:17\nmsgid \"/status - Show current session status\"\nmsgstr \"/status - Show current session status\"\n\n#: src/bot/commands.ts:100\nmsgid \"\"\n\"/status does not accept arguments.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"/status does not accept arguments.\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/commands.ts:13\nmsgid \"Available commands:\"\nmsgstr \"Available commands:\"\n\n#: src/bot/relay.ts:61\nmsgid \"Cannot identify sender. Please try again later.\"\nmsgstr \"Cannot identify sender. Please try again later.\"\n\n#. placeholder {0}: error.message\n#: src/bot/handler.ts:146\nmsgid \"Codex execution failed: {0}\"\nmsgstr \"Codex execution failed: {0}\"\n\n#: src/bot/handler.ts:149\nmsgid \"Codex execution failed. Please try again later.\"\nmsgstr \"Codex execution failed. Please try again later.\"\n\n#: src/bot/commands.ts:30\nmsgid \"\"\n\"Command cannot be empty.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"Command cannot be empty.\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/handler.ts:106\nmsgid \"Created a new session.\"\nmsgstr \"Created a new session.\"\n\n#: src/bot/handler.ts:85\nmsgid \"Current session has been cleared.\"\nmsgstr \"Current session has been cleared.\"\n\n#: src/bot/handler.ts:59\nmsgid \"Current session status:\"\nmsgstr \"Current session status:\"\n\n#: src/bot/handler.ts:77\nmsgid \"Current working directories:\"\nmsgstr \"Current working directories:\"\n\n#: src/index.ts:37\nmsgid \"Currently busy. Please try again later.\"\nmsgstr \"Currently busy. Please try again later.\"\n\n#. placeholder {0}: created.cwd\n#: src/bot/handler.ts:108\nmsgid \"cwd: {0}\"\nmsgstr \"cwd: {0}\"\n\n#: src/bot/relay.ts:44\n#: src/bot/relay.ts:49\nmsgid \"Failed to parse message. Please send a text message.\"\nmsgstr \"Failed to parse message. Please send a text message.\"\n\n#: src/index.ts:86\nmsgid \"Failed to process message. Please try again later.\"\nmsgstr \"Failed to process message. Please try again later.\"\n\n#. placeholder {0}: error.message\n#: src/bot/handler.ts:154\nmsgid \"Failed to read open projects: {0}\"\nmsgstr \"Failed to read open projects: {0}\"\n\n#: src/bot/handler.ts:157\nmsgid \"Failed to read open projects. Please try again later.\"\nmsgstr \"Failed to read open projects. Please try again later.\"\n\n#. placeholder {0}: formatError(error)\n#: src/core/config.ts:131\nmsgid \"Failed to read relay config at {configPath}: {0}\"\nmsgstr \"Failed to read relay config at {configPath}: {0}\"\n\n#: src/core/startup.ts:17\n#: src/index.ts:33\nmsgid \"Failed to start relay: {message}\"\nmsgstr \"Failed to start relay: {message}\"\n\n#. placeholder {0}: formatError(error)\n#: src/core/config.ts:140\nmsgid \"Invalid JSON in relay config at {configPath}: {0}\"\nmsgstr \"Invalid JSON in relay config at {configPath}: {0}\"\n\n#: src/bot/commands.ts:69\n#: src/bot/commands.ts:89\nmsgid \"\"\n\"Invalid mode \\\"{modeToken}\\\", only default or plan are supported.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"Invalid mode \\\"{modeToken}\\\", only default or plan are supported.\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/core/config.ts:160\nmsgid \"Invalid relay config at {configPath}: env must be a JSON object.\"\nmsgstr \"Invalid relay config at {configPath}: env must be a JSON object.\"\n\n#: src/core/config.ts:146\nmsgid \"Invalid relay config at {configPath}: root must be a JSON object.\"\nmsgstr \"Invalid relay config at {configPath}: root must be a JSON object.\"\n\n#: src/core/config.ts:174\nmsgid \"Invalid relay config: {field} is required and must be a non-empty string.\"\nmsgstr \"Invalid relay config: {field} is required and must be a non-empty string.\"\n\n#: src/core/config.ts:187\nmsgid \"Invalid relay config: {field} must be a string.\"\nmsgstr \"Invalid relay config: {field} must be a string.\"\n\n#: src/core/config.ts:208\n#: src/core/config.ts:219\n#: src/core/config.ts:227\nmsgid \"Invalid relay config: CODEX_TIMEOUT_MS must be a positive integer.\"\nmsgstr \"Invalid relay config: CODEX_TIMEOUT_MS must be a positive integer.\"\n\n#. placeholder {0}: formatInvalidLocale(value)\n#: src/core/config.ts:241\nmsgid \"Invalid relay config: locale \\\"{0}\\\" is not supported. Falling back to {systemLocale}.\"\nmsgstr \"Invalid relay config: locale \\\"{0}\\\" is not supported. Falling back to {systemLocale}.\"\n\n#: src/core/config.ts:226\n#~ msgid \"Invalid relay config: LOCALE \\\"{0}\\\" is not supported. Falling back to en.\"\n#~ msgstr \"Invalid relay config: LOCALE \\\"{0}\\\" is not supported. Falling back to en.\"\n\n#: src/core/config.ts:257\nmsgid \"Invalid relay config: locale \\\"{normalized}\\\" is not supported. Falling back to {systemLocale}.\"\nmsgstr \"Invalid relay config: locale \\\"{normalized}\\\" is not supported. Falling back to {systemLocale}.\"\n\n#: src/core/config.ts:241\n#~ msgid \"Invalid relay config: LOCALE \\\"{normalized}\\\" is not supported. Falling back to en.\"\n#~ msgstr \"Invalid relay config: LOCALE \\\"{normalized}\\\" is not supported. Falling back to en.\"\n\n#. placeholder {0}: created.mode\n#. placeholder {0}: currentSession.mode\n#: src/bot/handler.ts:62\n#: src/bot/handler.ts:109\nmsgid \"mode: {0}\"\nmsgstr \"mode: {0}\"\n\n#. placeholder {0}: created.model\n#. placeholder {0}: currentSession.model\n#: src/bot/handler.ts:63\n#: src/bot/handler.ts:110\nmsgid \"model: {0}\"\nmsgstr \"model: {0}\"\n\n#: src/bot/handler.ts:56\n#: src/bot/handler.ts:179\nmsgid \"New Session\"\nmsgstr \"New Session\"\n\n#: src/bot/handler.ts:90\nmsgid \"No active session. Send a normal message or use /new to create one first.\"\nmsgstr \"No active session. Send a normal message or use /new to create one first.\"\n\n#: src/bot/handler.ts:52\nmsgid \"No active session. Send a normal message or use /new to create one.\"\nmsgstr \"No active session. Send a normal message or use /new to create one.\"\n\n#: src/bot/handler.ts:71\nmsgid \"No working directories are currently open.\"\nmsgstr \"No working directories are currently open.\"\n\n#: src/bot/relay.ts:66\nmsgid \"Please send a text message.\"\nmsgstr \"Please send a text message.\"\n\n#: src/core/config.ts:80\nmsgid \"Relay config is missing. Template created at {configPath}. Please edit this file and restart.\"\nmsgstr \"Relay config is missing. Template created at {configPath}. Please edit this file and restart.\"\n\n#. placeholder {0}: parsed.mode\n#: src/bot/handler.ts:98\nmsgid \"Switched to {0} mode.\"\nmsgstr \"Switched to {0} mode.\"\n\n#. placeholder {0}: created.threadId\n#. placeholder {0}: currentSession.threadId\n#: src/bot/handler.ts:60\n#: src/bot/handler.ts:107\nmsgid \"thread: {0}\"\nmsgstr \"thread: {0}\"\n\n#: src/bot/handler.ts:61\nmsgid \"title: {title}\"\nmsgstr \"title: {title}\"\n\n#. placeholder {0}: command ?? normalized\n#: src/bot/commands.ts:131\nmsgid \"\"\n\"Unknown command \\\"{0}\\\".\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"Unknown command \\\"{0}\\\".\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/handler.ts:263\n#~ msgid \"User message: {0}\"\n#~ msgstr \"User message: {0}\"\n\n#: src/bot/handler.ts:248\n#~ msgid \"\"\n#~ \"You are a session title generator.\\n\"\n#~ \"Generate a short Chinese title based on the user message.\\n\"\n#~ \"Strict requirements:\\n\"\n#~ \"1. Output title text only, with no explanation.\\n\"\n#~ \"2. Output a single line with no line breaks.\\n\"\n#~ \"3. Do not use quotes or title marks.\\n\"\n#~ \"4. Keep the title within 24 characters.\"\n#~ msgstr \"\"\n#~ \"You are a session title generator.\\n\"\n#~ \"Generate a short Chinese title based on the user message.\\n\"\n#~ \"Strict requirements:\\n\"\n#~ \"1. Output title text only, with no explanation.\\n\"\n#~ \"2. Output a single line with no line breaks.\\n\"\n#~ \"3. Do not use quotes or title marks.\\n\"\n#~ \"4. Keep the title within 24 characters.\"\n\n#: src/bot/handler.ts:255\n#~ msgid \"\"\n#~ \"You are a session title generator.\\n\"\n#~ \"Generate a short English title based on the user message.\\n\"\n#~ \"Strict requirements:\\n\"\n#~ \"1. Output title text only, with no explanation.\\n\"\n#~ \"2. Output a single line with no line breaks.\\n\"\n#~ \"3. Do not use quotes.\\n\"\n#~ \"4. Keep the title within 24 characters.\"\n#~ msgstr \"\"\n#~ \"You are a session title generator.\\n\"\n#~ \"Generate a short English title based on the user message.\\n\"\n#~ \"Strict requirements:\\n\"\n#~ \"1. Output title text only, with no explanation.\\n\"\n#~ \"2. Output a single line with no line breaks.\\n\"\n#~ \"3. Do not use quotes.\\n\"\n#~ \"4. Keep the title within 24 characters.\"\n","msgid \"\"\nmsgstr \"\"\n\"POT-Creation-Date: 2026-02-20 17:04+0800\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=utf-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"X-Generator: @lingui/cli\\n\"\n\"Language: zh\\n\"\n\"Project-Id-Version: \\n\"\n\"Report-Msgid-Bugs-To: \\n\"\n\"PO-Revision-Date: \\n\"\n\"Last-Translator: \\n\"\n\"Language-Team: \\n\"\n\"Plural-Forms: \\n\"\n\n#. placeholder {0}: index + 1\n#: src/bot/handler.ts:75\nmsgid \"{0}. {root}\"\nmsgstr \"{0}. {root}\"\n\n#: src/bot/commands.ts:14\nmsgid \"/help - Show help\"\nmsgstr \"/help - 显示帮助\"\n\n#: src/bot/commands.ts:45\nmsgid \"\"\n\"/help does not accept arguments.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"/help 不接受参数。\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/commands.ts:16\nmsgid \"/mode <default|plan> - Switch current session mode\"\nmsgstr \"/mode <default|plan> - 切换当前会话模式\"\n\n#: src/bot/commands.ts:81\nmsgid \"\"\n\"/mode requires one argument: default or plan.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"/mode 需要一个参数:default 或 plan。\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/commands.ts:15\nmsgid \"/new [default|plan] - Create a new session\"\nmsgstr \"/new [default|plan] - 创建新会话\"\n\n#: src/bot/commands.ts:56\nmsgid \"\"\n\"/new accepts at most one optional argument: default or plan.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"/new 最多接受一个可选参数:default 或 plan。\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/commands.ts:18\nmsgid \"/projects - Show current working directories\"\nmsgstr \"/projects - 显示当前工作目录\"\n\n#: src/bot/commands.ts:111\nmsgid \"\"\n\"/projects does not accept arguments.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"/projects 不接受参数。\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/commands.ts:19\nmsgid \"/reset - Clear current session\"\nmsgstr \"/reset - 清空当前会话\"\n\n#: src/bot/commands.ts:122\nmsgid \"\"\n\"/reset does not accept arguments.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"/reset 不接受参数。\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/commands.ts:17\nmsgid \"/status - Show current session status\"\nmsgstr \"/status - 显示当前会话状态\"\n\n#: src/bot/commands.ts:100\nmsgid \"\"\n\"/status does not accept arguments.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"/status 不接受参数。\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/commands.ts:13\nmsgid \"Available commands:\"\nmsgstr \"可用命令:\"\n\n#: src/bot/relay.ts:61\nmsgid \"Cannot identify sender. Please try again later.\"\nmsgstr \"无法识别发送者,请稍后重试。\"\n\n#. placeholder {0}: error.message\n#: src/bot/handler.ts:146\nmsgid \"Codex execution failed: {0}\"\nmsgstr \"Codex 执行失败:{0}\"\n\n#: src/bot/handler.ts:149\nmsgid \"Codex execution failed. Please try again later.\"\nmsgstr \"Codex 执行失败,请稍后重试。\"\n\n#: src/bot/commands.ts:30\nmsgid \"\"\n\"Command cannot be empty.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"命令不能为空。\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/handler.ts:106\nmsgid \"Created a new session.\"\nmsgstr \"已创建新会话。\"\n\n#: src/bot/handler.ts:85\nmsgid \"Current session has been cleared.\"\nmsgstr \"当前会话已清空。\"\n\n#: src/bot/handler.ts:59\nmsgid \"Current session status:\"\nmsgstr \"当前会话状态:\"\n\n#: src/bot/handler.ts:77\nmsgid \"Current working directories:\"\nmsgstr \"当前工作目录:\"\n\n#: src/index.ts:37\nmsgid \"Currently busy. Please try again later.\"\nmsgstr \"当前忙碌,请稍后重试。\"\n\n#. placeholder {0}: created.cwd\n#: src/bot/handler.ts:108\nmsgid \"cwd: {0}\"\nmsgstr \"cwd: {0}\"\n\n#: src/bot/relay.ts:44\n#: src/bot/relay.ts:49\nmsgid \"Failed to parse message. Please send a text message.\"\nmsgstr \"解析消息失败,请发送文本消息。\"\n\n#: src/index.ts:86\nmsgid \"Failed to process message. Please try again later.\"\nmsgstr \"处理消息失败,请稍后重试。\"\n\n#. placeholder {0}: error.message\n#: src/bot/handler.ts:154\nmsgid \"Failed to read open projects: {0}\"\nmsgstr \"读取已打开项目失败:{0}\"\n\n#: src/bot/handler.ts:157\nmsgid \"Failed to read open projects. Please try again later.\"\nmsgstr \"读取已打开项目失败,请稍后重试。\"\n\n#. placeholder {0}: formatError(error)\n#: src/core/config.ts:131\nmsgid \"Failed to read relay config at {configPath}: {0}\"\nmsgstr \"读取relay 配置失败 {configPath}:{0}\"\n\n#: src/core/startup.ts:17\n#: src/index.ts:33\nmsgid \"Failed to start relay: {message}\"\nmsgstr \"启动 relay 失败:{message}\"\n\n#. placeholder {0}: formatError(error)\n#: src/core/config.ts:140\nmsgid \"Invalid JSON in relay config at {configPath}: {0}\"\nmsgstr \"relay 配置 {configPath} 中的 JSON 无效:{0}\"\n\n#: src/bot/commands.ts:69\n#: src/bot/commands.ts:89\nmsgid \"\"\n\"Invalid mode \\\"{modeToken}\\\", only default or plan are supported.\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"无效的模式 \\\"{modeToken}\\\",仅支持 default 或 plan。\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/core/config.ts:160\nmsgid \"Invalid relay config at {configPath}: env must be a JSON object.\"\nmsgstr \"relay 配置 {configPath} 无效:env 必须是 JSON 对象。\"\n\n#: src/core/config.ts:146\nmsgid \"Invalid relay config at {configPath}: root must be a JSON object.\"\nmsgstr \"relay 配置 {configPath} 无效:root 必须是 JSON 对象。\"\n\n#: src/core/config.ts:174\nmsgid \"Invalid relay config: {field} is required and must be a non-empty string.\"\nmsgstr \"relay 配置无效:{field} 为必填项且必须为非空字符串。\"\n\n#: src/core/config.ts:187\nmsgid \"Invalid relay config: {field} must be a string.\"\nmsgstr \"relay 配置无效:{field} 必须是字符串。\"\n\n#: src/core/config.ts:208\n#: src/core/config.ts:219\n#: src/core/config.ts:227\nmsgid \"Invalid relay config: CODEX_TIMEOUT_MS must be a positive integer.\"\nmsgstr \"relay 配置无效:CODEX_TIMEOUT_MS 必须是正整数。\"\n\n#. placeholder {0}: formatInvalidLocale(value)\n#: src/core/config.ts:241\nmsgid \"Invalid relay config: locale \\\"{0}\\\" is not supported. Falling back to {systemLocale}.\"\nmsgstr \"relay 配置无效:不支持的 locale \\\"{0}\\\",已回退为 {systemLocale}。\"\n\n#: src/core/config.ts:226\n#~ msgid \"Invalid relay config: LOCALE \\\"{0}\\\" is not supported. Falling back to en.\"\n#~ msgstr \"relay 配置无效:不支持的 LOCALE \\\"{0}\\\",已回退为 en。\"\n\n#: src/core/config.ts:257\nmsgid \"Invalid relay config: locale \\\"{normalized}\\\" is not supported. Falling back to {systemLocale}.\"\nmsgstr \"relay 配置无效:不支持的 locale \\\"{normalized}\\\",已回退为 {systemLocale}。\"\n\n#: src/core/config.ts:241\n#~ msgid \"Invalid relay config: LOCALE \\\"{normalized}\\\" is not supported. Falling back to en.\"\n#~ msgstr \"relay 配置无效:不支持的 LOCALE \\\"{normalized}\\\",已回退为 en。\"\n\n#. placeholder {0}: created.mode\n#. placeholder {0}: currentSession.mode\n#: src/bot/handler.ts:62\n#: src/bot/handler.ts:109\nmsgid \"mode: {0}\"\nmsgstr \"mode: {0}\"\n\n#. placeholder {0}: created.model\n#. placeholder {0}: currentSession.model\n#: src/bot/handler.ts:63\n#: src/bot/handler.ts:110\nmsgid \"model: {0}\"\nmsgstr \"model: {0}\"\n\n#: src/bot/handler.ts:56\n#: src/bot/handler.ts:179\nmsgid \"New Session\"\nmsgstr \"新会话\"\n\n#: src/bot/handler.ts:90\nmsgid \"No active session. Send a normal message or use /new to create one first.\"\nmsgstr \"没有活跃会话。请先发送普通消息或使用 /new 创建会话。\"\n\n#: src/bot/handler.ts:52\nmsgid \"No active session. Send a normal message or use /new to create one.\"\nmsgstr \"没有活跃会话。请发送普通消息或使用 /new 创建会话。\"\n\n#: src/bot/handler.ts:71\nmsgid \"No working directories are currently open.\"\nmsgstr \"当前没有打开的工作目录。\"\n\n#: src/bot/relay.ts:66\nmsgid \"Please send a text message.\"\nmsgstr \"请发送文本消息。\"\n\n#: src/core/config.ts:80\nmsgid \"Relay config is missing. Template created at {configPath}. Please edit this file and restart.\"\nmsgstr \"relay 配置缺失,已在 {configPath} 创建模板。请编辑该文件后重启。\"\n\n#. placeholder {0}: parsed.mode\n#: src/bot/handler.ts:98\nmsgid \"Switched to {0} mode.\"\nmsgstr \"已切换到 {0} 模式。\"\n\n#. placeholder {0}: created.threadId\n#. placeholder {0}: currentSession.threadId\n#: src/bot/handler.ts:60\n#: src/bot/handler.ts:107\nmsgid \"thread: {0}\"\nmsgstr \"thread: {0}\"\n\n#: src/bot/handler.ts:61\nmsgid \"title: {title}\"\nmsgstr \"title: {title}\"\n\n#. placeholder {0}: command ?? normalized\n#: src/bot/commands.ts:131\nmsgid \"\"\n\"Unknown command \\\"{0}\\\".\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"未知命令 \\\"{0}\\\"。\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#: src/bot/handler.ts:263\n#~ msgid \"User message: {0}\"\n#~ msgstr \"用户消息:{0}\"\n\n#: src/bot/handler.ts:248\n#~ msgid \"\"\n#~ \"You are a session title generator.\\n\"\n#~ \"Generate a short Chinese title based on the user message.\\n\"\n#~ \"Strict requirements:\\n\"\n#~ \"1. Output title text only, with no explanation.\\n\"\n#~ \"2. Output a single line with no line breaks.\\n\"\n#~ \"3. Do not use quotes or title marks.\\n\"\n#~ \"4. Keep the title within 24 characters.\"\n#~ msgstr \"\"\n#~ \"你是会话标题生成器。\\n\"\n#~ \"根据用户消息生成简短中文标题。\\n\"\n#~ \"严格要求:\\n\"\n#~ \"1. 仅输出标题文字,不要解释。\\n\"\n#~ \"2. 单行输出,不要换行。\\n\"\n#~ \"3. 不要使用引号或书名号。\\n\"\n#~ \"4. 标题不超过 24 个字符。\"\n\n#: src/bot/handler.ts:255\n#~ msgid \"\"\n#~ \"You are a session title generator.\\n\"\n#~ \"Generate a short English title based on the user message.\\n\"\n#~ \"Strict requirements:\\n\"\n#~ \"1. Output title text only, with no explanation.\\n\"\n#~ \"2. Output a single line with no line breaks.\\n\"\n#~ \"3. Do not use quotes.\\n\"\n#~ \"4. Keep the title within 24 characters.\"\n#~ msgstr \"\"\n#~ \"你是会话标题生成器。\\n\"\n#~ \"根据用户消息生成简短英文标题。\\n\"\n#~ \"严格要求:\\n\"\n#~ \"1. 仅输出标题文字,不要解释。\\n\"\n#~ \"2. 单行输出,不要换行。\\n\"\n#~ \"3. 不要使用引号。\\n\"\n#~ \"4. 标题不超过 24 个字符。\"\n","import { i18n } from '@lingui/core'\nimport { messages as enMessages } from '../locales/en/messages.po'\nimport { messages as zhMessages } from '../locales/zh/messages.po'\nimport type { Messages } from '@lingui/core'\n\nexport type AppLocale = 'en' | 'zh'\n\nconst DEFAULT_LOCALE: AppLocale = detectDefaultLocale()\n\nconst CATALOGS: Record<AppLocale, Messages> = {\n en: enMessages as Messages,\n zh: zhMessages as Messages,\n}\n\nlet activeLocale: AppLocale | null = null\n\n// Activate a default locale eagerly so top-level `t` calls in imported modules\n// never run before Lingui has an active locale.\ninitializeI18n(DEFAULT_LOCALE)\n\nexport function initializeI18n(locale?: string): AppLocale {\n const resolved = resolveLocale(locale)\n i18n.loadAndActivate({\n locale: resolved,\n messages: CATALOGS[resolved],\n })\n activeLocale = resolved\n return resolved\n}\n\nexport function getCurrentLocale(): AppLocale {\n ensureI18nInitialized()\n return activeLocale ?? DEFAULT_LOCALE\n}\n\nexport function isSupportedLocale(locale: string): locale is AppLocale {\n return locale === 'en' || locale === 'zh'\n}\n\nexport function getDefaultLocale(): AppLocale {\n return DEFAULT_LOCALE\n}\n\nfunction resolveLocale(locale?: string): AppLocale {\n if (!locale) {\n return DEFAULT_LOCALE\n }\n\n const mappedLocale = mapToAppLocale(locale)\n if (mappedLocale) {\n return mappedLocale\n }\n\n return DEFAULT_LOCALE\n}\n\nfunction ensureI18nInitialized(): void {\n if (!activeLocale) {\n initializeI18n(DEFAULT_LOCALE)\n }\n}\n\nfunction detectDefaultLocale(): AppLocale {\n const systemLocale = readSystemLocale()\n if (!systemLocale) {\n return 'en'\n }\n\n return mapToAppLocale(systemLocale) ?? 'en'\n}\n\nfunction readSystemLocale(): string | undefined {\n const locale = Intl.DateTimeFormat().resolvedOptions().locale\n if (typeof locale !== 'string') {\n return undefined\n }\n\n const normalized = locale.trim()\n if (normalized.length === 0) {\n return undefined\n }\n\n return normalized\n}\n\nfunction mapToAppLocale(locale: string): AppLocale | null {\n const normalized = locale.trim().toLowerCase().replaceAll('_', '-')\n\n if (normalized === 'zh' || normalized.startsWith('zh-')) {\n return 'zh'\n }\n\n if (normalized === 'en' || normalized.startsWith('en-')) {\n return 'en'\n }\n\n return null\n}\n","import fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { t } from '@lingui/core/macro'\nimport {\n getDefaultLocale,\n initializeI18n,\n isSupportedLocale,\n} from '../i18n/runtime'\nimport type { AppLocale } from '../i18n/runtime'\n\nconst DEFAULT_CODEX_BIN = 'codex'\n\nconst TEMPLATE_ENV_CONFIG: Required<RelayConfigEnv> = {\n BASE_DOMAIN: 'https://open.feishu.cn',\n APP_ID: 'your_app_id',\n APP_SECRET: 'your_app_secret',\n BOT_OPEN_ID: 'ou_xxx',\n CODEX_BIN: DEFAULT_CODEX_BIN,\n CODEX_TIMEOUT_MS: null,\n}\n\nconst TEMPLATE_CONFIG: {\n locale?: AppLocale\n env: Required<RelayConfigEnv>\n} = {\n env: TEMPLATE_ENV_CONFIG,\n}\n\nexport interface RelayConfigEnv {\n BASE_DOMAIN?: string\n APP_ID?: string\n APP_SECRET?: string\n BOT_OPEN_ID?: string\n CODEX_BIN?: string\n CODEX_TIMEOUT_MS?: number | string | null\n}\n\ninterface RelayConfigFile extends RelayConfigEnv {\n locale?: string\n env?: RelayConfigEnv\n}\n\ninterface ParsedRelayConfig {\n env: RelayConfigEnv\n localeValue: unknown\n}\n\nexport interface RelayConfig {\n baseConfig: {\n appId: string\n appSecret: string\n domain: string\n }\n homeDir: string\n botOpenId?: string\n codexBin: string\n codexTimeoutMs?: number\n workspaceCwd: string\n locale: AppLocale\n}\n\nexport interface LoadRelayConfigOptions {\n homeDir?: string\n workspaceCwd?: string\n}\n\nexport function loadRelayConfig(\n options: LoadRelayConfigOptions = {},\n): RelayConfig {\n const homeDir = options.homeDir ?? os.homedir()\n const workspaceCwd = options.workspaceCwd ?? process.cwd()\n const configDir = path.join(homeDir, '.relay')\n const configPath = path.join(configDir, 'config.json')\n\n if (!fs.existsSync(configPath)) {\n ensureConfigTemplate(configDir, configPath)\n throw new Error(\n t`Relay config is missing. Template created at ${configPath}. Please edit this file and restart.`,\n )\n }\n\n const parsed = parseConfigFile(configPath)\n const locale = readLocale(parsed.localeValue)\n initializeI18n(locale)\n\n const domain = readRequiredString(parsed.env.BASE_DOMAIN, 'BASE_DOMAIN')\n const appId = readRequiredString(parsed.env.APP_ID, 'APP_ID')\n const appSecret = readRequiredString(parsed.env.APP_SECRET, 'APP_SECRET')\n\n return {\n baseConfig: {\n appId,\n appSecret,\n domain,\n },\n homeDir,\n botOpenId: readOptionalString(parsed.env.BOT_OPEN_ID, 'BOT_OPEN_ID'),\n codexBin:\n readOptionalString(parsed.env.CODEX_BIN, 'CODEX_BIN') ??\n DEFAULT_CODEX_BIN,\n codexTimeoutMs: readTimeoutMs(parsed.env.CODEX_TIMEOUT_MS),\n workspaceCwd,\n locale,\n }\n}\n\nfunction ensureConfigTemplate(configDir: string, configPath: string): void {\n fs.mkdirSync(configDir, { recursive: true })\n if (fs.existsSync(configPath)) {\n return\n }\n\n fs.writeFileSync(\n configPath,\n `${JSON.stringify(TEMPLATE_CONFIG, null, 2)}\\n`,\n {\n encoding: 'utf-8',\n flag: 'wx',\n },\n )\n}\n\nfunction parseConfigFile(configPath: string): ParsedRelayConfig {\n let raw: string\n try {\n raw = fs.readFileSync(configPath, 'utf-8')\n } catch (error) {\n throw new Error(\n t`Failed to read relay config at ${configPath}: ${formatError(error)}`,\n )\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch (error) {\n throw new Error(\n t`Invalid JSON in relay config at ${configPath}: ${formatError(error)}`,\n )\n }\n\n if (!isObject(parsed)) {\n throw new Error(\n t`Invalid relay config at ${configPath}: root must be a JSON object.`,\n )\n }\n\n const configObject = parsed as RelayConfigFile\n if (configObject.env === undefined) {\n return {\n env: configObject,\n localeValue: configObject.locale,\n }\n }\n\n if (!isObject(configObject.env)) {\n throw new Error(\n t`Invalid relay config at ${configPath}: env must be a JSON object.`,\n )\n }\n\n return {\n env: configObject.env,\n localeValue: configObject.locale,\n }\n}\n\nfunction readRequiredString(value: unknown, field: string): string {\n const normalized = readOptionalString(value, field)\n if (!normalized) {\n throw new Error(\n t`Invalid relay config: ${field} is required and must be a non-empty string.`,\n )\n }\n\n return normalized\n}\n\nfunction readOptionalString(value: unknown, field: string): string | undefined {\n if (value === undefined) {\n return undefined\n }\n\n if (typeof value !== 'string') {\n throw new TypeError(t`Invalid relay config: ${field} must be a string.`)\n }\n\n const normalized = value.trim()\n if (normalized.length === 0) {\n return undefined\n }\n\n return normalized\n}\n\nfunction readTimeoutMs(value: unknown): number | undefined {\n if (value === undefined || value === null) {\n return undefined\n }\n\n if (typeof value === 'number') {\n if (Number.isInteger(value) && value > 0) {\n return value\n }\n throw new Error(\n t`Invalid relay config: CODEX_TIMEOUT_MS must be a positive integer.`,\n )\n }\n\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (trimmed.length === 0) {\n return undefined\n }\n if (!/^[1-9]\\d*$/.test(trimmed)) {\n throw new Error(\n t`Invalid relay config: CODEX_TIMEOUT_MS must be a positive integer.`,\n )\n }\n\n return Number.parseInt(trimmed, 10)\n }\n\n throw new Error(\n t`Invalid relay config: CODEX_TIMEOUT_MS must be a positive integer.`,\n )\n}\n\nfunction readLocale(value: unknown): AppLocale {\n // When locale is missing in config, use the system-detected default locale.\n const systemLocale = getDefaultLocale()\n\n if (value === undefined || value === null) {\n return systemLocale\n }\n\n if (typeof value !== 'string') {\n console.warn(\n t`Invalid relay config: locale \"${formatInvalidLocale(value)}\" is not supported. Falling back to ${systemLocale}.`,\n )\n return systemLocale\n }\n\n const normalized = value.trim()\n if (normalized.length === 0) {\n return systemLocale\n }\n\n const mapped = mapLocaleToAppLocale(normalized)\n if (mapped) {\n return mapped\n }\n\n console.warn(\n t`Invalid relay config: locale \"${normalized}\" is not supported. Falling back to ${systemLocale}.`,\n )\n\n return systemLocale\n}\n\nfunction formatInvalidLocale(value: unknown): string {\n if (typeof value === 'string') {\n return value\n }\n\n if (typeof value === 'number' || typeof value === 'boolean') {\n return String(value)\n }\n\n try {\n return JSON.stringify(value)\n } catch {\n return String(value)\n }\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction mapLocaleToAppLocale(value: string): AppLocale | null {\n if (isSupportedLocale(value)) {\n return value\n }\n\n const normalized = value.toLowerCase().replaceAll('_', '-')\n if (normalized === 'zh' || normalized.startsWith('zh-')) {\n return 'zh'\n }\n\n if (normalized === 'en' || normalized.startsWith('en-')) {\n return 'en'\n }\n\n return null\n}\n\nfunction formatError(error: unknown): string {\n if (error instanceof Error) {\n return error.message\n }\n\n return String(error)\n}\n","import process from 'node:process'\nimport { t } from '@lingui/core/macro'\nimport { loadRelayConfig } from './config'\nimport type { RelayConfig } from './config'\n\nexport function loadConfigOrExit(): RelayConfig {\n try {\n return loadRelayConfig()\n } catch (error) {\n console.error(formatStartupError(error))\n process.exit(1)\n }\n}\n\nfunction formatStartupError(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error)\n return t`Failed to start relay: ${message}`\n}\n","import https from 'node:https'\n\nconst DEFAULT_REGISTRY_BASE_URL = 'https://registry.npmjs.org'\nconst DEFAULT_TIMEOUT_MS = 2500\n\nexport interface UpdateCheckLogger {\n warn: (message: string) => void\n}\n\nexport type RequestRegistryJson = (\n url: URL,\n timeoutMs: number,\n) => Promise<unknown>\n\nexport interface CheckRelayPackageUpdateOptions {\n packageName: string\n currentVersion: string\n timeoutMs?: number\n registryBaseUrl?: string\n requestJson?: RequestRegistryJson\n logger?: UpdateCheckLogger\n}\n\ninterface ParsedSemver {\n major: number\n minor: number\n patch: number\n prerelease: string[]\n}\n\nexport async function checkRelayPackageUpdate(\n options: CheckRelayPackageUpdateOptions,\n): Promise<void> {\n const currentVersion = options.currentVersion.trim()\n if (currentVersion.length === 0) {\n return\n }\n\n const requestJson = options.requestJson ?? requestRegistryJson\n const logger = options.logger ?? console\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS\n const registryBaseUrl = normalizeRegistryBaseUrl(\n options.registryBaseUrl ?? DEFAULT_REGISTRY_BASE_URL,\n )\n const packagePath = encodeURIComponent(options.packageName)\n const requestUrl = new URL(`${registryBaseUrl}/${packagePath}`)\n\n let payload: unknown\n try {\n payload = await requestJson(requestUrl, timeoutMs)\n } catch {\n return\n }\n\n const latestVersion = readLatestVersion(payload)\n if (latestVersion === undefined) {\n return\n }\n\n if (compareSemverVersions(latestVersion, currentVersion) <= 0) {\n return\n }\n\n logger.warn(\n `[relay] A new version of ${options.packageName} is available: ${currentVersion} -> ${latestVersion}. Run \"npm i -g ${options.packageName}@latest\".`,\n )\n}\n\nexport function compareSemverVersions(left: string, right: string): number {\n const leftSemver = parseSemver(left)\n const rightSemver = parseSemver(right)\n\n if (leftSemver === null || rightSemver === null) {\n return 0\n }\n\n const coreDiff =\n compareNumber(leftSemver.major, rightSemver.major) ||\n compareNumber(leftSemver.minor, rightSemver.minor) ||\n compareNumber(leftSemver.patch, rightSemver.patch)\n if (coreDiff !== 0) {\n return coreDiff\n }\n\n return comparePrerelease(leftSemver.prerelease, rightSemver.prerelease)\n}\n\nfunction parseSemver(value: string): ParsedSemver | null {\n const normalized = value.trim()\n const semverPattern =\n /^v?(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?(?:\\+[0-9A-Za-z.-]+)?$/\n const match = semverPattern.exec(normalized)\n if (!match) {\n return null\n }\n\n return {\n major: Number.parseInt(match[1], 10),\n minor: Number.parseInt(match[2], 10),\n patch: Number.parseInt(match[3], 10),\n prerelease: match[4] === undefined ? [] : match[4].split('.'),\n }\n}\n\nfunction comparePrerelease(left: string[], right: string[]): number {\n if (left.length === 0 && right.length === 0) {\n return 0\n }\n if (left.length === 0) {\n return 1\n }\n if (right.length === 0) {\n return -1\n }\n\n const maxLength = Math.max(left.length, right.length)\n for (let index = 0; index < maxLength; index += 1) {\n const leftPart = left[index]\n const rightPart = right[index]\n if (leftPart === undefined) {\n return -1\n }\n if (rightPart === undefined) {\n return 1\n }\n\n const compared = comparePrereleasePart(leftPart, rightPart)\n if (compared !== 0) {\n return compared\n }\n }\n\n return 0\n}\n\nfunction comparePrereleasePart(left: string, right: string): number {\n const numericPattern = /^\\d+$/\n const leftIsNumeric = numericPattern.test(left)\n const rightIsNumeric = numericPattern.test(right)\n\n if (leftIsNumeric && rightIsNumeric) {\n return compareNumber(Number.parseInt(left, 10), Number.parseInt(right, 10))\n }\n if (leftIsNumeric) {\n return -1\n }\n if (rightIsNumeric) {\n return 1\n }\n\n return left.localeCompare(right)\n}\n\nfunction compareNumber(left: number, right: number): number {\n if (left === right) {\n return 0\n }\n return left > right ? 1 : -1\n}\n\nfunction readLatestVersion(payload: unknown): string | undefined {\n if (!isRecord(payload)) {\n return undefined\n }\n\n const distTags = payload['dist-tags']\n if (!isRecord(distTags)) {\n return undefined\n }\n\n const latest = distTags.latest\n if (typeof latest !== 'string') {\n return undefined\n }\n\n const normalized = latest.trim()\n if (normalized.length === 0) {\n return undefined\n }\n\n return normalized\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null\n}\n\nfunction normalizeRegistryBaseUrl(value: string): string {\n return value.endsWith('/') ? value.slice(0, -1) : value\n}\n\nasync function requestRegistryJson(\n url: URL,\n timeoutMs: number,\n): Promise<unknown> {\n return await new Promise((resolve, reject) => {\n const request = https.get(\n url,\n {\n headers: {\n accept: 'application/vnd.npm.install-v1+json',\n },\n },\n (response) => {\n const statusCode = response.statusCode ?? 0\n if (statusCode < 200 || statusCode >= 300) {\n response.resume()\n reject(new Error(`Unexpected npm registry response: ${statusCode}`))\n return\n }\n\n let body = ''\n response.setEncoding('utf-8')\n response.on('data', (chunk: string) => {\n body += chunk\n })\n response.on('end', () => {\n try {\n resolve(JSON.parse(body) as unknown)\n } catch (error) {\n reject(error)\n }\n })\n response.on('error', (error) => {\n reject(error)\n })\n },\n )\n\n request.setTimeout(timeoutMs, () => {\n request.destroy(new Error('Update check timed out.'))\n })\n request.on('error', (error) => {\n reject(error)\n })\n })\n}\n","import { resolveSenderId } from '../bot/message-filter'\nimport { getSession, getSessionKey } from '../session/store'\nimport type * as Lark from '@larksuiteoapi/node-sdk'\nimport type { ReceiveMessageEvent } from '../bot/relay'\n\nconst FALLBACK_REPLY_TAG = 'no-thread'\n\nexport interface SendReplyOptions {\n includeThreadTag?: boolean\n}\n\nexport interface FeishuReceiveMessageEvent extends ReceiveMessageEvent {\n event_id?: string\n message: ReceiveMessageEvent['message'] & {\n message_id: string\n }\n}\n\nexport async function sendReply(\n larkClient: Lark.Client,\n data: FeishuReceiveMessageEvent,\n text: string,\n options?: SendReplyOptions,\n): Promise<void> {\n const content = JSON.stringify({\n text: formatReplyTextWithThreadId(data, text, options),\n })\n\n if (data.message.chat_type === 'p2p') {\n await larkClient.im.v1.message.create({\n params: {\n receive_id_type: 'chat_id',\n },\n data: {\n receive_id: data.message.chat_id,\n msg_type: 'text',\n content,\n },\n })\n return\n }\n\n await larkClient.im.v1.message.reply({\n path: {\n message_id: data.message.message_id,\n },\n data: {\n msg_type: 'text',\n content,\n },\n })\n}\n\nfunction formatReplyTextWithThreadId(\n data: FeishuReceiveMessageEvent,\n text: string,\n options?: SendReplyOptions,\n): string {\n if (!options?.includeThreadTag) {\n return text.trim()\n }\n\n const replyTag = resolveReplyTag(data)\n const normalizedText = text.trim()\n if (normalizedText.length === 0) {\n return `${replyTag}\\n`\n }\n\n return `${replyTag}\\n\\n${normalizedText}`\n}\n\nfunction resolveReplyTag(data: FeishuReceiveMessageEvent): string {\n const senderId = resolveSenderId(data.sender.sender_id)\n if (!senderId) {\n return FALLBACK_REPLY_TAG\n }\n\n const sessionKey = getSessionKey({\n chatType: data.message.chat_type,\n chatId: data.message.chat_id,\n userId: senderId,\n })\n const session = getSession(sessionKey)\n if (!session || session.threadId.trim().length === 0) {\n return FALLBACK_REPLY_TAG\n }\n\n return session.threadId\n}\n","import process from 'node:process'\nimport * as Lark from '@larksuiteoapi/node-sdk'\nimport { t } from '@lingui/core/macro'\nimport { isPlainObject } from 'es-toolkit/predicate'\nimport { parseCommand } from './bot/commands'\nimport { handleIncomingText } from './bot/handler'\nimport { shouldProcessMessage } from './bot/message-filter'\nimport { buildReplyForMessageEvent, stripMentionTags } from './bot/relay'\nimport { createCodexThread, runCodexTurn } from './codex/app-server'\nimport { listOpenProjects } from './codex/state'\nimport { readRelayPackageMetadata } from './core/package-meta'\nimport { loadConfigOrExit } from './core/startup'\nimport { checkRelayPackageUpdate } from './core/update-check'\nimport { sendReply } from './feishu/reply'\nimport { initializeI18n } from './i18n/runtime'\nimport {\n clearSession,\n getSession,\n initializeSessionStore,\n setSession,\n withSessionLock,\n} from './session/store'\nimport type { FeishuReceiveMessageEvent } from './feishu/reply'\n\nconst relayConfig = loadConfigOrExit()\ninitializeI18n(relayConfig.locale)\n\ntry {\n initializeSessionStore({\n homeDir: relayConfig.homeDir,\n workspaceCwd: relayConfig.workspaceCwd,\n })\n} catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n console.error(t`Failed to start relay: ${message}`)\n process.exit(1)\n}\n\nif (process.env.RELAY_SKIP_UPDATE_CHECK !== '1') {\n const packageMetadata = readRelayPackageMetadata()\n void checkRelayPackageUpdate({\n packageName: packageMetadata.name,\n currentVersion: packageMetadata.version,\n })\n}\n\nconst BUSY_MESSAGE = t`Currently busy. Please try again later.`\n\nconst client = new Lark.Client(relayConfig.baseConfig)\nconst wsClient = new Lark.WSClient(relayConfig.baseConfig)\nlet isTaskRunning = false\n\nasync function processIncomingEvent(\n data: FeishuReceiveMessageEvent,\n): Promise<void> {\n try {\n const reply = await buildReplyForMessageEvent(data, {\n botOpenId: relayConfig.botOpenId,\n handleIncomingText: (input) =>\n handleIncomingText(input, {\n createThread: (mode) =>\n createCodexThread({\n mode,\n cwd: relayConfig.workspaceCwd,\n codexBin: relayConfig.codexBin,\n timeoutMs: relayConfig.codexTimeoutMs,\n }),\n runTurn: (params) =>\n runCodexTurn({\n ...params,\n cwd: relayConfig.workspaceCwd,\n codexBin: relayConfig.codexBin,\n timeoutMs: relayConfig.codexTimeoutMs,\n }),\n getSession,\n setSession,\n clearSession,\n withSessionLock,\n listOpenProjects,\n }),\n })\n\n if (reply === null) {\n return\n }\n\n await sendReply(client, data, reply, {\n includeThreadTag: shouldAttachThreadTag(data),\n })\n } catch (error) {\n console.error('failed to handle Feishu message', error)\n try {\n await sendReply(\n client,\n data,\n t`Failed to process message. Please try again later.`,\n )\n } catch (replyError) {\n console.error('failed to send failure message', replyError)\n }\n }\n}\n\nfunction shouldAttachThreadTag(data: FeishuReceiveMessageEvent): boolean {\n const rawText = parseEventText(data.message.content)\n if (rawText === null) {\n return false\n }\n\n const normalizedText = stripMentionTags(rawText).trim()\n if (normalizedText.length === 0) {\n return false\n }\n\n return parseCommand(normalizedText).type === 'prompt'\n}\n\nfunction parseEventText(content: string): string | null {\n try {\n const parsed: unknown = JSON.parse(content)\n if (!isPlainObject(parsed)) {\n return null\n }\n\n return typeof parsed.text === 'string' ? parsed.text : null\n } catch {\n return null\n }\n}\n\nconst eventDispatcher = new Lark.EventDispatcher({}).register({\n 'im.message.receive_v1': async (data: FeishuReceiveMessageEvent) => {\n // eslint-disable-next-line no-console\n console.info(\n 'feishu message received\\n',\n JSON.stringify(data, null, 2),\n '\\n',\n )\n\n if (!shouldProcessMessage(data, relayConfig.botOpenId)) {\n return\n }\n\n if (isTaskRunning) {\n void sendReply(client, data, BUSY_MESSAGE)\n return\n }\n\n isTaskRunning = true\n void processIncomingEvent(data).finally(() => {\n isTaskRunning = false\n })\n },\n})\n\nwsClient.start({ eventDispatcher })\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@debbl/relay",
3
3
  "type": "module",
4
- "version": "0.0.3",
4
+ "version": "0.0.4",
5
5
  "description": "relay",
6
6
  "author": "Brendan Dash <me@aiwan.run> (https://aiwan.run)",
7
7
  "license": "MIT",