codexui-android 0.1.108 → 0.1.109

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist-cli/index.js CHANGED
@@ -2047,6 +2047,9 @@ function deriveSkillPathInfo(skillPath, knownPaths = /* @__PURE__ */ new Set())
2047
2047
  function getSkillsInstallDir() {
2048
2048
  return join4(getCodexHomeDir2(), "skills");
2049
2049
  }
2050
+ function getSharedSkillsInstallDir() {
2051
+ return join4(getSkillsInstallDir(), "shared_skills");
2052
+ }
2050
2053
  var DEFAULT_COMMAND_TIMEOUT_MS = 12e4;
2051
2054
  var SKILL_SEARCH_METADATA_LIMIT = 20;
2052
2055
  var SKILL_SEARCH_METADATA_CONCURRENCY = 4;
@@ -2421,9 +2424,8 @@ var startupSyncStatus = {
2421
2424
  lastSuccessAtIso: "",
2422
2425
  lastError: ""
2423
2426
  };
2424
- async function scanInstalledSkillsFromDisk() {
2427
+ async function scanInstalledSkillsFromDir(skillsDir) {
2425
2428
  const map = /* @__PURE__ */ new Map();
2426
- const skillsDir = getSkillsInstallDir();
2427
2429
  try {
2428
2430
  const entries = await readdir(skillsDir, { withFileTypes: true });
2429
2431
  for (const entry of entries) {
@@ -2439,6 +2441,9 @@ async function scanInstalledSkillsFromDisk() {
2439
2441
  }
2440
2442
  return map;
2441
2443
  }
2444
+ async function scanInstalledSkillsFromDisk() {
2445
+ return await scanInstalledSkillsFromDir(getSkillsInstallDir());
2446
+ }
2442
2447
  async function collectInstalledSkillsMap(appServer) {
2443
2448
  const installedMap = await scanInstalledSkillsFromDisk();
2444
2449
  try {
@@ -2700,13 +2705,14 @@ async function writeRemoteSkillsManifest(token, repoOwner, repoName, skills) {
2700
2705
  function toGitHubTokenRemote(repoOwner, repoName, token) {
2701
2706
  return `https://x-access-token:${encodeURIComponent(token)}@github.com/${repoOwner}/${repoName}.git`;
2702
2707
  }
2703
- async function ensureSkillsWorkingTreeRepo(repoUrl, branch) {
2704
- const localDir = getSkillsInstallDir();
2708
+ async function ensureSkillsWorkingTreeRepo(repoUrl, branch, options = {}) {
2709
+ const localDir = options.localDir ?? getSkillsInstallDir();
2705
2710
  await mkdir3(localDir, { recursive: true });
2706
2711
  const gitDir = join4(localDir, ".git");
2707
2712
  let hasGitDir = false;
2708
2713
  try {
2709
- hasGitDir = (await stat3(gitDir)).isDirectory();
2714
+ const gitDirStat = await lstat(gitDir);
2715
+ hasGitDir = gitDirStat.isDirectory() || gitDirStat.isFile();
2710
2716
  } catch {
2711
2717
  hasGitDir = false;
2712
2718
  }
@@ -2726,6 +2732,14 @@ async function ensureSkillsWorkingTreeRepo(repoUrl, branch) {
2726
2732
  await runCommand2("git", ["remote", "set-url", "origin", repoUrl], { cwd: localDir });
2727
2733
  }
2728
2734
  await runGitFetchWithRefLockRetry(localDir);
2735
+ if (options.overwriteLocalFiles) {
2736
+ await runCommand2("git", ["reset", "--hard"], { cwd: localDir });
2737
+ await runCommand2("git", ["clean", "-fd"], { cwd: localDir });
2738
+ await runCommand2("git", ["checkout", "-B", branch, `origin/${branch}`], { cwd: localDir });
2739
+ await runCommand2("git", ["reset", "--hard", `origin/${branch}`], { cwd: localDir });
2740
+ await runCommand2("git", ["clean", "-fd"], { cwd: localDir });
2741
+ return localDir;
2742
+ }
2729
2743
  try {
2730
2744
  await runCommand2("git", ["merge", "--allow-unrelated-histories", "--no-edit", `origin/${branch}`], { cwd: localDir });
2731
2745
  } catch {
@@ -2734,6 +2748,17 @@ async function ensureSkillsWorkingTreeRepo(repoUrl, branch) {
2734
2748
  }
2735
2749
  await runCommand2("git", ["remote", "set-url", "origin", repoUrl], { cwd: localDir });
2736
2750
  await runGitFetchWithRefLockRetry(localDir);
2751
+ if (options.overwriteLocalFiles) {
2752
+ try {
2753
+ await runCommand2("git", ["reset", "--hard"], { cwd: localDir });
2754
+ } catch {
2755
+ }
2756
+ await runCommand2("git", ["clean", "-fd"], { cwd: localDir });
2757
+ await runCommand2("git", ["checkout", "-B", branch, `origin/${branch}`], { cwd: localDir });
2758
+ await runCommand2("git", ["reset", "--hard", `origin/${branch}`], { cwd: localDir });
2759
+ await runCommand2("git", ["clean", "-fd"], { cwd: localDir });
2760
+ return localDir;
2761
+ }
2737
2762
  const hasLocalChangesBeforeSync = await hasLocalUncommittedChanges(localDir);
2738
2763
  const localMtimesBeforeSync = hasLocalChangesBeforeSync ? await snapshotFileMtimes(localDir) : /* @__PURE__ */ new Map();
2739
2764
  await resolveMergeConflictsByNewerCommit(localDir, branch, localMtimesBeforeSync);
@@ -2994,13 +3019,19 @@ async function syncInstalledSkillsFolderToRepo(token, repoOwner, repoName, _inst
2994
3019
  }
2995
3020
  async function pullInstalledSkillsFolderFromRepo(token, repoOwner, repoName) {
2996
3021
  const remoteUrl = toGitHubTokenRemote(repoOwner, repoName, token);
2997
- const branch = PRIVATE_SYNC_BRANCH;
2998
- await ensureSkillsWorkingTreeRepo(remoteUrl, branch);
3022
+ const isUpstream = isUpstreamSkillsRepo(repoOwner, repoName);
3023
+ const branch = isUpstream ? PUBLIC_UPSTREAM_BRANCH_ANDROID : PRIVATE_SYNC_BRANCH;
3024
+ return await ensureSkillsWorkingTreeRepo(remoteUrl, branch, {
3025
+ ...isUpstream ? { localDir: getSharedSkillsInstallDir() } : {},
3026
+ overwriteLocalFiles: isUpstream
3027
+ });
2999
3028
  }
3000
3029
  async function bootstrapSkillsFromUpstreamIntoLocal() {
3001
3030
  const repoUrl = `https://github.com/${SYNC_UPSTREAM_SKILLS_OWNER}/${SYNC_UPSTREAM_SKILLS_REPO}.git`;
3002
- const branch = getPreferredPublicUpstreamBranch();
3003
- await ensureSkillsWorkingTreeRepo(repoUrl, branch);
3031
+ return await ensureSkillsWorkingTreeRepo(repoUrl, PUBLIC_UPSTREAM_BRANCH_ANDROID, {
3032
+ localDir: getSharedSkillsInstallDir(),
3033
+ overwriteLocalFiles: true
3034
+ });
3004
3035
  }
3005
3036
  async function collectLocalSyncedSkills(appServer) {
3006
3037
  const state = await readSkillsSyncState();
@@ -3305,12 +3336,31 @@ async function handleSkillsRoutes(req, res, url, context) {
3305
3336
  try {
3306
3337
  const state = await readSkillsSyncState();
3307
3338
  if (!state.githubToken || !state.repoOwner || !state.repoName) {
3308
- await bootstrapSkillsFromUpstreamIntoLocal();
3339
+ const repoDir = await bootstrapSkillsFromUpstreamIntoLocal();
3340
+ const localSkills2 = await scanInstalledSkillsFromDir(repoDir);
3341
+ try {
3342
+ await appServer.rpc("skills/list", { forceReload: true });
3343
+ } catch {
3344
+ }
3345
+ setJson3(res, 200, { ok: true, data: { synced: localSkills2.size, source: "upstream" } });
3346
+ return true;
3347
+ }
3348
+ if (isUpstreamSkillsRepo(state.repoOwner, state.repoName)) {
3349
+ const repoDir = await pullInstalledSkillsFolderFromRepo(state.githubToken, state.repoOwner, state.repoName);
3350
+ const localSkills2 = await scanInstalledSkillsFromDir(repoDir);
3351
+ const pulledHead2 = await runCommandWithOutput("git", ["rev-parse", "HEAD"], { cwd: repoDir }).catch(() => "");
3352
+ await writeSkillsSyncState({
3353
+ ...state,
3354
+ lastPullCommitSha: pulledHead2.trim(),
3355
+ lastSyncAttemptCount: 1,
3356
+ lastSyncError: "",
3357
+ lastSyncAtIso: (/* @__PURE__ */ new Date()).toISOString()
3358
+ });
3309
3359
  try {
3310
3360
  await appServer.rpc("skills/list", { forceReload: true });
3311
3361
  } catch {
3312
3362
  }
3313
- setJson3(res, 200, { ok: true, data: { synced: 0, source: "upstream" } });
3363
+ setJson3(res, 200, { ok: true, data: { synced: localSkills2.size, source: "upstream" } });
3314
3364
  return true;
3315
3365
  }
3316
3366
  const remote = await readRemoteSkillsManifest(state.githubToken, state.repoOwner, state.repoName);
@@ -4203,13 +4253,6 @@ var CUSTOM_PROVIDER_ID = "custom-endpoint";
4203
4253
  var OPENCODE_ZEN_PROVIDER_ID = "opencode-zen";
4204
4254
  var OPENCODE_ZEN_BASE_URL = "https://opencode.ai/zen/v1";
4205
4255
  var OPENCODE_ZEN_DEFAULT_MODEL = "big-pickle";
4206
- var OPENCODE_ZEN_FALLBACK_FREE_MODELS = [
4207
- OPENCODE_ZEN_DEFAULT_MODEL,
4208
- "minimax-m2.5-free",
4209
- "hy3-preview-free",
4210
- "nemotron-3-super-free",
4211
- "trinity-large-preview-free"
4212
- ];
4213
4256
  function createDefaultOpenCodeZenFreeModeState() {
4214
4257
  return {
4215
4258
  enabled: true,
@@ -4221,11 +4264,6 @@ function createDefaultOpenCodeZenFreeModeState() {
4221
4264
  providerKeys: {}
4222
4265
  };
4223
4266
  }
4224
- function getOpenCodeZenFreeModelIds(modelIds) {
4225
- const uniqueIds = modelIds.map((id) => id.trim()).filter((id, index, ids) => id.length > 0 && ids.indexOf(id) === index);
4226
- const freeIds = uniqueIds.filter((id) => id.endsWith("-free") || id === OPENCODE_ZEN_DEFAULT_MODEL);
4227
- return freeIds.length > 0 ? freeIds : OPENCODE_ZEN_FALLBACK_FREE_MODELS;
4228
- }
4229
4267
  function shouldCreateDefaultFreeModeStateForMissingAuth(current, hasUsableCodexAuth) {
4230
4268
  return current == null && !hasUsableCodexAuth;
4231
4269
  }
@@ -4358,14 +4396,6 @@ function extractTextParts(value) {
4358
4396
  if (!Array.isArray(value)) return "";
4359
4397
  return value.map((part) => part && typeof part === "object" && typeof part.text === "string" ? part.text : "").filter((part) => part.length > 0).join("\n");
4360
4398
  }
4361
- function buildReasoningOutputItem(reasoningContent) {
4362
- return {
4363
- type: "reasoning",
4364
- id: `rs_${Date.now()}`,
4365
- summary: [{ type: "summary_text", text: reasoningContent }],
4366
- content: []
4367
- };
4368
- }
4369
4399
  function responsesInputToMessages(input, instructions) {
4370
4400
  const messages = [];
4371
4401
  let pendingReasoningContent = "";
@@ -4487,7 +4517,12 @@ function chatCompletionToResponsesFormat(chatResponse, model) {
4487
4517
  });
4488
4518
  }
4489
4519
  if (message.reasoning_content) {
4490
- output.push(buildReasoningOutputItem(message.reasoning_content));
4520
+ output.push({
4521
+ type: "reasoning",
4522
+ id: `rs_${Date.now()}`,
4523
+ summary: [],
4524
+ content: [{ type: "reasoning_text", text: message.reasoning_content }]
4525
+ });
4491
4526
  }
4492
4527
  }
4493
4528
  const usage = chatResponse.usage;
@@ -4553,7 +4588,12 @@ function forwardStreamingTextResponse(upstreamRes, res, model) {
4553
4588
  const messageItem = { type: "message", role: "assistant", content: [{ type: "output_text", text: fullText }], status: "completed" };
4554
4589
  const output = [messageItem];
4555
4590
  if (fullReasoningText) {
4556
- output.push(buildReasoningOutputItem(fullReasoningText));
4591
+ output.push({
4592
+ type: "reasoning",
4593
+ id: `rs_${Date.now()}`,
4594
+ summary: [],
4595
+ content: [{ type: "reasoning_text", text: fullReasoningText }]
4596
+ });
4557
4597
  }
4558
4598
  res.write(`data: {"type":"response.output_text.done","output_index":0,"content_index":0,"text":"${escapedFull}"}
4559
4599
 
@@ -5196,6 +5236,7 @@ function shellQuote(value) {
5196
5236
  var COMPOSIO_CONNECTORS_PAGE_LIMIT_MAX = 1e3;
5197
5237
  var PROVIDER_MODELS_FETCH_TIMEOUT_MS = 5e3;
5198
5238
  var THREAD_RESPONSE_TURN_LIMIT = 10;
5239
+ var THREAD_TURN_PAGE_READ_CACHE_TTL_MS = 3e4;
5199
5240
  var THREAD_METHODS_WITH_TURNS = /* @__PURE__ */ new Set(["thread/read", "thread/resume", "thread/fork", "thread/rollback"]);
5200
5241
  var THREAD_SEARCH_FULL_TEXT_THREAD_LIMIT = 100;
5201
5242
  var PROJECTLESS_THREAD_DIRECTORY_MAX_ATTEMPTS = 100;
@@ -5698,11 +5739,13 @@ function trimThreadTurnsInRpcResult(method, result) {
5698
5739
  const thread = asRecord5(record?.thread);
5699
5740
  const turns = Array.isArray(thread?.turns) ? thread.turns : null;
5700
5741
  if (!record || !thread || !turns || turns.length <= THREAD_RESPONSE_TURN_LIMIT) return result;
5742
+ const startTurnIndex = Math.max(0, turns.length - THREAD_RESPONSE_TURN_LIMIT);
5701
5743
  return {
5702
5744
  ...record,
5745
+ threadTurnStartIndex: startTurnIndex,
5703
5746
  thread: {
5704
5747
  ...thread,
5705
- turns: turns.slice(-THREAD_RESPONSE_TURN_LIMIT)
5748
+ turns: turns.slice(startTurnIndex)
5706
5749
  }
5707
5750
  };
5708
5751
  }
@@ -5770,6 +5813,56 @@ async function createProjectlessThreadDirectory(prompt) {
5770
5813
  }
5771
5814
  throw new Error("Unable to create a unique new chat folder");
5772
5815
  }
5816
+ function normalizeGithubCloneUrl(rawUrl) {
5817
+ const trimmedUrl = rawUrl.trim();
5818
+ if (!trimmedUrl) throw new Error("Missing GitHub repository URL");
5819
+ const sshMatch = trimmedUrl.match(/^git@github\.com:([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:\.git)?$/u);
5820
+ if (sshMatch) {
5821
+ const repoName2 = sshMatch[2];
5822
+ return { url: `git@github.com:${sshMatch[1]}/${repoName2}.git`, repoName: repoName2 };
5823
+ }
5824
+ let parsed;
5825
+ try {
5826
+ parsed = new URL(trimmedUrl);
5827
+ } catch {
5828
+ throw new Error("Enter a valid GitHub repository URL");
5829
+ }
5830
+ if (parsed.hostname.toLowerCase() !== "github.com") {
5831
+ throw new Error("Only github.com repository URLs are supported");
5832
+ }
5833
+ const segments = parsed.pathname.split("/").filter(Boolean);
5834
+ if (segments.length < 2) {
5835
+ throw new Error("Enter a GitHub repository URL with owner and repository name");
5836
+ }
5837
+ const owner = segments[0];
5838
+ const repoName = segments[1].replace(/\.git$/iu, "");
5839
+ if (!/^[A-Za-z0-9_.-]+$/u.test(owner) || !/^[A-Za-z0-9_.-]+$/u.test(repoName)) {
5840
+ throw new Error("GitHub repository owner or name contains unsupported characters");
5841
+ }
5842
+ return { url: `https://github.com/${owner}/${repoName}.git`, repoName };
5843
+ }
5844
+ async function cloneGithubRepositoryIntoBase(rawUrl, rawBasePath) {
5845
+ const basePath = rawBasePath.trim();
5846
+ if (!basePath) throw new Error("Missing clone destination folder");
5847
+ const normalizedBasePath = isAbsolute2(basePath) ? basePath : resolve2(basePath);
5848
+ await ensureRealDirectory(normalizedBasePath, "Clone destination folder");
5849
+ const { url, repoName } = normalizeGithubCloneUrl(rawUrl);
5850
+ const targetPath = join6(normalizedBasePath, repoName);
5851
+ try {
5852
+ await stat4(targetPath);
5853
+ throw new Error(`Destination already exists: ${targetPath}`);
5854
+ } catch (error) {
5855
+ if (error?.code !== "ENOENT") throw error;
5856
+ }
5857
+ try {
5858
+ await runCommand3("git", ["clone", url, targetPath], { cwd: normalizedBasePath, timeoutMs: 5 * 6e4 });
5859
+ } catch (error) {
5860
+ await rm4(targetPath, { recursive: true, force: true }).catch(() => void 0);
5861
+ throw error;
5862
+ }
5863
+ await persistWorkspaceRoot(targetPath, "");
5864
+ return targetPath;
5865
+ }
5773
5866
  function normalizeHeaderValue(value) {
5774
5867
  if (typeof value === "string") {
5775
5868
  const trimmed = value.trim();
@@ -7179,14 +7272,33 @@ async function runCommand3(command, args, options = {}) {
7179
7272
  });
7180
7273
  let stdout = "";
7181
7274
  let stderr = "";
7275
+ let timedOut = false;
7276
+ let closed = false;
7277
+ const timeout = typeof options.timeoutMs === "number" && Number.isFinite(options.timeoutMs) && options.timeoutMs > 0 ? setTimeout(() => {
7278
+ timedOut = true;
7279
+ proc.kill("SIGTERM");
7280
+ setTimeout(() => {
7281
+ if (!closed) proc.kill("SIGKILL");
7282
+ }, 5e3).unref();
7283
+ }, options.timeoutMs) : null;
7284
+ timeout?.unref();
7182
7285
  proc.stdout.on("data", (chunk) => {
7183
7286
  stdout += chunk.toString();
7184
7287
  });
7185
7288
  proc.stderr.on("data", (chunk) => {
7186
7289
  stderr += chunk.toString();
7187
7290
  });
7188
- proc.on("error", reject);
7291
+ proc.on("error", (error) => {
7292
+ if (timeout) clearTimeout(timeout);
7293
+ reject(error);
7294
+ });
7189
7295
  proc.on("close", (code) => {
7296
+ closed = true;
7297
+ if (timeout) clearTimeout(timeout);
7298
+ if (timedOut) {
7299
+ reject(new Error(`Command timed out after ${options.timeoutMs}ms (${command} ${args.join(" ")})`));
7300
+ return;
7301
+ }
7190
7302
  if (code === 0) {
7191
7303
  resolve4();
7192
7304
  return;
@@ -8463,6 +8575,8 @@ var AppServerProcess = class {
8463
8575
  this.appServerArgs = buildAppServerArgs();
8464
8576
  this.streamEventsByThreadId = /* @__PURE__ */ new Map();
8465
8577
  this.lastThreadReadSnapshotByThreadId = /* @__PURE__ */ new Map();
8578
+ this.threadTurnPageReadCacheByThreadId = /* @__PURE__ */ new Map();
8579
+ this.threadTurnPageReadPromiseByThreadId = /* @__PURE__ */ new Map();
8466
8580
  this.capturedItemsByThreadId = /* @__PURE__ */ new Map();
8467
8581
  this.liveStateCache = /* @__PURE__ */ new Map();
8468
8582
  this.chatgptAuthRefreshPromise = null;
@@ -8575,7 +8689,10 @@ var AppServerProcess = class {
8575
8689
  this.recordStreamEvent(notification);
8576
8690
  this.captureItemFromNotification(notification);
8577
8691
  const nThreadId = this.extractThreadIdFromParams(notification.params);
8578
- if (nThreadId) this.invalidateLiveStateCache(nThreadId);
8692
+ if (nThreadId) {
8693
+ this.invalidateLiveStateCache(nThreadId);
8694
+ this.threadTurnPageReadCacheByThreadId.delete(nThreadId);
8695
+ }
8579
8696
  for (const listener of this.notificationListeners) {
8580
8697
  listener(notification);
8581
8698
  }
@@ -8619,10 +8736,33 @@ var AppServerProcess = class {
8619
8736
  }
8620
8737
  storeThreadReadSnapshot(threadId, snapshot) {
8621
8738
  this.lastThreadReadSnapshotByThreadId.set(threadId, snapshot);
8739
+ this.threadTurnPageReadCacheByThreadId.delete(threadId);
8622
8740
  }
8623
8741
  getLastThreadReadSnapshot(threadId) {
8624
8742
  return this.lastThreadReadSnapshotByThreadId.get(threadId) ?? null;
8625
8743
  }
8744
+ async readThreadForTurnPage(threadId) {
8745
+ const now = Date.now();
8746
+ const cached = this.threadTurnPageReadCacheByThreadId.get(threadId);
8747
+ if (cached && cached.expiresAt > now) return cached.result;
8748
+ if (cached) this.threadTurnPageReadCacheByThreadId.delete(threadId);
8749
+ const pending = this.threadTurnPageReadPromiseByThreadId.get(threadId);
8750
+ if (pending) return pending;
8751
+ const promise = this.rpc("thread/read", {
8752
+ threadId,
8753
+ includeTurns: true
8754
+ }).then((result) => {
8755
+ this.threadTurnPageReadCacheByThreadId.set(threadId, {
8756
+ result,
8757
+ expiresAt: Date.now() + THREAD_TURN_PAGE_READ_CACHE_TTL_MS
8758
+ });
8759
+ return result;
8760
+ }).finally(() => {
8761
+ this.threadTurnPageReadPromiseByThreadId.delete(threadId);
8762
+ });
8763
+ this.threadTurnPageReadPromiseByThreadId.set(threadId, promise);
8764
+ return promise;
8765
+ }
8626
8766
  cacheLiveState(threadId, data, turnCount, sessionSize) {
8627
8767
  this.liveStateCache.set(threadId, { data, turnCount, sessionSize });
8628
8768
  }
@@ -9291,11 +9431,10 @@ async function buildThreadSearchIndex(appServer) {
9291
9431
  const docsById = new Map(docs.map((doc) => [doc.id, doc]));
9292
9432
  return { docsById };
9293
9433
  }
9294
- function createCodexBridgeMiddleware(options = {}) {
9434
+ function createCodexBridgeMiddleware() {
9295
9435
  const { appServer, terminalManager, methodCatalog, telegramBridge, backendQueueProcessor } = getSharedBridgeState();
9296
9436
  let threadSearchIndex = null;
9297
9437
  let threadSearchIndexPromise = null;
9298
- let backgroundServicesStarted = false;
9299
9438
  async function getThreadSearchIndex() {
9300
9439
  if (threadSearchIndex) return threadSearchIndex;
9301
9440
  if (!threadSearchIndexPromise) {
@@ -9308,21 +9447,14 @@ function createCodexBridgeMiddleware(options = {}) {
9308
9447
  }
9309
9448
  return threadSearchIndexPromise;
9310
9449
  }
9311
- function startBackgroundServices() {
9312
- if (backgroundServicesStarted) return;
9313
- backgroundServicesStarted = true;
9314
- void initializeSkillsSyncOnStartup(appServer);
9315
- void readTelegramBridgeConfig().then((config) => {
9316
- if (!config.botToken) return;
9317
- telegramBridge.configureToken(config.botToken);
9318
- telegramBridge.configureAllowedUserIds(config.allowedUserIds);
9319
- telegramBridge.start();
9320
- }).catch(() => {
9321
- });
9322
- }
9323
- if (options.startBackgroundServices !== false) {
9324
- startBackgroundServices();
9325
- }
9450
+ void initializeSkillsSyncOnStartup(appServer);
9451
+ void readTelegramBridgeConfig().then((config) => {
9452
+ if (!config.botToken) return;
9453
+ telegramBridge.configureToken(config.botToken);
9454
+ telegramBridge.configureAllowedUserIds(config.allowedUserIds);
9455
+ telegramBridge.start();
9456
+ }).catch(() => {
9457
+ });
9326
9458
  const middleware = async (req, res, next) => {
9327
9459
  const requestStartNs = process.hrtime.bigint();
9328
9460
  const rawUrl = req.url ?? "";
@@ -9413,11 +9545,7 @@ function createCodexBridgeMiddleware(options = {}) {
9413
9545
  }
9414
9546
  if (url.pathname.startsWith("/codex-api/free-mode")) {
9415
9547
  let readFreeModeState2 = function() {
9416
- const state = ensureDefaultFreeModeStateForMissingAuthSync(statePath) ?? { enabled: false, apiKey: null, model: FREE_MODE_DEFAULT_MODEL };
9417
- if (state.provider === "opencode-zen" && !state.model?.trim()) {
9418
- return { ...state, model: OPENCODE_ZEN_DEFAULT_MODEL };
9419
- }
9420
- return state;
9548
+ return ensureDefaultFreeModeStateForMissingAuthSync(statePath) ?? { enabled: false, apiKey: null, model: FREE_MODE_DEFAULT_MODEL };
9421
9549
  };
9422
9550
  var readFreeModeState = readFreeModeState2;
9423
9551
  const statePath = join6(getCodexHomeDir3(), FREE_MODE_STATE_FILE);
@@ -9569,7 +9697,7 @@ function createCodexBridgeMiddleware(options = {}) {
9569
9697
  if (resolvedKey) {
9570
9698
  prevKeys[providerType] = resolvedKey;
9571
9699
  }
9572
- const resolvedModel = providerType === "openrouter" ? current.model || FREE_MODE_DEFAULT_MODEL : providerType === "custom" ? await fetchCustomEndpointDefaultModel(baseUrl, resolvedKey) : OPENCODE_ZEN_DEFAULT_MODEL;
9700
+ const resolvedModel = providerType === "openrouter" ? current.model || FREE_MODE_DEFAULT_MODEL : providerType === "custom" ? await fetchCustomEndpointDefaultModel(baseUrl, resolvedKey) : "";
9573
9701
  const state = {
9574
9702
  enabled: true,
9575
9703
  apiKey: resolvedKey,
@@ -9729,6 +9857,61 @@ function createCodexBridgeMiddleware(options = {}) {
9729
9857
  setJson4(res, 200, { result });
9730
9858
  return;
9731
9859
  }
9860
+ if (req.method === "GET" && url.pathname === "/codex-api/thread-turn-page") {
9861
+ try {
9862
+ const threadId = url.searchParams.get("threadId")?.trim() ?? "";
9863
+ const beforeTurnId = url.searchParams.get("beforeTurnId")?.trim() ?? "";
9864
+ const limitRaw = url.searchParams.get("limit")?.trim() ?? String(THREAD_RESPONSE_TURN_LIMIT);
9865
+ const limit = Math.max(1, Math.min(50, Number.parseInt(limitRaw, 10) || THREAD_RESPONSE_TURN_LIMIT));
9866
+ if (!threadId) {
9867
+ setJson4(res, 400, { error: "Missing threadId" });
9868
+ return;
9869
+ }
9870
+ const threadReadResult = await appServer.readThreadForTurnPage(threadId);
9871
+ const record = asRecord5(threadReadResult);
9872
+ const thread = asRecord5(record?.thread);
9873
+ if (!record || !thread) {
9874
+ setJson4(res, 502, { error: "thread/read returned an invalid thread response" });
9875
+ return;
9876
+ }
9877
+ const turns = Array.isArray(thread.turns) ? thread.turns : [];
9878
+ const beforeIndex = beforeTurnId ? turns.findIndex((turn) => asRecord5(turn)?.id === beforeTurnId) : turns.length;
9879
+ if (beforeTurnId && beforeIndex < 0) {
9880
+ setJson4(res, 200, {
9881
+ result: {
9882
+ ...record,
9883
+ thread: {
9884
+ ...thread,
9885
+ turns: []
9886
+ }
9887
+ },
9888
+ startTurnIndex: 0,
9889
+ hasMoreOlder: false
9890
+ });
9891
+ return;
9892
+ }
9893
+ const endIndex = beforeIndex;
9894
+ const startIndex = Math.max(0, endIndex - limit);
9895
+ const pageTurns = turns.slice(startIndex, endIndex);
9896
+ const pagedResult = {
9897
+ ...record,
9898
+ thread: {
9899
+ ...thread,
9900
+ turns: pageTurns
9901
+ }
9902
+ };
9903
+ const sanitized = await sanitizeThreadTurnsInlinePayloads("thread/read", pagedResult);
9904
+ const result = await mergeSessionSkillInputsIntoThreadResult(sanitized);
9905
+ setJson4(res, 200, {
9906
+ result,
9907
+ startTurnIndex: startIndex,
9908
+ hasMoreOlder: startIndex > 0
9909
+ });
9910
+ } catch (error) {
9911
+ setJson4(res, 500, { error: getErrorMessage5(error, "Failed to load earlier thread messages") });
9912
+ }
9913
+ return;
9914
+ }
9732
9915
  if (req.method === "GET" && url.pathname === "/codex-api/thread-file-change-fallback") {
9733
9916
  const threadId = url.searchParams.get("threadId")?.trim() ?? "";
9734
9917
  if (!threadId) {
@@ -10018,28 +10201,21 @@ function createCodexBridgeMiddleware(options = {}) {
10018
10201
  try {
10019
10202
  const modelsUrl = "https://opencode.ai/zen/v1/models";
10020
10203
  const headers = {};
10021
- const hasZenApiKey = Boolean(fmState.apiKey && fmState.apiKey !== "dummy");
10022
- if (hasZenApiKey) {
10204
+ if (fmState.apiKey && fmState.apiKey !== "dummy") {
10023
10205
  headers["Authorization"] = `Bearer ${fmState.apiKey}`;
10024
10206
  }
10025
10207
  const resp = await fetch(modelsUrl, { headers, signal: AbortSignal.timeout(8e3) });
10026
10208
  if (resp.ok) {
10027
10209
  const json = await resp.json();
10028
10210
  const allIds = (json.data ?? []).map((m) => m.id).filter(Boolean);
10029
- setJson4(res, 200, {
10030
- data: getOpenCodeZenFreeModelIds(allIds),
10031
- exclusive: true,
10032
- source: "opencode-zen"
10033
- });
10211
+ const freeIds = allIds.filter((id) => id.endsWith("-free") || id === "big-pickle");
10212
+ const paidIds = allIds.filter((id) => !id.endsWith("-free") && id !== "big-pickle");
10213
+ setJson4(res, 200, { data: [...freeIds, ...paidIds], exclusive: true, source: "opencode-zen" });
10034
10214
  return;
10035
10215
  }
10036
10216
  } catch {
10037
10217
  }
10038
- setJson4(res, 200, {
10039
- data: getOpenCodeZenFreeModelIds([]),
10040
- exclusive: true,
10041
- source: "opencode-zen"
10042
- });
10218
+ setJson4(res, 200, { data: ["big-pickle", "minimax-m2.5-free", "nemotron-3-super-free", "trinity-large-preview-free"], exclusive: true, source: "opencode-zen" });
10043
10219
  return;
10044
10220
  }
10045
10221
  if (fmState.provider === "custom" && fmState.customBaseUrl) {
@@ -10342,7 +10518,7 @@ function createCodexBridgeMiddleware(options = {}) {
10342
10518
  if (currentBranch && !branchActivityByName.has(currentBranch)) {
10343
10519
  branchActivityByName.set(currentBranch, { timestamp: Number.MAX_SAFE_INTEGER, isRemote: false });
10344
10520
  }
10345
- const options2 = Array.from(branchActivityByName.entries()).map(([value, metadata]) => ({
10521
+ const options = Array.from(branchActivityByName.entries()).map(([value, metadata]) => ({
10346
10522
  value,
10347
10523
  label: value,
10348
10524
  isCurrent: value === currentBranch,
@@ -10356,7 +10532,7 @@ function createCodexBridgeMiddleware(options = {}) {
10356
10532
  setJson4(res, 200, {
10357
10533
  data: {
10358
10534
  ...state,
10359
- options: options2
10535
+ options
10360
10536
  }
10361
10537
  });
10362
10538
  } catch (error) {
@@ -10602,6 +10778,18 @@ function createCodexBridgeMiddleware(options = {}) {
10602
10778
  setJson4(res, 200, { data: { path: normalizedPath } });
10603
10779
  return;
10604
10780
  }
10781
+ if (req.method === "POST" && url.pathname === "/codex-api/github-clone") {
10782
+ const payload = asRecord5(await readJsonBody2(req));
10783
+ const repoUrl = typeof payload?.url === "string" ? payload.url.trim() : "";
10784
+ const basePath = typeof payload?.basePath === "string" ? payload.basePath.trim() : "";
10785
+ try {
10786
+ const clonedPath = await cloneGithubRepositoryIntoBase(repoUrl, basePath);
10787
+ setJson4(res, 200, { data: { path: clonedPath } });
10788
+ } catch (error) {
10789
+ setJson4(res, 400, { error: error instanceof Error ? error.message : "Failed to clone GitHub repository" });
10790
+ }
10791
+ return;
10792
+ }
10605
10793
  if (req.method === "POST" && url.pathname === "/codex-api/projectless-thread-cwd") {
10606
10794
  const payload = asRecord5(await readJsonBody2(req));
10607
10795
  const prompt = typeof payload?.prompt === "string" ? payload.prompt : null;
@@ -10913,7 +11101,6 @@ data: ${JSON.stringify({ ok: true })}
10913
11101
  backendQueueProcessor.dispose();
10914
11102
  appServer.dispose();
10915
11103
  };
10916
- middleware.startBackgroundServices = startBackgroundServices;
10917
11104
  middleware.subscribeNotifications = (listener) => {
10918
11105
  const unsubscribeAppServer = appServer.onNotification((notification) => {
10919
11106
  listener({
@@ -11615,9 +11802,7 @@ function readWildcardPathParam(value) {
11615
11802
  }
11616
11803
  function createServer(options = {}) {
11617
11804
  const app = express();
11618
- const bridge = createCodexBridgeMiddleware({
11619
- startBackgroundServices: options.deferBridgeBackgroundServices !== true
11620
- });
11805
+ const bridge = createCodexBridgeMiddleware();
11621
11806
  const authSession = options.password ? createAuthSession(options.password) : null;
11622
11807
  if (authSession) {
11623
11808
  app.use(authSession.middleware);
@@ -11763,7 +11948,6 @@ function createServer(options = {}) {
11763
11948
  return {
11764
11949
  app,
11765
11950
  dispose: () => bridge.dispose(),
11766
- startBridgeBackgroundServices: () => bridge.startBackgroundServices(),
11767
11951
  attachWebSocket: (server) => {
11768
11952
  const wss = new WebSocketServer({ noServer: true });
11769
11953
  server.on("upgrade", (req, socket, head) => {
@@ -12243,15 +12427,10 @@ async function startServer(options) {
12243
12427
  const passwordResolution = resolvePassword(options.password);
12244
12428
  const password = passwordResolution.password;
12245
12429
  const generatedPasswordPath = password && passwordResolution.generated ? await persistGeneratedPassword(password) : null;
12246
- const { app, dispose, attachWebSocket, startBridgeBackgroundServices } = createServer({
12247
- password,
12248
- deferBridgeBackgroundServices: true
12249
- });
12430
+ const { app, dispose, attachWebSocket } = createServer({ password });
12250
12431
  const server = createServer2(app);
12251
12432
  attachWebSocket(server);
12252
12433
  const port = await listenWithFallback(server, requestedPort);
12253
- process.env.CODEXUI_SERVER_PORT = String(port);
12254
- startBridgeBackgroundServices();
12255
12434
  let tunnelChild = null;
12256
12435
  let tunnelUrl = null;
12257
12436
  if (options.tunnel) {