@debbl/relay 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +52 -22
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1133,12 +1133,13 @@ const messages = JSON.parse("{\"1xKjU/\":[\"读取已打开项目失败,请稍
|
|
|
1133
1133
|
|
|
1134
1134
|
//#endregion
|
|
1135
1135
|
//#region src/i18n/runtime.ts
|
|
1136
|
-
const DEFAULT_LOCALE =
|
|
1136
|
+
const DEFAULT_LOCALE = detectDefaultLocale();
|
|
1137
1137
|
const CATALOGS = {
|
|
1138
1138
|
en: messages$1,
|
|
1139
1139
|
zh: messages
|
|
1140
1140
|
};
|
|
1141
1141
|
let activeLocale = null;
|
|
1142
|
+
initializeI18n(DEFAULT_LOCALE);
|
|
1142
1143
|
function initializeI18n(locale) {
|
|
1143
1144
|
const resolved = resolveLocale(locale);
|
|
1144
1145
|
i18n.loadAndActivate({
|
|
@@ -1156,25 +1157,40 @@ function getDefaultLocale() {
|
|
|
1156
1157
|
}
|
|
1157
1158
|
function resolveLocale(locale) {
|
|
1158
1159
|
if (!locale) return DEFAULT_LOCALE;
|
|
1159
|
-
|
|
1160
|
+
const mappedLocale = mapToAppLocale(locale);
|
|
1161
|
+
if (mappedLocale) return mappedLocale;
|
|
1160
1162
|
return DEFAULT_LOCALE;
|
|
1161
1163
|
}
|
|
1164
|
+
function detectDefaultLocale() {
|
|
1165
|
+
const systemLocale = readSystemLocale();
|
|
1166
|
+
if (!systemLocale) return "en";
|
|
1167
|
+
return mapToAppLocale(systemLocale) ?? "en";
|
|
1168
|
+
}
|
|
1169
|
+
function readSystemLocale() {
|
|
1170
|
+
const locale = Intl.DateTimeFormat().resolvedOptions().locale;
|
|
1171
|
+
if (typeof locale !== "string") return;
|
|
1172
|
+
const normalized = locale.trim();
|
|
1173
|
+
if (normalized.length === 0) return;
|
|
1174
|
+
return normalized;
|
|
1175
|
+
}
|
|
1176
|
+
function mapToAppLocale(locale) {
|
|
1177
|
+
const normalized = locale.trim().toLowerCase().replaceAll("_", "-");
|
|
1178
|
+
if (normalized === "zh" || normalized.startsWith("zh-")) return "zh";
|
|
1179
|
+
if (normalized === "en" || normalized.startsWith("en-")) return "en";
|
|
1180
|
+
return null;
|
|
1181
|
+
}
|
|
1162
1182
|
|
|
1163
1183
|
//#endregion
|
|
1164
1184
|
//#region src/core/config.ts
|
|
1165
1185
|
const DEFAULT_CODEX_BIN = "codex";
|
|
1166
|
-
const
|
|
1186
|
+
const TEMPLATE_CONFIG = { env: {
|
|
1167
1187
|
BASE_DOMAIN: "https://open.feishu.cn",
|
|
1168
1188
|
APP_ID: "your_app_id",
|
|
1169
1189
|
APP_SECRET: "your_app_secret",
|
|
1170
1190
|
BOT_OPEN_ID: "ou_xxx",
|
|
1171
1191
|
CODEX_BIN: DEFAULT_CODEX_BIN,
|
|
1172
1192
|
CODEX_TIMEOUT_MS: null
|
|
1173
|
-
};
|
|
1174
|
-
const TEMPLATE_CONFIG = {
|
|
1175
|
-
locale: getDefaultLocale(),
|
|
1176
|
-
env: TEMPLATE_ENV_CONFIG
|
|
1177
|
-
};
|
|
1193
|
+
} };
|
|
1178
1194
|
function loadRelayConfig(options = {}) {
|
|
1179
1195
|
const homeDir = options.homeDir ?? os.homedir();
|
|
1180
1196
|
const workspaceCwd = options.workspaceCwd ?? process.cwd();
|
|
@@ -1249,7 +1265,7 @@ function parseConfigFile(configPath) {
|
|
|
1249
1265
|
const configObject = parsed;
|
|
1250
1266
|
if (configObject.env === void 0) return {
|
|
1251
1267
|
env: configObject,
|
|
1252
|
-
localeValue: configObject.locale
|
|
1268
|
+
localeValue: configObject.locale
|
|
1253
1269
|
};
|
|
1254
1270
|
if (!isObject(configObject.env)) throw new Error(i18n._({
|
|
1255
1271
|
id: "CfFOzJ",
|
|
@@ -1258,7 +1274,7 @@ function parseConfigFile(configPath) {
|
|
|
1258
1274
|
}));
|
|
1259
1275
|
return {
|
|
1260
1276
|
env: configObject.env,
|
|
1261
|
-
localeValue: configObject.locale
|
|
1277
|
+
localeValue: configObject.locale
|
|
1262
1278
|
};
|
|
1263
1279
|
}
|
|
1264
1280
|
function readRequiredString(value, field) {
|
|
@@ -1305,25 +1321,32 @@ function readTimeoutMs(value) {
|
|
|
1305
1321
|
}));
|
|
1306
1322
|
}
|
|
1307
1323
|
function readLocale(value) {
|
|
1308
|
-
const
|
|
1309
|
-
if (value === void 0 || value === null) return
|
|
1324
|
+
const systemLocale = getDefaultLocale();
|
|
1325
|
+
if (value === void 0 || value === null) return systemLocale;
|
|
1310
1326
|
if (typeof value !== "string") {
|
|
1311
1327
|
console.warn(i18n._({
|
|
1312
|
-
id: "
|
|
1313
|
-
message: "Invalid relay config: locale \"{0}\" is not supported. Falling back to
|
|
1314
|
-
values: {
|
|
1328
|
+
id: "CgiJb7",
|
|
1329
|
+
message: "Invalid relay config: locale \"{0}\" is not supported. Falling back to {systemLocale}.",
|
|
1330
|
+
values: {
|
|
1331
|
+
systemLocale,
|
|
1332
|
+
0: formatInvalidLocale(value)
|
|
1333
|
+
}
|
|
1315
1334
|
}));
|
|
1316
|
-
return
|
|
1335
|
+
return systemLocale;
|
|
1317
1336
|
}
|
|
1318
1337
|
const normalized = value.trim();
|
|
1319
|
-
if (normalized.length === 0) return
|
|
1320
|
-
|
|
1338
|
+
if (normalized.length === 0) return systemLocale;
|
|
1339
|
+
const mapped = mapLocaleToAppLocale(normalized);
|
|
1340
|
+
if (mapped) return mapped;
|
|
1321
1341
|
console.warn(i18n._({
|
|
1322
|
-
id: "
|
|
1323
|
-
message: "Invalid relay config: locale \"{normalized}\" is not supported. Falling back to
|
|
1324
|
-
values: {
|
|
1342
|
+
id: "xBlpjC",
|
|
1343
|
+
message: "Invalid relay config: locale \"{normalized}\" is not supported. Falling back to {systemLocale}.",
|
|
1344
|
+
values: {
|
|
1345
|
+
normalized,
|
|
1346
|
+
systemLocale
|
|
1347
|
+
}
|
|
1325
1348
|
}));
|
|
1326
|
-
return
|
|
1349
|
+
return systemLocale;
|
|
1327
1350
|
}
|
|
1328
1351
|
function formatInvalidLocale(value) {
|
|
1329
1352
|
if (typeof value === "string") return value;
|
|
@@ -1337,6 +1360,13 @@ function formatInvalidLocale(value) {
|
|
|
1337
1360
|
function isObject(value) {
|
|
1338
1361
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1339
1362
|
}
|
|
1363
|
+
function mapLocaleToAppLocale(value) {
|
|
1364
|
+
if (isSupportedLocale(value)) return value;
|
|
1365
|
+
const normalized = value.toLowerCase().replaceAll("_", "-");
|
|
1366
|
+
if (normalized === "zh" || normalized.startsWith("zh-")) return "zh";
|
|
1367
|
+
if (normalized === "en" || normalized.startsWith("en-")) return "en";
|
|
1368
|
+
return null;
|
|
1369
|
+
}
|
|
1340
1370
|
function formatError(error) {
|
|
1341
1371
|
if (error instanceof Error) return error.message;
|
|
1342
1372
|
return String(error);
|
package/dist/index.mjs.map
CHANGED
|
@@ -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","CATALOGS","en","zh","activeLocale","initializeI18n","locale","resolved","resolveLocale","loadAndActivate","getCurrentLocale","ensureI18nInitialized","isSupportedLocale","getDefaultLocale","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","locale","env","loadRelayConfig","options","homeDir","homedir","workspaceCwd","cwd","configDir","join","configPath","existsSync","ensureConfigTemplate","Error","t","parsed","parseConfigFile","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","LOCALE","value","field","normalized","TypeError","trim","length","Number","isInteger","trimmed","test","parseInt","defaultLocale","console","warn","formatInvalidLocale","String","Array","isArray","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\n#. placeholder {0}: index + 1\n#: src/bot/handler.ts:83\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:83\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:57\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:114\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:125\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:103\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:60\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:155\nmsgid \"Codex execution failed: {0}\"\nmsgstr \"Codex execution failed: {0}\"\n\n#: src/bot/handler.ts:158\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:113\nmsgid \"Created a new session.\"\nmsgstr \"Created a new session.\"\n\n#: src/bot/handler.ts:92\nmsgid \"Current session has been cleared.\"\nmsgstr \"Current session has been cleared.\"\n\n#: src/bot/handler.ts:68\nmsgid \"Current session status:\"\nmsgstr \"Current session status:\"\n\n#: src/bot/handler.ts:84\nmsgid \"Current working directories:\"\nmsgstr \"Current working directories:\"\n\n#: src/index.ts:21\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:115\nmsgid \"cwd: {0}\"\nmsgstr \"cwd: {0}\"\n\n#: src/bot/relay.ts:43\n#: src/bot/relay.ts:48\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:68\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:163\nmsgid \"Failed to read open projects: {0}\"\nmsgstr \"Failed to read open projects: {0}\"\n\n#: src/bot/handler.ts:166\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:121\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\nmsgid \"Failed to start relay: {message}\"\nmsgstr \"Failed to start relay: {message}\"\n\n#. placeholder {0}: formatError(error)\n#: src/core/config.ts:130\nmsgid \"Invalid JSON in relay config at {configPath}: {0}\"\nmsgstr \"Invalid JSON in relay config at {configPath}: {0}\"\n\n#: src/bot/commands.ts:71\n#: src/bot/commands.ts:92\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:147\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:136\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:158\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:172\nmsgid \"Invalid relay config: {field} must be a string.\"\nmsgstr \"Invalid relay config: {field} must be a string.\"\n\n#: src/core/config.ts:194\n#: src/core/config.ts:205\n#: src/core/config.ts:213\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:226\nmsgid \"Invalid relay config: LOCALE \\\"{0}\\\" is not supported. Falling back to en.\"\nmsgstr \"Invalid relay config: LOCALE \\\"{0}\\\" is not supported. Falling back to en.\"\n\n#: src/core/config.ts:241\nmsgid \"Invalid relay config: LOCALE \\\"{normalized}\\\" is not supported. Falling back to en.\"\nmsgstr \"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:71\n#: src/bot/handler.ts:116\nmsgid \"mode: {0}\"\nmsgstr \"mode: {0}\"\n\n#. placeholder {0}: created.model\n#. placeholder {0}: currentSession.model\n#: src/bot/handler.ts:72\n#: src/bot/handler.ts:117\nmsgid \"model: {0}\"\nmsgstr \"model: {0}\"\n\n#: src/bot/handler.ts:65\n#: src/bot/handler.ts:225\nmsgid \"New Session\"\nmsgstr \"New Session\"\n\n#: src/bot/handler.ts:97\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:62\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:80\nmsgid \"No working directories are currently open.\"\nmsgstr \"No working directories are currently open.\"\n\n#: src/bot/relay.ts:65\nmsgid \"Please send a text message.\"\nmsgstr \"Please send a text message.\"\n\n#: src/core/config.ts:72\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:105\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:69\n#: src/bot/handler.ts:114\nmsgid \"thread: {0}\"\nmsgstr \"thread: {0}\"\n\n#: src/bot/handler.ts:70\nmsgid \"title: {title}\"\nmsgstr \"title: {title}\"\n\n#. placeholder {0}: command ?? normalized\n#: src/bot/commands.ts:134\nmsgid \"\"\n\"Unknown command \\\"{0}\\\".\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"Unknown command \\\"{0}\\\".\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#. placeholder {0}: normalizePrompt(prompt)\n#: src/bot/handler.ts:263\nmsgid \"User message: {0}\"\nmsgstr \"User message: {0}\"\n\n#: src/bot/handler.ts:248\nmsgid \"\"\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.\"\nmsgstr \"\"\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\nmsgid \"\"\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.\"\nmsgstr \"\"\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\n#. placeholder {0}: index + 1\n#: src/bot/handler.ts:83\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:83\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:57\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:114\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:125\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:103\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:60\nmsgid \"Cannot identify sender. Please try again later.\"\nmsgstr \"无法识别发送者,请稍后重试。\"\n\n#. placeholder {0}: error.message\n#: src/bot/handler.ts:155\nmsgid \"Codex execution failed: {0}\"\nmsgstr \"Codex 执行失败:{0}\"\n\n#: src/bot/handler.ts:158\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:113\nmsgid \"Created a new session.\"\nmsgstr \"已创建新会话。\"\n\n#: src/bot/handler.ts:92\nmsgid \"Current session has been cleared.\"\nmsgstr \"当前会话已清空。\"\n\n#: src/bot/handler.ts:68\nmsgid \"Current session status:\"\nmsgstr \"当前会话状态:\"\n\n#: src/bot/handler.ts:84\nmsgid \"Current working directories:\"\nmsgstr \"当前工作目录:\"\n\n#: src/index.ts:21\nmsgid \"Currently busy. Please try again later.\"\nmsgstr \"当前忙碌,请稍后重试。\"\n\n#. placeholder {0}: created.cwd\n#: src/bot/handler.ts:115\nmsgid \"cwd: {0}\"\nmsgstr \"cwd: {0}\"\n\n#: src/bot/relay.ts:43\n#: src/bot/relay.ts:48\nmsgid \"Failed to parse message. Please send a text message.\"\nmsgstr \"解析消息失败,请发送文本消息。\"\n\n#: src/index.ts:68\nmsgid \"Failed to process message. Please try again later.\"\nmsgstr \"处理消息失败,请稍后重试。\"\n\n#. placeholder {0}: error.message\n#: src/bot/handler.ts:163\nmsgid \"Failed to read open projects: {0}\"\nmsgstr \"读取已打开项目失败:{0}\"\n\n#: src/bot/handler.ts:166\nmsgid \"Failed to read open projects. Please try again later.\"\nmsgstr \"读取已打开项目失败,请稍后重试。\"\n\n#. placeholder {0}: formatError(error)\n#: src/core/config.ts:121\nmsgid \"Failed to read relay config at {configPath}: {0}\"\nmsgstr \"读取中继配置失败 {configPath}:{0}\"\n\n#: src/core/startup.ts:17\nmsgid \"Failed to start relay: {message}\"\nmsgstr \"启动中继失败:{message}\"\n\n#. placeholder {0}: formatError(error)\n#: src/core/config.ts:130\nmsgid \"Invalid JSON in relay config at {configPath}: {0}\"\nmsgstr \"中继配置 {configPath} 中的 JSON 无效:{0}\"\n\n#: src/bot/commands.ts:71\n#: src/bot/commands.ts:92\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:147\nmsgid \"Invalid relay config at {configPath}: env must be a JSON object.\"\nmsgstr \"中继配置 {configPath} 无效:env 必须是 JSON 对象。\"\n\n#: src/core/config.ts:136\nmsgid \"Invalid relay config at {configPath}: root must be a JSON object.\"\nmsgstr \"中继配置 {configPath} 无效:root 必须是 JSON 对象。\"\n\n#: src/core/config.ts:158\nmsgid \"Invalid relay config: {field} is required and must be a non-empty string.\"\nmsgstr \"中继配置无效:{field} 为必填项且必须为非空字符串。\"\n\n#: src/core/config.ts:172\nmsgid \"Invalid relay config: {field} must be a string.\"\nmsgstr \"中继配置无效:{field} 必须是字符串。\"\n\n#: src/core/config.ts:194\n#: src/core/config.ts:205\n#: src/core/config.ts:213\nmsgid \"Invalid relay config: CODEX_TIMEOUT_MS must be a positive integer.\"\nmsgstr \"中继配置无效:CODEX_TIMEOUT_MS 必须是正整数。\"\n\n#. placeholder {0}: formatInvalidLocale(value)\n#: src/core/config.ts:226\nmsgid \"Invalid relay config: LOCALE \\\"{0}\\\" is not supported. Falling back to en.\"\nmsgstr \"中继配置无效:不支持的 LOCALE \\\"{0}\\\",已回退为 en。\"\n\n#: src/core/config.ts:241\nmsgid \"Invalid relay config: LOCALE \\\"{normalized}\\\" is not supported. Falling back to en.\"\nmsgstr \"中继配置无效:不支持的 LOCALE \\\"{normalized}\\\",已回退为 en。\"\n\n#. placeholder {0}: created.mode\n#. placeholder {0}: currentSession.mode\n#: src/bot/handler.ts:71\n#: src/bot/handler.ts:116\nmsgid \"mode: {0}\"\nmsgstr \"mode: {0}\"\n\n#. placeholder {0}: created.model\n#. placeholder {0}: currentSession.model\n#: src/bot/handler.ts:72\n#: src/bot/handler.ts:117\nmsgid \"model: {0}\"\nmsgstr \"model: {0}\"\n\n#: src/bot/handler.ts:65\n#: src/bot/handler.ts:225\nmsgid \"New Session\"\nmsgstr \"新会话\"\n\n#: src/bot/handler.ts:97\nmsgid \"No active session. Send a normal message or use /new to create one first.\"\nmsgstr \"没有活跃会话。请先发送普通消息或使用 /new 创建会话。\"\n\n#: src/bot/handler.ts:62\nmsgid \"No active session. Send a normal message or use /new to create one.\"\nmsgstr \"没有活跃会话。请发送普通消息或使用 /new 创建会话。\"\n\n#: src/bot/handler.ts:80\nmsgid \"No working directories are currently open.\"\nmsgstr \"当前没有打开的工作目录。\"\n\n#: src/bot/relay.ts:65\nmsgid \"Please send a text message.\"\nmsgstr \"请发送文本消息。\"\n\n#: src/core/config.ts:72\nmsgid \"Relay config is missing. Template created at {configPath}. Please edit this file and restart.\"\nmsgstr \"中继配置缺失,已在 {configPath} 创建模板。请编辑该文件后重启。\"\n\n#. placeholder {0}: parsed.mode\n#: src/bot/handler.ts:105\nmsgid \"Switched to {0} mode.\"\nmsgstr \"已切换到 {0} 模式。\"\n\n#. placeholder {0}: created.threadId\n#. placeholder {0}: currentSession.threadId\n#: src/bot/handler.ts:69\n#: src/bot/handler.ts:114\nmsgid \"thread: {0}\"\nmsgstr \"thread: {0}\"\n\n#: src/bot/handler.ts:70\nmsgid \"title: {title}\"\nmsgstr \"title: {title}\"\n\n#. placeholder {0}: command ?? normalized\n#: src/bot/commands.ts:134\nmsgid \"\"\n\"Unknown command \\\"{0}\\\".\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"未知命令 \\\"{0}\\\"。\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#. placeholder {0}: normalizePrompt(prompt)\n#: src/bot/handler.ts:263\nmsgid \"User message: {0}\"\nmsgstr \"用户消息:{0}\"\n\n#: src/bot/handler.ts:248\nmsgid \"\"\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.\"\nmsgstr \"\"\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\nmsgid \"\"\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.\"\nmsgstr \"\"\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 = 'en'\n\nconst CATALOGS: Record<AppLocale, Messages> = {\n en: enMessages as Messages,\n zh: zhMessages as Messages,\n}\n\nlet activeLocale: AppLocale | null = null\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 if (isSupportedLocale(locale)) {\n return locale\n }\n\n return DEFAULT_LOCALE\n}\n\nfunction ensureI18nInitialized(): void {\n if (!activeLocale) {\n initializeI18n(DEFAULT_LOCALE)\n }\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 locale: getDefaultLocale(),\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 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 ?? 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 ?? 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 const defaultLocale = getDefaultLocale()\n\n if (value === undefined || value === null) {\n return defaultLocale\n }\n\n if (typeof value !== 'string') {\n console.warn(\n t`Invalid relay config: locale \"${formatInvalidLocale(value)}\" is not supported. Falling back to en.`,\n )\n return defaultLocale\n }\n\n const normalized = value.trim()\n if (normalized.length === 0) {\n return defaultLocale\n }\n\n if (isSupportedLocale(normalized)) {\n return normalized\n }\n\n console.warn(\n t`Invalid relay config: locale \"${normalized}\" is not supported. Falling back to en.`,\n )\n\n return defaultLocale\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 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":";;;;;;;;;;;AAGA,MAAMA,eAAe;AACrB,MAAMC,cAAc;AACpB,MAAMC,eAAe;AACrB,MAAMC,iBAAiB;AACvB,MAAMC,mBAAmB;AACzB,MAAMC,gBAAgB;AAEtB,SAAgBC,cAAAA;AACd,QAAO;EACLC,KAAAA,EAAC;;;GAAoB,CAAA;EACrBA,KAAAA,EAAC;;;GAAkB,CAAA;EACnBA,KAAAA,EAAC;;;GAA2C,CAAA;EAC5CA,KAAAA,EAAC;;;GAAmD,CAAA;EACpDA,KAAAA,EAAC;;;GAAsC,CAAA;EACvCA,KAAAA,EAAC;;;GAA6C,CAAA;EAC9CA,KAAAA,EAAC;;;GAA+B,CAAA;EACjC,CAACC,KAAK,KAAA;;AAGT,SAAgBC,aAAaC,OAAa;CACxC,MAAMC,aAAaD,MAAME,MAAI;CAC7B,MAAMC,WAAWP,aAAAA;AAEjB,KAAIK,WAAWG,WAAW,EACxB,QAAO;EACLC,MAAM;EACNC,SAAST,KAAAA,EAAC;;;aAA+BM;GAAS,CAAA;EACpD;AAGF,KAAI,CAACF,WAAWM,WAAW,IAAA,CACzB,QAAO;EAAEF,MAAM;EAAUG,QAAQP;EAAW;CAG9C,MAAMQ,QAAQR,WAAWS,MAAM,MAAA;CAC/B,MAAMC,UAAUF,MAAM,IAAIG,aAAAA;AAE1B,KAAID,YAAYrB,cAAc;AAC5B,MAAImB,MAAML,SAAS,EACjB,QAAO;GACLC,MAAM;GACNC,SAAST,KAAAA,EAAC;;;cAAuCM;IAAS,CAAA;GAC5D;AAGF,SAAO,EAAEE,MAAM,QAAO;;AAGxB,KAAIM,YAAYpB,aAAa;AAC3B,MAAIkB,MAAML,SAAS,EACjB,QAAO;GACLC,MAAM;GACNC,SAAST,KAAAA,EAAC;;;cAAmEM;IAAS,CAAA;GACxF;EAGF,MAAMU,YAAYJ,MAAM;AACxB,MAAI,CAACI,UACH,QAAO;GAAER,MAAM;GAAOS,MAAM;GAAU;EAGxC,MAAMA,OAAOC,UAAUF,UAAAA;AACvB,MAAI,CAACC,KACH,QAAO;GACLT,MAAM;GACNC,SAAST,KAAAA,EAAC;;;;KAAiBgB;KAAsDV;;IAAS,CAAA;GAC5F;AAGF,SAAO;GAAEE,MAAM;GAAOS;GAAK;;AAG7B,KAAIH,YAAYnB,cAAc;EAC5B,MAAMqB,YAAYJ,MAAM;AACxB,MAAI,CAACI,aAAaJ,MAAML,SAAS,EAC/B,QAAO;GACLC,MAAM;GACNC,SAAST,KAAAA,EAAC;;;cAAoDM;IAAS,CAAA;GACzE;EAGF,MAAMW,OAAOC,UAAUF,UAAAA;AACvB,MAAI,CAACC,KACH,QAAO;GACLT,MAAM;GACNC,SAAST,KAAAA,EAAC;;;;KAAiBgB;KAAsDV;;IAAS,CAAA;GAC5F;AAGF,SAAO;GAAEE,MAAM;GAAQS;GAAK;;AAG9B,KAAIH,YAAYlB,gBAAgB;AAC9B,MAAIgB,MAAML,SAAS,EACjB,QAAO;GACLC,MAAM;GACNC,SAAST,KAAAA,EAAC;;;cAAyCM;IAAS,CAAA;GAC9D;AAGF,SAAO,EAAEE,MAAM,UAAS;;AAG1B,KAAIM,YAAYjB,kBAAkB;AAChC,MAAIe,MAAML,SAAS,EACjB,QAAO;GACLC,MAAM;GACNC,SAAST,KAAAA,EAAC;;;cAA2CM;IAAS,CAAA;GAChE;AAGF,SAAO,EAAEE,MAAM,YAAW;;AAG5B,KAAIM,YAAYhB,eAAe;AAC7B,MAAIc,MAAML,SAAS,EACjB,QAAO;GACLC,MAAM;GACNC,SAAST,KAAAA,EAAC;;;cAAwCM;IAAS,CAAA;GAC7D;AAGF,SAAO,EAAEE,MAAM,SAAQ;;AAGzB,QAAO;EACLA,MAAM;EACNC,SAAST,KAAAA,EAAC;;;;IAAkDM;OAA9BQ,WAAWV;;GAA4B,CAAA;EACvE;;AAGF,SAASc,UAAUf,OAAa;CAC9B,MAAMC,aAAaD,MAAMY,aAAW;AACpC,KAAIX,eAAe,aAAaA,eAAe,OAC7C,QAAOA;AAGT,QAAO;;;;;ACxIT,MAAMiB,+BAAe,IAAIC,KAAAA;AACzB,MAAMC,+BAAe,IAAID,KAAAA;AACzB,MAAME,oBAAoB;AAC1B,MAAMC,uBAAuB;AA+B7B,IAAIC,mBAAwD;AAE5D,SAAgBC,uBAAuBC,OAGtC;CACC,MAAMC,WAAWT,KAAKU,KAAKF,MAAMG,SAAS,SAAA;CAC1C,MAAMC,WAAWZ,KAAKU,KAAKD,UAAUL,kBAAAA;AAErCL,IAAGc,UAAUJ,UAAU,EAAEK,WAAW,MAAK,CAAA;AACzCC,yBAAwBH,SAAAA;CAExB,MAAMI,YAAYC,0BAA0BL,SAAAA;CAC5C,MAAMM,oBAAoBF,UAAUG,WAAWX,MAAMY;AAErDnB,cAAaoB,OAAK;AAClBlB,cAAakB,OAAK;AAElB,KAAIH,mBACF;MAAIA,kBAAkBI,oBAAoB;GACxC,MAAMC,aAAaL,kBAAkBI;AACrCrB,gBAAauB,IACXD,WAAWE,YACXC,eAAeH,YAAYf,MAAMY,aAAY,CAAA;;;AAKnDd,oBAAmB;EACjBM;EACAQ,cAAcZ,MAAMY;EACpBO,MAAMX;EACR;;AAGF,SAAgBY,cAAcpB,OAAsB;AAClD,KAAIA,MAAMqB,aAAa,MACrB,QAAO,OAAOrB,MAAMsB;AAGtB,QAAO,SAAStB,MAAMsB,OAAO,GAAGtB,MAAMuB;;AAGxC,SAAgBC,WAAWP,YAAkB;AAC3C,QAAOxB,aAAagC,IAAIR,WAAAA;;AAG1B,SAAgBS,WAAWT,YAAoBU,SAAmB;AAChElC,cAAauB,IAAIC,YAAYU,QAAAA;AAC7BC,mBAAkBX,YAAYU,QAAAA;;AAGhC,SAAgBE,aAAaZ,YAAkB;AAC7CxB,cAAaqC,OAAOb,WAAAA;AACpBc,qBAAoBd,WAAAA;;AAGtB,eAAsBe,gBACpBf,YACAgB,KAAqB;CAGrB,MAAMI,WADW1C,aAAa8B,IAAIR,WAAAA,IAAekB,QAAQC,SAAO,EACvCE,WACjBL,KAAAA,QACAA,KAAAA,CAAAA;CAER,MAAMM,YAAYF,QAAQC,WAClBE,cACAA,OAAAA;AAGR7C,cAAaqB,IAAIC,YAAYsB,UAAAA;AAE7B,KAAI;AACF,SAAO,MAAMF;WACL;AACR,MAAI1C,aAAa8B,IAAIR,WAAAA,KAAgBsB,UACnC5C,cAAamC,OAAOb,WAAAA;;;AAW1B,SAASW,kBAAkBX,YAAoBU,SAAmB;CAChE,MAAMe,QAAQ5C;AACd,KAAI,CAAC4C,MACH;CAGF,MAAMC,2BAAU,IAAIC,MAAAA,EAAOC,aAAW;CACtC,MAAMC,gBAAgBC,yBAAyB9B,YAAYU,SAASgB,QAAAA;CACpE,MAAMK,iBAAiBC,2BAA2BtB,SAASgB,QAAAA;CAC3D,MAAMjC,oBAAoBwC,6BACxBR,MAAMvB,MACNuB,MAAM9B,aAAY;AAGpBF,mBAAkBI,qBAAqBgC;CACvC,MAAMK,UAAUzC,kBAAkB0C,oBAAoBzB,QAAQ0B,aAAa,EAAE;AAC7EF,SAAQG,KAAKN,eAAAA;AACbtC,mBAAkB0C,oBAAoBzB,QAAQ0B,YAAYF;AAE1DT,OAAMvB,KAAKoC,YAAYZ;AACvBa,4BAA2Bd,MAAMtC,UAAUsC,MAAMvB,KAAI;;AAGvD,SAASY,oBAAoBd,YAAkB;CAC7C,MAAMyB,QAAQ5C;AACd,KAAI,CAAC4C,MACH;CAGF,MAAMhC,oBAAoBgC,MAAMvB,KAAKR,WAAW+B,MAAM9B;AACtD,KAAI,CAACF,kBACH;AAGF,KACE,CAACA,kBAAkBI,sBACnBJ,kBAAkBI,mBAAmBG,eAAeA,WAEpD;AAGFP,mBAAkBI,qBAAqB;AAEvC4B,OAAMvB,KAAKoC,6BAAY,IAAIX,MAAAA,EAAOC,aAAW;AAC7CW,4BAA2Bd,MAAMtC,UAAUsC,MAAMvB,KAAI;;AAGvD,SAASZ,wBAAwBH,UAAgB;AAC/C,KAAIb,GAAGkE,WAAWrD,SAAAA,CAChB;CAGF,MAAMsD,iBAAiB,GAAGC,KAAKC,UAAUC,kCAAAA,EAAoC,MAAM,EAAA,CAAG;AACtFtE,IAAGuE,cAAc1D,UAAUsD,gBAAgB;EAAEK,UAAU;EAASC,MAAM;EAAK,CAAA;;AAG7E,SAASH,mCAAAA;AACP,QAAO;EACLI,SAASpE;EACT0D,4BAAW,IAAIX,MAAAA,EAAOC,aAAW;EACjClC,YAAY,EAAC;EACf;;AAGF,SAASF,0BAA0BL,UAAgB;CACjD,IAAI8D;AACJ,KAAI;AACFA,QAAM3E,GAAG4E,aAAa/D,UAAU,QAAA;UACzBgE,OAAO;AACd,QAAM,IAAIC,MACR,yCAAyCjE,SAAS,IAAIkE,cAAYF,MAAAA,GAAQ;;CAI9E,IAAIG;AACJ,KAAI;AACFA,WAASZ,KAAKa,MAAMN,IAAAA;UACbE,OAAO;AACd,QAAM,IAAIC,MACR,0CAA0CjE,SAAS,IAAIkE,cAAYF,MAAAA,GAAQ;;AAI/E,QAAOK,2BAA2BF,QAAQnE,SAAAA;;AAG5C,SAASqE,2BACPC,OACAtE,UAAgB;AAEhB,KAAI,CAACuE,WAASD,MAAAA,CACZ,OAAM,IAAIL,MACR,kCAAkCjE,SAAS,+BAA8B;AAI7E,KAAIsE,MAAMT,YAAYpE,qBACpB,OAAM,IAAIwE,MACR,kCAAkCjE,SAAS,oBAAoBP,qBAAqB,GAAE;AAI1F,KAAI,OAAO6E,MAAMnB,cAAc,SAC7B,OAAM,IAAIqB,UACR,kCAAkCxE,SAAS,+BAA8B;AAI7E,KAAI,CAACuE,WAASD,MAAM/D,WAAU,CAC5B,OAAM,IAAI0D,MACR,kCAAkCjE,SAAS,qCAAoC;CAInF,MAAMO,aAAyD,EAAC;AAChE,MAAK,MAAM,CAACC,cAAciE,mBAAmBC,OAAOC,QAClDL,MAAM/D,WAAU,CAEhBA,YAAWC,gBAAgBoE,uBACzBH,gBACAzE,UACAQ,aAAAA;AAIJ,QAAO;EACLqD,SAASpE;EACT0D,WAAWmB,MAAMnB;EACjB5C;EACF;;AAGF,SAASqE,uBACPN,OACAtE,UACAQ,cAAoB;AAEpB,KAAI,CAAC+D,WAASD,MAAAA,CACZ,OAAM,IAAIL,MACR,kCAAkCjE,SAAS,eAAeQ,aAAa,0BAAyB;AAepG,QAAO;EACLE,oBAZyBmE,4BACzBP,MAAM5D,oBACNV,UACAQ,aAAAA;EAUAwC,qBAR0B8B,8BAC1BR,MAAMtB,qBACNhD,UACAQ,aAAAA;EAMF;;AAGF,SAASqE,4BACPP,OACAtE,UACAQ,cAAoB;AAEpB,KAAI8D,UAAU,QAAQA,UAAUlC,OAC9B,QAAO;AAGT,KAAI,CAACmC,WAASD,MAAAA,CACZ,OAAM,IAAIL,MACR,kCAAkCjE,SAAS,sCAAsCQ,aAAa,kCAAiC;AAInI,QAAOuE,4BACLT,OACAtE,UACA,qCAAqCQ,aAAa,GAAE;;AAIxD,SAASsE,8BACPR,OACAtE,UACAQ,cAAoB;AAEpB,KAAI,CAAC+D,WAASD,MAAAA,CACZ,OAAM,IAAIL,MACR,kCAAkCjE,SAAS,uCAAuCQ,aAAa,0BAAyB;CAI5H,MAAMwC,sBAAkE,EAAC;AACzE,MAAK,MAAM,CAACgC,UAAUC,iBAAiBP,OAAOC,QAAQL,MAAAA,EAAQ;AAC5D,MAAIU,SAASE,MAAI,CAAGC,WAAW,EAC7B,OAAM,IAAIlB,MACR,kCAAkCjE,SAAS,0CAA0CQ,aAAa,iCAAgC;AAItI,MAAI,CAAC4E,MAAMC,QAAQJ,aAAAA,CACjB,OAAM,IAAIT,UACR,kCAAkCxE,SAAS,wBAAwBgF,SAAS,oBAAmB;AAInGhC,sBAAoBgC,YAAYC,aAAaK,KAAKC,MAAMC,UACtDC,8BACEF,MACAvF,UACA,uBAAuBgF,SAAS,GAAGQ,MAAM,GAAE,CAAA;;AAKjD,QAAOxC;;AAGT,SAAS+B,4BACPT,OACAtE,UACA0F,UAAgB;AAEhB,KAAI,CAACnB,WAASD,MAAAA,CACZ,OAAM,IAAIL,MACR,kCAAkCjE,SAAS,IAAI0F,SAAS,yBAAwB;AAepF,QAAO;EACL7E,YAZiB8E,oBACjBrB,MAAMzD,YACNb,UACA,GAAG0F,SAAS,aAAY;EAUxBzC,UARe0C,oBACfrB,MAAMrB,UACNjD,UACA,GAAG0F,SAAS,WAAU;EAMtB,GAJeD,8BAA8BnB,OAAOtE,UAAU0F,SAAAA;EAKhE;;AAGF,SAASD,8BACPnB,OACAtE,UACA0F,UAAgB;AAEhB,KAAI,CAACnB,WAASD,MAAAA,CACZ,OAAM,IAAIL,MACR,kCAAkCjE,SAAS,IAAI0F,SAAS,yBAAwB;CAIpF,MAAMG,QAAQF,oBAAoBrB,MAAMuB,OAAO7F,UAAU,GAAG0F,SAAS,QAAO;AAC5E,KAAIpB,MAAMwB,SAAS,aAAaxB,MAAMwB,SAAS,OAC7C,OAAM,IAAI7B,MACR,kCAAkCjE,SAAS,IAAI0F,SAAS,oCAAmC;AAI/F,KAAI,OAAOpB,MAAM/B,YAAY,SAC3B,OAAM,IAAIiC,UACR,kCAAkCxE,SAAS,IAAI0F,SAAS,4BAA2B;CAIvF,MAAMK,QAAQC,uBAAuB1B,MAAMyB,MAAK;AAChD,QAAO;EACLD,MAAMxB,MAAMwB;EACZD;EACAE;EACAxD,SAAS+B,MAAM/B;EACjB;;AAGF,SAASoD,oBACPrB,OACAtE,UACA0F,UAAgB;AAEhB,KAAI,OAAOpB,UAAU,YAAYA,MAAMY,MAAI,CAAGC,WAAW,EACvD,OAAM,IAAIlB,MACR,kCAAkCjE,SAAS,IAAI0F,SAAS,8BAA6B;AAIzF,QAAOpB;;AAGT,SAASxD,eACPH,YACAsF,KAAW;AAEX,QAAO;EACLhD,UAAUtC,WAAWsC;EACrB6C,MAAMnF,WAAWmF;EACjBD,OAAOlF,WAAWkF;EAClBI;EACAF,OAAOC,uBAAuBrF,WAAWoF,MAAK;EAChD;;AAGF,SAASpD,yBACP9B,YACAU,SACAgB,SAAe;AAEf,QAAO;EACL1B;EACAoC,UAAU1B,QAAQ0B;EAClB,GAAGJ,2BAA2BtB,SAASgB,QAAQ;EACjD;;AAGF,SAASM,2BACPtB,SACAgB,SAAe;CAEf,MAAMwD,QAAQC,uBAAuBzE,QAAQwE,MAAK;AAElD,QAAO;EACLD,MAAMvE,QAAQuE;EACdD,OAAOtE,QAAQsE;EACfE;EACAxD;EACF;;AAGF,SAASyD,uBAAuBD,OAAc;AAC5C,KAAI,OAAOA,UAAU,SACnB;CAGF,MAAMG,aAAaH,MAAMb,MAAI;AAC7B,KAAIgB,WAAWf,WAAW,EACxB;AAGF,QAAOe;;AAGT,SAASpD,6BACP/B,MACAP,cAAoB;CAEpB,MAAM2F,WAAWpF,KAAKR,WAAWC;AACjC,KAAI2F,SACF,QAAOA;CAGT,MAAMC,UAAsC;EAC1C1F,oBAAoB;EACpBsC,qBAAqB,EAAC;EACxB;AACAjC,MAAKR,WAAWC,gBAAgB4F;AAChC,QAAOA;;AAGT,SAAShD,2BACPpD,UACAe,MAA2B;CAE3B,MAAMsF,WAAW,GAAGrG,SAAS,OAAOwC,KAAK8D,KAAG,CAAG,GAAGC,KAAKC,QAAM,CAAGC,SAAS,GAAA,CAAIC,MAAM,EAAA;CACnF,MAAMC,UAAU,GAAGpD,KAAKC,UAAUzC,MAAM,MAAM,EAAA,CAAG;AAEjD,KAAI;AACF5B,KAAGuE,cAAc2C,UAAUM,SAAS,QAAA;AACpCxH,KAAGyH,WAAWP,UAAUrG,SAAAA;UACjBgE,OAAO;AACd,MAAI;AACF,OAAI7E,GAAGkE,WAAWgD,SAAAA,CAChBlH,IAAG0H,OAAOR,UAAU,EAAES,OAAO,MAAK,CAAA;UAE9B;AAIR,QAAM,IAAI7C,MACR,0CAA0CjE,SAAS,IAAIkE,cAAYF,MAAAA,GAAQ;;;AAKjF,SAASO,WAASD,OAAc;AAC9B,QAAO,OAAOA,UAAU,YAAYA,UAAU,QAAQ,CAACc,MAAMC,QAAQf,MAAAA;;AAGvE,SAASJ,cAAYF,OAAc;AACjC,KAAIA,iBAAiBC,MACnB,QAAOD,MAAM+C;AAGf,QAAOC,OAAOhD,MAAAA;;;;;ACjgBhB,MAAMoD,2BAA2B;AAgBjC,eAAsBC,mBACpBC,OACAC,MAA4B;CAE5B,MAAMC,aAAaP,cAAc;EAC/BQ,UAAUH,MAAMG;EAChBC,QAAQJ,MAAMI;EACdC,QAAQL,MAAMM;EAChB,CAAA;AAEA,QAAOL,KAAKM,gBAAgBL,YAAY,YAAA;EACtC,MAAMM,SAASX,aAAaG,MAAMS,KAAI;EACtC,MAAMC,iBAAiBT,KAAKU,WAAWT,WAAAA;AAEvC,MAAIM,OAAOI,SAAS,UAClB,QAAOJ,OAAOK;AAGhB,MAAIL,OAAOI,SAAS,OAClB,QAAOhB,aAAAA;AAGT,MAAIY,OAAOI,SAAS,UAAU;AAC5B,OAAI,CAACF,eACH,QAAOI,KAAAA,EAAC;;;IAAoE,CAAA;GAG9E,MAAMC,QACJC,sBAAsBN,eAAeK,MAAK,IAAKD,KAAAA,EAAC;;;IAAY,CAAA;AAE9D,UAAO;IACLA,KAAAA,EAAC;;;KAAwB,CAAA;IACzBA,KAAAA,EAAC;;;kBAAWJ,eAAeO;KAAS,CAAA;IACpCH,KAAAA,EAAC;;;eAAUC;KAAM,CAAA;IACjBD,KAAAA,EAAC;;;kBAASJ,eAAeQ;KAAK,CAAA;IAC9BJ,KAAAA,EAAC;;;kBAAUJ,eAAeS;KAAM,CAAA;IACjC,CAACC,KAAK,KAAA;;AAGT,MAAIZ,OAAOI,SAAS,WAClB,KAAI;GACF,MAAMS,SAAS,MAAMpB,KAAKqB,kBAAgB;AAC1C,OAAID,OAAOE,MAAMC,WAAW,EAC1B,QAAOV,KAAAA,EAAC;;;IAA2C,CAAA;GAGrD,MAAMW,QAAQJ,OAAOE,MAAMG,KACxBC,MAAMC,UAAUd,KAAAA,EAAC;;;;KAAiBa;QAAdC,QAAQ;;IAAW,CAAA,CAAA;AAE1C,UAAO,CAACd,KAAAA,EAAC;;;IAA6B,CAAA,KAAMW,MAAM,CAACL,KAAK,KAAA;WACjDS,OAAO;AACd,UAAOC,oBAAoBD,MAAAA;;AAI/B,MAAIrB,OAAOI,SAAS,SAAS;AAC3BX,QAAK8B,aAAa7B,WAAAA;AAClB,UAAOY,KAAAA,EAAC;;;IAAkC,CAAA;;AAG5C,MAAIN,OAAOI,SAAS,QAAQ;AAC1B,OAAI,CAACF,eACH,QAAOI,KAAAA,EAAC;;;IAA0E,CAAA;AAGpFb,QAAK+B,WAAW9B,YAAY;IAC1B,GAAGQ;IACHQ,MAAMV,OAAOU;IACf,CAAA;AAEA,UAAOJ,KAAAA,EAAC;;;iBAAeN,OAAOU;IAAW,CAAA;;AAG3C,MAAIV,OAAOI,SAAS,MAClB,KAAI;GACF,MAAMqB,UAAU,MAAMhC,KAAKiC,aAAa1B,OAAOU,KAAI;AACnDjB,QAAK+B,WAAW9B,YAAY+B,QAAAA;AAC5B,UAAO;IACLnB,KAAAA,EAAC;;;KAAuB,CAAA;IACxBA,KAAAA,EAAC;;;kBAAWmB,QAAQhB;KAAS,CAAA;IAC7BH,KAAAA,EAAC;;;kBAAQmB,QAAQE;KAAI,CAAA;IACrBrB,KAAAA,EAAC;;;kBAASmB,QAAQf;KAAK,CAAA;IACvBJ,KAAAA,EAAC;;;kBAAUmB,QAAQd;KAAM,CAAA;IAC1B,CAACC,KAAK,KAAA;WACAS,OAAO;AACd,UAAOO,iBAAiBP,MAAAA;;AAI5B,MAAI;GACF,MAAMX,OAAOR,gBAAgBQ,QAAQ;GACrC,MAAMG,SAAS,MAAMpB,KAAKoC,QAAQ;IAChCC,QAAQ9B,OAAO8B;IACfpB;IACAqB,SAAS7B,kBAAkB;IAC7B,CAAA;GAEA,MAAMK,QAAQ,MAAMyB,oBAAoB;IACtC9B;IACA4B,QAAQ9B,OAAO8B;IACjB,CAAA;AAEArC,QAAK+B,WAAW9B,YAAY;IAC1Be,UAAUI,OAAOJ;IACjBE,OAAOE,OAAOF;IACdD,MAAMG,OAAOH;IACbiB,KAAKd,OAAOc;IACZpB;IACF,CAAA;AACA,UAAOM,OAAOR;WACPgB,OAAO;AACd,UAAOO,iBAAiBP,MAAAA;;GAE5B;;AAGF,SAASO,iBAAiBP,OAAc;AACtC,KAAIA,iBAAiBY,SAASZ,MAAMhB,QAAQ6B,MAAI,CAAGlB,SAAS,EAC1D,QAAOV,KAAAA,EAAC;;;eAA2Be,MAAMhB;EAAQ,CAAA;AAGnD,QAAOC,KAAAA,EAAC;;;EAAgD,CAAA;;AAG1D,SAASgB,oBAAoBD,OAAc;AACzC,KAAIA,iBAAiBY,SAASZ,MAAMhB,QAAQ6B,MAAI,CAAGlB,SAAS,EAC1D,QAAOV,KAAAA,EAAC;;;eAAiCe,MAAMhB;EAAQ,CAAA;AAGzD,QAAOC,KAAAA,EAAC;;;EAAsD,CAAA;;AAGhE,eAAe0B,oBAAoBxC,OAGlC;CACC,MAAM2C,eAAe3B,sBAAsBhB,MAAMU,gBAAgBK,MAAAA;AACjE,KAAI4B,aACF,QAAOA;AAGT,KAAI,CAAC3C,MAAMU,eACT;AAGF,QAAOmC,0BAA0B7C,MAAMsC,OAAM;;AAG/C,SAASO,0BAA0BP,QAAc;CAC/C,MAAMQ,mBAAmBC,gBAAgBT,OAAAA;AACzC,KAAIQ,iBAAiBtB,WAAW,EAC9B,QAAOV,KAAAA,EAAC;;;EAAY,CAAA;AAGtB,QAAOkC,cAAcF,iBAAAA;;AAGvB,SAAS9B,sBAAsBD,OAAyB;AACtD,KAAI,CAACA,MACH,QAAO;CAGT,MAAMkC,aAAalC,MAAM2B,MAAI;AAC7B,KAAIO,WAAWzB,WAAW,EACxB,QAAO;AAGT,QAAOyB;;AAGT,SAASD,cAAchD,OAAa;CAClC,MAAMkD,QAAQC,MAAMC,KAAKpD,MAAAA;AACzB,KAAIkD,MAAM1B,UAAU1B,yBAClB,QAAOE;AAGT,KAAIF,4BAA4B,EAC9B,QAAOoD,MAAMG,MAAM,GAAGvD,yBAAAA,CAA0BsB,KAAK,GAAA;AAGvD,QAAO,GAAG8B,MAAMG,MAAM,GAAGvD,2BAA2B,EAAA,CAAGsB,KAAK,GAAA,CAAI;;AAGlE,SAAS2B,gBAAgB/C,OAAa;AACpC,QAAOA,MAAMsD,QAAQ,QAAQ,IAAA,CAAKZ,MAAI;;;;;AChMxC,SAAgBa,qBACdC,OACAC,WAAkB;AAElB,KAAIC,iBAAiBF,OAAOC,UAAAA,CAC1B,QAAO;AAGT,KAAID,MAAMG,QAAQC,cAAc,MAC9B,QAAO;AAGT,QAAOC,yBAAyBL,MAAMG,QAAQG,UAAUL,UAAAA;;AAG1D,SAAgBI,yBACdC,UACAL,WAAkB;AAElB,KAAI,CAACK,YAAYA,SAASC,WAAW,EACnC,QAAO;AAGT,KAAI,CAACN,UACH,QAAO;AAGT,QAAOK,SAASE,MAAMC,YAAYA,QAAQC,IAAIC,YAAYV,UAAAA;;AAG5D,SAAgBW,gBACdC,UAAmC;AAEnC,KAAI,CAACA,SACH,QAAO;AAGT,QAAOA,SAASF,WAAWE,SAASC,WAAWD,SAASE,YAAY;;AAGtE,SAAgBb,iBACdF,OACAC,WAAkB;AAGlB,KADmBD,MAAMiB,OAAOC,aAAaC,aAAAA,KAC1B,MACjB,QAAO;AAGT,KAAI,CAAClB,UACH,QAAO;AAIT,QADiBW,gBAAgBZ,MAAMiB,OAAOG,UAAS,KACnCnB;;;;;ACvCtB,eAAsBwB,0BACpBC,OACAC,MAAkB;AAElB,KAAIL,iBAAiBI,OAAOC,KAAKC,UAAS,CACxC,QAAO;AAGT,KAAIF,MAAMG,QAAQC,iBAAiB,OACjC,QAAOC,KAAAA,EAAC;;;EAAqD,CAAA;CAG/D,MAAMC,OAAOC,iBAAiBP,MAAMG,QAAQK,QAAO;AACnD,KAAI,CAACF,KACH,QAAOD,KAAAA,EAAC;;;EAAqD,CAAA;AAG/D,KACEL,MAAMG,QAAQM,cAAc,SAC5B,CAACX,yBAAyBE,MAAMG,QAAQO,UAAUT,KAAKC,UAAS,CAEhE,QAAO;CAGT,MAAMS,WAAWd,gBAAgBG,MAAMY,OAAOC,UAAS;AACvD,KAAI,CAACF,SACH,QAAON,KAAAA,EAAC;;;EAAgD,CAAA;CAG1D,MAAMS,iBAAiBC,iBAAiBT,KAAAA,CAAMU,MAAI;AAClD,KAAIF,eAAeG,WAAW,EAC5B,QAAOZ,KAAAA,EAAC;;;EAA4B,CAAA;AAGtC,QAAOJ,KAAKiB,mBAAmB;EAC7BC,UAAUnB,MAAMG,QAAQM;EACxBW,QAAQpB,MAAMG,QAAQkB;EACtBV;EACAL,MAAMQ;EACR,CAAA;;AAGF,SAAgBC,iBAAiBT,MAAY;AAC3C,QAAOA,KAAKgB,QAAQ,yBAAyB,GAAA,CAAIN,MAAI;;AAGvD,SAAST,iBAAiBC,SAAe;AACvC,KAAI;EACF,MAAMe,SAAkBC,KAAKC,MAAMjB,QAAAA;AACnC,MAAI,CAACb,cAAc4B,OAAAA,CACjB,QAAO;AAGT,SAAO,OAAOA,OAAOjB,SAAS,WAAWiB,OAAOjB,OAAO;SACjD;AACN,SAAO;;;;;;AC/EX,SAAgBqB,aAAaC,MAAY;CACvC,IAAIC;AACJ,KAAI;AACFA,WAASC,KAAKC,MAAMH,KAAAA;SACd;AACN,SAAO;;AAGT,KAAI,CAACF,cAAcG,OAAAA,CACjB,QAAO;AAGT,KAAI,OAAOA,OAAOG,WAAW,UAAU;AACrC,MAAIC,eAAeJ,OAAOK,GAAE,CAC1B,QAAO;GACLA,IAAIL,OAAOK;GACXF,QAAQH,OAAOG;GACfG,QAAQN,OAAOM;GACjB;AAGF,SAAO;GACLH,QAAQH,OAAOG;GACfG,QAAQN,OAAOM;GACjB;;AAGF,KAAI,CAACF,eAAeJ,OAAOK,GAAE,CAC3B,QAAO;AAGT,KAAI,WAAWL,UAAUO,iBAAiBP,OAAOQ,MAAK,CACpD,QAAO;EACLH,IAAIL,OAAOK;EACXG,OAAOR,OAAOQ;EAChB;AAGF,KAAI,YAAYR,OACd,QAAO;EACLK,IAAIL,OAAOK;EACXI,QAAQT,OAAOS;EACjB;AAGF,QAAO;;AAGT,SAAgBC,eAAeF,OAAqB;AAClD,QAAO,oBAAoBA,MAAMG,KAAK,KAAKH,MAAMI;;AAGnD,SAAgBR,eAAeS,OAAc;AAC3C,QAAO,OAAOA,UAAU,YAAY,OAAOA,UAAU;;AAGvD,SAAgBN,iBAAiBM,OAAc;AAC7C,KAAI,CAAChB,cAAcgB,MAAAA,CACjB,QAAO;AAGT,QAAO,OAAOA,MAAMF,SAAS,YAAY,OAAOE,MAAMD,YAAY;;AAGpE,SAAgBE,mBACdD,OAAsB;AAEtB,QAAO,WAAWA;;AAGpB,SAAgBE,qBACdF,OAAsB;AAEtB,QAAO,YAAYA;;AAGrB,SAAgBG,mBACdH,OAAsB;AAEtB,QAAO,YAAYA,SAAS,QAAQA;;AAGtC,SAAgBI,uBAAuBd,QAAc;AACnD,KAAIA,WAAW,wCACb,QAAO;EACLe,UAAU;EACVC,gBAAgB,EACdC,YAAY,MACd;EACF;AAGF,KAAIjB,WAAW,kCACb,QAAO,EAAEe,UAAU,UAAS;AAG9B,KAAIf,OAAOkB,SAAS,mBAAA,CAClB,QAAO,EAAEH,UAAU,UAAS;AAG9B,KAAIf,WAAW,sBACb,QAAO,EAAEe,UAAU,SAAQ;AAG7B,KAAIf,WAAW,qBACb,QAAO,EAAEe,UAAU,SAAQ;AAG7B,KAAIf,OAAOkB,SAAS,WAAA,CAClB,QAAO,EAAEH,UAAU,SAAQ;AAG7B,KAAIf,WAAW,6BACb,QAAO,EAAEmB,SAAS,EAAC,EAAE;AAGvB,KAAInB,WAAW,iBACb,QAAO;EACLoB,SAAS;EACTC,cAAc,CACZ;GACEC,MAAM;GACNC,MAAM;GACR,CACD;EACH;AAGF,QAAO;;;;;ACnHT,IAAaS,uBAAb,MAAaA;CAwDXC,uBACEC,SACM;AACN,OAAKC,sBAAsBD;;CAG7B,MAAME,QAAWC,QAAgBC,QAA6B;AAC5D,MAAI,KAAKC,OACP,OAAM,IAAIC,MAAM,KAAKC,iBAAiB,MAAM,KAAA,CAAA;EAG9C,MAAMC,YAAY,KAAKC;AACvB,OAAKA,UAAU;EAEf,MAAMC,kBAAkB,IAAIC,SAAYC,SAASC,WAAAA;AAC/C,QAAKC,QAAQC,IAAIP,WAAW;IAC1BI,UAAUI,UAAmBJ,QAAQI,MAAAA;IACrCH;IACF,CAAA;IACF;EAEA,MAAMI,UAAUC,KAAKC,UAAU;GAC7BC,SAAS;GACTC,IAAIb;GACJL;GACAC;GACF,CAAA;AAEA,QAAM,IAAIO,SAAeC,SAASC,WAAAA;AAChC,QAAKS,MAAMC,MAAMC,MAAM,GAAGP,QAAQ,MAAMQ,UAAAA;AACtC,QAAIA,OAAO;AACT,UAAKX,QAAQY,OAAOlB,UAAAA;AACpBK,YAAOY,MAAAA;AACP;;AAEFb,aAAAA;KACF;IACF;AAEA,SAAOF;;CAGTiB,UAAgB;AACd,OAAKC,WAAWC,OAAK;AACrB,MAAI,CAAC,KAAKP,MAAMQ,OACd,MAAKR,MAAMS,KAAK,UAAA;;CAIZC,iBAAiBC,MAAoB;EAC3C,MAAMC,SAASrC,aAAaoC,KAAAA;AAC5B,MAAI,CAACC,OACH;AAGF,MAAI,YAAYA,QAAQ;AACtB,OAAIvC,mBAAmBuC,OAAAA,EAAS;AAC9B,IAAK,KAAKC,uBAAuBD,OAAAA,CAAQE,OAAOX,UAAAA;AAC9C,UAAKY,aAAaC,KAChB,wCAAwCJ,OAAO/B,OAAO,KAAKoC,OACzDd,MAAAA,GACC;MAEP;AACA;;AAGF,QAAKxB,sBAAsBiC,OAAAA;AAC3B;;EAGF,MAAMpB,UAAU,KAAKA,QAAQ0B,IAAIN,OAAOb,GAAE;AAC1C,MAAI,CAACP,QACH;AAGF,OAAKA,QAAQY,OAAOQ,OAAOb,GAAE;AAC7B,MAAI3B,mBAAmBwC,OAAAA,EAAS;AAC9BpB,WAAQD,OAAO,IAAIP,MAAMd,eAAe0C,OAAOT,MAAK,CAAA,CAAA;AACpD;;AAGF,MAAI7B,qBAAqBsC,OAAAA,CACvBpB,SAAQF,QAAQsB,OAAOO,OAAM;;CAIjC,MAAcN,uBACZjC,SACe;EACf,MAAMuC,SAAShD,uBAAuBS,QAAQC,OAAM;AACpD,MAAIsC,WAAW,MAAM;AACnB,SAAM,KAAKC,cAAcxC,QAAQmB,IAAIoB,OAAAA;AACrC;;AAGF,QAAM,KAAKE,aACTzC,QAAQmB,IACR,QACA,sCAAsCnB,QAAQC,SAAQ;;CAI1D,MAAcuC,cACZrB,IACAoB,QACe;AACf,QAAM,KAAKG,gBAAgB;GACzBxB,SAAS;GACTC;GACAoB;GACF,CAAA;;CAGF,MAAcE,aACZtB,IACAwB,MACAC,SACe;AACf,QAAM,KAAKF,gBAAgB;GACzBxB,SAAS;GACTC;GACAI,OAAO;IACLoB;IACAC;IACF;GACF,CAAA;;CAGF,MAAcF,gBAAgB3B,SAQZ;AAChB,MAAI,KAAKZ,OACP;EAGF,MAAM0C,aAAa7B,KAAKC,UAAUF,QAAAA;AAClC,QAAM,IAAIN,SAAeC,SAASC,WAAAA;AAChC,QAAKS,MAAMC,MAAMC,MAAM,GAAGuB,WAAW,MAAMtB,UAAAA;AACzC,QAAIA,OAAO;AACTZ,YAAOY,MAAAA;AACP;;AAEFb,aAAAA;KACF;IACF;;CAGML,iBACNsC,MACAG,QACQ;EACR,MAAMC,SACJ,KAAKZ,aAAaa,SAAS,IACvB,aAAa,KAAKb,aAAac,GAAG,GAAC,KACnC;AACN,SAAO,iCAAiCN,QAAQ,OAAO,WACrDG,UAAU,OACX,GAAGC;;CAtMN,YAAYG,SAA4C;OAdvCtC,0BAAU,IAAIuC,KAAAA;OAEdhB,eAAyB,EAAE;OAIpC5B,SAAS;OAETR,sBAEG;OAEHI,SAAS;AAGf,OAAK+C,UAAUA;AAGf,OAAK9B,QAAQhC,MAAM,KAAK8D,QAAQG,UAFZ,CAAC,aAAa,EAEqB;GACrDC,KAAK,KAAKJ,QAAQI;GAClBC,OAAO;IAAC;IAAQ;IAAQ;IAAO;GACjC,CAAA;AACA,OAAK7B,aAAarC,gBAAgB;GAChCmE,OAAO,KAAKpC,MAAMqC;GAClBC,WAAWC;GACb,CAAA;AAEA,OAAKjC,WAAWkC,GAAG,SAAS7B,SAAAA;AAC1B,QAAKD,iBAAiBC,KAAAA;IACxB;AAEA,OAAKX,MAAMyC,OAAOD,GAAG,SAASE,UAAAA;GAC5B,MAAMC,OAAO1B,OAAOyB,MAAAA,CAAOE,MAAI;AAC/B,OAAID,KAAKf,SAAS,EAChB,MAAKb,aAAaC,KAAK2B,KAAAA;IAE3B;AAEA,OAAK3C,MAAMwC,GAAG,SAASjB,MAAMG,WAAAA;AAC3B,QAAK3C,SAAS;GACd,MAAMoB,QAAQ,IAAInB,MAAM,KAAKC,iBAAiBsC,MAAMG,OAAAA,CAAAA;AACpD,QAAK,MAAMlC,WAAW,KAAKA,QAAQqD,QAAM,CACvCrD,SAAQD,OAAOY,MAAAA;AAEjB,QAAKX,QAAQsD,OAAK;IACpB;;;;;;ACnDJ,eAAsBE,iBACpBC,QAA4B;AAE5B,OAAMA,OAAOC,QAAQ,cAAc;EACjCC,YAAY;GACVC,MAAM;GACNC,OAAO;GACPC,SAAS;GACX;EACAC,cAAc,EACZC,iBAAiB,MACnB;EACF,CAAA;;AAGF,eAAsBC,sBACpBR,QAA4B;CAE5B,MAAMS,MAAM,MAAMT,OAAOC,QAAQ,0BAA0B,EAAC,CAAA;AAC5D,KAAI,CAACS,gCAAgCD,IAAAA,CACnC,OAAM,IAAIE,MAAM,iDAAA;AAGlB,QAAOF,IAAIG;;AAGb,eAAsBC,WACpBb,QACAc,SACAC,KAAW;AAEX,KAAI,CAACD,QACH,QAAOE,YAAYhB,QAAQe,IAAAA;AAG7B,KAAID,QAAQC,QAAQA,IAClB,QAAOC,YAAYhB,QAAQe,IAAAA;AAG7B,KAAI;EACF,MAAME,UAAU,MAAMC,aAAalB,QAAQc,QAAQK,SAAQ;AAC3D,MAAIF,QAAQF,QAAQA,IAClB,QAAOC,YAAYhB,QAAQe,IAAAA;AAG7B,SAAOE;UACAG,OAAO;AACd,MAAIC,qBAAqBD,MAAAA,CACvB,QAAOJ,YAAYhB,QAAQe,IAAAA;AAE7B,QAAMK;;;AAIV,eAAsBJ,YACpBhB,QACAe,KAAW;AASX,QAAOU,kBAPK,MAAMzB,OAAOC,QAAQ,gBAAgB;EAC/Cc;EACAO,gBAAgB;EAChBC,SAAS;EACTC,uBAAuB;EACzB,CAAA,CAEyBf;;AAG3B,SAAgBiB,+BACdC,OACAC,MACAC,OAAa;CAEb,MAAMC,WAAWH,MAAMI,MAAMC,SAAAA;AAC3B,MAAIA,KAAKJ,SAASA,KAChB,QAAO;AAGT,SAAOI,KAAK7B,KAAK8B,aAAW,KAAOL;GACrC;AAEA,KAAI,CAACE,SACH,OAAM,IAAInB,MAAM,uBAAuBiB,KAAK,kBAAiB;AAG/D,QAAO;EACLA;EACAM,UAAU;GACRL;GACAM,kBAAkBL,SAASK;GAC3BC,wBAAwBN,SAASM;GACnC;EACF;;AAGF,eAAelB,aACblB,QACAmB,UAAgB;AAMhB,QAAOM,kBAJK,MAAMzB,OAAOC,QAAQ,iBAAiB,EAChDkB,UACF,CAAA,CAEyBV;;AAG3B,SAASgB,kBAAkBhB,KAAY;AACrC,KAAI,CAAC4B,eAAe5B,IAAAA,CAClB,OAAM,IAAIE,MAAM,qCAAA;AAGlB,QAAO;EACLQ,UAAUV,IAAI6B,OAAOC;EACrBV,OAAOpB,IAAIoB;EACXd,KAAKN,IAAIM;EACX;;AAGF,SAASM,qBAAqBD,OAAc;AAC1C,KAAI,EAAEA,iBAAiBT,OACrB,QAAO;AAGT,QAAOS,MAAMoB,QAAQC,SAAS,mBAAA;;AAGhC,SAASC,wBACPC,OAAc;AAEd,KAAI,CAAC7C,cAAc6C,MAAAA,CACjB,QAAO;CAGT,MAAMC,cACJD,MAAMf,SAAS,QAAQe,MAAMf,SAAS,aAAae,MAAMf,SAAS;AAEpE,QACE,OAAOe,MAAMxC,SAAS,YACtByC,gBACC,OAAOD,MAAMd,UAAU,YAAYc,MAAMd,UAAU,UACnD,OAAOc,MAAMR,qBAAqB,YACjCQ,MAAMR,qBAAqB,UAC5B,OAAOQ,MAAMP,2BAA2B,YACvCO,MAAMP,2BAA2B;;AAIvC,SAAS1B,gCACPiC,OAAc;AAEd,KAAI,CAAC7C,cAAc6C,MAAAA,IAAU,CAACE,MAAMC,QAAQH,MAAM/B,KAAI,CACpD,QAAO;AAGT,QAAO+B,MAAM/B,KAAKmC,MAAML,wBAAAA;;AAG1B,SAASL,eAAeM,OAAc;AACpC,KAAI,CAAC7C,cAAc6C,MAAAA,IAAU,CAAC7C,cAAc6C,MAAML,OAAM,CACtD,QAAO;AAGT,QAAO,OAAOK,MAAML,OAAOC,OAAO,YAAY,OAAOI,MAAMd,UAAU;;;;;AClLvE,SAAgBoB,wBAAAA;AACd,QAAO;EACLC,eAAe;EACfC,WAAW;EACXC,wBAAwB;EACxBC,wBAAwB;EAC1B;;AAGF,SAAgBC,sBACdC,aACAC,cAAsC;AAEtC,KAAIA,aAAaC,WAAW,SAAS;AACnC,MACET,cAAcQ,aAAaE,OAAM,IACjC,OAAOF,aAAaE,OAAOC,YAAY,SAEvCJ,aAAYJ,YAAYK,aAAaE,OAAOC;MAE5CJ,aAAYJ,YAAY;AAE1BI,cAAYL,gBAAgB;AAC5B;;AAGF,KAAIM,aAAaC,WAAW,kBAAkB;EAE5C,MAAMG,OADSJ,aAAaE,OACRE;AACpB,MAAIA,MAAMC,SAAS,kBAAkB,OAAOD,KAAKE,SAAS,SACxDP,aAAYH,yBAAyBQ,KAAKE;AAE5C;;AAGF,KAAIN,aAAaC,WAAW,6BAA6B;EAEvD,MAAME,UADSH,aAAaE,OACLK,KAAKC;AAC5B,MAAI,OAAOL,YAAY,SACrBJ,aAAYF,yBAAyBM;AAEvC;;AAGF,KAAIH,aAAaC,WAAW,kBAAkB;EAC5C,MAAMC,SAASF,aAAaE;AAC5BH,cAAYL,gBAAgB;AAC5B,MAAIQ,OAAOO,MAAMC,OAAOP,SAAS;AAC/BJ,eAAYJ,YAAYO,OAAOO,KAAKC,MAAMP;AAC1C;;AAGF,MAAID,OAAOO,MAAME,WAAW,SAC1BZ,aAAYJ,YAAY;;;AAK9B,SAAgBiB,mBACdb,aAA4B;AAE5B,QACEA,YAAYF,0BAA0BE,YAAYH;;;;;ACjDtD,MAAM4B,sBAAoB;AAwB1B,eAAsBC,kBACpBC,OAA6B;CAE7B,MAAMC,SAAS,IAAId,qBAAqB;EACtCe,KAAKF,MAAME;EACXC,UAAUH,MAAMG,YAAYL;EAC9B,CAAA;AAEA,KAAI;AACF,SAAO,MAAMM,uBACX,YAAA;AACE,SAAMf,iBAAiBY,OAAAA;GACvB,MAAMI,SAAS,MAAMb,YAAYS,QAAQD,MAAME,IAAG;AAClD,UAAO;IACLI,UAAUD,OAAOC;IACjBC,OAAOF,OAAOE;IACdC,MAAMR,MAAMQ;IACZN,KAAKG,OAAOH;IACd;KAEFF,MAAMS,iBACAR,OAAOS,SAAO,CAAA;WAEd;AACRT,SAAOS,SAAO;;;AAIlB,eAAsBC,aACpBX,OAAwB;CAExB,MAAMC,SAAS,IAAId,qBAAqB;EACtCe,KAAKF,MAAME;EACXC,UAAUH,MAAMG,YAAYL;EAC9B,CAAA;CAEA,MAAMc,cAAclB,uBAAAA;CACpB,MAAMmB,WAAWC,gBAAAA;CACjB,IAAIC,mBAAmB;AAEvBd,QAAOe,wBAAwBC,iBAAAA;AAC7BxB,wBAAsBmB,aAAaK,aAAAA;AACnC,MAAIL,YAAYM,iBAAiB,CAACH,kBAAkB;AAClDA,sBAAmB;AACnBF,YAASM,SAAO;;GAEpB;AAEA,KAAI;AACF,SAAO,MAAMf,uBACX,YAAA;AACE,SAAMf,iBAAiBY,OAAAA;GACvB,MAAMmB,YAAY,MAAMhC,sBAAsBa,OAAAA;GAC9C,MAAMI,SAAS,MAAMf,WAAWW,QAAQD,MAAMqB,SAASrB,MAAME,IAAG;GAChE,MAAMoB,oBAAoB/B,+BACxB6B,WACApB,MAAMQ,MACNH,OAAOE,MAAK;AAGd,SAAMN,OAAOsB,QAAQ,cAAc;IACjCjB,UAAUD,OAAOC;IACjBN,OAAO,CACL;KACEwB,MAAM;KACNC,MAAMzB,MAAM0B;KACZC,eAAe,EAAE;KACnB,CACD;IACDL;IACF,CAAA;AAEA,SAAMT,SAASe;AAEf,OAAIhB,YAAYiB,UACd,OAAM,IAAIC,MAAMlB,YAAYiB,UAAS;GAGvC,MAAME,UAAUpC,mBAAmBiB,YAAAA;AACnC,OAAI,CAACmB,WAAWA,QAAQC,MAAI,CAAGC,WAAW,EACxC,OAAM,IAAIH,MAAM,iCAAA;AAGlB,UAAO;IACLxB,UAAUD,OAAOC;IACjBC,OAAOF,OAAOE;IACdC,MAAMR,MAAMQ;IACZuB;IACA7B,KAAKG,OAAOH;IACd;KAEFF,MAAMS,iBACN;AACE,OAAI,CAACM,kBAAkB;AACrBA,uBAAmB;AACnBF,aAASqB,uBAAO,IAAIJ,MAAM,4BAAA,CAAA;;AAE5B7B,UAAOS,SAAO;IAChB;WAEM;AACRT,SAAOS,SAAO;;;AAIlB,eAAeN,uBACb+B,KACA1B,WACA2B,WAAqB;AAErB,KAAI,OAAO3B,cAAc,YAAYA,aAAa,EAChD,QAAO0B,KAAAA;AAGT,QAAOE,YAAYF,KAAK1B,WAAW2B,UAAAA;;AAGrC,SAAStB,iBAAAA;CACP,IAAIK;CACJ,IAAIe;AAMJ,QAAO;EAAEN,SALO,IAAIU,SAAYC,cAAcC,gBAAAA;AAC5CrB,aAAUoB;AACVL,YAASM;IACX;EAEkBrB;EAASe;EAAO;;AAGpC,eAAeG,YACbF,KACA1B,WACA2B,WAAqB;CAErB,IAAIK;CACJ,MAAMC,iBAAiB,IAAIJ,SAAYK,GAAGT,WAAAA;AACxCO,kBAAgBG,iBAAW;AACzBR,cAAAA;AACAF,0BAAO,IAAIJ,MAAM,iCAAiCrB,UAAU,IAAG,CAAA;KAC9DA,UAAAA;GACL;AAEA,KAAI;AACF,SAAO,MAAM6B,QAAQO,KAAK,CAACV,KAAAA,EAAOO,eAAe,CAAA;WACzC;AACR,MAAID,cACFK,cAAaL,cAAAA;;;;;;AC5LnB,eAAsBO,mBAAAA;AACpB,QAAO,EACLC,OAAO,CAACF,QAAQG,KAAG,CAAG,EACxB;;;;;ACNK,MAAA,aAAA,KAAA,MAAA,k6HAAA;;;;ACAA,MAAA,WAAA,KAAA,MAAA,m+EAAA;;;;ACOP,MAAMK,iBAA4B;AAElC,MAAMC,WAAwC;CAC5CC,IAAIJ;CACJK,IAAIJ;CACN;AAEA,IAAIK,eAAiC;AAErC,SAAgBC,eAAeC,QAAe;CAC5C,MAAMC,WAAWC,cAAcF,OAAAA;AAC/BV,MAAKa,gBAAgB;EACnBH,QAAQC;EACRV,UAAUI,SAASM;EACrB,CAAA;AACAH,gBAAeG;AACf,QAAOA;;AAQT,SAAgBK,kBAAkBN,QAAc;AAC9C,QAAOA,WAAW,QAAQA,WAAW;;AAGvC,SAAgBO,mBAAAA;AACd,QAAOb;;AAGT,SAASQ,cAAcF,QAAe;AACpC,KAAI,CAACA,OACH,QAAON;AAGT,KAAIY,kBAAkBN,OAAAA,CACpB,QAAOA;AAGT,QAAON;;;;;ACpCT,MAAMqB,oBAAoB;AAE1B,MAAMC,sBAAgD;CACpDC,aAAa;CACbC,QAAQ;CACRC,YAAY;CACZC,aAAa;CACbC,WAAWN;CACXO,kBAAkB;CACpB;AAEA,MAAMC,kBAGF;CACFC,QAAQZ,kBAAAA;CACRa,KAAKT;CACP;AAyCA,SAAgBU,gBACdC,UAAkC,EAAE,EAAA;CAEpC,MAAMC,UAAUD,QAAQC,WAAWnB,GAAGoB,SAAO;CAC7C,MAAMC,eAAeH,QAAQG,gBAAgBnB,QAAQoB,KAAG;CACxD,MAAMC,YAAYtB,KAAKuB,KAAKL,SAAS,SAAA;CACrC,MAAMM,aAAaxB,KAAKuB,KAAKD,WAAW,cAAA;AAExC,KAAI,CAACxB,GAAG2B,WAAWD,WAAAA,EAAa;AAC9BE,uBAAqBJ,WAAWE,WAAAA;AAChC,QAAM,IAAIG,MACRC,KAAAA,EAAC;;;aAAgDJ;GAA+C,CAAA,CAAA;;CAIpG,MAAMK,SAASC,gBAAgBN,WAAAA;CAC/B,MAAMV,SAASiB,WAAWF,OAAOG,YAAW;AAC5C7B,gBAAeW,OAAAA;CAEf,MAAMmB,SAASC,mBAAmBL,OAAOd,IAAIR,aAAa,cAAA;AAI1D,QAAO;EACL8B,YAAY;GACVF,OALUD,mBAAmBL,OAAOd,IAAIP,QAAQ,SAAA;GAMhD4B,WALcF,mBAAmBL,OAAOd,IAAIN,YAAY,aAAA;GAMxDwB;GACF;EACAf;EACAoB,WAAWC,mBAAmBV,OAAOd,IAAIL,aAAa,cAAA;EACtD8B,UACED,mBAAmBV,OAAOd,IAAIJ,WAAW,YAAA,IACzCN;EACFoC,gBAAgBC,cAAcb,OAAOd,IAAIH,iBAAgB;EACzDQ;EACAN;EACF;;AAGF,SAASY,qBAAqBJ,WAAmBE,YAAkB;AACjE1B,IAAG6C,UAAUrB,WAAW,EAAEsB,WAAW,MAAK,CAAA;AAC1C,KAAI9C,GAAG2B,WAAWD,WAAAA,CAChB;AAGF1B,IAAG+C,cACDrB,YACA,GAAGsB,KAAKC,UAAUlC,iBAAiB,MAAM,EAAA,CAAG,KAC5C;EACEmC,UAAU;EACVC,MAAM;EACR,CAAA;;AAIJ,SAASnB,gBAAgBN,YAAkB;CACzC,IAAI0B;AACJ,KAAI;AACFA,QAAMpD,GAAGqD,aAAa3B,YAAY,QAAA;UAC3B4B,OAAO;AACd,QAAM,IAAIzB,MACRC,KAAAA,EAAC;;;;IAAkCJ;OAAe6B,YAAYD,MAAAA;;GAAO,CAAA,CAAA;;CAIzE,IAAIvB;AACJ,KAAI;AACFA,WAASiB,KAAKQ,MAAMJ,IAAAA;UACbE,OAAO;AACd,QAAM,IAAIzB,MACRC,KAAAA,EAAC;;;;IAAmCJ;OAAe6B,YAAYD,MAAAA;;GAAO,CAAA,CAAA;;AAI1E,KAAI,CAACG,SAAS1B,OAAAA,CACZ,OAAM,IAAIF,MACRC,KAAAA,EAAC;;;YAA2BJ;EAAwC,CAAA,CAAA;CAIxE,MAAMgC,eAAe3B;AACrB,KAAI2B,aAAazC,QAAQ0C,OACvB,QAAO;EACL1C,KAAKyC;EACLxB,aAAawB,aAAa1C,UAAU0C,aAAaE;EACnD;AAGF,KAAI,CAACH,SAASC,aAAazC,IAAG,CAC5B,OAAM,IAAIY,MACRC,KAAAA,EAAC;;;YAA2BJ;EAAuC,CAAA,CAAA;AAIvE,QAAO;EACLT,KAAKyC,aAAazC;EAClBiB,aAAawB,aAAa1C,UAAU0C,aAAaE;EACnD;;AAGF,SAASxB,mBAAmByB,OAAgBC,OAAa;CACvD,MAAMC,aAAatB,mBAAmBoB,OAAOC,MAAAA;AAC7C,KAAI,CAACC,WACH,OAAM,IAAIlC,MACRC,KAAAA,EAAC;;;YAAyBgC;EAAkD,CAAA,CAAA;AAIhF,QAAOC;;AAGT,SAAStB,mBAAmBoB,OAAgBC,OAAa;AACvD,KAAID,UAAUF,OACZ;AAGF,KAAI,OAAOE,UAAU,SACnB,OAAM,IAAIG,UAAUlC,KAAAA,EAAC;;;YAAyBgC;EAAwB,CAAA,CAAA;CAGxE,MAAMC,aAAaF,MAAMI,MAAI;AAC7B,KAAIF,WAAWG,WAAW,EACxB;AAGF,QAAOH;;AAGT,SAASnB,cAAciB,OAAc;AACnC,KAAIA,UAAUF,UAAaE,UAAU,KACnC;AAGF,KAAI,OAAOA,UAAU,UAAU;AAC7B,MAAIM,OAAOC,UAAUP,MAAAA,IAAUA,QAAQ,EACrC,QAAOA;AAET,QAAM,IAAIhC,MACRC,KAAAA,EAAC;;;GAAmE,CAAA,CAAA;;AAIxE,KAAI,OAAO+B,UAAU,UAAU;EAC7B,MAAMQ,UAAUR,MAAMI,MAAI;AAC1B,MAAII,QAAQH,WAAW,EACrB;AAEF,MAAI,CAAC,aAAaI,KAAKD,QAAAA,CACrB,OAAM,IAAIxC,MACRC,KAAAA,EAAC;;;GAAmE,CAAA,CAAA;AAIxE,SAAOqC,OAAOI,SAASF,SAAS,GAAA;;AAGlC,OAAM,IAAIxC,MACRC,KAAAA,EAAC;;;EAAmE,CAAA,CAAA;;AAIxE,SAASG,WAAW4B,OAAc;CAChC,MAAMW,gBAAgBpE,kBAAAA;AAEtB,KAAIyD,UAAUF,UAAaE,UAAU,KACnC,QAAOW;AAGT,KAAI,OAAOX,UAAU,UAAU;AAC7BY,UAAQC,KACN5C,KAAAA,EAAC;;;gBAAiC6C,oBAAoBd,MAAAA;GAA8C,CAAA,CAAA;AAEtG,SAAOW;;CAGT,MAAMT,aAAaF,MAAMI,MAAI;AAC7B,KAAIF,WAAWG,WAAW,EACxB,QAAOM;AAGT,KAAIlE,kBAAkByD,WAAAA,CACpB,QAAOA;AAGTU,SAAQC,KACN5C,KAAAA,EAAC;;;YAAiCiC;EAAkD,CAAA,CAAA;AAGtF,QAAOS;;AAGT,SAASG,oBAAoBd,OAAc;AACzC,KAAI,OAAOA,UAAU,SACnB,QAAOA;AAGT,KAAI,OAAOA,UAAU,YAAY,OAAOA,UAAU,UAChD,QAAOe,OAAOf,MAAAA;AAGhB,KAAI;AACF,SAAOb,KAAKC,UAAUY,MAAAA;SAChB;AACN,SAAOe,OAAOf,MAAAA;;;AAIlB,SAASJ,SAASI,OAAc;AAC9B,QAAO,OAAOA,UAAU,YAAYA,UAAU,QAAQ,CAACgB,MAAMC,QAAQjB,MAAAA;;AAGvE,SAASN,YAAYD,OAAc;AACjC,KAAIA,iBAAiBzB,MACnB,QAAOyB,MAAMyB;AAGf,QAAOH,OAAOtB,MAAAA;;;;;AC1RhB,SAAgB4B,mBAAAA;AACd,KAAI;AACF,SAAOD,iBAAAA;UACAE,OAAO;AACdC,UAAQD,MAAME,mBAAmBF,MAAAA,CAAAA;AACjCH,UAAQM,KAAK,EAAA;;;AAIjB,SAASD,mBAAmBF,OAAc;CACxC,MAAMI,UAAUJ,iBAAiBK,QAAQL,MAAMI,UAAUE,OAAON,MAAAA;AAChE,QAAOO,KAAAA,EAAC;;;YAA0BH;EAAQ,CAAA;;;;;ACX5C,MAAMO,qBAAqB;AAa3B,eAAsBC,UACpBC,YACAC,MACAC,MACAC,SAA0B;CAE1B,MAAMC,UAAUC,KAAKC,UAAU,EAC7BJ,MAAMK,4BAA4BN,MAAMC,MAAMC,QAAAA,EAChD,CAAA;AAEA,KAAIF,KAAKO,QAAQC,cAAc,OAAO;AACpC,QAAMT,WAAWU,GAAGC,GAAGH,QAAQI,OAAO;GACpCC,QAAQ,EACNC,iBAAiB,WACnB;GACAb,MAAM;IACJc,YAAYd,KAAKO,QAAQQ;IACzBC,UAAU;IACVb;IACF;GACF,CAAA;AACA;;AAGF,OAAMJ,WAAWU,GAAGC,GAAGH,QAAQU,MAAM;EACnCC,MAAM,EACJC,YAAYnB,KAAKO,QAAQY,YAC3B;EACAnB,MAAM;GACJgB,UAAU;GACVb;GACF;EACF,CAAA;;AAGF,SAASG,4BACPN,MACAC,MACAC,SAA0B;AAE1B,KAAI,CAACA,SAASkB,iBACZ,QAAOnB,KAAKoB,MAAI;CAGlB,MAAMC,WAAWC,gBAAgBvB,KAAAA;CACjC,MAAMwB,iBAAiBvB,KAAKoB,MAAI;AAChC,KAAIG,eAAeC,WAAW,EAC5B,QAAO,GAAGH,SAAS;AAGrB,QAAO,GAAGA,SAAS,MAAME;;AAG3B,SAASD,gBAAgBvB,MAA+B;CACtD,MAAM0B,WAAWhC,gBAAgBM,KAAK2B,OAAOC,UAAS;AACtD,KAAI,CAACF,SACH,QAAO7B;CAQT,MAAMoC,UAAUtC,WALGC,cAAc;EAC/BkC,UAAU9B,KAAKO,QAAQC;EACvBuB,QAAQ/B,KAAKO,QAAQQ;EACrBiB,QAAQN;EACV,CAAA,CAC2BG;AAC3B,KAAI,CAACI,WAAWA,QAAQC,SAASb,MAAI,CAAGI,WAAW,EACjD,QAAO5B;AAGT,QAAOoC,QAAQC;;;;;ACjEjB,MAAMoB,cAAcR,kBAAAA;AACpBE,eAAeM,YAAYC,OAAM;AAEjC,IAAI;AACFJ,wBAAuB;EACrBK,SAASF,YAAYE;EACrBC,cAAcH,YAAYG;EAC5B,CAAA;SACOC,OAAO;CACd,MAAMC,UAAUD,iBAAiBE,QAAQF,MAAMC,UAAUE,OAAOH,MAAAA;AAChEI,SAAQJ,MAAMK,KAAAA,EAAC;;;YAA0BJ;EAAQ,CAAA,CAAA;AACjDxB,SAAQ6B,KAAK,EAAA;;AAGf,MAAMC,eAAeF,KAAAA,EAAC;;;CAAwC,CAAA;AAE9D,MAAMG,SAAS,IAAI9B,KAAK+B,OAAOb,YAAYc,WAAU;AACrD,MAAMC,WAAW,IAAIjC,KAAKkC,SAAShB,YAAYc,WAAU;AACzD,IAAIG,gBAAgB;AAEpB,eAAeC,qBACbC,MAA+B;AAE/B,KAAI;EACF,MAAMC,QAAQ,MAAMjC,0BAA0BgC,MAAM;GAClDE,WAAWrB,YAAYqB;GACvBpC,qBAAqBqC,UACnBrC,mBAAmBqC,OAAO;IACxBC,eAAeC,SACbnC,kBAAkB;KAChBmC;KACAC,KAAKzB,YAAYG;KACjBuB,UAAU1B,YAAY0B;KACtBC,WAAW3B,YAAY4B;KACzB,CAAA;IACFC,UAAUC,WACRxC,aAAa;KACX,GAAGwC;KACHL,KAAKzB,YAAYG;KACjBuB,UAAU1B,YAAY0B;KACtBC,WAAW3B,YAAY4B;KACzB,CAAA;IACFhC;IACAE;IACAH;IACAI;IACAR;IACF,CAAA;GACJ,CAAA;AAEA,MAAI6B,UAAU,KACZ;AAGF,QAAM3B,UAAUmB,QAAQO,MAAMC,OAAO,EACnCW,kBAAkBC,sBAAsBb,KAAAA,EAC1C,CAAA;UACOf,OAAO;AACdI,UAAQJ,MAAM,mCAAmCA,MAAAA;AACjD,MAAI;AACF,SAAMX,UACJmB,QACAO,MACAV,KAAAA,EAAC;;;IAAmD,CAAA,CAAA;WAE/CwB,YAAY;AACnBzB,WAAQJ,MAAM,kCAAkC6B,WAAAA;;;;AAKtD,SAASD,sBAAsBb,MAA+B;CAC5D,MAAMe,UAAUC,eAAehB,KAAKd,QAAQ+B,QAAO;AACnD,KAAIF,YAAY,KACd,QAAO;CAGT,MAAMG,iBAAiBjD,iBAAiB8C,QAAAA,CAASI,MAAI;AACrD,KAAID,eAAeE,WAAW,EAC5B,QAAO;AAGT,QAAOvD,aAAaqD,eAAAA,CAAgBG,SAAS;;AAG/C,SAASL,eAAeC,SAAe;AACrC,KAAI;EACF,MAAMK,SAAkBC,KAAKC,MAAMP,QAAAA;AACnC,MAAI,CAACrD,cAAc0D,OAAAA,CACjB,QAAO;AAGT,SAAO,OAAOA,OAAOG,SAAS,WAAWH,OAAOG,OAAO;SACjD;AACN,SAAO;;;AAIX,MAAMC,kBAAkB,IAAI/D,KAAKgE,gBAAgB,EAAC,CAAA,CAAGC,SAAS,EAC5D,yBAAyB,OAAO5B,SAAAA;AAE9BX,SAAQwC,KACN,6BACAN,KAAKO,UAAU9B,MAAM,MAAM,EAAA,EAC3B,KAAA;AAGF,KAAI,CAACjC,qBAAqBiC,MAAMnB,YAAYqB,UAAS,CACnD;AAGF,KAAIJ,eAAe;AACjB,EAAKxB,UAAUmB,QAAQO,MAAMR,aAAAA;AAC7B;;AAGFM,iBAAgB;AAChB,CAAKC,qBAAqBC,KAAAA,CAAM+B,cAAQ;AACtCjC,kBAAgB;GAClB;GAEJ,CAAA;AAEAF,SAASoC,MAAM,EAAEN,iBAAgB,CAAA"}
|
|
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\n#. placeholder {0}: index + 1\n#: src/bot/handler.ts:83\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:83\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:57\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:114\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:125\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:103\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:60\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:155\nmsgid \"Codex execution failed: {0}\"\nmsgstr \"Codex execution failed: {0}\"\n\n#: src/bot/handler.ts:158\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:113\nmsgid \"Created a new session.\"\nmsgstr \"Created a new session.\"\n\n#: src/bot/handler.ts:92\nmsgid \"Current session has been cleared.\"\nmsgstr \"Current session has been cleared.\"\n\n#: src/bot/handler.ts:68\nmsgid \"Current session status:\"\nmsgstr \"Current session status:\"\n\n#: src/bot/handler.ts:84\nmsgid \"Current working directories:\"\nmsgstr \"Current working directories:\"\n\n#: src/index.ts:21\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:115\nmsgid \"cwd: {0}\"\nmsgstr \"cwd: {0}\"\n\n#: src/bot/relay.ts:43\n#: src/bot/relay.ts:48\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:68\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:163\nmsgid \"Failed to read open projects: {0}\"\nmsgstr \"Failed to read open projects: {0}\"\n\n#: src/bot/handler.ts:166\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:121\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\nmsgid \"Failed to start relay: {message}\"\nmsgstr \"Failed to start relay: {message}\"\n\n#. placeholder {0}: formatError(error)\n#: src/core/config.ts:130\nmsgid \"Invalid JSON in relay config at {configPath}: {0}\"\nmsgstr \"Invalid JSON in relay config at {configPath}: {0}\"\n\n#: src/bot/commands.ts:71\n#: src/bot/commands.ts:92\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:147\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:136\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:158\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:172\nmsgid \"Invalid relay config: {field} must be a string.\"\nmsgstr \"Invalid relay config: {field} must be a string.\"\n\n#: src/core/config.ts:194\n#: src/core/config.ts:205\n#: src/core/config.ts:213\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:226\nmsgid \"Invalid relay config: LOCALE \\\"{0}\\\" is not supported. Falling back to en.\"\nmsgstr \"Invalid relay config: LOCALE \\\"{0}\\\" is not supported. Falling back to en.\"\n\n#: src/core/config.ts:241\nmsgid \"Invalid relay config: LOCALE \\\"{normalized}\\\" is not supported. Falling back to en.\"\nmsgstr \"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:71\n#: src/bot/handler.ts:116\nmsgid \"mode: {0}\"\nmsgstr \"mode: {0}\"\n\n#. placeholder {0}: created.model\n#. placeholder {0}: currentSession.model\n#: src/bot/handler.ts:72\n#: src/bot/handler.ts:117\nmsgid \"model: {0}\"\nmsgstr \"model: {0}\"\n\n#: src/bot/handler.ts:65\n#: src/bot/handler.ts:225\nmsgid \"New Session\"\nmsgstr \"New Session\"\n\n#: src/bot/handler.ts:97\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:62\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:80\nmsgid \"No working directories are currently open.\"\nmsgstr \"No working directories are currently open.\"\n\n#: src/bot/relay.ts:65\nmsgid \"Please send a text message.\"\nmsgstr \"Please send a text message.\"\n\n#: src/core/config.ts:72\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:105\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:69\n#: src/bot/handler.ts:114\nmsgid \"thread: {0}\"\nmsgstr \"thread: {0}\"\n\n#: src/bot/handler.ts:70\nmsgid \"title: {title}\"\nmsgstr \"title: {title}\"\n\n#. placeholder {0}: command ?? normalized\n#: src/bot/commands.ts:134\nmsgid \"\"\n\"Unknown command \\\"{0}\\\".\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"Unknown command \\\"{0}\\\".\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#. placeholder {0}: normalizePrompt(prompt)\n#: src/bot/handler.ts:263\nmsgid \"User message: {0}\"\nmsgstr \"User message: {0}\"\n\n#: src/bot/handler.ts:248\nmsgid \"\"\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.\"\nmsgstr \"\"\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\nmsgid \"\"\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.\"\nmsgstr \"\"\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\n#. placeholder {0}: index + 1\n#: src/bot/handler.ts:83\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:83\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:57\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:114\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:125\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:103\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:60\nmsgid \"Cannot identify sender. Please try again later.\"\nmsgstr \"无法识别发送者,请稍后重试。\"\n\n#. placeholder {0}: error.message\n#: src/bot/handler.ts:155\nmsgid \"Codex execution failed: {0}\"\nmsgstr \"Codex 执行失败:{0}\"\n\n#: src/bot/handler.ts:158\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:113\nmsgid \"Created a new session.\"\nmsgstr \"已创建新会话。\"\n\n#: src/bot/handler.ts:92\nmsgid \"Current session has been cleared.\"\nmsgstr \"当前会话已清空。\"\n\n#: src/bot/handler.ts:68\nmsgid \"Current session status:\"\nmsgstr \"当前会话状态:\"\n\n#: src/bot/handler.ts:84\nmsgid \"Current working directories:\"\nmsgstr \"当前工作目录:\"\n\n#: src/index.ts:21\nmsgid \"Currently busy. Please try again later.\"\nmsgstr \"当前忙碌,请稍后重试。\"\n\n#. placeholder {0}: created.cwd\n#: src/bot/handler.ts:115\nmsgid \"cwd: {0}\"\nmsgstr \"cwd: {0}\"\n\n#: src/bot/relay.ts:43\n#: src/bot/relay.ts:48\nmsgid \"Failed to parse message. Please send a text message.\"\nmsgstr \"解析消息失败,请发送文本消息。\"\n\n#: src/index.ts:68\nmsgid \"Failed to process message. Please try again later.\"\nmsgstr \"处理消息失败,请稍后重试。\"\n\n#. placeholder {0}: error.message\n#: src/bot/handler.ts:163\nmsgid \"Failed to read open projects: {0}\"\nmsgstr \"读取已打开项目失败:{0}\"\n\n#: src/bot/handler.ts:166\nmsgid \"Failed to read open projects. Please try again later.\"\nmsgstr \"读取已打开项目失败,请稍后重试。\"\n\n#. placeholder {0}: formatError(error)\n#: src/core/config.ts:121\nmsgid \"Failed to read relay config at {configPath}: {0}\"\nmsgstr \"读取中继配置失败 {configPath}:{0}\"\n\n#: src/core/startup.ts:17\nmsgid \"Failed to start relay: {message}\"\nmsgstr \"启动中继失败:{message}\"\n\n#. placeholder {0}: formatError(error)\n#: src/core/config.ts:130\nmsgid \"Invalid JSON in relay config at {configPath}: {0}\"\nmsgstr \"中继配置 {configPath} 中的 JSON 无效:{0}\"\n\n#: src/bot/commands.ts:71\n#: src/bot/commands.ts:92\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:147\nmsgid \"Invalid relay config at {configPath}: env must be a JSON object.\"\nmsgstr \"中继配置 {configPath} 无效:env 必须是 JSON 对象。\"\n\n#: src/core/config.ts:136\nmsgid \"Invalid relay config at {configPath}: root must be a JSON object.\"\nmsgstr \"中继配置 {configPath} 无效:root 必须是 JSON 对象。\"\n\n#: src/core/config.ts:158\nmsgid \"Invalid relay config: {field} is required and must be a non-empty string.\"\nmsgstr \"中继配置无效:{field} 为必填项且必须为非空字符串。\"\n\n#: src/core/config.ts:172\nmsgid \"Invalid relay config: {field} must be a string.\"\nmsgstr \"中继配置无效:{field} 必须是字符串。\"\n\n#: src/core/config.ts:194\n#: src/core/config.ts:205\n#: src/core/config.ts:213\nmsgid \"Invalid relay config: CODEX_TIMEOUT_MS must be a positive integer.\"\nmsgstr \"中继配置无效:CODEX_TIMEOUT_MS 必须是正整数。\"\n\n#. placeholder {0}: formatInvalidLocale(value)\n#: src/core/config.ts:226\nmsgid \"Invalid relay config: LOCALE \\\"{0}\\\" is not supported. Falling back to en.\"\nmsgstr \"中继配置无效:不支持的 LOCALE \\\"{0}\\\",已回退为 en。\"\n\n#: src/core/config.ts:241\nmsgid \"Invalid relay config: LOCALE \\\"{normalized}\\\" is not supported. Falling back to en.\"\nmsgstr \"中继配置无效:不支持的 LOCALE \\\"{normalized}\\\",已回退为 en。\"\n\n#. placeholder {0}: created.mode\n#. placeholder {0}: currentSession.mode\n#: src/bot/handler.ts:71\n#: src/bot/handler.ts:116\nmsgid \"mode: {0}\"\nmsgstr \"mode: {0}\"\n\n#. placeholder {0}: created.model\n#. placeholder {0}: currentSession.model\n#: src/bot/handler.ts:72\n#: src/bot/handler.ts:117\nmsgid \"model: {0}\"\nmsgstr \"model: {0}\"\n\n#: src/bot/handler.ts:65\n#: src/bot/handler.ts:225\nmsgid \"New Session\"\nmsgstr \"新会话\"\n\n#: src/bot/handler.ts:97\nmsgid \"No active session. Send a normal message or use /new to create one first.\"\nmsgstr \"没有活跃会话。请先发送普通消息或使用 /new 创建会话。\"\n\n#: src/bot/handler.ts:62\nmsgid \"No active session. Send a normal message or use /new to create one.\"\nmsgstr \"没有活跃会话。请发送普通消息或使用 /new 创建会话。\"\n\n#: src/bot/handler.ts:80\nmsgid \"No working directories are currently open.\"\nmsgstr \"当前没有打开的工作目录。\"\n\n#: src/bot/relay.ts:65\nmsgid \"Please send a text message.\"\nmsgstr \"请发送文本消息。\"\n\n#: src/core/config.ts:72\nmsgid \"Relay config is missing. Template created at {configPath}. Please edit this file and restart.\"\nmsgstr \"中继配置缺失,已在 {configPath} 创建模板。请编辑该文件后重启。\"\n\n#. placeholder {0}: parsed.mode\n#: src/bot/handler.ts:105\nmsgid \"Switched to {0} mode.\"\nmsgstr \"已切换到 {0} 模式。\"\n\n#. placeholder {0}: created.threadId\n#. placeholder {0}: currentSession.threadId\n#: src/bot/handler.ts:69\n#: src/bot/handler.ts:114\nmsgid \"thread: {0}\"\nmsgstr \"thread: {0}\"\n\n#: src/bot/handler.ts:70\nmsgid \"title: {title}\"\nmsgstr \"title: {title}\"\n\n#. placeholder {0}: command ?? normalized\n#: src/bot/commands.ts:134\nmsgid \"\"\n\"Unknown command \\\"{0}\\\".\\n\"\n\"\\n\"\n\"{helpText}\"\nmsgstr \"\"\n\"未知命令 \\\"{0}\\\"。\\n\"\n\"\\n\"\n\"{helpText}\"\n\n#. placeholder {0}: normalizePrompt(prompt)\n#: src/bot/handler.ts:263\nmsgid \"User message: {0}\"\nmsgstr \"用户消息:{0}\"\n\n#: src/bot/handler.ts:248\nmsgid \"\"\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.\"\nmsgstr \"\"\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\nmsgid \"\"\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.\"\nmsgstr \"\"\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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"}
|