codexuse-cli 2.5.4 → 2.5.5

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.js CHANGED
@@ -743,6 +743,7 @@ var REFRESH_TOKEN_REDEEMED_SNIPPET = "refresh token was already used";
743
743
  var REFRESH_TOKEN_REDEEMED_REASON = "Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.";
744
744
  var AUTH_BACKUP_MISSING_MARKER = "__codexuse_missing_auth__";
745
745
  var DEFAULT_WORKSPACE_ID = "__default__";
746
+ var globalAuthSwapLock = Promise.resolve();
746
747
  var ProfileManager = class {
747
748
  constructor() {
748
749
  const homeDir = process.env.HOME || process.env.USERPROFILE || "";
@@ -751,7 +752,6 @@ var ProfileManager = class {
751
752
  this.activeAuth = (0, import_path2.join)(this.codexDir, "auth.json");
752
753
  this.activeAuthBackup = `${this.activeAuth}.swap`;
753
754
  this.lastActiveAuthErrorSignature = null;
754
- this.authSwapLock = Promise.resolve();
755
755
  }
756
756
  computeExpiryIso(data) {
757
757
  if (!data) {
@@ -1195,8 +1195,8 @@ var ProfileManager = class {
1195
1195
  await this.recoverActiveAuthBackup();
1196
1196
  }
1197
1197
  enqueueAuthSwap(task) {
1198
- const run = this.authSwapLock.then(task, task);
1199
- this.authSwapLock = run.then(
1198
+ const run = globalAuthSwapLock.then(task, task);
1199
+ globalAuthSwapLock = run.then(
1200
1200
  () => void 0,
1201
1201
  () => void 0
1202
1202
  );
@@ -2266,17 +2266,51 @@ async function getLicenseSecret() {
2266
2266
  return secret;
2267
2267
  }
2268
2268
 
2269
+ // ../../lib/offer-config.ts
2270
+ function getActiveOffer() {
2271
+ const basePriceUsd = 39;
2272
+ const discountPercent = 50;
2273
+ const salePriceUsd = basePriceUsd * (100 - discountPercent) / 100;
2274
+ return {
2275
+ basePriceUsd,
2276
+ basePriceDisplay: `$${basePriceUsd}`,
2277
+ couponCode: "SPRING50",
2278
+ discountPercent,
2279
+ salePriceUsd,
2280
+ salePriceDisplay: `$${salePriceUsd.toFixed(2)}`,
2281
+ isActive: true,
2282
+ campaign: "spring-2026",
2283
+ productPermalink: "codex-use",
2284
+ checkoutBaseUrl: "https://hweihwang.gumroad.com/l/codex-use"
2285
+ };
2286
+ }
2287
+ function buildCheckoutUrl(offer2, utm) {
2288
+ const base = offer2.checkoutBaseUrl;
2289
+ const withCoupon = offer2.isActive && offer2.couponCode ? `${base}/${offer2.couponCode}` : base;
2290
+ if (!utm) {
2291
+ return withCoupon;
2292
+ }
2293
+ const params = new URLSearchParams();
2294
+ params.set("utm_source", utm.source);
2295
+ params.set("utm_medium", utm.medium);
2296
+ if (utm.campaign) {
2297
+ params.set("utm_campaign", utm.campaign);
2298
+ } else if (offer2.campaign) {
2299
+ params.set("utm_campaign", offer2.campaign);
2300
+ }
2301
+ return `${withCoupon}?${params.toString()}`;
2302
+ }
2303
+
2269
2304
  // ../../lib/license-service.ts
2270
- var PRODUCT_PERMALINK = "codex-use";
2305
+ var offer = getActiveOffer();
2306
+ var PRODUCT_PERMALINK = offer.productPermalink;
2271
2307
  var PRODUCT_ID = "3_CcyVEXt2FOMiEpPx8xzw==";
2272
- var BASE_PRICE = 19;
2273
- var BASE_PRICE_DISPLAY = "$19";
2274
- var PROMO_CODE = "NOELNEWYEAR50";
2275
- var PROMO_PERCENT = 50;
2276
- var PROMO_ACTIVE = true;
2277
- var PROMO_PRICE_DISPLAY = `$${(BASE_PRICE * (100 - PROMO_PERCENT) / 100).toFixed(2)}`;
2278
- var PRODUCT_URL = PROMO_ACTIVE ? `https://hweihwang.gumroad.com/l/${PRODUCT_PERMALINK}/${PROMO_CODE}` : "https://hweihwang.gumroad.com/l/codex-use";
2279
- var LICENSE_PRICE_DISPLAY = PROMO_ACTIVE ? PROMO_PRICE_DISPLAY : BASE_PRICE_DISPLAY;
2308
+ var PRODUCT_URL = buildCheckoutUrl(offer);
2309
+ var LICENSE_PRICE_DISPLAY = offer.isActive ? offer.salePriceDisplay : offer.basePriceDisplay;
2310
+ var BASE_PRICE_DISPLAY = offer.basePriceDisplay;
2311
+ var PROMO_CODE = offer.couponCode;
2312
+ var PROMO_PERCENT = offer.discountPercent;
2313
+ var PROMO_ACTIVE = offer.isActive;
2280
2314
  var LICENSE_MAX_USES = 5;
2281
2315
  var FREE_PROFILE_LIMIT = 2;
2282
2316
  var LICENSE_REFRESH_INTERVAL_MS = 5 * 60 * 1e3;
@@ -2438,7 +2472,8 @@ function toLicenseStatus(stored, overrides = {}) {
2438
2472
  priceDisplay: LICENSE_PRICE_DISPLAY,
2439
2473
  basePriceDisplay: BASE_PRICE_DISPLAY,
2440
2474
  promoCode: PROMO_ACTIVE ? PROMO_CODE : null,
2441
- promoPercent: PROMO_ACTIVE ? PROMO_PERCENT : null
2475
+ promoPercent: PROMO_ACTIVE ? PROMO_PERCENT : null,
2476
+ maxDevices: LICENSE_MAX_USES
2442
2477
  };
2443
2478
  return { ...base, ...overrides };
2444
2479
  }
@@ -2607,6 +2642,25 @@ var LicenseService = class {
2607
2642
  }
2608
2643
  };
2609
2644
  var licenseService = new LicenseService();
2645
+ function isExplicitInvalidation(message) {
2646
+ if (!message) {
2647
+ return false;
2648
+ }
2649
+ const normalized = message.toLowerCase();
2650
+ return normalized.includes("revoked") || normalized.includes("refunded") || normalized.includes("invalid") || normalized.includes("no longer active");
2651
+ }
2652
+ function shouldEnforceProfileLimit(status) {
2653
+ if (status.isPro) {
2654
+ return false;
2655
+ }
2656
+ if (status.state === "grace" || status.state === "verifying" || status.state === "error") {
2657
+ return false;
2658
+ }
2659
+ if (isExplicitInvalidation(status.error)) {
2660
+ return true;
2661
+ }
2662
+ return status.state === "inactive";
2663
+ }
2610
2664
 
2611
2665
  // ../../lib/cloud-sync-service.ts
2612
2666
  var import_node_fs4 = require("fs");
@@ -2780,6 +2834,7 @@ var import_node_path4 = __toESM(require("path"));
2780
2834
  var import_node_fs3 = require("fs");
2781
2835
  var import_node_path3 = __toESM(require("path"));
2782
2836
  var STABLE_CHANNEL = "stable";
2837
+ var ENV_HINTS = ["CODEX_BINARY", "CODEX_CLI_PATH", "CODEX_PATH"];
2783
2838
  var cachedStatus = null;
2784
2839
  function fileExists(candidate) {
2785
2840
  if (!candidate) {
@@ -2819,16 +2874,68 @@ function resolveBundledCodexBinary() {
2819
2874
  }
2820
2875
  return null;
2821
2876
  }
2822
- function buildUnavailableStatus() {
2877
+ function isElectronRuntime() {
2878
+ return typeof process.versions?.electron === "string" && process.versions.electron.length > 0;
2879
+ }
2880
+ function resolveCodexFromEnv() {
2881
+ for (const key of ENV_HINTS) {
2882
+ const value = process.env[key];
2883
+ if (!value) {
2884
+ continue;
2885
+ }
2886
+ const resolved = fileExists(value);
2887
+ if (resolved) {
2888
+ return resolved;
2889
+ }
2890
+ }
2891
+ return null;
2892
+ }
2893
+ function resolveCodexFromNodeModules() {
2894
+ const packageSegments = ["@openai", "codex", "bin"];
2895
+ const fileNames = ["codex.js", "codex"];
2896
+ let current = import_node_path3.default.resolve(__dirname);
2897
+ let last = "";
2898
+ while (current !== last) {
2899
+ for (const fileName of fileNames) {
2900
+ const candidate = fileExists(
2901
+ import_node_path3.default.join(current, "node_modules", ...packageSegments, fileName)
2902
+ );
2903
+ if (candidate) {
2904
+ return candidate;
2905
+ }
2906
+ }
2907
+ last = current;
2908
+ current = import_node_path3.default.dirname(current);
2909
+ }
2910
+ return null;
2911
+ }
2912
+ function resolveCodexFromPath() {
2913
+ const pathValue = process.env.PATH ?? "";
2914
+ if (!pathValue) {
2915
+ return null;
2916
+ }
2917
+ const entries = pathValue.split(import_node_path3.default.delimiter).map((entry) => entry.trim()).filter(Boolean);
2918
+ const names = process.platform === "win32" ? ["codex.exe", "codex.cmd", "codex.bat", "codex"] : ["codex"];
2919
+ for (const entry of entries) {
2920
+ for (const name of names) {
2921
+ const candidate = fileExists(import_node_path3.default.join(entry, name));
2922
+ if (candidate) {
2923
+ return candidate;
2924
+ }
2925
+ }
2926
+ }
2927
+ return null;
2928
+ }
2929
+ function buildUnavailableStatus(reason) {
2823
2930
  return {
2824
2931
  available: false,
2825
2932
  path: null,
2826
- reason: "Bundled Codex CLI is missing. Reinstall CodexUse to restore the CLI.",
2933
+ reason: reason ?? "Codex CLI not found. Install @openai/codex or set CODEX_BINARY.",
2827
2934
  source: null,
2828
2935
  channel: STABLE_CHANNEL
2829
2936
  };
2830
2937
  }
2831
- function evaluateCodexCliStatus() {
2938
+ function evaluateBundledOnlyStatus() {
2832
2939
  const resolvedPath = resolveBundledCodexBinary();
2833
2940
  if (resolvedPath) {
2834
2941
  return {
@@ -2839,8 +2946,55 @@ function evaluateCodexCliStatus() {
2839
2946
  channel: STABLE_CHANNEL
2840
2947
  };
2841
2948
  }
2949
+ return buildUnavailableStatus("Bundled Codex CLI is missing. Reinstall CodexUse.");
2950
+ }
2951
+ function evaluateExternalStatus() {
2952
+ const envPath = resolveCodexFromEnv();
2953
+ if (envPath) {
2954
+ return {
2955
+ available: true,
2956
+ path: envPath,
2957
+ reason: null,
2958
+ source: "env",
2959
+ channel: STABLE_CHANNEL
2960
+ };
2961
+ }
2962
+ const nodeModulesPath = resolveCodexFromNodeModules();
2963
+ if (nodeModulesPath) {
2964
+ return {
2965
+ available: true,
2966
+ path: nodeModulesPath,
2967
+ reason: null,
2968
+ source: "node_modules",
2969
+ channel: STABLE_CHANNEL
2970
+ };
2971
+ }
2972
+ const pathResolved = resolveCodexFromPath();
2973
+ if (pathResolved) {
2974
+ return {
2975
+ available: true,
2976
+ path: pathResolved,
2977
+ reason: null,
2978
+ source: "path",
2979
+ channel: STABLE_CHANNEL
2980
+ };
2981
+ }
2842
2982
  return buildUnavailableStatus();
2843
2983
  }
2984
+ function evaluateCodexCliStatus() {
2985
+ const bundledStatus = evaluateBundledOnlyStatus();
2986
+ if (bundledStatus.available) {
2987
+ return bundledStatus;
2988
+ }
2989
+ const externalStatus = evaluateExternalStatus();
2990
+ if (externalStatus.available) {
2991
+ return externalStatus;
2992
+ }
2993
+ if (isElectronRuntime()) {
2994
+ return bundledStatus;
2995
+ }
2996
+ return externalStatus;
2997
+ }
2844
2998
  async function refreshCodexStatus() {
2845
2999
  cachedStatus = evaluateCodexCliStatus();
2846
3000
  return { ...cachedStatus };
@@ -3950,6 +4104,7 @@ async function pullCloudSync() {
3950
4104
  }
3951
4105
 
3952
4106
  // ../../lib/license-guard.ts
4107
+ var PROJECT_LIMIT_MESSAGE = "CodexUse Free supports up to 2 projects. Upgrade to CodexUse Pro for unlimited projects.";
3953
4108
  async function assertProfileCreationAllowed(profileManager) {
3954
4109
  const manager = profileManager ?? new ProfileManager();
3955
4110
  await manager.initialize();
@@ -3960,13 +4115,25 @@ async function assertProfileCreationAllowed(profileManager) {
3960
4115
  throw new Error("CodexUse Free supports up to 2 profiles. Upgrade to CodexUse Pro for unlimited profiles.");
3961
4116
  }
3962
4117
  }
4118
+ function countMainProjects(workspaces) {
4119
+ return workspaces.filter((workspace) => (workspace.kind ?? "main") !== "worktree").length;
4120
+ }
4121
+ async function assertProjectCreationAllowed(workspaces) {
4122
+ const license = await licenseService.getStatus();
4123
+ if (!shouldEnforceProfileLimit(license)) {
4124
+ return;
4125
+ }
4126
+ const mainProjectCount = countMainProjects(workspaces);
4127
+ if (mainProjectCount >= 2) {
4128
+ throw new Error(PROJECT_LIMIT_MESSAGE);
4129
+ }
4130
+ }
3963
4131
 
3964
4132
  // src/codex-cli.ts
3965
4133
  var import_node_child_process2 = require("child_process");
3966
4134
  var import_node_fs5 = require("fs");
3967
4135
  var import_node_path7 = __toESM(require("path"));
3968
- var ENV_HINTS = ["CODEX_BINARY", "CODEX_CLI_PATH", "CODEX_PATH"];
3969
- var NODE_MODULE_CANDIDATES = [["@openai", "codex"]];
4136
+ var ENV_HINTS2 = ["CODEX_BINARY", "CODEX_CLI_PATH", "CODEX_PATH"];
3970
4137
  function fileExists2(candidate) {
3971
4138
  if (!candidate) return null;
3972
4139
  const resolved = import_node_path7.default.resolve(candidate);
@@ -3979,7 +4146,7 @@ function fileExists2(candidate) {
3979
4146
  return null;
3980
4147
  }
3981
4148
  function resolveFromEnv() {
3982
- for (const key of ENV_HINTS) {
4149
+ for (const key of ENV_HINTS2) {
3983
4150
  const value = process.env[key];
3984
4151
  if (!value) continue;
3985
4152
  const resolved = fileExists2(value);
@@ -3987,37 +4154,6 @@ function resolveFromEnv() {
3987
4154
  }
3988
4155
  return null;
3989
4156
  }
3990
- function resolveFromAppResources() {
3991
- const roots = [
3992
- import_node_path7.default.resolve(__dirname, ".."),
3993
- import_node_path7.default.resolve(__dirname, "..", "..")
3994
- ];
3995
- for (const root of roots) {
3996
- const base = import_node_path7.default.join(root, "app.asar.unpacked", "node_modules");
3997
- for (const segments of NODE_MODULE_CANDIDATES) {
3998
- const candidate = fileExists2(import_node_path7.default.join(base, ...segments, "bin", "codex"));
3999
- if (candidate) return candidate;
4000
- const jsCandidate = fileExists2(import_node_path7.default.join(base, ...segments, "bin", "codex.js"));
4001
- if (jsCandidate) return jsCandidate;
4002
- }
4003
- }
4004
- return null;
4005
- }
4006
- function resolveFromNodeModules() {
4007
- let current = import_node_path7.default.resolve(__dirname, "..");
4008
- let last = "";
4009
- while (current !== last) {
4010
- for (const segments of NODE_MODULE_CANDIDATES) {
4011
- const candidate = fileExists2(import_node_path7.default.join(current, "node_modules", ...segments, "bin", "codex"));
4012
- if (candidate) return candidate;
4013
- const jsCandidate = fileExists2(import_node_path7.default.join(current, "node_modules", ...segments, "bin", "codex.js"));
4014
- if (jsCandidate) return jsCandidate;
4015
- }
4016
- last = current;
4017
- current = import_node_path7.default.dirname(current);
4018
- }
4019
- return null;
4020
- }
4021
4157
  function resolveFromPath() {
4022
4158
  const pathValue = process.env.PATH ?? "";
4023
4159
  const entries = pathValue.split(import_node_path7.default.delimiter).filter(Boolean);
@@ -4031,7 +4167,7 @@ function resolveFromPath() {
4031
4167
  return null;
4032
4168
  }
4033
4169
  function resolveCodexBinary() {
4034
- return resolveFromEnv() || resolveFromAppResources() || resolveFromNodeModules() || resolveFromPath();
4170
+ return resolveFromEnv() || resolveFromPath();
4035
4171
  }
4036
4172
  function requireCodexBinary(context) {
4037
4173
  const resolved = resolveCodexBinary();
@@ -4093,6 +4229,98 @@ var import_node_fs6 = require("fs");
4093
4229
  var import_node_os2 = __toESM(require("os"));
4094
4230
  var import_node_path8 = __toESM(require("path"));
4095
4231
  var RPC_TIMEOUT_MS = 1e4;
4232
+ var MAX_STDERR_CAPTURE_CHARS = 32768;
4233
+ var REFRESH_TOKEN_REDEEMED_SNIPPET2 = "refresh token was already used";
4234
+ function parseTimestamp2(value) {
4235
+ if (typeof value !== "string" || value.trim().length === 0) {
4236
+ return null;
4237
+ }
4238
+ const parsed = Date.parse(value);
4239
+ return Number.isNaN(parsed) ? null : parsed;
4240
+ }
4241
+ function decodeJwtPayload(token) {
4242
+ if (typeof token !== "string" || token.trim().length === 0) {
4243
+ return null;
4244
+ }
4245
+ const segments = token.split(".");
4246
+ if (segments.length < 2) {
4247
+ return null;
4248
+ }
4249
+ try {
4250
+ const payloadRaw = Buffer.from(segments[1], "base64url").toString("utf8");
4251
+ const payload = JSON.parse(payloadRaw);
4252
+ return payload && typeof payload === "object" ? payload : null;
4253
+ } catch {
4254
+ return null;
4255
+ }
4256
+ }
4257
+ function extractJwtIssuedAtMs(token) {
4258
+ const payload = decodeJwtPayload(token);
4259
+ if (!payload) {
4260
+ return null;
4261
+ }
4262
+ const issuedAt = payload["iat"];
4263
+ if (typeof issuedAt !== "number" || !Number.isFinite(issuedAt)) {
4264
+ return null;
4265
+ }
4266
+ return issuedAt * 1e3;
4267
+ }
4268
+ function parseAuthRecord(content) {
4269
+ try {
4270
+ const parsed = JSON.parse(content);
4271
+ return parsed && typeof parsed === "object" ? parsed : null;
4272
+ } catch {
4273
+ return null;
4274
+ }
4275
+ }
4276
+ function extractAuthRecencyMs(content) {
4277
+ const parsed = parseAuthRecord(content);
4278
+ if (!parsed) {
4279
+ return null;
4280
+ }
4281
+ const rootLastRefresh = parseTimestamp2(parsed.last_refresh);
4282
+ const nestedLastRefresh = parseTimestamp2(parsed.tokens?.last_refresh);
4283
+ const rootAccessIssuedAt = extractJwtIssuedAtMs(parsed.access_token);
4284
+ const nestedAccessIssuedAt = extractJwtIssuedAtMs(parsed.tokens?.access_token);
4285
+ const candidates = [rootLastRefresh, nestedLastRefresh, rootAccessIssuedAt, nestedAccessIssuedAt].filter((value) => typeof value === "number" && Number.isFinite(value));
4286
+ if (candidates.length === 0) {
4287
+ return null;
4288
+ }
4289
+ return Math.max(...candidates);
4290
+ }
4291
+ function shouldWriteBackAuth(initialSourceAuth, currentSourceAuth, updatedAuth) {
4292
+ if (updatedAuth.trim().length === 0) {
4293
+ return false;
4294
+ }
4295
+ if (!currentSourceAuth) {
4296
+ return true;
4297
+ }
4298
+ if (currentSourceAuth === updatedAuth) {
4299
+ return false;
4300
+ }
4301
+ if (currentSourceAuth === initialSourceAuth) {
4302
+ return true;
4303
+ }
4304
+ const currentRecency = extractAuthRecencyMs(currentSourceAuth);
4305
+ const updatedRecency = extractAuthRecencyMs(updatedAuth);
4306
+ if (typeof updatedRecency === "number" && typeof currentRecency === "number") {
4307
+ return updatedRecency > currentRecency;
4308
+ }
4309
+ if (typeof updatedRecency === "number" && typeof currentRecency !== "number") {
4310
+ return true;
4311
+ }
4312
+ return false;
4313
+ }
4314
+ function inferRefreshFailureHint(stderrOutput) {
4315
+ if (!stderrOutput) {
4316
+ return null;
4317
+ }
4318
+ const normalized = stderrOutput.toLowerCase();
4319
+ if (normalized.includes(REFRESH_TOKEN_REDEEMED_SNIPPET2)) {
4320
+ return REFRESH_TOKEN_REDEEMED_SNIPPET2;
4321
+ }
4322
+ return null;
4323
+ }
4096
4324
  async function sendPayload(child, payload) {
4097
4325
  child.stdin?.write(JSON.stringify(payload));
4098
4326
  child.stdin?.write("\n");
@@ -4198,17 +4426,18 @@ async function fetchRateLimitsViaRpc(envOverride, options = {}) {
4198
4426
  const binaryPath = options.codexPath ?? await requireCodexCli();
4199
4427
  const tempHome = await import_node_fs6.promises.mkdtemp(import_node_path8.default.join(import_node_os2.default.tmpdir(), "codex-rpc-"));
4200
4428
  const tempAuthPath = import_node_path8.default.join(tempHome, "auth.json");
4429
+ let initialSourceAuth = null;
4201
4430
  const sourceAuthPath = options.authPath ?? (envOverride?.CODEX_HOME ? import_node_path8.default.join(envOverride.CODEX_HOME, "auth.json") : import_node_path8.default.join(
4202
4431
  envOverride?.HOME ?? process.env.HOME ?? process.env.USERPROFILE ?? import_node_os2.default.homedir(),
4203
4432
  ".codex",
4204
4433
  "auth.json"
4205
4434
  ));
4206
4435
  try {
4207
- const authContent = await import_node_fs6.promises.readFile(sourceAuthPath, "utf8").catch(() => null);
4208
- if (!authContent) {
4436
+ initialSourceAuth = await import_node_fs6.promises.readFile(sourceAuthPath, "utf8").catch(() => null);
4437
+ if (!initialSourceAuth) {
4209
4438
  return null;
4210
4439
  }
4211
- await import_node_fs6.promises.writeFile(tempAuthPath, authContent, "utf8");
4440
+ await import_node_fs6.promises.writeFile(tempAuthPath, initialSourceAuth, "utf8");
4212
4441
  } catch {
4213
4442
  await import_node_fs6.promises.rm(tempHome, { recursive: true, force: true }).catch(() => {
4214
4443
  });
@@ -4230,6 +4459,15 @@ async function fetchRateLimitsViaRpc(envOverride, options = {}) {
4230
4459
  input: child.stdout,
4231
4460
  crlfDelay: Infinity
4232
4461
  });
4462
+ let stderrOutput = "";
4463
+ child.stderr?.on("data", (chunk) => {
4464
+ if (stderrOutput.length >= MAX_STDERR_CAPTURE_CHARS) {
4465
+ return;
4466
+ }
4467
+ const text = chunk.toString("utf8");
4468
+ const remaining = MAX_STDERR_CAPTURE_CHARS - stderrOutput.length;
4469
+ stderrOutput += text.slice(0, Math.max(0, remaining));
4470
+ });
4233
4471
  try {
4234
4472
  await sendPayload(child, {
4235
4473
  id: 1,
@@ -4243,6 +4481,14 @@ async function fetchRateLimitsViaRpc(envOverride, options = {}) {
4243
4481
  await sendPayload(child, { method: "initialized", params: {} });
4244
4482
  await sendPayload(child, { id: 2, method: "account/rateLimits/read", params: {} });
4245
4483
  const message = await readRpcResponseById(rl, 2, RPC_TIMEOUT_MS);
4484
+ if (message.error) {
4485
+ const base = formatRpcError("account/rateLimits/read", message.error);
4486
+ const hint = inferRefreshFailureHint(stderrOutput);
4487
+ if (hint && !base.toLowerCase().includes(hint)) {
4488
+ throw new Error(`${base}; ${hint}`);
4489
+ }
4490
+ throw new Error(base);
4491
+ }
4246
4492
  return parseRateLimitSnapshotFromRpcMessage(message);
4247
4493
  } finally {
4248
4494
  child.kill();
@@ -4250,7 +4496,16 @@ async function fetchRateLimitsViaRpc(envOverride, options = {}) {
4250
4496
  try {
4251
4497
  const updatedAuth = await import_node_fs6.promises.readFile(tempAuthPath, "utf8");
4252
4498
  if (updatedAuth.trim().length > 0) {
4253
- await import_node_fs6.promises.writeFile(sourceAuthPath, updatedAuth, "utf8");
4499
+ const currentSourceAuth = await import_node_fs6.promises.readFile(sourceAuthPath, "utf8").catch(() => null);
4500
+ if (shouldWriteBackAuth(initialSourceAuth, currentSourceAuth, updatedAuth)) {
4501
+ await import_node_fs6.promises.writeFile(sourceAuthPath, updatedAuth, "utf8");
4502
+ } else if (currentSourceAuth && currentSourceAuth !== updatedAuth && currentSourceAuth !== initialSourceAuth) {
4503
+ logWarn("Skipped stale auth sync-back after rate-limit probe; source auth changed in flight.", {
4504
+ sourceAuthPath,
4505
+ currentRecencyMs: extractAuthRecencyMs(currentSourceAuth),
4506
+ updatedRecencyMs: extractAuthRecencyMs(updatedAuth)
4507
+ });
4508
+ }
4254
4509
  }
4255
4510
  } catch {
4256
4511
  }
@@ -5035,110 +5290,3936 @@ async function importLegacyLocalStorageOnce(payload) {
5035
5290
  return { completed: true, importedKeys: consumedKeys, skippedKeys };
5036
5291
  }
5037
5292
 
5038
- // src/index.ts
5039
- var VERSION = true ? "2.5.4" : "0.0.0";
5040
- var cliStorageReadyPromise = null;
5041
- async function ensureCliStorageReady() {
5042
- if (cliStorageReadyPromise) {
5043
- return cliStorageReadyPromise;
5044
- }
5045
- cliStorageReadyPromise = (async () => {
5046
- await initializeAppState(getUserDataDir());
5047
- const migrated = await runStorageMigrationV1();
5048
- if (migrated.migration.status === "pending_local_storage") {
5049
- await importLegacyLocalStorageOnce({});
5050
- }
5051
- const state = await getAppState();
5052
- if (state.migration.status !== "complete") {
5053
- throw new Error(
5054
- `Storage migration is not complete (status: ${state.migration.status}). CLI cannot continue until migration succeeds.`
5055
- );
5056
- }
5057
- })().catch((error) => {
5058
- cliStorageReadyPromise = null;
5059
- throw error;
5060
- });
5061
- return cliStorageReadyPromise;
5062
- }
5063
- function printHelp() {
5064
- console.log(`CodexUse CLI v${VERSION}
5065
-
5066
- Usage:
5067
- codexuse profile list [--no-usage] [--compact]
5068
- codexuse profile current
5069
- codexuse profile add <name> [--skip-login] [--device-auth] [--login=browser|device]
5070
- codexuse profile refresh <name> [--skip-login] [--device-auth] [--login=browser|device]
5071
- codexuse profile switch <name>
5072
- codexuse profile autoroll [--threshold=50-100] [--dry-run] [--watch] [--interval=seconds]
5073
- codexuse profile delete <name>
5074
- codexuse profile rename <old> <new>
5075
-
5076
- codexuse license status [--refresh]
5077
- codexuse license activate <license-key>
5293
+ // src/daemon.ts
5294
+ var import_node_path14 = __toESM(require("path"));
5078
5295
 
5079
- codexuse sync status
5080
- codexuse sync pull
5081
- codexuse sync push
5296
+ // ../../lib/codex-app-server.ts
5297
+ var import_node_child_process4 = require("child_process");
5298
+ var import_node_events = require("events");
5299
+ var import_node_readline2 = __toESM(require("readline"));
5300
+ var import_node_path10 = __toESM(require("path"));
5082
5301
 
5083
- Flags:
5084
- -h, --help Show help
5085
- -v, --version Show version
5086
- --no-usage Skip rate-limit usage fetch
5087
- --compact Names only
5088
- --device-auth Use device auth for Codex login
5089
- --login=MODE Login mode: browser | device
5090
- --threshold=NN Auto-roll switch threshold percent (50-100)
5091
- --watch Keep checking and auto-switch when threshold is reached
5092
- --interval=SEC Watch interval in seconds (default: 30)
5093
- --dry-run Print planned switch without changing active profile
5094
- `);
5095
- }
5096
- function hasFlag(args, flag) {
5097
- return args.includes(flag);
5302
+ // ../../lib/apps-list-rpc.ts
5303
+ var APPS_LIST_METHODS = /* @__PURE__ */ new Set(["app/list", "apps/list", "apps_list"]);
5304
+ function isAppsListMethod(method) {
5305
+ return APPS_LIST_METHODS.has(method);
5098
5306
  }
5099
- function stripFlags(args) {
5100
- return args.filter((arg) => !arg.startsWith("-"));
5307
+ function buildAppsListPayload(params) {
5308
+ return {
5309
+ threadId: params?.threadId ?? null,
5310
+ cursor: params?.cursor ?? null,
5311
+ limit: params?.limit ?? null
5312
+ };
5101
5313
  }
5102
- function parseLoginMode(flags) {
5103
- if (flags.includes("--device-auth")) return "device";
5104
- const explicit = flags.find((flag) => flag.startsWith("--login="));
5105
- if (!explicit) return null;
5106
- const value = explicit.split("=")[1];
5107
- if (value === "browser" || value === "device") {
5108
- return value;
5314
+ function isMethodUnavailableError(error, method) {
5315
+ if (!error || typeof error !== "object") {
5316
+ return false;
5109
5317
  }
5110
- return null;
5111
- }
5112
- var DEFAULT_AUTOROLL_INTERVAL_SECONDS = 30;
5113
- var DEFAULT_AUTOROLL_THRESHOLD = 95;
5114
- function parseNumericFlag(flags, name) {
5115
- const explicit = flags.find((flag) => flag.startsWith(`${name}=`));
5116
- if (!explicit) {
5117
- return null;
5318
+ const code = typeof error.code === "number" ? error.code : null;
5319
+ if (code !== -32601 && code !== -32600) {
5320
+ return false;
5118
5321
  }
5119
- const raw = explicit.slice(`${name}=`.length);
5120
- const value = Number.parseFloat(raw);
5121
- return Number.isFinite(value) ? value : Number.NaN;
5122
- }
5123
- function parseIntegerFlag(flags, name) {
5124
- const explicit = flags.find((flag) => flag.startsWith(`${name}=`));
5125
- if (!explicit) {
5126
- return null;
5322
+ const message = typeof error.message === "string" ? error.message.toLowerCase() : "";
5323
+ if (!message) {
5324
+ return false;
5127
5325
  }
5128
- const raw = explicit.slice(`${name}=`.length);
5129
- const value = Number.parseInt(raw, 10);
5130
- return Number.isFinite(value) ? value : Number.NaN;
5326
+ const normalizedMethod = method.toLowerCase();
5327
+ if (message.includes("method not found") || message.includes("unknown method")) {
5328
+ return true;
5329
+ }
5330
+ return message.includes("unknown variant") && message.includes(normalizedMethod);
5131
5331
  }
5132
- function formatProfileLabel(name, displayName) {
5133
- if (displayName && displayName.trim() && displayName !== name) {
5134
- return `${displayName} (${name})`;
5332
+ function isAppsListThreadNotFoundError(error) {
5333
+ if (!error || typeof error !== "object") {
5334
+ return false;
5135
5335
  }
5136
- return name;
5336
+ const message = typeof error.message === "string" ? error.message.toLowerCase() : "";
5337
+ if (!message) {
5338
+ return false;
5339
+ }
5340
+ const mentionsThread = message.includes("thread");
5341
+ const mentionsNotFound = message.includes("not found") || message.includes("unknown thread");
5342
+ return mentionsThread && mentionsNotFound;
5137
5343
  }
5138
- function toTitleCase(value) {
5139
- return value.replace(/\w\S*/g, (word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase());
5344
+
5345
+ // ../../lib/cli-env.ts
5346
+ var import_path3 = __toESM(require("path"));
5347
+ function buildCliEnv(codexPath, overrides = {}) {
5348
+ const env = { ...process.env, ...overrides };
5349
+ const currentPath = env.PATH ?? "";
5350
+ const codexDir = import_path3.default.dirname(codexPath);
5351
+ const homeDir = process.env.HOME ?? "";
5352
+ const extraPathHints = (process.env.CODEX_PATH_HINTS ?? "").split(import_path3.default.delimiter).map((entry) => entry.trim()).filter(Boolean);
5353
+ const extras = [
5354
+ codexDir,
5355
+ "/usr/local/bin",
5356
+ "/opt/homebrew/bin",
5357
+ import_path3.default.join(homeDir, ".local", "bin"),
5358
+ import_path3.default.join(homeDir, ".fnm", "aliases", "default", "bin"),
5359
+ import_path3.default.join(homeDir, ".fnm", "current", "bin"),
5360
+ ...extraPathHints
5361
+ ].filter(Boolean);
5362
+ const segments = [
5363
+ ...extras,
5364
+ ...currentPath.split(import_path3.default.delimiter).filter(Boolean)
5365
+ ];
5366
+ env.PATH = Array.from(new Set(segments)).join(import_path3.default.delimiter);
5367
+ return env;
5140
5368
  }
5141
- function formatShortDate(value) {
5369
+
5370
+ // ../../lib/codex-app-server.ts
5371
+ function isRecord6(value) {
5372
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
5373
+ }
5374
+ function requestIdToToken(id) {
5375
+ return typeof id === "string" ? id : String(id);
5376
+ }
5377
+ function isAlreadyInitializedError(error) {
5378
+ if (!error || typeof error !== "object") return false;
5379
+ const message = error.message;
5380
+ return typeof message === "string" && message.toLowerCase().includes("already initialized");
5381
+ }
5382
+ function isThreadNotFoundError(error) {
5383
+ if (!error || typeof error !== "object") return false;
5384
+ const message = error.message;
5385
+ if (typeof message !== "string") return false;
5386
+ const lower = message.toLowerCase();
5387
+ return lower.includes("thread not found") || lower.includes("rollout");
5388
+ }
5389
+ function isMethodPayloadUnsupportedError(error) {
5390
+ if (!error || typeof error !== "object") {
5391
+ return false;
5392
+ }
5393
+ const code = error.code;
5394
+ if (typeof code !== "number" || code !== -32600) {
5395
+ return false;
5396
+ }
5397
+ const message = error.message;
5398
+ if (typeof message !== "string") {
5399
+ return false;
5400
+ }
5401
+ const normalized = message.toLowerCase();
5402
+ return normalized.includes("invalid request") || normalized.includes("missing field") || normalized.includes("unknown field");
5403
+ }
5404
+ function asString4(value) {
5405
+ return typeof value === "string" ? value : value != null ? String(value) : "";
5406
+ }
5407
+ function isInlineImageValue(value) {
5408
+ const normalized = value.trim().toLowerCase();
5409
+ return normalized.startsWith("data:") || normalized.startsWith("http://") || normalized.startsWith("https://");
5410
+ }
5411
+ function normalizeSendUserMessageItem(value) {
5412
+ if (!isRecord6(value)) {
5413
+ return null;
5414
+ }
5415
+ const type = asString4(value.type).trim();
5416
+ if (!type) {
5417
+ return null;
5418
+ }
5419
+ const data = isRecord6(value.data) ? value.data : {};
5420
+ if (type === "text") {
5421
+ const text = asString4(data.text ?? value.text);
5422
+ if (!text.trim()) {
5423
+ return null;
5424
+ }
5425
+ return { type: "text", data: { text } };
5426
+ }
5427
+ if (type === "image") {
5428
+ const imageUrl = asString4(
5429
+ data.image_url ?? data.imageUrl ?? data.url ?? value.image_url ?? value.imageUrl ?? value.url
5430
+ ).trim();
5431
+ if (!imageUrl) {
5432
+ return null;
5433
+ }
5434
+ return { type: "image", data: { image_url: imageUrl } };
5435
+ }
5436
+ if (type === "localImage") {
5437
+ const path17 = asString4(data.path ?? value.path).trim();
5438
+ if (!path17) {
5439
+ return null;
5440
+ }
5441
+ return { type: "localImage", data: { path: path17 } };
5442
+ }
5443
+ return value;
5444
+ }
5445
+ function buildSendUserMessageItemsFromLegacyParams(params) {
5446
+ const items = [];
5447
+ const text = asString4(params.text);
5448
+ if (text.trim()) {
5449
+ items.push({
5450
+ type: "text",
5451
+ data: { text }
5452
+ });
5453
+ }
5454
+ if (Array.isArray(params.images)) {
5455
+ for (const candidate of params.images) {
5456
+ const image = asString4(candidate).trim();
5457
+ if (!image) {
5458
+ continue;
5459
+ }
5460
+ if (isInlineImageValue(image)) {
5461
+ items.push({
5462
+ type: "image",
5463
+ data: { image_url: image }
5464
+ });
5465
+ continue;
5466
+ }
5467
+ items.push({
5468
+ type: "localImage",
5469
+ data: { path: image }
5470
+ });
5471
+ }
5472
+ }
5473
+ if (items.length === 0) {
5474
+ return null;
5475
+ }
5476
+ return items;
5477
+ }
5478
+ function normalizeSendUserMessageParams(params) {
5479
+ const normalized = { ...params };
5480
+ const conversationId = asString4(
5481
+ params.conversationId ?? params.conversation_id ?? params.threadId ?? params.thread_id ?? ""
5482
+ ).trim();
5483
+ if (conversationId && !asString4(params.conversationId).trim()) {
5484
+ normalized.conversationId = conversationId;
5485
+ }
5486
+ const items = Array.isArray(params.items) ? params.items.map((item) => normalizeSendUserMessageItem(item)).filter((item) => item !== null) : null;
5487
+ if (items && items.length > 0) {
5488
+ normalized.items = items;
5489
+ return normalized;
5490
+ }
5491
+ const legacyItems = buildSendUserMessageItemsFromLegacyParams(params);
5492
+ if (legacyItems) {
5493
+ normalized.items = legacyItems;
5494
+ }
5495
+ return normalized;
5496
+ }
5497
+ function normalizeTurnInputParams(params) {
5498
+ const normalized = { ...params };
5499
+ const threadId = asString4(
5500
+ params.threadId ?? params.thread_id ?? params.conversationId ?? params.conversation_id ?? ""
5501
+ ).trim();
5502
+ if (threadId && !asString4(params.threadId).trim()) {
5503
+ normalized.threadId = threadId;
5504
+ }
5505
+ const expectedTurnId = asString4(
5506
+ params.expectedTurnId ?? params.expected_turn_id ?? params.turnId ?? params.turn_id ?? ""
5507
+ ).trim();
5508
+ if (expectedTurnId && !asString4(params.expectedTurnId).trim()) {
5509
+ normalized.expectedTurnId = expectedTurnId;
5510
+ }
5511
+ const turnId = asString4(params.turnId ?? params.turn_id ?? expectedTurnId).trim();
5512
+ if (turnId && !asString4(params.turnId).trim()) {
5513
+ normalized.turnId = turnId;
5514
+ }
5515
+ const providedInput = Array.isArray(params.input) ? params.input.filter((entry) => isRecord6(entry)) : [];
5516
+ if (providedInput.length > 0) {
5517
+ normalized.input = providedInput;
5518
+ return normalized;
5519
+ }
5520
+ const input = [];
5521
+ const text = asString4(params.text);
5522
+ if (text.trim()) {
5523
+ input.push({ type: "text", text });
5524
+ }
5525
+ if (Array.isArray(params.images)) {
5526
+ for (const candidate of params.images) {
5527
+ const image = asString4(candidate).trim();
5528
+ if (!image || isInlineImageValue(image)) {
5529
+ continue;
5530
+ }
5531
+ input.push({
5532
+ type: "localImage",
5533
+ path: image
5534
+ });
5535
+ }
5536
+ }
5537
+ if (input.length > 0) {
5538
+ normalized.input = input;
5539
+ }
5540
+ return normalized;
5541
+ }
5542
+ function pickMethodKind(method, params) {
5543
+ const normalized = method.toLowerCase();
5544
+ if (normalized.includes("requestapproval")) return "approval";
5545
+ if (normalized.includes("requestuserinput")) return "user_input";
5546
+ if (isRecord6(params)) {
5547
+ if ("file_changes" in params || "fileChanges" in params || "grant_root" in params || "grantRoot" in params) {
5548
+ return "patch";
5549
+ }
5550
+ if ("command" in params || "parsed_cmd" in params || "parsedCmd" in params || "cwd" in params) {
5551
+ return "exec";
5552
+ }
5553
+ }
5554
+ if (normalized.includes("apply") && normalized.includes("patch")) return "patch";
5555
+ if (normalized.includes("file") && normalized.includes("change")) return "patch";
5556
+ if (normalized.includes("exec") || normalized.includes("command")) return "exec";
5557
+ return null;
5558
+ }
5559
+ var DEFAULT_REQUEST_TIMEOUT_MS = 45e3;
5560
+ var THREAD_LIST_REQUEST_TIMEOUT_MS = 18e4;
5561
+ var MAX_PARSE_BUFFER_CHARS = 2e6;
5562
+ var DEFAULT_THREAD_LIVE_WORKSPACE_ID = "__default__";
5563
+ var REQUEST_TIMEOUT_WARNING_COOLDOWN_MS = 3e4;
5564
+ function isLikelyRecoverableJsonParseError(error) {
5565
+ if (!(error instanceof Error)) {
5566
+ return false;
5567
+ }
5568
+ const message = error.message.toLowerCase();
5569
+ return message.includes("unterminated string") || message.includes("unexpected end of json input") || message.includes("bad control character");
5570
+ }
5571
+ function isIgnorableAppsListTransportStderr(message) {
5572
+ const normalized = message.toLowerCase();
5573
+ if (!normalized.includes("backend-api/wham/apps")) {
5574
+ return false;
5575
+ }
5576
+ return normalized.includes("worker quit with fatal") && normalized.includes("transport channel closed");
5577
+ }
5578
+ function isIgnorableMissingSkillsSymlinkStderr(message) {
5579
+ const normalized = message.toLowerCase();
5580
+ return normalized.includes("codex_core::skills::loader") && normalized.includes("failed to stat skills entry") && normalized.includes("(symlink)") && normalized.includes("no such file or directory");
5581
+ }
5582
+ function isRequestTimeoutErrorMessage(value) {
5583
+ if (typeof value !== "string") {
5584
+ return false;
5585
+ }
5586
+ return value.toLowerCase().includes("request timed out");
5587
+ }
5588
+ function getRequestTimeoutMs(method) {
5589
+ if (method === "thread/list") {
5590
+ return THREAD_LIST_REQUEST_TIMEOUT_MS;
5591
+ }
5592
+ return DEFAULT_REQUEST_TIMEOUT_MS;
5593
+ }
5594
+ function normalizeThreadLiveWorkspaceId(value) {
5595
+ const workspaceId = asString4(value).trim();
5596
+ return workspaceId || DEFAULT_THREAD_LIVE_WORKSPACE_ID;
5597
+ }
5598
+ function resolveThreadLiveWorkspacePayloadId(workspaceId) {
5599
+ return workspaceId === DEFAULT_THREAD_LIVE_WORKSPACE_ID ? null : workspaceId;
5600
+ }
5601
+ function threadLiveKey(workspaceId, threadId) {
5602
+ return `${workspaceId}:${threadId}`;
5603
+ }
5604
+ function buildThreadRealtimePayloads(threadId, workspaceId) {
5605
+ const payloads = [];
5606
+ const seen = /* @__PURE__ */ new Set();
5607
+ const pushPayload = (payload) => {
5608
+ const signature = JSON.stringify(payload);
5609
+ if (seen.has(signature)) {
5610
+ return;
5611
+ }
5612
+ seen.add(signature);
5613
+ payloads.push(payload);
5614
+ };
5615
+ pushPayload({ threadId });
5616
+ pushPayload({ conversationId: threadId });
5617
+ pushPayload({ threadId, conversationId: threadId });
5618
+ if (!workspaceId) {
5619
+ return payloads;
5620
+ }
5621
+ pushPayload({ threadId, workspaceId });
5622
+ pushPayload({ conversationId: threadId, workspaceId });
5623
+ pushPayload({ threadId, conversationId: threadId, workspaceId });
5624
+ pushPayload({ threadId, workspace_id: workspaceId });
5625
+ pushPayload({ conversationId: threadId, workspace_id: workspaceId });
5626
+ pushPayload({ threadId, conversationId: threadId, workspace_id: workspaceId });
5627
+ return payloads;
5628
+ }
5629
+ function escapeInvalidJsonStringChars(input) {
5630
+ let output = "";
5631
+ let inString = false;
5632
+ let escaped = false;
5633
+ for (let i = 0; i < input.length; i += 1) {
5634
+ const char = input[i];
5635
+ if (escaped) {
5636
+ output += char;
5637
+ escaped = false;
5638
+ continue;
5639
+ }
5640
+ if (char === "\\") {
5641
+ output += char;
5642
+ escaped = true;
5643
+ continue;
5644
+ }
5645
+ if (char === '"') {
5646
+ output += char;
5647
+ inString = !inString;
5648
+ continue;
5649
+ }
5650
+ if (inString) {
5651
+ if (char === "\n") {
5652
+ output += "\\n";
5653
+ continue;
5654
+ }
5655
+ if (char === "\r") {
5656
+ output += "\\r";
5657
+ continue;
5658
+ }
5659
+ if (char === " ") {
5660
+ output += "\\t";
5661
+ continue;
5662
+ }
5663
+ const code = char.charCodeAt(0);
5664
+ if (code >= 0 && code < 32) {
5665
+ output += `\\u${code.toString(16).padStart(4, "0")}`;
5666
+ continue;
5667
+ }
5668
+ }
5669
+ output += char;
5670
+ }
5671
+ return output;
5672
+ }
5673
+ var cachedRuntimeBinary = null;
5674
+ function resolveRuntimeBinary(env) {
5675
+ const override = env?.CODEX_NODE_RUNTIME?.trim() || env?.CODEX_NODE_BIN?.trim() || env?.CODEX_NODE?.trim() || process.env.CODEX_NODE_RUNTIME?.trim() || process.env.CODEX_NODE_BIN?.trim() || process.env.CODEX_NODE?.trim() || null;
5676
+ if (override) {
5677
+ return override;
5678
+ }
5679
+ if (cachedRuntimeBinary) {
5680
+ return cachedRuntimeBinary;
5681
+ }
5682
+ const runtimeEnv = { ...process.env, ...env };
5683
+ const homeDir = runtimeEnv.HOME ?? runtimeEnv.USERPROFILE ?? "";
5684
+ const pathHints = (runtimeEnv.CODEX_PATH_HINTS ?? process.env.CODEX_PATH_HINTS ?? "").split(import_node_path10.default.delimiter).map((entry) => entry.trim()).filter(Boolean);
5685
+ const extraPathEntries = [
5686
+ "/usr/local/bin",
5687
+ "/opt/homebrew/bin",
5688
+ import_node_path10.default.join(homeDir, ".local", "bin"),
5689
+ import_node_path10.default.join(homeDir, ".fnm", "aliases", "default", "bin"),
5690
+ import_node_path10.default.join(homeDir, ".fnm", "current", "bin"),
5691
+ ...pathHints
5692
+ ].filter(Boolean);
5693
+ const currentPath = runtimeEnv.PATH ?? "";
5694
+ runtimeEnv.PATH = Array.from(
5695
+ /* @__PURE__ */ new Set([...extraPathEntries, ...currentPath.split(import_node_path10.default.delimiter).filter(Boolean)])
5696
+ ).join(import_node_path10.default.delimiter);
5697
+ const candidates = Array.from(
5698
+ new Set(
5699
+ [
5700
+ runtimeEnv.CODEX_NODE_RUNTIME?.trim(),
5701
+ runtimeEnv.CODEX_NODE_BIN?.trim(),
5702
+ runtimeEnv.CODEX_NODE?.trim(),
5703
+ "/opt/homebrew/bin/node",
5704
+ "/usr/local/bin/node",
5705
+ "node"
5706
+ ].filter(Boolean)
5707
+ )
5708
+ );
5709
+ for (const candidate of candidates) {
5710
+ const probe = (0, import_node_child_process4.spawnSync)(candidate, ["-v"], { env: runtimeEnv, stdio: "ignore" });
5711
+ if (!probe.error && probe.status === 0) {
5712
+ cachedRuntimeBinary = candidate;
5713
+ return candidate;
5714
+ }
5715
+ }
5716
+ cachedRuntimeBinary = process.execPath;
5717
+ return process.execPath;
5718
+ }
5719
+ var CodexAppServer = class extends import_node_events.EventEmitter {
5720
+ constructor() {
5721
+ super();
5722
+ this.child = null;
5723
+ this.reader = null;
5724
+ this.nextRequestId = 1;
5725
+ this.pendingRequests = /* @__PURE__ */ new Map();
5726
+ this.pendingServerRequests = /* @__PURE__ */ new Map();
5727
+ this.timeoutRecoveryPromise = null;
5728
+ this.lastTimeoutWarningAtByMethod = /* @__PURE__ */ new Map();
5729
+ this.startPromise = null;
5730
+ this.initializePromise = null;
5731
+ this.initializeResponse = null;
5732
+ this.parseLineBuffer = null;
5733
+ this.threadLiveSubscriptionIdByKey = /* @__PURE__ */ new Map();
5734
+ this.threadLiveModeByKey = /* @__PURE__ */ new Map();
5735
+ this.setMaxListeners(50);
5736
+ }
5737
+ async start() {
5738
+ if (this.child) {
5739
+ return;
5740
+ }
5741
+ if (!this.startPromise) {
5742
+ this.startPromise = this.spawnProcess().finally(() => {
5743
+ this.startPromise = null;
5744
+ });
5745
+ }
5746
+ await this.startPromise;
5747
+ }
5748
+ async stop() {
5749
+ if (this.reader) {
5750
+ this.reader.close();
5751
+ this.reader = null;
5752
+ }
5753
+ if (this.child) {
5754
+ const child = this.child;
5755
+ this.child = null;
5756
+ try {
5757
+ child.kill();
5758
+ } catch {
5759
+ }
5760
+ }
5761
+ this.initializePromise = null;
5762
+ this.initializeResponse = null;
5763
+ this.parseLineBuffer = null;
5764
+ this.threadLiveSubscriptionIdByKey.clear();
5765
+ this.threadLiveModeByKey.clear();
5766
+ this.clearPendingRequests(new Error("codex app-server stopped"));
5767
+ }
5768
+ isRunning() {
5769
+ return Boolean(this.child && !this.child.killed);
5770
+ }
5771
+ async initialize(clientInfo) {
5772
+ if (this.initializeResponse) {
5773
+ return this.initializeResponse;
5774
+ }
5775
+ if (!this.initializePromise) {
5776
+ this.initializePromise = (async () => {
5777
+ await this.start();
5778
+ const payload = {
5779
+ clientInfo: clientInfo ?? {
5780
+ name: "codexuse",
5781
+ title: "CodexUse",
5782
+ version: "0.0.0"
5783
+ }
5784
+ };
5785
+ try {
5786
+ const response = await this.request("initialize", payload);
5787
+ await this.notify("initialized", {});
5788
+ this.initializeResponse = response;
5789
+ return response;
5790
+ } catch (error) {
5791
+ if (isAlreadyInitializedError(error)) {
5792
+ this.initializeResponse = this.initializeResponse ?? {};
5793
+ return this.initializeResponse;
5794
+ }
5795
+ throw error;
5796
+ }
5797
+ })().finally(() => {
5798
+ this.initializePromise = null;
5799
+ });
5800
+ }
5801
+ return this.initializePromise;
5802
+ }
5803
+ async getAccount(refreshToken = false) {
5804
+ return this.request("account/read", { refreshToken });
5805
+ }
5806
+ async getAccountRateLimits() {
5807
+ return this.request("account/rateLimits/read");
5808
+ }
5809
+ async loginAccount(params) {
5810
+ return this.request("account/login/start", params);
5811
+ }
5812
+ async cancelLoginAccount(loginId) {
5813
+ return this.request("account/login/cancel", { loginId });
5814
+ }
5815
+ async logoutAccount() {
5816
+ return this.request("account/logout");
5817
+ }
5818
+ async newConversation(params, overrides) {
5819
+ const merged = overrides ? { ...params, ...overrides } : params;
5820
+ return this.request("newConversation", merged);
5821
+ }
5822
+ async resumeConversation(params, overrides) {
5823
+ const merged = overrides ? { ...params, ...overrides } : params;
5824
+ return this.request("resumeConversation", merged);
5825
+ }
5826
+ async addConversationListenerWithResumeFallback(threadId, workspaceId = null) {
5827
+ const listenerParams = {
5828
+ conversationId: threadId
5829
+ };
5830
+ const resolvedWorkspaceId = workspaceId ? workspaceId.trim() : "";
5831
+ if (resolvedWorkspaceId) {
5832
+ listenerParams.workspaceId = resolvedWorkspaceId;
5833
+ listenerParams.workspace_id = resolvedWorkspaceId;
5834
+ }
5835
+ try {
5836
+ const response = await this.request("addConversationListener", listenerParams);
5837
+ return isRecord6(response) ? response : null;
5838
+ } catch (error) {
5839
+ if (!isThreadNotFoundError(error)) {
5840
+ throw error;
5841
+ }
5842
+ await this.threadResume(
5843
+ resolvedWorkspaceId ? { threadId, workspaceId: resolvedWorkspaceId, workspace_id: resolvedWorkspaceId } : { threadId }
5844
+ );
5845
+ const response = await this.request("addConversationListener", listenerParams);
5846
+ return isRecord6(response) ? response : null;
5847
+ }
5848
+ }
5849
+ async removeConversationListenerBySubscriptionId(subscriptionId) {
5850
+ if (!subscriptionId) {
5851
+ return;
5852
+ }
5853
+ await this.request("removeConversationListener", { subscriptionId });
5854
+ }
5855
+ trackThreadLiveSubscriptionId(key, subscriptionId) {
5856
+ if (!subscriptionId) {
5857
+ return;
5858
+ }
5859
+ const previousSubscriptionId = this.threadLiveSubscriptionIdByKey.get(key) ?? null;
5860
+ this.threadLiveSubscriptionIdByKey.set(key, subscriptionId);
5861
+ if (previousSubscriptionId && previousSubscriptionId !== subscriptionId) {
5862
+ void this.removeConversationListenerBySubscriptionId(
5863
+ previousSubscriptionId
5864
+ ).catch(() => {
5865
+ });
5866
+ }
5867
+ }
5868
+ async startThreadRealtime(threadId, workspaceId = null) {
5869
+ const payloads = buildThreadRealtimePayloads(threadId, workspaceId);
5870
+ let resumed = false;
5871
+ for (let index = 0; index < payloads.length; index += 1) {
5872
+ const payload = payloads[index];
5873
+ try {
5874
+ const response = await this.request("thread/realtime/start", payload);
5875
+ return isRecord6(response) ? response : {};
5876
+ } catch (error) {
5877
+ if (isMethodUnavailableError(error, "thread/realtime/start")) {
5878
+ return null;
5879
+ }
5880
+ if (isMethodPayloadUnsupportedError(error)) {
5881
+ continue;
5882
+ }
5883
+ if (!isThreadNotFoundError(error)) {
5884
+ throw error;
5885
+ }
5886
+ if (resumed) {
5887
+ continue;
5888
+ }
5889
+ resumed = true;
5890
+ await this.threadResume({ threadId });
5891
+ index = -1;
5892
+ }
5893
+ }
5894
+ return null;
5895
+ }
5896
+ async stopThreadRealtime(threadId, workspaceId = null) {
5897
+ const payloads = buildThreadRealtimePayloads(threadId, workspaceId);
5898
+ for (const payload of payloads) {
5899
+ try {
5900
+ await this.request("thread/realtime/stop", payload);
5901
+ return true;
5902
+ } catch (error) {
5903
+ if (isMethodUnavailableError(error, "thread/realtime/stop")) {
5904
+ return false;
5905
+ }
5906
+ if (isMethodPayloadUnsupportedError(error)) {
5907
+ continue;
5908
+ }
5909
+ if (isThreadNotFoundError(error)) {
5910
+ return true;
5911
+ }
5912
+ throw error;
5913
+ }
5914
+ }
5915
+ return false;
5916
+ }
5917
+ async addConversationListener(params) {
5918
+ const conversationId = asString4(
5919
+ params.conversationId ?? params.threadId ?? params.thread_id ?? ""
5920
+ ).trim();
5921
+ const workspaceId = asString4(
5922
+ params.workspaceId ?? params.workspace_id ?? ""
5923
+ ).trim();
5924
+ if (!conversationId) {
5925
+ return this.request("addConversationListener", params);
5926
+ }
5927
+ return this.addConversationListenerWithResumeFallback(
5928
+ conversationId,
5929
+ workspaceId || null
5930
+ );
5931
+ }
5932
+ async removeConversationListener(params) {
5933
+ return this.request("removeConversationListener", params);
5934
+ }
5935
+ async threadLiveSubscribe(params) {
5936
+ const workspaceId = normalizeThreadLiveWorkspaceId(
5937
+ params.workspaceId ?? params.workspace_id
5938
+ );
5939
+ const workspaceIdForPayload = resolveThreadLiveWorkspacePayloadId(workspaceId);
5940
+ const threadId = asString4(
5941
+ params.threadId ?? params.thread_id ?? params.conversationId
5942
+ ).trim();
5943
+ if (!threadId) {
5944
+ return {};
5945
+ }
5946
+ const key = threadLiveKey(workspaceId, threadId);
5947
+ const realtimeResponse = await this.startThreadRealtime(
5948
+ threadId,
5949
+ workspaceIdForPayload
5950
+ );
5951
+ if (realtimeResponse) {
5952
+ const previousSubscriptionId = this.threadLiveSubscriptionIdByKey.get(key) ?? null;
5953
+ this.threadLiveSubscriptionIdByKey.delete(key);
5954
+ this.threadLiveModeByKey.set(key, "realtime");
5955
+ if (previousSubscriptionId) {
5956
+ void this.removeConversationListenerBySubscriptionId(
5957
+ previousSubscriptionId
5958
+ ).catch(() => {
5959
+ });
5960
+ }
5961
+ return realtimeResponse;
5962
+ }
5963
+ const response = await this.addConversationListenerWithResumeFallback(
5964
+ threadId,
5965
+ workspaceIdForPayload
5966
+ );
5967
+ this.threadLiveModeByKey.set(key, "listener");
5968
+ this.trackThreadLiveSubscriptionId(
5969
+ key,
5970
+ asString4(response?.subscriptionId ?? "").trim() || null
5971
+ );
5972
+ return response ?? {};
5973
+ }
5974
+ async threadLiveUnsubscribe(params) {
5975
+ const workspaceId = normalizeThreadLiveWorkspaceId(
5976
+ params.workspaceId ?? params.workspace_id
5977
+ );
5978
+ const workspaceIdForPayload = resolveThreadLiveWorkspacePayloadId(workspaceId);
5979
+ const threadId = asString4(
5980
+ params.threadId ?? params.thread_id ?? params.conversationId
5981
+ ).trim();
5982
+ if (!threadId) {
5983
+ return {};
5984
+ }
5985
+ const key = threadLiveKey(workspaceId, threadId);
5986
+ const mode = this.threadLiveModeByKey.get(key) ?? null;
5987
+ this.threadLiveModeByKey.delete(key);
5988
+ const subscriptionId = this.threadLiveSubscriptionIdByKey.get(key) ?? null;
5989
+ this.threadLiveSubscriptionIdByKey.delete(key);
5990
+ if (mode === "realtime") {
5991
+ const stoppedRealtime = await this.stopThreadRealtime(
5992
+ threadId,
5993
+ workspaceIdForPayload
5994
+ );
5995
+ if (stoppedRealtime) {
5996
+ return {};
5997
+ }
5998
+ }
5999
+ if (subscriptionId) {
6000
+ await this.removeConversationListenerBySubscriptionId(subscriptionId);
6001
+ return {};
6002
+ }
6003
+ if (mode !== "listener") {
6004
+ const stoppedRealtime = await this.stopThreadRealtime(
6005
+ threadId,
6006
+ workspaceIdForPayload
6007
+ );
6008
+ if (stoppedRealtime) {
6009
+ return {};
6010
+ }
6011
+ }
6012
+ return this.removeConversationListener(
6013
+ workspaceIdForPayload ? {
6014
+ conversationId: threadId,
6015
+ workspaceId: workspaceIdForPayload,
6016
+ workspace_id: workspaceIdForPayload
6017
+ } : { conversationId: threadId }
6018
+ );
6019
+ }
6020
+ async sendUserMessage(params) {
6021
+ return this.request("sendUserMessage", normalizeSendUserMessageParams(params));
6022
+ }
6023
+ async threadStart(params) {
6024
+ return this.request("thread/start", params);
6025
+ }
6026
+ async threadResume(params) {
6027
+ const sanitized = { ...params };
6028
+ delete sanitized.rolloutPath;
6029
+ delete sanitized.rollout_path;
6030
+ return this.request("thread/resume", sanitized);
6031
+ }
6032
+ async turnStart(params) {
6033
+ return this.request("turn/start", normalizeTurnInputParams(params));
6034
+ }
6035
+ async steerTurn(params) {
6036
+ return this.request("turn/steer", normalizeTurnInputParams(params));
6037
+ }
6038
+ async interruptTurn(params) {
6039
+ return this.request("turn/interrupt", params);
6040
+ }
6041
+ async turnSteer(params) {
6042
+ return this.steerTurn(params);
6043
+ }
6044
+ async turnInterrupt(params) {
6045
+ return this.interruptTurn(params);
6046
+ }
6047
+ async interruptConversation(params) {
6048
+ return this.request("interruptConversation", params);
6049
+ }
6050
+ async archiveConversation(conversationId) {
6051
+ try {
6052
+ await this.request("thread/archive", { threadId: conversationId });
6053
+ return;
6054
+ } catch {
6055
+ }
6056
+ try {
6057
+ await this.request("archiveConversation", { conversationId });
6058
+ } catch {
6059
+ }
6060
+ }
6061
+ async listConversations(params) {
6062
+ return this.request("thread/list", {
6063
+ cursor: params.cursor ?? null,
6064
+ limit: params.limit ?? 20,
6065
+ cwd: params.cwd ?? null,
6066
+ sortKey: params.sortKey ?? null,
6067
+ searchQuery: params.searchQuery ?? null,
6068
+ sourceKinds: params.sourceKinds ?? null,
6069
+ archived: params.archived ?? null,
6070
+ modelProviders: params.modelProviders ?? null
6071
+ });
6072
+ }
6073
+ async listSkillsCore(params) {
6074
+ const cwd = typeof params?.cwd === "string" ? params.cwd.trim() : "";
6075
+ return this.request("skills/list", {
6076
+ cwds: cwd ? [cwd] : [],
6077
+ forceReload: Boolean(params?.forceReload)
6078
+ });
6079
+ }
6080
+ async listApps(params) {
6081
+ const payload = buildAppsListPayload(params);
6082
+ const methods = ["app/list", "apps/list", "apps_list"];
6083
+ for (let index = 0; index < methods.length; index += 1) {
6084
+ const method = methods[index];
6085
+ try {
6086
+ return await this.request(method, payload);
6087
+ } catch (error) {
6088
+ const hasLegacyFallback = index < methods.length - 1;
6089
+ if (hasLegacyFallback && isMethodUnavailableError(error, method)) {
6090
+ continue;
6091
+ }
6092
+ if (isAppsListThreadNotFoundError(error)) {
6093
+ return { data: [] };
6094
+ }
6095
+ throw error;
6096
+ }
6097
+ }
6098
+ throw new Error("Apps list is unavailable from the connected Codex backend.");
6099
+ }
6100
+ async setSkillEnabledCore(params) {
6101
+ const skillPath = typeof params.path === "string" ? params.path.trim() : "";
6102
+ if (!skillPath) {
6103
+ throw new Error("Skill path is required.");
6104
+ }
6105
+ return this.request("skills/config/write", {
6106
+ path: skillPath,
6107
+ enabled: Boolean(params.enabled)
6108
+ });
6109
+ }
6110
+ async readThread(params) {
6111
+ return this.request("thread/read", {
6112
+ threadId: params.threadId,
6113
+ includeTurns: Boolean(params.includeTurns)
6114
+ });
6115
+ }
6116
+ async setThreadName(threadId, name) {
6117
+ return this.request("thread/name/set", { threadId, name });
6118
+ }
6119
+ async startReview(params) {
6120
+ return this.request("review/start", params);
6121
+ }
6122
+ async startCompact(params) {
6123
+ try {
6124
+ return await this.request("thread/compact/start", params);
6125
+ } catch {
6126
+ try {
6127
+ return await this.request("thread/compact", params);
6128
+ } catch {
6129
+ return this.request("compactThread", params);
6130
+ }
6131
+ }
6132
+ }
6133
+ async respondExecCommandRequest(requestToken, decision) {
6134
+ await this.respondReviewDecision(requestToken, "exec", decision);
6135
+ }
6136
+ async respondApplyPatchRequest(requestToken, decision) {
6137
+ await this.respondReviewDecision(requestToken, "patch", decision);
6138
+ }
6139
+ async respondToServerRequest(requestId, result) {
6140
+ const token = requestIdToToken(requestId);
6141
+ const pending = this.pendingServerRequests.get(token);
6142
+ if (!pending) {
6143
+ throw new Error(`Unknown server request: ${requestId}`);
6144
+ }
6145
+ this.pendingServerRequests.delete(token);
6146
+ await this.sendResponse(pending.id, result ?? {});
6147
+ }
6148
+ async spawnProcess() {
6149
+ const codexPath = await requireCodexCli();
6150
+ const env = buildCliEnv(codexPath, { ELECTRON_RUN_AS_NODE: "1" });
6151
+ const runtimeBin = resolveRuntimeBinary(env);
6152
+ const child = (0, import_node_child_process4.spawn)(runtimeBin, [codexPath, "app-server"], {
6153
+ stdio: ["pipe", "pipe", "pipe"],
6154
+ env
6155
+ });
6156
+ this.child = child;
6157
+ this.initializeResponse = null;
6158
+ this.initializePromise = null;
6159
+ this.parseLineBuffer = null;
6160
+ child.on("exit", (code, signal) => {
6161
+ logWarn("[app-server] exited", { code, signal });
6162
+ this.child = null;
6163
+ this.initializeResponse = null;
6164
+ this.initializePromise = null;
6165
+ this.threadLiveSubscriptionIdByKey.clear();
6166
+ this.threadLiveModeByKey.clear();
6167
+ this.clearPendingRequests(new Error("codex app-server exited"));
6168
+ this.emit("codex:process-exited", {});
6169
+ });
6170
+ child.on("error", (error) => {
6171
+ logError("[app-server] process error", error);
6172
+ this.threadLiveSubscriptionIdByKey.clear();
6173
+ this.threadLiveModeByKey.clear();
6174
+ this.emit("codex:process-exited", {});
6175
+ });
6176
+ if (child.stderr) {
6177
+ child.stderr.on("data", (chunk) => {
6178
+ const lines = chunk.toString().split(/\r?\n/);
6179
+ for (const line of lines) {
6180
+ const message = line.trim();
6181
+ if (!message) continue;
6182
+ const normalized = message.toLowerCase();
6183
+ if (normalized.includes("state db missing rollout path for thread")) {
6184
+ continue;
6185
+ }
6186
+ if (normalized.includes("rollout::recorder") && normalized.includes("falling back on rollout system")) {
6187
+ continue;
6188
+ }
6189
+ if (isIgnorableAppsListTransportStderr(message)) {
6190
+ continue;
6191
+ }
6192
+ if (isIgnorableMissingSkillsSymlinkStderr(message)) {
6193
+ continue;
6194
+ }
6195
+ logWarn("[app-server] stderr", message);
6196
+ }
6197
+ });
6198
+ }
6199
+ if (!child.stdout) {
6200
+ throw new Error("codex app-server missing stdout");
6201
+ }
6202
+ this.reader = import_node_readline2.default.createInterface({
6203
+ input: child.stdout,
6204
+ crlfDelay: Infinity
6205
+ });
6206
+ this.reader.on("line", (line) => {
6207
+ this.handleIncomingLine(line);
6208
+ });
6209
+ logInfo("[app-server] started");
6210
+ }
6211
+ handleIncomingLine(line) {
6212
+ if (this.parseLineBuffer !== null) {
6213
+ const merged = `${this.parseLineBuffer}
6214
+ ${line}`;
6215
+ const handled2 = this.tryHandleMessage(merged);
6216
+ if (handled2) {
6217
+ this.parseLineBuffer = null;
6218
+ return;
6219
+ }
6220
+ if (merged.length < MAX_PARSE_BUFFER_CHARS) {
6221
+ this.parseLineBuffer = merged;
6222
+ return;
6223
+ }
6224
+ this.logParseFailure(
6225
+ merged,
6226
+ new Error("Buffered JSON message exceeded max size while recovering malformed output.")
6227
+ );
6228
+ this.parseLineBuffer = null;
6229
+ return;
6230
+ }
6231
+ if (!line.trim()) {
6232
+ return;
6233
+ }
6234
+ const handled = this.tryHandleMessage(line);
6235
+ if (handled) {
6236
+ return;
6237
+ }
6238
+ if (line.trimStart().startsWith("{")) {
6239
+ this.parseLineBuffer = line;
6240
+ }
6241
+ }
6242
+ tryHandleMessage(line) {
6243
+ let parsed;
6244
+ let parseError = null;
6245
+ try {
6246
+ parsed = JSON.parse(line);
6247
+ } catch (error) {
6248
+ parseError = error;
6249
+ const repaired = escapeInvalidJsonStringChars(line);
6250
+ if (repaired !== line) {
6251
+ try {
6252
+ parsed = JSON.parse(repaired);
6253
+ parseError = null;
6254
+ } catch (repairError) {
6255
+ parseError = repairError;
6256
+ }
6257
+ }
6258
+ }
6259
+ if (parseError) {
6260
+ if (isLikelyRecoverableJsonParseError(parseError)) {
6261
+ return false;
6262
+ }
6263
+ this.logParseFailure(line, parseError);
6264
+ return true;
6265
+ }
6266
+ if (!isRecord6(parsed)) {
6267
+ logWarn("[app-server] unexpected JSON message", parsed);
6268
+ return true;
6269
+ }
6270
+ const hasId = "id" in parsed;
6271
+ const hasMethod = typeof parsed.method === "string";
6272
+ const hasResult = "result" in parsed;
6273
+ const hasError = "error" in parsed;
6274
+ if (hasMethod && hasId) {
6275
+ this.handleServerRequest(parsed);
6276
+ return true;
6277
+ }
6278
+ if (hasMethod) {
6279
+ this.handleNotification(parsed);
6280
+ return true;
6281
+ }
6282
+ if (hasId && (hasResult || hasError)) {
6283
+ this.handleResponse(parsed);
6284
+ return true;
6285
+ }
6286
+ logWarn("[app-server] unknown message shape", parsed);
6287
+ return true;
6288
+ }
6289
+ logParseFailure(line, error) {
6290
+ const message = error instanceof Error ? error.message : String(error);
6291
+ logWarn("[app-server] failed to parse JSON", {
6292
+ error: message,
6293
+ length: line.length,
6294
+ startsWithJsonObject: line.trimStart().startsWith("{")
6295
+ });
6296
+ }
6297
+ handleResponse(response) {
6298
+ const token = requestIdToToken(response.id);
6299
+ const pending = this.pendingRequests.get(token);
6300
+ if (!pending) {
6301
+ logWarn("[app-server] response with no pending request", response.id);
6302
+ return;
6303
+ }
6304
+ this.pendingRequests.delete(token);
6305
+ if (response.error) {
6306
+ const error = new Error(response.error.message || "codex app-server error");
6307
+ error.code = response.error.code;
6308
+ error.data = response.error.data;
6309
+ const suppressBackendErrorEvent = isAppsListMethod(pending.method) || pending.method === "addConversationListener" && isThreadNotFoundError(error) || pending.method === "thread/resume" && isThreadNotFoundError(error);
6310
+ if (!suppressBackendErrorEvent) {
6311
+ this.emit("codex:backend-error", {
6312
+ code: response.error.code,
6313
+ message: response.error.message,
6314
+ data: response.error.data
6315
+ });
6316
+ }
6317
+ pending.reject(error);
6318
+ return;
6319
+ }
6320
+ pending.resolve(response.result);
6321
+ }
6322
+ handleNotification(notification) {
6323
+ const { method, params } = notification;
6324
+ if (method.startsWith("codex/event/")) {
6325
+ this.emit("codex:event", { method, params });
6326
+ return;
6327
+ }
6328
+ const normalized = method.toLowerCase();
6329
+ if (normalized.includes("auth") && normalized.includes("status")) {
6330
+ this.emit("codex:auth-status", params ?? {});
6331
+ return;
6332
+ }
6333
+ if (normalized.includes("login") && normalized.includes("complete")) {
6334
+ this.emit("codex:login-complete", params ?? {});
6335
+ return;
6336
+ }
6337
+ this.emit("codex:notification", { method, params });
6338
+ }
6339
+ handleServerRequest(request) {
6340
+ const kind = pickMethodKind(request.method, request.params);
6341
+ if (!kind) {
6342
+ void this.sendError(request.id, {
6343
+ code: -32601,
6344
+ message: `Unsupported request: ${request.method}`
6345
+ });
6346
+ return;
6347
+ }
6348
+ const token = requestIdToToken(request.id);
6349
+ this.pendingServerRequests.set(token, { id: request.id, kind, method: request.method });
6350
+ const params = isRecord6(request.params) ? request.params : {};
6351
+ if (kind === "exec") {
6352
+ this.emit("codex:exec-command-request", {
6353
+ requestToken: token,
6354
+ params
6355
+ });
6356
+ } else if (kind === "patch") {
6357
+ this.emit("codex:apply-patch-request", {
6358
+ requestToken: token,
6359
+ params
6360
+ });
6361
+ } else {
6362
+ this.emit("codex:server-request", {
6363
+ requestId: request.id,
6364
+ method: request.method,
6365
+ params
6366
+ });
6367
+ }
6368
+ }
6369
+ async respondReviewDecision(requestToken, kind, decision) {
6370
+ const pending = this.pendingServerRequests.get(requestToken);
6371
+ if (!pending) {
6372
+ throw new Error(`Unknown approval request: ${requestToken}`);
6373
+ }
6374
+ if (pending.kind !== kind) {
6375
+ throw new Error(`Mismatched approval request type for ${requestToken}`);
6376
+ }
6377
+ this.pendingServerRequests.delete(requestToken);
6378
+ await this.sendResponse(pending.id, { decision });
6379
+ }
6380
+ async request(method, params) {
6381
+ if (method !== "initialize") {
6382
+ await this.initialize();
6383
+ } else {
6384
+ await this.start();
6385
+ }
6386
+ const id = this.nextRequestId++;
6387
+ const token = requestIdToToken(id);
6388
+ const timeoutMs = getRequestTimeoutMs(method);
6389
+ const payload = params ? { id, method, params } : { id, method };
6390
+ const result = new Promise((resolve, reject) => {
6391
+ const timeout = setTimeout(() => {
6392
+ const pending = this.pendingRequests.get(token);
6393
+ if (!pending) {
6394
+ return;
6395
+ }
6396
+ this.pendingRequests.delete(token);
6397
+ const timeoutError = new Error(
6398
+ `codex app-server request timed out after ${timeoutMs}ms (${method})`
6399
+ );
6400
+ pending.reject(timeoutError);
6401
+ this.maybeRecoverFromTimeout(method, timeoutError);
6402
+ }, timeoutMs);
6403
+ this.pendingRequests.set(token, {
6404
+ method,
6405
+ resolve: (value) => {
6406
+ clearTimeout(timeout);
6407
+ resolve(value);
6408
+ },
6409
+ reject: (error) => {
6410
+ clearTimeout(timeout);
6411
+ reject(error);
6412
+ }
6413
+ });
6414
+ });
6415
+ await this.writeMessage(payload);
6416
+ return result;
6417
+ }
6418
+ async notify(method, params) {
6419
+ await this.start();
6420
+ const payload = params ? { method, params } : { method };
6421
+ await this.writeMessage(payload);
6422
+ }
6423
+ async sendResponse(id, result) {
6424
+ await this.writeMessage({ id, result });
6425
+ }
6426
+ async sendError(id, error) {
6427
+ await this.writeMessage({ id, error });
6428
+ }
6429
+ async writeMessage(message) {
6430
+ if (!this.child || !this.child.stdin) {
6431
+ throw new Error("codex app-server is not running");
6432
+ }
6433
+ const payload = JSON.stringify({ jsonrpc: "2.0", ...message });
6434
+ this.child.stdin.write(`${payload}
6435
+ `);
6436
+ }
6437
+ clearPendingRequests(error) {
6438
+ for (const pending of this.pendingRequests.values()) {
6439
+ pending.reject(error);
6440
+ }
6441
+ this.pendingRequests.clear();
6442
+ this.pendingServerRequests.clear();
6443
+ this.threadLiveSubscriptionIdByKey.clear();
6444
+ this.threadLiveModeByKey.clear();
6445
+ }
6446
+ maybeRecoverFromTimeout(method, error) {
6447
+ const message = error instanceof Error ? error.message : String(error ?? "");
6448
+ if (!isRequestTimeoutErrorMessage(message)) {
6449
+ return;
6450
+ }
6451
+ if (method !== "initialize") {
6452
+ const now = Date.now();
6453
+ const lastWarning = this.lastTimeoutWarningAtByMethod.get(method) ?? 0;
6454
+ if (now - lastWarning >= REQUEST_TIMEOUT_WARNING_COOLDOWN_MS) {
6455
+ this.lastTimeoutWarningAtByMethod.set(method, now);
6456
+ logWarn("[app-server] request timed out (keeping process alive)", { method });
6457
+ }
6458
+ return;
6459
+ }
6460
+ if (this.timeoutRecoveryPromise) {
6461
+ return;
6462
+ }
6463
+ this.timeoutRecoveryPromise = (async () => {
6464
+ logWarn("[app-server] recycling after request timeout", { method });
6465
+ try {
6466
+ await this.stop();
6467
+ } catch (stopError) {
6468
+ logWarn("[app-server] failed to recycle after timeout", stopError);
6469
+ }
6470
+ })().finally(() => {
6471
+ this.timeoutRecoveryPromise = null;
6472
+ });
6473
+ }
6474
+ };
6475
+
6476
+ // ../../lib/workspace-parity-store.ts
6477
+ var import_node_path12 = __toESM(require("path"));
6478
+ var import_promises3 = __toESM(require("fs/promises"));
6479
+ var import_node_crypto3 = require("crypto");
6480
+
6481
+ // ../../lib/git/git-service.ts
6482
+ var import_node_fs8 = __toESM(require("fs"));
6483
+ var import_node_path11 = __toESM(require("path"));
6484
+ var import_node_child_process6 = require("child_process");
6485
+ var import_node_util2 = require("util");
6486
+
6487
+ // ../../lib/git/git-cli.ts
6488
+ var import_node_child_process5 = require("child_process");
6489
+ var import_node_util = require("util");
6490
+ var execFileAsync = (0, import_node_util.promisify)(import_node_child_process5.execFile);
6491
+ var MAX_BUFFER_BYTES = 10 * 1024 * 1024;
6492
+
6493
+ // ../../lib/git/git-service.ts
6494
+ var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process6.execFile);
6495
+
6496
+ // ../../lib/workspace-parity-store.ts
6497
+ var STORE_FILE = "workspace-parity.json";
6498
+ var LEGACY_STORE_FILE = "workspace-parity.json";
6499
+ var PROFILE_ROOT_DIR = "profiles";
6500
+ var PROFILE_PARITY_DIR = "parity";
6501
+ var LEGACY_MIGRATION_MARKER_FILE = "workspace-parity-profile-migration.json";
6502
+ function defaultSettings() {
6503
+ return {
6504
+ sidebarCollapsed: false,
6505
+ sortOrder: null,
6506
+ groupId: null,
6507
+ cloneSourceWorkspaceId: null,
6508
+ gitRoot: null,
6509
+ launchScript: null,
6510
+ launchScripts: null,
6511
+ worktreeSetupScript: null
6512
+ };
6513
+ }
6514
+ function defaultStore() {
6515
+ return {
6516
+ version: 1,
6517
+ workspaces: []
6518
+ };
6519
+ }
6520
+ function normalizeProfileName(value) {
6521
+ if (typeof value !== "string") {
6522
+ return null;
6523
+ }
6524
+ const trimmed = value.trim();
6525
+ return trimmed.length > 0 ? trimmed : null;
6526
+ }
6527
+ function toProfileStorageKey(profileName) {
6528
+ const normalized = (profileName ?? "default").toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
6529
+ return normalized || "default";
6530
+ }
6531
+ async function getActiveProfileContext() {
6532
+ let profileName = null;
6533
+ try {
6534
+ const state = await getAppState();
6535
+ profileName = normalizeProfileName(state.app.lastProfileName);
6536
+ } catch {
6537
+ profileName = null;
6538
+ }
6539
+ const profileKey = toProfileStorageKey(profileName);
6540
+ const profileRootDir = import_node_path12.default.join(getUserDataDir(), PROFILE_ROOT_DIR, profileKey);
6541
+ return {
6542
+ profileName,
6543
+ profileKey,
6544
+ profileRootDir,
6545
+ parityStoreDir: import_node_path12.default.join(profileRootDir, PROFILE_PARITY_DIR)
6546
+ };
6547
+ }
6548
+ function toWorkspaceName(inputPath) {
6549
+ const normalized = inputPath.replace(/\\/g, "/").replace(/\/+$/, "");
6550
+ const parts = normalized.split("/").filter(Boolean);
6551
+ return parts[parts.length - 1] ?? inputPath;
6552
+ }
6553
+ async function ensureStoreDir() {
6554
+ const context = await getActiveProfileContext();
6555
+ const dir = context.parityStoreDir;
6556
+ await import_promises3.default.mkdir(dir, { recursive: true });
6557
+ return dir;
6558
+ }
6559
+ async function fileExists3(filePath) {
6560
+ try {
6561
+ await import_promises3.default.access(filePath);
6562
+ return true;
6563
+ } catch {
6564
+ return false;
6565
+ }
6566
+ }
6567
+ function legacyStorePath() {
6568
+ return import_node_path12.default.join(getUserDataDir(), LEGACY_STORE_FILE);
6569
+ }
6570
+ function legacyMigrationMarkerPath() {
6571
+ return import_node_path12.default.join(getUserDataDir(), LEGACY_MIGRATION_MARKER_FILE);
6572
+ }
6573
+ async function readLegacyMigrationMarker() {
6574
+ try {
6575
+ const raw = await import_promises3.default.readFile(legacyMigrationMarkerPath(), "utf8");
6576
+ const parsed = JSON.parse(raw);
6577
+ if (parsed && parsed.version === 1 && typeof parsed.migratedToProfileKey === "string" && parsed.migratedToProfileKey.trim()) {
6578
+ return parsed;
6579
+ }
6580
+ return null;
6581
+ } catch {
6582
+ return null;
6583
+ }
6584
+ }
6585
+ async function writeLegacyMigrationMarker(profileKey) {
6586
+ const marker = {
6587
+ version: 1,
6588
+ migratedToProfileKey: profileKey,
6589
+ migratedAt: (/* @__PURE__ */ new Date()).toISOString()
6590
+ };
6591
+ const markerPath = legacyMigrationMarkerPath();
6592
+ await import_promises3.default.mkdir(import_node_path12.default.dirname(markerPath), { recursive: true });
6593
+ await import_promises3.default.writeFile(markerPath, JSON.stringify(marker, null, 2), "utf8");
6594
+ }
6595
+ async function migrateLegacyStoreIfNeeded(scopedStorePath) {
6596
+ if (await fileExists3(scopedStorePath)) {
6597
+ return;
6598
+ }
6599
+ const context = await getActiveProfileContext();
6600
+ const legacyPath = legacyStorePath();
6601
+ if (!await fileExists3(legacyPath)) {
6602
+ return;
6603
+ }
6604
+ const marker = await readLegacyMigrationMarker();
6605
+ if (marker && marker.migratedToProfileKey && marker.migratedToProfileKey !== context.profileKey) {
6606
+ return;
6607
+ }
6608
+ await import_promises3.default.mkdir(import_node_path12.default.dirname(scopedStorePath), { recursive: true });
6609
+ await import_promises3.default.copyFile(legacyPath, scopedStorePath);
6610
+ await writeLegacyMigrationMarker(context.profileKey);
6611
+ }
6612
+ async function storePath() {
6613
+ const dir = await ensureStoreDir();
6614
+ const scopedPath = import_node_path12.default.join(dir, STORE_FILE);
6615
+ await migrateLegacyStoreIfNeeded(scopedPath);
6616
+ return scopedPath;
6617
+ }
6618
+ function sanitizeWorkspace(entry) {
6619
+ if (!entry || typeof entry !== "object") {
6620
+ return null;
6621
+ }
6622
+ const id = typeof entry.id === "string" && entry.id.trim() ? entry.id.trim() : null;
6623
+ const workspacePath = typeof entry.path === "string" && entry.path.trim() ? import_node_path12.default.resolve(entry.path) : null;
6624
+ if (!id || !workspacePath) {
6625
+ return null;
6626
+ }
6627
+ const name = typeof entry.name === "string" && entry.name.trim() ? entry.name.trim() : toWorkspaceName(workspacePath);
6628
+ const branch = entry.worktree && typeof entry.worktree === "object" && typeof entry.worktree.branch === "string" ? entry.worktree.branch.trim() || "main" : "main";
6629
+ return {
6630
+ id,
6631
+ name,
6632
+ path: workspacePath,
6633
+ connected: Boolean(entry.connected),
6634
+ kind: entry.kind === "worktree" ? "worktree" : "main",
6635
+ parentId: typeof entry.parentId === "string" && entry.parentId.trim() ? entry.parentId.trim() : null,
6636
+ worktree: entry.kind === "worktree" ? { branch } : null,
6637
+ settings: {
6638
+ ...defaultSettings(),
6639
+ ...entry.settings ?? {}
6640
+ }
6641
+ };
6642
+ }
6643
+ async function readStore() {
6644
+ const filePath = await storePath();
6645
+ try {
6646
+ const raw = await import_promises3.default.readFile(filePath, "utf8");
6647
+ const parsed = JSON.parse(raw);
6648
+ const source = Array.isArray(parsed?.workspaces) ? parsed.workspaces : [];
6649
+ const workspaces = source.map((entry) => sanitizeWorkspace(entry)).filter((entry) => Boolean(entry));
6650
+ return {
6651
+ version: 1,
6652
+ workspaces
6653
+ };
6654
+ } catch {
6655
+ return defaultStore();
6656
+ }
6657
+ }
6658
+ async function writeStore(store) {
6659
+ const filePath = await storePath();
6660
+ await import_promises3.default.writeFile(filePath, JSON.stringify(store, null, 2), "utf8");
6661
+ }
6662
+ function dedupeByPath(workspaces) {
6663
+ const seen = /* @__PURE__ */ new Set();
6664
+ const ordered = [];
6665
+ for (const workspace of workspaces) {
6666
+ const key = workspace.path.toLowerCase();
6667
+ if (seen.has(key)) {
6668
+ continue;
6669
+ }
6670
+ seen.add(key);
6671
+ ordered.push(workspace);
6672
+ }
6673
+ return ordered;
6674
+ }
6675
+ async function upsertStore(mutator) {
6676
+ const current = await readStore();
6677
+ const next = mutator(current);
6678
+ await writeStore(next);
6679
+ return next;
6680
+ }
6681
+ function isMainWorkspace(workspace) {
6682
+ return (workspace.kind ?? "main") !== "worktree";
6683
+ }
6684
+ function pruneToFreeProjectLimit(workspaces) {
6685
+ const mainWorkspaces = workspaces.filter((workspace) => isMainWorkspace(workspace));
6686
+ if (mainWorkspaces.length <= FREE_PROFILE_LIMIT) {
6687
+ return workspaces;
6688
+ }
6689
+ const keptMainIds = new Set(mainWorkspaces.slice(0, FREE_PROFILE_LIMIT).map((workspace) => workspace.id));
6690
+ return workspaces.filter((workspace) => {
6691
+ if (isMainWorkspace(workspace)) {
6692
+ return keptMainIds.has(workspace.id);
6693
+ }
6694
+ return typeof workspace.parentId === "string" && keptMainIds.has(workspace.parentId);
6695
+ });
6696
+ }
6697
+ async function enforceFreeProjectLimitIfNeeded(workspaces) {
6698
+ const license = await licenseService.getStatus().catch(() => null);
6699
+ if (!license || !shouldEnforceProfileLimit(license)) {
6700
+ return workspaces;
6701
+ }
6702
+ const pruned = pruneToFreeProjectLimit(workspaces);
6703
+ if (pruned.length === workspaces.length) {
6704
+ return workspaces;
6705
+ }
6706
+ await writeStore({
6707
+ version: 1,
6708
+ workspaces: pruned
6709
+ });
6710
+ return pruned;
6711
+ }
6712
+ async function listParityWorkspaces() {
6713
+ const store = await readStore();
6714
+ const deduped = dedupeByPath(store.workspaces);
6715
+ return enforceFreeProjectLimitIfNeeded(deduped);
6716
+ }
6717
+ async function getParityWorkspaceById(id) {
6718
+ const workspaces = await listParityWorkspaces();
6719
+ return workspaces.find((entry) => entry.id === id) ?? null;
6720
+ }
6721
+ async function addParityWorkspace(targetPath) {
6722
+ const resolved = import_node_path12.default.resolve(targetPath);
6723
+ const name = toWorkspaceName(resolved);
6724
+ await import_promises3.default.mkdir(resolved, { recursive: true });
6725
+ const nextWorkspace = {
6726
+ id: (0, import_node_crypto3.randomUUID)(),
6727
+ name,
6728
+ path: resolved,
6729
+ connected: true,
6730
+ kind: "main",
6731
+ parentId: null,
6732
+ worktree: null,
6733
+ settings: defaultSettings()
6734
+ };
6735
+ await upsertStore((store) => ({
6736
+ version: 1,
6737
+ workspaces: dedupeByPath([nextWorkspace, ...store.workspaces])
6738
+ }));
6739
+ return nextWorkspace;
6740
+ }
6741
+ async function connectParityWorkspace(id) {
6742
+ let updated = null;
6743
+ await upsertStore((store) => {
6744
+ const next = store.workspaces.map((entry) => {
6745
+ if (entry.id !== id) {
6746
+ return entry;
6747
+ }
6748
+ updated = {
6749
+ ...entry,
6750
+ connected: true
6751
+ };
6752
+ return updated;
6753
+ });
6754
+ return {
6755
+ version: 1,
6756
+ workspaces: next
6757
+ };
6758
+ });
6759
+ if (!updated) {
6760
+ throw new Error(`Workspace '${id}' not found.`);
6761
+ }
6762
+ return updated;
6763
+ }
6764
+
6765
+ // ../../electron/telegram-bridge.ts
6766
+ var import_node_crypto4 = require("crypto");
6767
+ var import_promises4 = __toESM(require("fs/promises"));
6768
+ var import_node_path13 = __toESM(require("path"));
6769
+
6770
+ // ../../lib/retryable-turn-error.ts
6771
+ var RETRY_FLAG_KEYS = [
6772
+ "willRetry",
6773
+ "will_retry",
6774
+ "retryable",
6775
+ "retryable_error",
6776
+ "shouldRetry",
6777
+ "should_retry",
6778
+ "retry"
6779
+ ];
6780
+ var RETRY_PROGRESS_MESSAGE_RE = /(?:reconnecting|retrying)(?:\.\.\.)?(?:\s+\d+\s*\/\s*\d+)?/i;
6781
+ function asRecord(value) {
6782
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
6783
+ }
6784
+ function asString5(value) {
6785
+ return typeof value === "string" ? value : value != null ? String(value) : "";
6786
+ }
6787
+ function parseBooleanLike(value) {
6788
+ if (typeof value === "boolean") {
6789
+ return value;
6790
+ }
6791
+ if (typeof value === "number") {
6792
+ if (value === 1) {
6793
+ return true;
6794
+ }
6795
+ if (value === 0) {
6796
+ return false;
6797
+ }
6798
+ return null;
6799
+ }
6800
+ if (typeof value === "string") {
6801
+ const normalized = value.trim().toLowerCase();
6802
+ if (!normalized) {
6803
+ return null;
6804
+ }
6805
+ if (normalized === "true" || normalized === "1") {
6806
+ return true;
6807
+ }
6808
+ if (normalized === "false" || normalized === "0") {
6809
+ return false;
6810
+ }
6811
+ }
6812
+ return null;
6813
+ }
6814
+ function extractRetryFlag(payload) {
6815
+ const root = asRecord(payload);
6816
+ const nestedCandidates = [
6817
+ root,
6818
+ asRecord(root.error),
6819
+ asRecord(root.details),
6820
+ asRecord(root.data),
6821
+ asRecord(root.meta)
6822
+ ];
6823
+ for (const candidate of nestedCandidates) {
6824
+ for (const key of RETRY_FLAG_KEYS) {
6825
+ const parsed = parseBooleanLike(candidate[key]);
6826
+ if (parsed !== null) {
6827
+ return parsed;
6828
+ }
6829
+ }
6830
+ }
6831
+ return null;
6832
+ }
6833
+ function isRetryProgressMessage(message) {
6834
+ const trimmed = message.trim();
6835
+ if (!trimmed) {
6836
+ return false;
6837
+ }
6838
+ return RETRY_PROGRESS_MESSAGE_RE.test(trimmed);
6839
+ }
6840
+ function extractErrorMessageCandidates(payload) {
6841
+ const root = asRecord(payload);
6842
+ const error = asRecord(root.error);
6843
+ return [
6844
+ asString5(root.message),
6845
+ asString5(root.detail),
6846
+ asString5(root.reason),
6847
+ asString5(root.error),
6848
+ asString5(error.message),
6849
+ asString5(error.detail),
6850
+ asString5(error.reason),
6851
+ asString5(error.error)
6852
+ ].map((value) => value.trim()).filter(Boolean);
6853
+ }
6854
+ function isRetryableTurnErrorPayload(payload) {
6855
+ const retryFlag = extractRetryFlag(payload);
6856
+ if (retryFlag !== null) {
6857
+ return retryFlag;
6858
+ }
6859
+ return extractErrorMessageCandidates(payload).some(isRetryProgressMessage);
6860
+ }
6861
+
6862
+ // ../../electron/telegram-bridge.ts
6863
+ var TELEGRAM_API_BASE = "https://api.telegram.org";
6864
+ var TELEGRAM_POLL_TIMEOUT_SECONDS = 30;
6865
+ var TELEGRAM_API_TIMEOUT_MS = 4e4;
6866
+ var TELEGRAM_STREAM_FLUSH_MS = 300;
6867
+ var TELEGRAM_MAX_MESSAGE_LENGTH = 4096;
6868
+ var TELEGRAM_PENDING_TTL_MS = 30 * 60 * 1e3;
6869
+ var TELEGRAM_RETRY_LIMIT_MS = 3e4;
6870
+ var TELEGRAM_MIN_SEND_INTERVAL_MS = 500;
6871
+ var TELEGRAM_FINAL_TEXT_DEDUPE_MS = 2500;
6872
+ var TELEGRAM_RECENT_FINALIZED_TURNS_MAX = 24;
6873
+ var TELEGRAM_FINAL_SIGNATURE_DEDUPE_MS = 2e4;
6874
+ var TELEGRAM_RECENT_FINAL_SIGNATURES_MAX = 40;
6875
+ var TELEGRAM_RECENT_INCOMING_MESSAGES_MAX = 32;
6876
+ var TELEGRAM_TYPING_HEARTBEAT_MS = 4e3;
6877
+ var TELEGRAM_BRIDGE_LOCK_FILE_PREFIX = "telegram-bridge";
6878
+ function asRecord2(value) {
6879
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
6880
+ }
6881
+ function asString6(value) {
6882
+ return typeof value === "string" ? value : value != null ? String(value) : "";
6883
+ }
6884
+ function asTrimmedString(value) {
6885
+ return asString6(value).trim();
6886
+ }
6887
+ function asNumber(value) {
6888
+ if (typeof value === "number" && Number.isFinite(value)) {
6889
+ return value;
6890
+ }
6891
+ if (typeof value === "string" && value.trim()) {
6892
+ const parsed = Number(value.trim());
6893
+ if (Number.isFinite(parsed)) {
6894
+ return parsed;
6895
+ }
6896
+ }
6897
+ return null;
6898
+ }
6899
+ function normalizeChatId(value) {
6900
+ const trimmed = asTrimmedString(value);
6901
+ if (!trimmed) {
6902
+ return null;
6903
+ }
6904
+ if (!/^-?[0-9]+$/.test(trimmed)) {
6905
+ return null;
6906
+ }
6907
+ return trimmed;
6908
+ }
6909
+ function extractThreadId(value) {
6910
+ const params = asRecord2(value);
6911
+ const turn = asRecord2(params.turn);
6912
+ const thread = asRecord2(params.thread);
6913
+ const item = asRecord2(params.item);
6914
+ const msg = asRecord2(params.msg);
6915
+ const msgItem = asRecord2(msg.item);
6916
+ return asTrimmedString(
6917
+ params.threadId ?? params.thread_id ?? params.conversationId ?? params.conversation_id ?? turn.threadId ?? turn.thread_id ?? thread.id ?? thread.threadId ?? thread.thread_id ?? item.threadId ?? item.thread_id ?? item.conversationId ?? item.conversation_id ?? msg.threadId ?? msg.thread_id ?? msg.conversationId ?? msg.conversation_id ?? msgItem.threadId ?? msgItem.thread_id ?? msgItem.conversationId ?? msgItem.conversation_id ?? ""
6918
+ );
6919
+ }
6920
+ function extractTurnId(value) {
6921
+ const params = asRecord2(value);
6922
+ const turn = asRecord2(params.turn);
6923
+ const msg = asRecord2(params.msg);
6924
+ const msgTurn = asRecord2(msg.turn);
6925
+ return asTrimmedString(
6926
+ turn.id ?? turn.turnId ?? turn.turn_id ?? params.turnId ?? params.turn_id ?? msg.turnId ?? msg.turn_id ?? msgTurn.id ?? msgTurn.turnId ?? msgTurn.turn_id ?? ""
6927
+ );
6928
+ }
6929
+ function extractText(value, depth = 0) {
6930
+ if (typeof value === "string") {
6931
+ return value;
6932
+ }
6933
+ if (depth > 4 || value == null) {
6934
+ return "";
6935
+ }
6936
+ if (Array.isArray(value)) {
6937
+ return value.map((entry) => extractText(entry, depth + 1)).join("");
6938
+ }
6939
+ if (typeof value !== "object") {
6940
+ return "";
6941
+ }
6942
+ const record = value;
6943
+ return extractText(record.text, depth + 1) || extractText(record.delta, depth + 1) || extractText(record.message, depth + 1) || extractText(record.content, depth + 1) || extractText(record.value, depth + 1);
6944
+ }
6945
+ function extractDeltaFromNotification(params) {
6946
+ const msg = asRecord2(params.msg);
6947
+ return asString6(params.delta) || extractText(params.delta) || extractText(msg.delta ?? msg.message ?? msg.content ?? msg.value);
6948
+ }
6949
+ function looksLikeAssistantItem(item) {
6950
+ const itemType = asTrimmedString(item.type).toLowerCase();
6951
+ if (itemType === "agentmessage" || itemType === "agent_message" || itemType === "assistantmessage" || itemType === "assistant_message") {
6952
+ return true;
6953
+ }
6954
+ const itemRole = asTrimmedString(
6955
+ item.role ?? item.authorRole ?? item.author_role ?? item.senderRole ?? item.sender_role
6956
+ ).toLowerCase();
6957
+ if (itemRole === "assistant" || itemRole === "agent") {
6958
+ return true;
6959
+ }
6960
+ const itemId = asTrimmedString(item.id);
6961
+ return itemId.startsWith("msg_");
6962
+ }
6963
+ function extractAssistantTextFromItemCompleted(params) {
6964
+ const item = asRecord2(params.item);
6965
+ if (!looksLikeAssistantItem(item)) {
6966
+ return "";
6967
+ }
6968
+ return asString6(item.text) || extractText(item.content) || extractText(params.msg) || "";
6969
+ }
6970
+ function extractCommandText(params) {
6971
+ const command = params.command;
6972
+ if (typeof command === "string") {
6973
+ return command.trim();
6974
+ }
6975
+ if (Array.isArray(command)) {
6976
+ const parts = command.map((entry) => asString6(entry).trim()).filter(Boolean);
6977
+ if (parts.length > 0) {
6978
+ return parts.join(" ");
6979
+ }
6980
+ }
6981
+ const parsed = asRecord2(params.parsed_cmd ?? params.parsedCmd);
6982
+ const parsedText = asTrimmedString(parsed.text ?? parsed.command);
6983
+ if (parsedText) {
6984
+ return parsedText;
6985
+ }
6986
+ return asTrimmedString(params.cmd ?? "");
6987
+ }
6988
+ function summarizePatchRequest(params) {
6989
+ const fileChanges = params.file_changes ?? params.fileChanges;
6990
+ if (Array.isArray(fileChanges)) {
6991
+ const count = fileChanges.length;
6992
+ return `${count} file ${count === 1 ? "change" : "changes"}`;
6993
+ }
6994
+ const summary = extractText(params);
6995
+ return summary.trim() || "Apply patch request";
6996
+ }
6997
+ function splitMessage(text, maxLength = TELEGRAM_MAX_MESSAGE_LENGTH) {
6998
+ const normalized = text.trim();
6999
+ if (!normalized) {
7000
+ return [];
7001
+ }
7002
+ if (normalized.length <= maxLength) {
7003
+ return [normalized];
7004
+ }
7005
+ const parts = [];
7006
+ let index = 0;
7007
+ while (index < normalized.length) {
7008
+ const remaining = normalized.slice(index);
7009
+ if (remaining.length <= maxLength) {
7010
+ parts.push(remaining);
7011
+ break;
7012
+ }
7013
+ let cut = maxLength;
7014
+ const newlineCut = remaining.lastIndexOf("\n", maxLength);
7015
+ if (newlineCut >= Math.floor(maxLength * 0.5)) {
7016
+ cut = newlineCut;
7017
+ }
7018
+ const chunk = remaining.slice(0, cut).trim();
7019
+ parts.push(chunk || remaining.slice(0, maxLength));
7020
+ index += cut;
7021
+ }
7022
+ return parts;
7023
+ }
7024
+ function mergeStreamDeltaBuffer(current, incoming) {
7025
+ if (!incoming) {
7026
+ return current;
7027
+ }
7028
+ if (!current) {
7029
+ return incoming;
7030
+ }
7031
+ if (current.endsWith(incoming)) {
7032
+ return current;
7033
+ }
7034
+ if (incoming.startsWith(current)) {
7035
+ return incoming;
7036
+ }
7037
+ const maxProbe = Math.min(current.length, incoming.length, 2048);
7038
+ for (let size = maxProbe; size > 0; size -= 1) {
7039
+ if (current.slice(-size) === incoming.slice(0, size)) {
7040
+ return current + incoming.slice(size);
7041
+ }
7042
+ }
7043
+ return current + incoming;
7044
+ }
7045
+ function nowMs() {
7046
+ return Date.now();
7047
+ }
7048
+ var TelegramBridge = class {
7049
+ constructor(options) {
7050
+ this.settings = {
7051
+ enabled: false,
7052
+ token: null
7053
+ };
7054
+ this.status = {
7055
+ state: "stopped",
7056
+ running: false,
7057
+ botUsername: null,
7058
+ allowedChats: 0,
7059
+ activeChats: 0,
7060
+ streamMode: "message",
7061
+ lastError: null,
7062
+ startedAtMs: null
7063
+ };
7064
+ this.running = false;
7065
+ this.pollAbortController = null;
7066
+ this.pollLoopPromise = null;
7067
+ this.updateOffset = null;
7068
+ this.draftStreamingEnabled = false;
7069
+ this.messageStreamingEnabled = true;
7070
+ this.sessionsByChatId = /* @__PURE__ */ new Map();
7071
+ this.chatIdByThreadId = /* @__PURE__ */ new Map();
7072
+ this.pendingApprovalById = /* @__PURE__ */ new Map();
7073
+ this.pendingUserInputByRequestId = /* @__PURE__ */ new Map();
7074
+ this.appServerListenersBound = false;
7075
+ this.recentDeltaBySignature = /* @__PURE__ */ new Map();
7076
+ this.lastSendAtByChatId = /* @__PURE__ */ new Map();
7077
+ this.runtimeLockPath = null;
7078
+ this.handleCodexNotification = (payload) => {
7079
+ this.handleNotification(payload).catch((error) => {
7080
+ logWarn("[telegram-bridge] notification handling failed", error);
7081
+ });
7082
+ };
7083
+ this.handleCodexEvent = (payload) => {
7084
+ this.handleEvent(payload).catch((error) => {
7085
+ logWarn("[telegram-bridge] event handling failed", error);
7086
+ });
7087
+ };
7088
+ this.handleExecApprovalRequest = (payload) => {
7089
+ this.handleApprovalRequest("exec", payload.requestToken, payload.params).catch((error) => {
7090
+ logWarn("[telegram-bridge] exec approval handling failed", error);
7091
+ });
7092
+ };
7093
+ this.handlePatchApprovalRequest = (payload) => {
7094
+ this.handleApprovalRequest("patch", payload.requestToken, payload.params).catch((error) => {
7095
+ logWarn("[telegram-bridge] patch approval handling failed", error);
7096
+ });
7097
+ };
7098
+ this.handleServerRequest = (payload) => {
7099
+ this.handleUserInputRequest(payload).catch((error) => {
7100
+ logWarn("[telegram-bridge] user-input request handling failed", error);
7101
+ });
7102
+ };
7103
+ this.options = options;
7104
+ }
7105
+ getCurrentStreamMode() {
7106
+ if (this.messageStreamingEnabled) {
7107
+ return "message";
7108
+ }
7109
+ if (this.draftStreamingEnabled) {
7110
+ return "draft";
7111
+ }
7112
+ return "none";
7113
+ }
7114
+ getStatus() {
7115
+ return {
7116
+ ...this.status,
7117
+ allowedChats: 0,
7118
+ activeChats: this.sessionsByChatId.size,
7119
+ streamMode: this.getCurrentStreamMode()
7120
+ };
7121
+ }
7122
+ async testToken(tokenInput) {
7123
+ const token = asTrimmedString(tokenInput) || this.settings.token || "";
7124
+ if (!token) {
7125
+ return {
7126
+ ok: false,
7127
+ username: null,
7128
+ error: "Telegram bot token is required."
7129
+ };
7130
+ }
7131
+ try {
7132
+ const response = await this.callTelegramApi(
7133
+ token,
7134
+ "getMe",
7135
+ {},
7136
+ { retry429: false }
7137
+ );
7138
+ const username = asTrimmedString(asRecord2(response).username);
7139
+ return {
7140
+ ok: true,
7141
+ username: username || null,
7142
+ error: null
7143
+ };
7144
+ } catch (error) {
7145
+ return {
7146
+ ok: false,
7147
+ username: null,
7148
+ error: error instanceof Error ? error.message : String(error)
7149
+ };
7150
+ }
7151
+ }
7152
+ async applyRuntimeSettings(settings) {
7153
+ const next = this.extractSettings(settings);
7154
+ const tokenChanged = next.token !== this.settings.token;
7155
+ const enabledChanged = next.enabled !== this.settings.enabled;
7156
+ this.settings = next;
7157
+ this.status.allowedChats = 0;
7158
+ if (tokenChanged) {
7159
+ this.status.botUsername = null;
7160
+ this.draftStreamingEnabled = false;
7161
+ this.messageStreamingEnabled = true;
7162
+ this.status.streamMode = this.getCurrentStreamMode();
7163
+ }
7164
+ const shouldRestart = tokenChanged || enabledChanged;
7165
+ await this.syncRuntimeState({ shouldRestart });
7166
+ }
7167
+ async dispose() {
7168
+ await this.stop("Bridge disposed.");
7169
+ }
7170
+ async stop(reason) {
7171
+ if (!this.running && !this.pollLoopPromise) {
7172
+ await this.releaseRuntimeLock();
7173
+ this.status.state = "stopped";
7174
+ this.status.running = false;
7175
+ this.status.startedAtMs = null;
7176
+ if (reason) {
7177
+ this.status.lastError = null;
7178
+ }
7179
+ return;
7180
+ }
7181
+ this.running = false;
7182
+ this.status.running = false;
7183
+ this.status.state = "stopped";
7184
+ this.status.startedAtMs = null;
7185
+ if (this.pollAbortController) {
7186
+ this.pollAbortController.abort();
7187
+ this.pollAbortController = null;
7188
+ }
7189
+ if (this.pollLoopPromise) {
7190
+ try {
7191
+ await this.pollLoopPromise;
7192
+ } catch {
7193
+ }
7194
+ this.pollLoopPromise = null;
7195
+ }
7196
+ this.detachAppServerListeners();
7197
+ await this.detachAllConversationListeners();
7198
+ this.clearSessionState();
7199
+ await this.releaseRuntimeLock();
7200
+ if (reason) {
7201
+ logInfo("[telegram-bridge] stopped", reason);
7202
+ }
7203
+ }
7204
+ extractSettings(raw) {
7205
+ return {
7206
+ enabled: raw.telegramBridgeEnabled === true,
7207
+ token: (() => {
7208
+ const token = asTrimmedString(raw.telegramBotToken);
7209
+ return token || null;
7210
+ })()
7211
+ };
7212
+ }
7213
+ async syncRuntimeState(options) {
7214
+ const shouldRun = this.settings.enabled && Boolean(this.settings.token);
7215
+ if (!shouldRun) {
7216
+ await this.stop("Disabled or incomplete Telegram settings.");
7217
+ this.status.state = "stopped";
7218
+ this.status.lastError = null;
7219
+ return;
7220
+ }
7221
+ const isPro = await this.options.isProEnabled().catch((error) => {
7222
+ logWarn("[telegram-bridge] failed to verify Pro status", error);
7223
+ return false;
7224
+ });
7225
+ if (!isPro) {
7226
+ await this.stop("Telegram bridge requires Pro.");
7227
+ this.status.state = "error";
7228
+ this.status.lastError = "Telegram mobile access requires Pro.";
7229
+ return;
7230
+ }
7231
+ if (this.running && options.shouldRestart) {
7232
+ await this.stop("Restarting Telegram bridge after settings change.");
7233
+ }
7234
+ if (this.running) {
7235
+ return;
7236
+ }
7237
+ await this.start();
7238
+ }
7239
+ async start() {
7240
+ const token = this.settings.token;
7241
+ if (!token) {
7242
+ this.status.state = "error";
7243
+ this.status.lastError = "Telegram bot token is missing.";
7244
+ return;
7245
+ }
7246
+ const me = await this.testToken(token);
7247
+ if (!me.ok) {
7248
+ this.status.state = "error";
7249
+ this.status.lastError = me.error;
7250
+ this.status.botUsername = null;
7251
+ return;
7252
+ }
7253
+ try {
7254
+ await this.acquireRuntimeLock(token);
7255
+ } catch (error) {
7256
+ this.status.state = "error";
7257
+ this.status.lastError = error instanceof Error ? error.message : String(error);
7258
+ return;
7259
+ }
7260
+ this.status.botUsername = me.username;
7261
+ this.status.lastError = null;
7262
+ this.status.state = "running";
7263
+ this.status.running = true;
7264
+ this.status.startedAtMs = nowMs();
7265
+ this.running = true;
7266
+ this.attachAppServerListeners();
7267
+ this.pollAbortController = new AbortController();
7268
+ this.pollLoopPromise = this.runPolling(this.pollAbortController.signal).finally(() => {
7269
+ this.pollLoopPromise = null;
7270
+ });
7271
+ logInfo("[telegram-bridge] started", {
7272
+ allowedChats: "all-private-chats",
7273
+ botUsername: this.status.botUsername
7274
+ });
7275
+ }
7276
+ async runPolling(signal) {
7277
+ const token = this.settings.token;
7278
+ if (!token) {
7279
+ return;
7280
+ }
7281
+ await this.callTelegramApi(token, "deleteWebhook", { drop_pending_updates: false }).catch(
7282
+ (error) => {
7283
+ logWarn("[telegram-bridge] deleteWebhook failed", error);
7284
+ }
7285
+ );
7286
+ let backoffMs = 1e3;
7287
+ while (!signal.aborted && this.running) {
7288
+ try {
7289
+ this.prunePendingActions();
7290
+ const updates = await this.callTelegramApi(
7291
+ token,
7292
+ "getUpdates",
7293
+ {
7294
+ offset: this.updateOffset,
7295
+ timeout: TELEGRAM_POLL_TIMEOUT_SECONDS,
7296
+ allowed_updates: ["message", "callback_query", "my_chat_member"]
7297
+ },
7298
+ {
7299
+ signal,
7300
+ retry429: true
7301
+ }
7302
+ );
7303
+ const list = Array.isArray(updates) ? updates : [];
7304
+ for (const update of list) {
7305
+ const updateId = asNumber(update.update_id);
7306
+ if (updateId !== null) {
7307
+ this.updateOffset = updateId + 1;
7308
+ }
7309
+ await this.handleUpdate(update);
7310
+ }
7311
+ backoffMs = 1e3;
7312
+ } catch (error) {
7313
+ if (signal.aborted || !this.running) {
7314
+ break;
7315
+ }
7316
+ const message = error instanceof Error ? error.message : String(error);
7317
+ this.status.state = "error";
7318
+ this.status.lastError = message;
7319
+ logWarn("[telegram-bridge] polling error", message);
7320
+ await this.sleep(Math.min(backoffMs, TELEGRAM_RETRY_LIMIT_MS));
7321
+ backoffMs = Math.min(backoffMs * 2, TELEGRAM_RETRY_LIMIT_MS);
7322
+ }
7323
+ }
7324
+ if (!signal.aborted && this.running) {
7325
+ this.status.state = "error";
7326
+ this.status.lastError = "Telegram polling stopped unexpectedly.";
7327
+ }
7328
+ }
7329
+ async handleUpdate(update) {
7330
+ const record = asRecord2(update);
7331
+ if (record.callback_query) {
7332
+ await this.handleCallbackQuery(asRecord2(record.callback_query));
7333
+ return;
7334
+ }
7335
+ if (record.message) {
7336
+ await this.handleMessage(asRecord2(record.message));
7337
+ return;
7338
+ }
7339
+ if (record.my_chat_member) {
7340
+ this.handleMembershipUpdate(asRecord2(record.my_chat_member));
7341
+ return;
7342
+ }
7343
+ }
7344
+ handleMembershipUpdate(payload) {
7345
+ const chat = asRecord2(payload.chat);
7346
+ const chatId = normalizeChatId(chat.id);
7347
+ if (!chatId) {
7348
+ return;
7349
+ }
7350
+ const newMember = asRecord2(payload.new_chat_member);
7351
+ const status = asTrimmedString(newMember.status).toLowerCase();
7352
+ if (status === "kicked" || status === "left") {
7353
+ void this.resetSession(chatId);
7354
+ }
7355
+ }
7356
+ async handleMessage(message) {
7357
+ const chat = asRecord2(message.chat);
7358
+ const chatId = normalizeChatId(chat.id);
7359
+ if (!chatId) {
7360
+ return;
7361
+ }
7362
+ const session = this.getOrCreateSession(chatId);
7363
+ const messageId = asNumber(message.message_id ?? message.messageId);
7364
+ if (messageId !== null && this.isDuplicateIncomingMessage(session, messageId)) {
7365
+ return;
7366
+ }
7367
+ const chatType = asTrimmedString(chat.type).toLowerCase();
7368
+ if (chatType && chatType !== "private") {
7369
+ await this.sendSimpleMessage(chatId, "Private chats only. Use me in a direct chat.");
7370
+ return;
7371
+ }
7372
+ const text = asTrimmedString(message.text);
7373
+ if (!text) {
7374
+ return;
7375
+ }
7376
+ const parsedCommand = this.parseCommand(text);
7377
+ if (parsedCommand) {
7378
+ const handled = await this.handleCommand(chatId, parsedCommand.command, parsedCommand.args);
7379
+ if (handled) {
7380
+ return;
7381
+ }
7382
+ }
7383
+ await this.handlePromptText(chatId, text);
7384
+ }
7385
+ async handleCallbackQuery(callbackQuery) {
7386
+ const callbackId = asTrimmedString(callbackQuery.id);
7387
+ const message = asRecord2(callbackQuery.message);
7388
+ const chat = asRecord2(message.chat);
7389
+ const chatId = normalizeChatId(chat.id);
7390
+ const data = asTrimmedString(callbackQuery.data);
7391
+ if (!callbackId || !chatId || !data) {
7392
+ return;
7393
+ }
7394
+ const approvalMatch = /^cu:ap:([a-z0-9]+):(approved|approved_for_session|denied|abort)$/i.exec(
7395
+ data
7396
+ );
7397
+ if (approvalMatch) {
7398
+ const actionId = approvalMatch[1];
7399
+ const decision = approvalMatch[2];
7400
+ const action = this.pendingApprovalById.get(actionId);
7401
+ if (!action || action.chatId !== chatId) {
7402
+ await this.answerCallbackQuery(callbackId, "Action expired.");
7403
+ return;
7404
+ }
7405
+ const appServer = this.options.getAppServer();
7406
+ if (!appServer) {
7407
+ await this.answerCallbackQuery(callbackId, "Backend unavailable.");
7408
+ return;
7409
+ }
7410
+ try {
7411
+ if (action.kind === "exec") {
7412
+ await appServer.respondExecCommandRequest(action.requestToken, decision);
7413
+ } else {
7414
+ await appServer.respondApplyPatchRequest(action.requestToken, decision);
7415
+ }
7416
+ this.pendingApprovalById.delete(actionId);
7417
+ await this.answerCallbackQuery(callbackId, `Recorded: ${decision}`);
7418
+ await this.sendSimpleMessage(chatId, `Approval decision sent: ${decision}.`);
7419
+ } catch (error) {
7420
+ await this.answerCallbackQuery(callbackId, "Failed to submit decision.");
7421
+ await this.sendSimpleMessage(
7422
+ chatId,
7423
+ `Failed to submit approval decision: ${error instanceof Error ? error.message : String(error)}`
7424
+ );
7425
+ }
7426
+ return;
7427
+ }
7428
+ await this.answerCallbackQuery(callbackId, "Unknown action.");
7429
+ }
7430
+ parseCommand(text) {
7431
+ const trimmed = text.trim();
7432
+ if (!trimmed.startsWith("/")) {
7433
+ return null;
7434
+ }
7435
+ const [head, ...rest] = trimmed.split(/\s+/g);
7436
+ const commandWithSlash = head.slice(1);
7437
+ const command = commandWithSlash.split("@")[0]?.trim().toLowerCase();
7438
+ if (!command) {
7439
+ return null;
7440
+ }
7441
+ return {
7442
+ command,
7443
+ args: rest.join(" ").trim()
7444
+ };
7445
+ }
7446
+ async handleCommand(chatId, command, args) {
7447
+ switch (command) {
7448
+ case "start":
7449
+ case "help": {
7450
+ await this.sendHelp(chatId);
7451
+ return true;
7452
+ }
7453
+ case "status": {
7454
+ await this.sendStatus(chatId);
7455
+ return true;
7456
+ }
7457
+ case "projects":
7458
+ case "workspaces": {
7459
+ await this.sendProjects(chatId);
7460
+ return true;
7461
+ }
7462
+ case "project":
7463
+ case "workspace": {
7464
+ await this.selectProject(chatId, args);
7465
+ return true;
7466
+ }
7467
+ case "new": {
7468
+ await this.createNewThread(chatId);
7469
+ return true;
7470
+ }
7471
+ case "threads": {
7472
+ await this.listThreads(chatId);
7473
+ return true;
7474
+ }
7475
+ case "thread": {
7476
+ await this.selectThread(chatId, args);
7477
+ return true;
7478
+ }
7479
+ case "interrupt": {
7480
+ await this.interruptTurn(chatId);
7481
+ return true;
7482
+ }
7483
+ case "input": {
7484
+ await this.submitUserInput(chatId, args);
7485
+ return true;
7486
+ }
7487
+ default:
7488
+ return false;
7489
+ }
7490
+ }
7491
+ async sendHelp(chatId) {
7492
+ const lines = [
7493
+ "CodexUse Telegram bridge is connected.",
7494
+ "",
7495
+ "Commands:",
7496
+ "/status - Show current status",
7497
+ "/projects - List available projects",
7498
+ "/project <index|id> - Select active project",
7499
+ "/new - Start a new thread in active project",
7500
+ "/threads - List recent threads in active project",
7501
+ "/thread <index|id> - Switch active thread",
7502
+ "/interrupt - Interrupt active turn",
7503
+ "/input <request-id> qid=answer;... - Reply to request-user-input",
7504
+ "",
7505
+ "Send any normal text message to run it in the active thread."
7506
+ ];
7507
+ await this.sendSimpleMessage(chatId, lines.join("\n"));
7508
+ }
7509
+ async sendStatus(chatId) {
7510
+ const session = this.getOrCreateSession(chatId);
7511
+ const status = this.getStatus();
7512
+ const lines = [
7513
+ `Bridge: ${status.state}`,
7514
+ `Streaming mode: ${status.streamMode}`,
7515
+ "Allowed chats: all private chats",
7516
+ `Bot: ${status.botUsername ? `@${status.botUsername}` : "(unknown)"}`,
7517
+ `Active project: ${session.projectId ?? "(not selected)"}`,
7518
+ `Active thread: ${session.threadId ?? "(none)"}`,
7519
+ `Processing: ${session.processing ? "yes" : "no"}`
7520
+ ];
7521
+ if (status.lastError) {
7522
+ lines.push(`Last error: ${status.lastError}`);
7523
+ }
7524
+ await this.sendSimpleMessage(chatId, lines.join("\n"));
7525
+ }
7526
+ async sendProjects(chatId) {
7527
+ const projects = await this.options.listProjects();
7528
+ const session = this.getOrCreateSession(chatId);
7529
+ if (projects.length === 0) {
7530
+ await this.sendSimpleMessage(chatId, "No projects found in this desktop app.");
7531
+ return;
7532
+ }
7533
+ session.lastProjectOrder = projects.map((entry) => entry.id);
7534
+ const lines = ["Projects:"];
7535
+ projects.forEach((project, index) => {
7536
+ const isActive = project.id === session.projectId;
7537
+ lines.push(
7538
+ `${index + 1}. ${project.name}${isActive ? " (active)" : ""} [${project.id}]`
7539
+ );
7540
+ });
7541
+ await this.sendSimpleMessage(chatId, lines.join("\n"));
7542
+ }
7543
+ async selectProject(chatId, args) {
7544
+ const session = this.getOrCreateSession(chatId);
7545
+ const projects = await this.options.listProjects();
7546
+ if (projects.length === 0) {
7547
+ await this.sendSimpleMessage(chatId, "No projects available.");
7548
+ return;
7549
+ }
7550
+ const selected = this.resolveProjectSelection(args, projects, session.lastProjectOrder);
7551
+ if (!selected) {
7552
+ await this.sendSimpleMessage(
7553
+ chatId,
7554
+ "Project not found. Use /projects, then /project <index|id>."
7555
+ );
7556
+ return;
7557
+ }
7558
+ if (session.projectId !== selected.id) {
7559
+ await this.detachConversationListener(session);
7560
+ session.projectId = selected.id;
7561
+ session.threadId = null;
7562
+ session.activeTurnId = null;
7563
+ session.processing = false;
7564
+ session.completedTextCandidate = null;
7565
+ this.resetStream(session);
7566
+ }
7567
+ await this.sendSimpleMessage(chatId, `Active project set to: ${selected.name}.`);
7568
+ }
7569
+ resolveProjectSelection(args, projects, order) {
7570
+ const input = args.trim();
7571
+ if (!input) {
7572
+ return null;
7573
+ }
7574
+ const numeric = Number(input);
7575
+ if (Number.isInteger(numeric) && numeric >= 1) {
7576
+ if (order.length > 0 && numeric <= order.length) {
7577
+ const id = order[numeric - 1];
7578
+ return projects.find((entry) => entry.id === id) ?? null;
7579
+ }
7580
+ if (numeric <= projects.length) {
7581
+ return projects[numeric - 1] ?? null;
7582
+ }
7583
+ }
7584
+ return projects.find((entry) => entry.id === input) ?? null;
7585
+ }
7586
+ async createNewThread(chatId) {
7587
+ const session = this.getOrCreateSession(chatId);
7588
+ const project = await this.ensureSessionProject(session, chatId);
7589
+ if (!project) {
7590
+ return;
7591
+ }
7592
+ const appServer = this.options.getAppServer();
7593
+ if (!appServer) {
7594
+ await this.sendSimpleMessage(chatId, "Backend unavailable.");
7595
+ return;
7596
+ }
7597
+ try {
7598
+ const response = await appServer.threadStart({
7599
+ workspaceId: project.id,
7600
+ workspace_id: project.id,
7601
+ cwd: project.path
7602
+ });
7603
+ const threadId = this.extractThreadIdFromResponse(response);
7604
+ if (!threadId) {
7605
+ await this.sendSimpleMessage(chatId, "Thread started, but thread id was missing.");
7606
+ return;
7607
+ }
7608
+ session.threadId = threadId;
7609
+ session.activeTurnId = null;
7610
+ session.processing = false;
7611
+ session.completedTextCandidate = null;
7612
+ this.resetStream(session);
7613
+ await this.attachConversationListener(session, project.id, threadId);
7614
+ await this.sendSimpleMessage(chatId, `Started new thread: ${threadId}`);
7615
+ } catch (error) {
7616
+ await this.sendSimpleMessage(
7617
+ chatId,
7618
+ `Failed to start a new thread: ${error instanceof Error ? error.message : String(error)}`
7619
+ );
7620
+ }
7621
+ }
7622
+ async listThreads(chatId) {
7623
+ const session = this.getOrCreateSession(chatId);
7624
+ const project = await this.ensureSessionProject(session, chatId);
7625
+ if (!project) {
7626
+ return;
7627
+ }
7628
+ const appServer = this.options.getAppServer();
7629
+ if (!appServer) {
7630
+ await this.sendSimpleMessage(chatId, "Backend unavailable.");
7631
+ return;
7632
+ }
7633
+ try {
7634
+ const response = await appServer.listConversations({
7635
+ cwd: project.path,
7636
+ limit: 20,
7637
+ sortKey: "updated_at",
7638
+ archived: false
7639
+ });
7640
+ const threads = this.extractThreadList(response);
7641
+ if (threads.length === 0) {
7642
+ await this.sendSimpleMessage(chatId, "No threads found for this project.");
7643
+ return;
7644
+ }
7645
+ session.lastThreadOrder = threads.map((entry) => entry.id);
7646
+ const lines = ["Threads:"];
7647
+ threads.forEach((thread, index) => {
7648
+ const isActive = thread.id === session.threadId;
7649
+ const title = thread.title || "(untitled)";
7650
+ lines.push(`${index + 1}. ${title}${isActive ? " (active)" : ""} [${thread.id}]`);
7651
+ });
7652
+ await this.sendSimpleMessage(chatId, lines.join("\n"));
7653
+ } catch (error) {
7654
+ await this.sendSimpleMessage(
7655
+ chatId,
7656
+ `Failed to list threads: ${error instanceof Error ? error.message : String(error)}`
7657
+ );
7658
+ }
7659
+ }
7660
+ async selectThread(chatId, args) {
7661
+ const session = this.getOrCreateSession(chatId);
7662
+ const project = await this.ensureSessionProject(session, chatId);
7663
+ if (!project) {
7664
+ return;
7665
+ }
7666
+ const appServer = this.options.getAppServer();
7667
+ if (!appServer) {
7668
+ await this.sendSimpleMessage(chatId, "Backend unavailable.");
7669
+ return;
7670
+ }
7671
+ const input = args.trim();
7672
+ if (!input) {
7673
+ await this.sendSimpleMessage(chatId, "Usage: /thread <index|id>");
7674
+ return;
7675
+ }
7676
+ let threadId = "";
7677
+ const numeric = Number(input);
7678
+ if (Number.isInteger(numeric) && numeric >= 1) {
7679
+ if (session.lastThreadOrder.length > 0 && numeric <= session.lastThreadOrder.length) {
7680
+ threadId = session.lastThreadOrder[numeric - 1] ?? "";
7681
+ }
7682
+ }
7683
+ if (!threadId) {
7684
+ threadId = input;
7685
+ }
7686
+ try {
7687
+ await appServer.threadResume({
7688
+ workspaceId: project.id,
7689
+ workspace_id: project.id,
7690
+ threadId
7691
+ });
7692
+ session.threadId = threadId;
7693
+ session.activeTurnId = null;
7694
+ session.processing = false;
7695
+ session.completedTextCandidate = null;
7696
+ this.resetStream(session);
7697
+ await this.attachConversationListener(session, project.id, threadId);
7698
+ await this.sendSimpleMessage(chatId, `Active thread set to: ${threadId}`);
7699
+ } catch (error) {
7700
+ await this.sendSimpleMessage(
7701
+ chatId,
7702
+ `Failed to switch thread: ${error instanceof Error ? error.message : String(error)}`
7703
+ );
7704
+ }
7705
+ }
7706
+ async interruptTurn(chatId) {
7707
+ const session = this.getOrCreateSession(chatId);
7708
+ const project = await this.ensureSessionProject(session, chatId);
7709
+ if (!project || !session.threadId) {
7710
+ await this.sendSimpleMessage(chatId, "No active thread to interrupt.");
7711
+ return;
7712
+ }
7713
+ const appServer = this.options.getAppServer();
7714
+ if (!appServer) {
7715
+ await this.sendSimpleMessage(chatId, "Backend unavailable.");
7716
+ return;
7717
+ }
7718
+ const turnId = session.activeTurnId || "pending";
7719
+ try {
7720
+ await appServer.interruptTurn({
7721
+ workspaceId: project.id,
7722
+ workspace_id: project.id,
7723
+ threadId: session.threadId,
7724
+ turnId
7725
+ });
7726
+ session.processing = false;
7727
+ session.activeTurnId = null;
7728
+ this.resetStream(session);
7729
+ await this.sendSimpleMessage(chatId, "Interrupt requested.");
7730
+ } catch (error) {
7731
+ await this.sendSimpleMessage(
7732
+ chatId,
7733
+ `Failed to interrupt turn: ${error instanceof Error ? error.message : String(error)}`
7734
+ );
7735
+ }
7736
+ }
7737
+ async submitUserInput(chatId, args) {
7738
+ const appServer = this.options.getAppServer();
7739
+ if (!appServer) {
7740
+ await this.sendSimpleMessage(chatId, "Backend unavailable.");
7741
+ return;
7742
+ }
7743
+ const trimmed = args.trim();
7744
+ if (!trimmed) {
7745
+ await this.sendSimpleMessage(
7746
+ chatId,
7747
+ "Usage: /input <request-id> qid=answer; qid2=answer"
7748
+ );
7749
+ return;
7750
+ }
7751
+ const [requestTokenPart, ...restParts] = trimmed.split(/\s+/g);
7752
+ let requestIdToken = requestTokenPart.trim();
7753
+ let bodyText = restParts.join(" ").trim();
7754
+ if (!requestIdToken || requestIdToken.includes("=")) {
7755
+ const pendingForChat = Array.from(this.pendingUserInputByRequestId.values()).filter(
7756
+ (entry) => entry.chatId === chatId
7757
+ );
7758
+ if (pendingForChat.length !== 1) {
7759
+ await this.sendSimpleMessage(
7760
+ chatId,
7761
+ "Request id is required. Use /input <request-id> qid=answer;..."
7762
+ );
7763
+ return;
7764
+ }
7765
+ requestIdToken = String(pendingForChat[0].requestId);
7766
+ bodyText = trimmed;
7767
+ }
7768
+ const pending = this.pendingUserInputByRequestId.get(requestIdToken);
7769
+ if (!pending || pending.chatId !== chatId) {
7770
+ await this.sendSimpleMessage(chatId, "Unknown or expired request id.");
7771
+ return;
7772
+ }
7773
+ const answers = this.parseUserInputAnswers(bodyText, pending.params);
7774
+ if (!answers) {
7775
+ await this.sendSimpleMessage(
7776
+ chatId,
7777
+ "Could not parse answers. Format: /input <request-id> qid=answer; qid2=answer"
7778
+ );
7779
+ return;
7780
+ }
7781
+ try {
7782
+ await appServer.respondToServerRequest(pending.requestId, {
7783
+ answers
7784
+ });
7785
+ this.pendingUserInputByRequestId.delete(requestIdToken);
7786
+ await this.sendSimpleMessage(chatId, "Input submitted.");
7787
+ } catch (error) {
7788
+ await this.sendSimpleMessage(
7789
+ chatId,
7790
+ `Failed to submit input: ${error instanceof Error ? error.message : String(error)}`
7791
+ );
7792
+ }
7793
+ }
7794
+ parseUserInputAnswers(bodyText, params) {
7795
+ const questionsRaw = Array.isArray(params.questions) ? params.questions : [];
7796
+ const questions = questionsRaw.map((entry) => asRecord2(entry)).filter((entry) => asTrimmedString(entry.id));
7797
+ const questionIds = new Set(questions.map((entry) => asTrimmedString(entry.id)));
7798
+ const answers = {};
7799
+ const segments = bodyText.split(";").map((segment) => segment.trim()).filter(Boolean);
7800
+ if (segments.length === 0) {
7801
+ if (questions.length === 1 && bodyText.trim()) {
7802
+ const id = asTrimmedString(questions[0].id);
7803
+ answers[id] = { answers: [bodyText.trim()] };
7804
+ return answers;
7805
+ }
7806
+ return null;
7807
+ }
7808
+ for (const segment of segments) {
7809
+ const eqIndex = segment.indexOf("=");
7810
+ if (eqIndex <= 0) {
7811
+ if (questions.length === 1) {
7812
+ const id = asTrimmedString(questions[0].id);
7813
+ answers[id] = { answers: [segment.trim()] };
7814
+ continue;
7815
+ }
7816
+ return null;
7817
+ }
7818
+ const key = segment.slice(0, eqIndex).trim();
7819
+ const value = segment.slice(eqIndex + 1).trim();
7820
+ if (!key || !value || !questionIds.has(key)) {
7821
+ return null;
7822
+ }
7823
+ const splitValues = value.split(",").map((entry) => entry.trim()).filter(Boolean);
7824
+ answers[key] = {
7825
+ answers: splitValues.length > 0 ? splitValues : [value]
7826
+ };
7827
+ }
7828
+ return Object.keys(answers).length > 0 ? answers : null;
7829
+ }
7830
+ async handlePromptText(chatId, text) {
7831
+ const session = this.getOrCreateSession(chatId);
7832
+ if (session.processing) {
7833
+ await this.sendSimpleMessage(
7834
+ chatId,
7835
+ "A response is already in progress. Wait for completion or use /interrupt."
7836
+ );
7837
+ return;
7838
+ }
7839
+ const project = await this.ensureSessionProject(session, chatId);
7840
+ if (!project) {
7841
+ return;
7842
+ }
7843
+ const threadId = session.threadId || await this.startThreadForSession(chatId, session, project);
7844
+ if (!threadId) {
7845
+ return;
7846
+ }
7847
+ const appServer = this.options.getAppServer();
7848
+ if (!appServer) {
7849
+ await this.sendSimpleMessage(chatId, "Backend unavailable.");
7850
+ return;
7851
+ }
7852
+ try {
7853
+ session.completedTextCandidate = null;
7854
+ this.resetStream(session);
7855
+ const response = await appServer.sendUserMessage({
7856
+ workspaceId: project.id,
7857
+ workspace_id: project.id,
7858
+ threadId,
7859
+ thread_id: threadId,
7860
+ conversationId: threadId,
7861
+ conversation_id: threadId,
7862
+ text
7863
+ });
7864
+ const turnId = this.extractTurnIdFromResponse(response);
7865
+ session.processing = true;
7866
+ session.activeTurnId = turnId || null;
7867
+ this.ensureTypingIndicator(session);
7868
+ } catch (error) {
7869
+ await this.sendSimpleMessage(
7870
+ chatId,
7871
+ `Failed to send message: ${error instanceof Error ? error.message : String(error)}`
7872
+ );
7873
+ }
7874
+ }
7875
+ async startThreadForSession(chatId, session, project) {
7876
+ const appServer = this.options.getAppServer();
7877
+ if (!appServer) {
7878
+ await this.sendSimpleMessage(chatId, "Backend unavailable.");
7879
+ return null;
7880
+ }
7881
+ try {
7882
+ const response = await appServer.threadStart({
7883
+ workspaceId: project.id,
7884
+ workspace_id: project.id,
7885
+ cwd: project.path
7886
+ });
7887
+ const threadId = this.extractThreadIdFromResponse(response);
7888
+ if (!threadId) {
7889
+ await this.sendSimpleMessage(chatId, "Could not create thread (missing thread id).");
7890
+ return null;
7891
+ }
7892
+ session.threadId = threadId;
7893
+ await this.attachConversationListener(session, project.id, threadId);
7894
+ return threadId;
7895
+ } catch (error) {
7896
+ await this.sendSimpleMessage(
7897
+ chatId,
7898
+ `Failed to create thread: ${error instanceof Error ? error.message : String(error)}`
7899
+ );
7900
+ return null;
7901
+ }
7902
+ }
7903
+ async ensureSessionProject(session, chatId) {
7904
+ if (session.projectId) {
7905
+ const existing = await this.options.getProjectById(session.projectId);
7906
+ if (existing) {
7907
+ return existing;
7908
+ }
7909
+ session.projectId = null;
7910
+ session.threadId = null;
7911
+ }
7912
+ const defaultProjectId = await this.resolveDefaultProjectId();
7913
+ if (defaultProjectId) {
7914
+ const preferred = await this.options.getProjectById(defaultProjectId).catch(() => null);
7915
+ if (preferred) {
7916
+ session.projectId = preferred.id;
7917
+ return preferred;
7918
+ }
7919
+ }
7920
+ const projects = await this.options.listProjects();
7921
+ if (projects.length === 0) {
7922
+ await this.sendSimpleMessage(chatId, "No projects found. Add a project in desktop settings first.");
7923
+ return null;
7924
+ }
7925
+ const connected = projects.find((entry) => entry.connected);
7926
+ const selected = connected ?? projects[0];
7927
+ session.projectId = selected.id;
7928
+ session.lastProjectOrder = projects.map((entry) => entry.id);
7929
+ return selected;
7930
+ }
7931
+ async resolveDefaultProjectId() {
7932
+ const resolver = this.options.getDefaultProjectId;
7933
+ if (!resolver) {
7934
+ return null;
7935
+ }
7936
+ try {
7937
+ const value = await resolver();
7938
+ const normalized = asTrimmedString(value);
7939
+ return normalized || null;
7940
+ } catch {
7941
+ return null;
7942
+ }
7943
+ }
7944
+ extractThreadIdFromResponse(value) {
7945
+ const record = asRecord2(value);
7946
+ const result = asRecord2(record.result);
7947
+ const thread = asRecord2(result.thread ?? record.thread);
7948
+ return asTrimmedString(
7949
+ thread.id ?? thread.threadId ?? thread.thread_id ?? result.threadId ?? result.thread_id ?? record.threadId ?? record.thread_id ?? result.conversationId ?? result.conversation_id ?? ""
7950
+ );
7951
+ }
7952
+ extractTurnIdFromResponse(value) {
7953
+ const record = asRecord2(value);
7954
+ const result = asRecord2(record.result);
7955
+ const turn = asRecord2(result.turn ?? record.turn);
7956
+ return asTrimmedString(
7957
+ turn.id ?? turn.turnId ?? turn.turn_id ?? result.turnId ?? result.turn_id ?? record.turnId ?? record.turn_id ?? ""
7958
+ );
7959
+ }
7960
+ extractThreadList(value) {
7961
+ const root = asRecord2(value);
7962
+ const primary = Array.isArray(value) ? value : Array.isArray(root.data) ? root.data : Array.isArray(root.threads) ? root.threads : Array.isArray(root.items) ? root.items : [];
7963
+ const result = [];
7964
+ for (const entry of primary) {
7965
+ const thread = asRecord2(entry);
7966
+ const id = asTrimmedString(
7967
+ thread.id ?? thread.threadId ?? thread.thread_id ?? thread.conversationId ?? thread.conversation_id ?? ""
7968
+ );
7969
+ if (!id) {
7970
+ continue;
7971
+ }
7972
+ const title = asTrimmedString(
7973
+ thread.name ?? thread.title ?? thread.preview ?? thread.summary ?? thread.path ?? ""
7974
+ ) || "(untitled)";
7975
+ result.push({ id, title });
7976
+ }
7977
+ return result;
7978
+ }
7979
+ getOrCreateSession(chatId) {
7980
+ const existing = this.sessionsByChatId.get(chatId);
7981
+ if (existing) {
7982
+ return existing;
7983
+ }
7984
+ const session = {
7985
+ chatId,
7986
+ projectId: null,
7987
+ threadId: null,
7988
+ listenerSubscriptionId: null,
7989
+ activeTurnId: null,
7990
+ processing: false,
7991
+ finalizeInFlight: false,
7992
+ lastProjectOrder: [],
7993
+ lastThreadOrder: [],
7994
+ completedTextCandidate: null,
7995
+ recentFinalizedTurnIds: [],
7996
+ recentFinalSignatures: [],
7997
+ recentIncomingMessageIds: [],
7998
+ streamFlushPromise: null,
7999
+ streamFlushPending: false,
8000
+ lastDeliveredFinalText: null,
8001
+ lastDeliveredFinalAtMs: 0,
8002
+ typingTimer: null,
8003
+ stream: null
8004
+ };
8005
+ this.sessionsByChatId.set(chatId, session);
8006
+ this.status.activeChats = this.sessionsByChatId.size;
8007
+ return session;
8008
+ }
8009
+ async resetSession(chatId) {
8010
+ const session = this.sessionsByChatId.get(chatId);
8011
+ if (!session) {
8012
+ return;
8013
+ }
8014
+ await this.detachConversationListener(session);
8015
+ this.stopTypingIndicator(session);
8016
+ if (session.threadId) {
8017
+ this.chatIdByThreadId.delete(session.threadId);
8018
+ }
8019
+ this.sessionsByChatId.delete(chatId);
8020
+ this.status.activeChats = this.sessionsByChatId.size;
8021
+ }
8022
+ async attachConversationListener(session, projectId, threadId) {
8023
+ const appServer = this.options.getAppServer();
8024
+ if (!appServer) {
8025
+ return;
8026
+ }
8027
+ await this.detachConversationListener(session);
8028
+ try {
8029
+ const response = await appServer.addConversationListener({
8030
+ conversationId: threadId,
8031
+ workspaceId: projectId,
8032
+ workspace_id: projectId
8033
+ });
8034
+ const responseRecord = asRecord2(response);
8035
+ const subscriptionId = asTrimmedString(responseRecord.subscriptionId);
8036
+ session.listenerSubscriptionId = subscriptionId || null;
8037
+ session.threadId = threadId;
8038
+ this.chatIdByThreadId.set(threadId, session.chatId);
8039
+ } catch (error) {
8040
+ logWarn("[telegram-bridge] failed to attach conversation listener", error);
8041
+ }
8042
+ }
8043
+ async detachConversationListener(session) {
8044
+ const appServer = this.options.getAppServer();
8045
+ if (!appServer) {
8046
+ session.listenerSubscriptionId = null;
8047
+ return;
8048
+ }
8049
+ const subscriptionId = session.listenerSubscriptionId;
8050
+ session.listenerSubscriptionId = null;
8051
+ if (!subscriptionId) {
8052
+ return;
8053
+ }
8054
+ try {
8055
+ await appServer.removeConversationListener({
8056
+ subscriptionId
8057
+ });
8058
+ } catch (error) {
8059
+ logWarn("[telegram-bridge] failed to remove conversation listener", error);
8060
+ }
8061
+ }
8062
+ async detachAllConversationListeners() {
8063
+ const sessions = Array.from(this.sessionsByChatId.values());
8064
+ await Promise.all(sessions.map((session) => this.detachConversationListener(session)));
8065
+ }
8066
+ clearSessionState() {
8067
+ for (const session of this.sessionsByChatId.values()) {
8068
+ this.resetStream(session);
8069
+ }
8070
+ this.sessionsByChatId.clear();
8071
+ this.chatIdByThreadId.clear();
8072
+ this.pendingApprovalById.clear();
8073
+ this.pendingUserInputByRequestId.clear();
8074
+ this.recentDeltaBySignature.clear();
8075
+ this.lastSendAtByChatId.clear();
8076
+ this.status.activeChats = 0;
8077
+ }
8078
+ attachAppServerListeners() {
8079
+ if (this.appServerListenersBound) {
8080
+ return;
8081
+ }
8082
+ const appServer = this.options.getAppServer();
8083
+ if (!appServer) {
8084
+ this.status.state = "error";
8085
+ this.status.lastError = "Codex app-server is unavailable.";
8086
+ return;
8087
+ }
8088
+ appServer.on("codex:notification", this.handleCodexNotification);
8089
+ appServer.on("codex:event", this.handleCodexEvent);
8090
+ appServer.on("codex:exec-command-request", this.handleExecApprovalRequest);
8091
+ appServer.on("codex:apply-patch-request", this.handlePatchApprovalRequest);
8092
+ appServer.on("codex:server-request", this.handleServerRequest);
8093
+ this.appServerListenersBound = true;
8094
+ }
8095
+ detachAppServerListeners() {
8096
+ if (!this.appServerListenersBound) {
8097
+ return;
8098
+ }
8099
+ const appServer = this.options.getAppServer();
8100
+ if (!appServer) {
8101
+ this.appServerListenersBound = false;
8102
+ return;
8103
+ }
8104
+ appServer.off("codex:notification", this.handleCodexNotification);
8105
+ appServer.off("codex:event", this.handleCodexEvent);
8106
+ appServer.off("codex:exec-command-request", this.handleExecApprovalRequest);
8107
+ appServer.off("codex:apply-patch-request", this.handlePatchApprovalRequest);
8108
+ appServer.off("codex:server-request", this.handleServerRequest);
8109
+ this.appServerListenersBound = false;
8110
+ }
8111
+ async handleNotification(payload) {
8112
+ if (!this.running) {
8113
+ return;
8114
+ }
8115
+ const method = asTrimmedString(payload.method);
8116
+ if (!method) {
8117
+ return;
8118
+ }
8119
+ const params = asRecord2(payload.params);
8120
+ const threadId = extractThreadId(params);
8121
+ if (!threadId) {
8122
+ return;
8123
+ }
8124
+ const session = this.resolveSessionByThreadId(threadId);
8125
+ if (!session) {
8126
+ return;
8127
+ }
8128
+ if (method === "turn/started") {
8129
+ const turnId = extractTurnId(params);
8130
+ session.processing = true;
8131
+ if (turnId) {
8132
+ session.activeTurnId = turnId;
8133
+ }
8134
+ this.ensureTypingIndicator(session);
8135
+ return;
8136
+ }
8137
+ if (method === "item/agentMessage/delta") {
8138
+ const delta = extractDeltaFromNotification(params);
8139
+ if (!delta) {
8140
+ return;
8141
+ }
8142
+ if (this.isDuplicateDelta(`${threadId}|${delta}`)) {
8143
+ return;
8144
+ }
8145
+ this.appendAssistantDelta(session, delta);
8146
+ return;
8147
+ }
8148
+ if (method === "item/completed") {
8149
+ const text = extractAssistantTextFromItemCompleted(params);
8150
+ if (text.trim()) {
8151
+ session.completedTextCandidate = text;
8152
+ }
8153
+ return;
8154
+ }
8155
+ if (method === "turn/completed") {
8156
+ await this.finalizeTurn(session);
8157
+ return;
8158
+ }
8159
+ if (method === "error") {
8160
+ if (!isRetryableTurnErrorPayload(params)) {
8161
+ const errorMessage = asTrimmedString(asRecord2(params.error).message) || asTrimmedString(params.message) || "Turn failed.";
8162
+ await this.sendSimpleMessage(session.chatId, `Turn error: ${errorMessage}`);
8163
+ }
8164
+ await this.finalizeTurn(session);
8165
+ return;
8166
+ }
8167
+ }
8168
+ async handleEvent(payload) {
8169
+ if (!this.running) {
8170
+ return;
8171
+ }
8172
+ const method = asTrimmedString(payload.method);
8173
+ if (!method.startsWith("codex/event/")) {
8174
+ return;
8175
+ }
8176
+ const params = asRecord2(payload.params);
8177
+ const threadId = extractThreadId(params);
8178
+ if (!threadId) {
8179
+ return;
8180
+ }
8181
+ const session = this.resolveSessionByThreadId(threadId);
8182
+ if (!session) {
8183
+ return;
8184
+ }
8185
+ const msg = asRecord2(params.msg);
8186
+ const msgType = asTrimmedString(msg.type).toLowerCase();
8187
+ if (msgType === "agent_message_delta" || msgType === "agent_message_content_delta") {
8188
+ const delta = extractText(msg.delta ?? msg.message ?? msg.content ?? msg.value);
8189
+ if (!delta) {
8190
+ return;
8191
+ }
8192
+ if (this.isDuplicateDelta(`${threadId}|${delta}`)) {
8193
+ return;
8194
+ }
8195
+ this.appendAssistantDelta(session, delta);
8196
+ return;
8197
+ }
8198
+ if (msgType === "agent_message") {
8199
+ const text = extractText(msg.message ?? msg.content ?? msg.text ?? msg.value);
8200
+ if (text.trim()) {
8201
+ session.completedTextCandidate = text;
8202
+ }
8203
+ return;
8204
+ }
8205
+ if (msgType === "task_started") {
8206
+ session.processing = true;
8207
+ const turnId = extractTurnId(params);
8208
+ if (turnId) {
8209
+ session.activeTurnId = turnId;
8210
+ }
8211
+ this.ensureTypingIndicator(session);
8212
+ return;
8213
+ }
8214
+ if (msgType === "task_complete" || msgType === "turn_aborted") {
8215
+ await this.finalizeTurn(session);
8216
+ return;
8217
+ }
8218
+ if (msgType === "error" || msgType === "stream_error") {
8219
+ if (!isRetryableTurnErrorPayload(msg)) {
8220
+ const errorMessage = asTrimmedString(msg.message) || asTrimmedString(msg.error) || "Turn failed.";
8221
+ await this.sendSimpleMessage(session.chatId, `Turn error: ${errorMessage}`);
8222
+ }
8223
+ await this.finalizeTurn(session);
8224
+ }
8225
+ }
8226
+ resolveSessionByThreadId(threadId) {
8227
+ const chatId = this.chatIdByThreadId.get(threadId);
8228
+ if (!chatId) {
8229
+ return null;
8230
+ }
8231
+ const session = this.sessionsByChatId.get(chatId);
8232
+ if (!session || session.threadId !== threadId) {
8233
+ return null;
8234
+ }
8235
+ return session;
8236
+ }
8237
+ appendAssistantDelta(session, delta) {
8238
+ if (!session.stream) {
8239
+ session.stream = {
8240
+ draftId: (0, import_node_crypto4.randomUUID)().slice(0, 12),
8241
+ buffer: "",
8242
+ lastSent: "",
8243
+ flushTimer: null,
8244
+ messageId: null
8245
+ };
8246
+ }
8247
+ const nextBuffer = mergeStreamDeltaBuffer(session.stream.buffer, delta);
8248
+ if (nextBuffer === session.stream.buffer) {
8249
+ return;
8250
+ }
8251
+ session.stream.buffer = nextBuffer;
8252
+ session.processing = true;
8253
+ this.ensureTypingIndicator(session);
8254
+ this.scheduleDraftFlush(session, false);
8255
+ }
8256
+ scheduleDraftFlush(session, force) {
8257
+ if (!session.stream) {
8258
+ return;
8259
+ }
8260
+ if (force) {
8261
+ if (session.stream.flushTimer) {
8262
+ clearTimeout(session.stream.flushTimer);
8263
+ session.stream.flushTimer = null;
8264
+ }
8265
+ if (session.streamFlushPromise) {
8266
+ session.streamFlushPending = true;
8267
+ return;
8268
+ }
8269
+ void this.flushDraft(session, true);
8270
+ return;
8271
+ }
8272
+ if (session.streamFlushPromise) {
8273
+ session.streamFlushPending = true;
8274
+ return;
8275
+ }
8276
+ if (session.stream.flushTimer) {
8277
+ return;
8278
+ }
8279
+ session.stream.flushTimer = setTimeout(() => {
8280
+ if (session.stream) {
8281
+ session.stream.flushTimer = null;
8282
+ }
8283
+ void this.flushDraft(session, false);
8284
+ }, TELEGRAM_STREAM_FLUSH_MS);
8285
+ }
8286
+ async flushDraft(session, force) {
8287
+ if (!this.running) {
8288
+ return;
8289
+ }
8290
+ if (session.streamFlushPromise) {
8291
+ if (force) {
8292
+ session.streamFlushPending = true;
8293
+ }
8294
+ return;
8295
+ }
8296
+ const currentStream = session.stream;
8297
+ if (!currentStream) {
8298
+ return;
8299
+ }
8300
+ const draftId = currentStream.draftId;
8301
+ const rawText = currentStream.buffer;
8302
+ if (!rawText.trim()) {
8303
+ return;
8304
+ }
8305
+ const draftText = rawText.length <= TELEGRAM_MAX_MESSAGE_LENGTH ? rawText : rawText.slice(rawText.length - TELEGRAM_MAX_MESSAGE_LENGTH);
8306
+ if (!force && draftText === currentStream.lastSent) {
8307
+ return;
8308
+ }
8309
+ const runFlush = async () => {
8310
+ if (this.draftStreamingEnabled) {
8311
+ try {
8312
+ await this.sendDraftMessage(session.chatId, draftText, draftId);
8313
+ const activeStream = this.getStreamByDraftId(session, draftId);
8314
+ if (activeStream) {
8315
+ activeStream.lastSent = draftText;
8316
+ }
8317
+ this.status.streamMode = this.getCurrentStreamMode();
8318
+ return;
8319
+ } catch (error) {
8320
+ const message = error instanceof Error ? error.message : String(error);
8321
+ if (this.isDraftStreamingUnsupportedError(message)) {
8322
+ this.draftStreamingEnabled = false;
8323
+ this.status.streamMode = this.getCurrentStreamMode();
8324
+ logWarn(
8325
+ "[telegram-bridge] sendMessageDraft unavailable, falling back to message edit streaming",
8326
+ message
8327
+ );
8328
+ } else {
8329
+ logWarn("[telegram-bridge] failed to flush draft", message);
8330
+ return;
8331
+ }
8332
+ }
8333
+ }
8334
+ if (!this.messageStreamingEnabled) {
8335
+ return;
8336
+ }
8337
+ try {
8338
+ await this.sendOrEditStreamMessage(session, draftText, draftId);
8339
+ const activeStream = this.getStreamByDraftId(session, draftId);
8340
+ if (activeStream) {
8341
+ activeStream.lastSent = draftText;
8342
+ }
8343
+ this.status.streamMode = this.getCurrentStreamMode();
8344
+ } catch (error) {
8345
+ const message = error instanceof Error ? error.message : String(error);
8346
+ if (this.isMessageStreamingUnsupportedError(message)) {
8347
+ this.messageStreamingEnabled = false;
8348
+ this.status.streamMode = this.getCurrentStreamMode();
8349
+ logWarn("[telegram-bridge] edit streaming unavailable, disabling streaming", message);
8350
+ return;
8351
+ }
8352
+ logWarn("[telegram-bridge] failed to flush message-edit stream", message);
8353
+ }
8354
+ };
8355
+ const flushPromise = runFlush();
8356
+ session.streamFlushPromise = flushPromise;
8357
+ try {
8358
+ await flushPromise;
8359
+ } finally {
8360
+ if (session.streamFlushPromise === flushPromise) {
8361
+ session.streamFlushPromise = null;
8362
+ }
8363
+ if (session.streamFlushPending && this.running && session.stream) {
8364
+ session.streamFlushPending = false;
8365
+ void this.flushDraft(session, false);
8366
+ }
8367
+ }
8368
+ }
8369
+ async finalizeTurn(session) {
8370
+ if (session.finalizeInFlight) {
8371
+ return;
8372
+ }
8373
+ if (!session.processing && !session.stream?.buffer && !session.completedTextCandidate) {
8374
+ return;
8375
+ }
8376
+ session.finalizeInFlight = true;
8377
+ try {
8378
+ await this.flushDraft(session, true);
8379
+ if (session.streamFlushPromise) {
8380
+ await session.streamFlushPromise.catch(() => void 0);
8381
+ }
8382
+ if (session.streamFlushPending && session.stream) {
8383
+ session.streamFlushPending = false;
8384
+ await this.flushDraft(session, true);
8385
+ if (session.streamFlushPromise) {
8386
+ await session.streamFlushPromise.catch(() => void 0);
8387
+ }
8388
+ }
8389
+ const finalText = (session.completedTextCandidate ?? session.stream?.buffer ?? "").trim();
8390
+ const turnId = session.activeTurnId;
8391
+ const alreadyFinalizedTurn = turnId ? session.recentFinalizedTurnIds.includes(turnId) : false;
8392
+ const now = nowMs();
8393
+ const duplicateByRecentText = finalText.length > 0 && session.lastDeliveredFinalText === finalText && now - session.lastDeliveredFinalAtMs <= TELEGRAM_FINAL_TEXT_DEDUPE_MS;
8394
+ const duplicateByFinalSignature = this.isDuplicateFinalSignature(session, finalText, now);
8395
+ if (finalText && !alreadyFinalizedTurn && !duplicateByRecentText && !duplicateByFinalSignature) {
8396
+ const chunks = splitMessage(finalText, TELEGRAM_MAX_MESSAGE_LENGTH);
8397
+ let chunkStartIndex = 0;
8398
+ const streamMessageId = session.stream?.messageId ?? null;
8399
+ if (streamMessageId !== null && chunks.length > 0) {
8400
+ const reused = await this.tryFinalizeStreamMessage(session.chatId, streamMessageId, chunks[0]);
8401
+ if (!reused) {
8402
+ await this.sendSimpleMessage(session.chatId, chunks[0]);
8403
+ }
8404
+ chunkStartIndex = 1;
8405
+ }
8406
+ for (let index = chunkStartIndex; index < chunks.length; index += 1) {
8407
+ await this.sendSimpleMessage(session.chatId, chunks[index]);
8408
+ }
8409
+ session.lastDeliveredFinalText = finalText;
8410
+ session.lastDeliveredFinalAtMs = now;
8411
+ this.recordFinalSignature(session, finalText, now);
8412
+ if (turnId) {
8413
+ session.recentFinalizedTurnIds.push(turnId);
8414
+ if (session.recentFinalizedTurnIds.length > TELEGRAM_RECENT_FINALIZED_TURNS_MAX) {
8415
+ session.recentFinalizedTurnIds.splice(
8416
+ 0,
8417
+ session.recentFinalizedTurnIds.length - TELEGRAM_RECENT_FINALIZED_TURNS_MAX
8418
+ );
8419
+ }
8420
+ }
8421
+ }
8422
+ } finally {
8423
+ session.processing = false;
8424
+ session.activeTurnId = null;
8425
+ session.completedTextCandidate = null;
8426
+ this.resetStream(session);
8427
+ session.finalizeInFlight = false;
8428
+ }
8429
+ }
8430
+ resetStream(session) {
8431
+ this.stopTypingIndicator(session);
8432
+ if (session.stream?.flushTimer) {
8433
+ clearTimeout(session.stream.flushTimer);
8434
+ }
8435
+ session.streamFlushPending = false;
8436
+ session.streamFlushPromise = null;
8437
+ session.stream = null;
8438
+ }
8439
+ ensureTypingIndicator(session) {
8440
+ if (!this.running || !session.processing) {
8441
+ return;
8442
+ }
8443
+ if (session.typingTimer) {
8444
+ return;
8445
+ }
8446
+ void this.sendTypingAction(session.chatId);
8447
+ session.typingTimer = setInterval(() => {
8448
+ if (!this.running || !session.processing) {
8449
+ this.stopTypingIndicator(session);
8450
+ return;
8451
+ }
8452
+ void this.sendTypingAction(session.chatId);
8453
+ }, TELEGRAM_TYPING_HEARTBEAT_MS);
8454
+ }
8455
+ stopTypingIndicator(session) {
8456
+ if (session.typingTimer) {
8457
+ clearInterval(session.typingTimer);
8458
+ session.typingTimer = null;
8459
+ }
8460
+ }
8461
+ isDraftStreamingUnsupportedError(message) {
8462
+ const lower = message.toLowerCase();
8463
+ return lower.includes("sendmessagedraft") || lower.includes("method not found") || lower.includes("there is no method") || lower.includes("text must be non-empty") || lower.includes("random_id_invalid") || lower.includes("random id invalid") || lower.includes("not found");
8464
+ }
8465
+ isMessageStreamingUnsupportedError(message) {
8466
+ const lower = message.toLowerCase();
8467
+ return lower.includes("editmessagetext") && (lower.includes("method not found") || lower.includes("there is no method"));
8468
+ }
8469
+ async sendOrEditStreamMessage(session, text, draftId) {
8470
+ const stream = this.getStreamByDraftId(session, draftId);
8471
+ if (!stream) {
8472
+ return;
8473
+ }
8474
+ const existingMessageId = stream.messageId;
8475
+ if (existingMessageId !== null) {
8476
+ const edited = await this.tryEditMessageText(session.chatId, existingMessageId, text);
8477
+ if (edited) {
8478
+ return;
8479
+ }
8480
+ const activeStream = this.getStreamByDraftId(session, draftId);
8481
+ if (activeStream) {
8482
+ activeStream.messageId = null;
8483
+ }
8484
+ }
8485
+ const sent = await this.sendSimpleMessage(session.chatId, text);
8486
+ const messageId = this.extractTelegramMessageId(sent);
8487
+ if (messageId !== null) {
8488
+ const activeStream = this.getStreamByDraftId(session, draftId);
8489
+ if (activeStream) {
8490
+ activeStream.messageId = messageId;
8491
+ }
8492
+ }
8493
+ }
8494
+ getStreamByDraftId(session, draftId) {
8495
+ const stream = session.stream;
8496
+ if (!stream || stream.draftId !== draftId) {
8497
+ return null;
8498
+ }
8499
+ return stream;
8500
+ }
8501
+ async tryFinalizeStreamMessage(chatId, messageId, text) {
8502
+ return this.tryEditMessageText(chatId, messageId, text);
8503
+ }
8504
+ async tryEditMessageText(chatId, messageId, text) {
8505
+ try {
8506
+ await this.editMessageText(chatId, messageId, text);
8507
+ return true;
8508
+ } catch (error) {
8509
+ const message = error instanceof Error ? error.message : String(error);
8510
+ const lower = message.toLowerCase();
8511
+ if (lower.includes("message is not modified")) {
8512
+ return true;
8513
+ }
8514
+ if (lower.includes("message to edit not found") || lower.includes("message can't be edited") || lower.includes("message cant be edited")) {
8515
+ return false;
8516
+ }
8517
+ throw error;
8518
+ }
8519
+ }
8520
+ isDuplicateIncomingMessage(session, messageId) {
8521
+ if (session.recentIncomingMessageIds.includes(messageId)) {
8522
+ return true;
8523
+ }
8524
+ session.recentIncomingMessageIds.push(messageId);
8525
+ if (session.recentIncomingMessageIds.length > TELEGRAM_RECENT_INCOMING_MESSAGES_MAX) {
8526
+ session.recentIncomingMessageIds.splice(
8527
+ 0,
8528
+ session.recentIncomingMessageIds.length - TELEGRAM_RECENT_INCOMING_MESSAGES_MAX
8529
+ );
8530
+ }
8531
+ return false;
8532
+ }
8533
+ buildFinalSignature(session, finalText) {
8534
+ const normalized = finalText.trim();
8535
+ if (!normalized) {
8536
+ return "";
8537
+ }
8538
+ const threadKey = session.threadId ?? "no-thread";
8539
+ const digest = (0, import_node_crypto4.createHash)("sha1").update(normalized).digest("hex");
8540
+ return `${threadKey}|${digest}`;
8541
+ }
8542
+ isDuplicateFinalSignature(session, finalText, now) {
8543
+ const signature = this.buildFinalSignature(session, finalText);
8544
+ if (!signature) {
8545
+ return false;
8546
+ }
8547
+ let duplicated = false;
8548
+ const kept = [];
8549
+ for (const entry of session.recentFinalSignatures) {
8550
+ if (now - entry.at > TELEGRAM_FINAL_SIGNATURE_DEDUPE_MS) {
8551
+ continue;
8552
+ }
8553
+ if (entry.signature === signature) {
8554
+ duplicated = true;
8555
+ }
8556
+ kept.push(entry);
8557
+ }
8558
+ session.recentFinalSignatures = kept;
8559
+ return duplicated;
8560
+ }
8561
+ recordFinalSignature(session, finalText, now) {
8562
+ const signature = this.buildFinalSignature(session, finalText);
8563
+ if (!signature) {
8564
+ return;
8565
+ }
8566
+ const kept = session.recentFinalSignatures.filter(
8567
+ (entry) => entry.signature !== signature && now - entry.at <= TELEGRAM_FINAL_SIGNATURE_DEDUPE_MS
8568
+ );
8569
+ kept.push({ signature, at: now });
8570
+ if (kept.length > TELEGRAM_RECENT_FINAL_SIGNATURES_MAX) {
8571
+ kept.splice(0, kept.length - TELEGRAM_RECENT_FINAL_SIGNATURES_MAX);
8572
+ }
8573
+ session.recentFinalSignatures = kept;
8574
+ }
8575
+ isDuplicateDelta(signature) {
8576
+ const now = nowMs();
8577
+ const previous = this.recentDeltaBySignature.get(signature) ?? 0;
8578
+ this.recentDeltaBySignature.set(signature, now);
8579
+ if (this.recentDeltaBySignature.size > 2048) {
8580
+ for (const [key, at] of this.recentDeltaBySignature) {
8581
+ if (now - at > 5e3) {
8582
+ this.recentDeltaBySignature.delete(key);
8583
+ }
8584
+ }
8585
+ }
8586
+ return previous > 0 && now - previous <= 500;
8587
+ }
8588
+ async handleApprovalRequest(kind, requestToken, params) {
8589
+ if (!this.running) {
8590
+ return;
8591
+ }
8592
+ const chatId = this.resolveChatIdForRequest(params);
8593
+ if (!chatId) {
8594
+ return;
8595
+ }
8596
+ const actionId = (0, import_node_crypto4.randomUUID)().replace(/-/g, "").slice(0, 10);
8597
+ this.pendingApprovalById.set(actionId, {
8598
+ id: actionId,
8599
+ kind,
8600
+ chatId,
8601
+ requestToken,
8602
+ createdAtMs: nowMs()
8603
+ });
8604
+ const threadId = extractThreadId(params);
8605
+ const title = kind === "exec" ? "Command approval required" : "Patch approval required";
8606
+ const detail = kind === "exec" ? extractCommandText(params) : summarizePatchRequest(params);
8607
+ const lines = [title];
8608
+ if (threadId) {
8609
+ lines.push(`Thread: ${threadId}`);
8610
+ }
8611
+ if (detail) {
8612
+ lines.push(`Detail: ${detail}`);
8613
+ }
8614
+ await this.sendSimpleMessage(chatId, lines.join("\n"), {
8615
+ reply_markup: {
8616
+ inline_keyboard: [
8617
+ [
8618
+ { text: "Approve", callback_data: `cu:ap:${actionId}:approved` },
8619
+ { text: "Approve session", callback_data: `cu:ap:${actionId}:approved_for_session` }
8620
+ ],
8621
+ [
8622
+ { text: "Deny", callback_data: `cu:ap:${actionId}:denied` },
8623
+ { text: "Abort", callback_data: `cu:ap:${actionId}:abort` }
8624
+ ]
8625
+ ]
8626
+ }
8627
+ });
8628
+ }
8629
+ async handleUserInputRequest(payload) {
8630
+ if (!this.running) {
8631
+ return;
8632
+ }
8633
+ const method = asTrimmedString(payload.method).toLowerCase();
8634
+ if (!method.includes("requestuserinput")) {
8635
+ return;
8636
+ }
8637
+ const params = asRecord2(payload.params);
8638
+ const chatId = this.resolveChatIdForRequest(params);
8639
+ if (!chatId) {
8640
+ return;
8641
+ }
8642
+ const token = String(payload.requestId);
8643
+ this.pendingUserInputByRequestId.set(token, {
8644
+ chatId,
8645
+ requestId: payload.requestId,
8646
+ method: payload.method,
8647
+ params,
8648
+ createdAtMs: nowMs()
8649
+ });
8650
+ const questions = Array.isArray(params.questions) ? params.questions.map((entry) => asRecord2(entry)).filter((entry) => asTrimmedString(entry.id)) : [];
8651
+ const lines = [
8652
+ "Input required.",
8653
+ `Request ID: ${token}`,
8654
+ "Reply with: /input <request-id> qid=answer; qid2=answer"
8655
+ ];
8656
+ if (questions.length > 0) {
8657
+ lines.push("");
8658
+ for (const question of questions) {
8659
+ const qid = asTrimmedString(question.id);
8660
+ const qtext = asTrimmedString(question.question) || asTrimmedString(question.header) || "Question";
8661
+ lines.push(`${qid}: ${qtext}`);
8662
+ const options = Array.isArray(question.options) ? question.options.map((entry) => asRecord2(entry)).map((entry) => asTrimmedString(entry.label)).filter(Boolean) : [];
8663
+ if (options.length > 0) {
8664
+ lines.push(`Options: ${options.join(", ")}`);
8665
+ }
8666
+ }
8667
+ }
8668
+ await this.sendSimpleMessage(chatId, lines.join("\n"));
8669
+ }
8670
+ resolveChatIdForRequest(params) {
8671
+ const threadId = extractThreadId(params);
8672
+ if (threadId) {
8673
+ const mapped = this.chatIdByThreadId.get(threadId);
8674
+ if (mapped) {
8675
+ return mapped;
8676
+ }
8677
+ }
8678
+ const sessions = Array.from(this.sessionsByChatId.values());
8679
+ if (sessions.length === 1) {
8680
+ return sessions[0].chatId;
8681
+ }
8682
+ return null;
8683
+ }
8684
+ prunePendingActions() {
8685
+ const threshold = nowMs() - TELEGRAM_PENDING_TTL_MS;
8686
+ for (const [id, entry] of this.pendingApprovalById) {
8687
+ if (entry.createdAtMs < threshold) {
8688
+ this.pendingApprovalById.delete(id);
8689
+ }
8690
+ }
8691
+ for (const [requestId, entry] of this.pendingUserInputByRequestId) {
8692
+ if (entry.createdAtMs < threshold) {
8693
+ this.pendingUserInputByRequestId.delete(requestId);
8694
+ }
8695
+ }
8696
+ }
8697
+ async answerCallbackQuery(callbackQueryId, text) {
8698
+ const token = this.settings.token;
8699
+ if (!token) {
8700
+ return;
8701
+ }
8702
+ await this.callTelegramApi(
8703
+ token,
8704
+ "answerCallbackQuery",
8705
+ {
8706
+ callback_query_id: callbackQueryId,
8707
+ text: text || void 0
8708
+ },
8709
+ {
8710
+ retry429: false
8711
+ }
8712
+ ).catch(() => void 0);
8713
+ }
8714
+ async sendTypingAction(chatId) {
8715
+ const token = this.settings.token;
8716
+ if (!token) {
8717
+ return;
8718
+ }
8719
+ await this.callTelegramApi(
8720
+ token,
8721
+ "sendChatAction",
8722
+ {
8723
+ chat_id: chatId,
8724
+ action: "typing"
8725
+ },
8726
+ {
8727
+ retry429: false
8728
+ }
8729
+ ).catch(() => void 0);
8730
+ }
8731
+ async sendDraftMessage(chatId, text, draftId) {
8732
+ const token = this.settings.token;
8733
+ if (!token) {
8734
+ return;
8735
+ }
8736
+ await this.sendWithRateLimit(chatId);
8737
+ await this.callTelegramApi(
8738
+ token,
8739
+ "sendMessageDraft",
8740
+ {
8741
+ chat_id: chatId,
8742
+ text,
8743
+ message_text: text,
8744
+ draft_id: draftId
8745
+ },
8746
+ {
8747
+ retry429: true
8748
+ }
8749
+ );
8750
+ }
8751
+ async sendSimpleMessage(chatId, text, extra) {
8752
+ const token = this.settings.token;
8753
+ if (!token) {
8754
+ return null;
8755
+ }
8756
+ const payload = {
8757
+ chat_id: chatId,
8758
+ text,
8759
+ ...extra
8760
+ };
8761
+ await this.sendWithRateLimit(chatId);
8762
+ const result = await this.callTelegramApi(token, "sendMessage", payload, {
8763
+ retry429: true
8764
+ });
8765
+ return asRecord2(result);
8766
+ }
8767
+ async editMessageText(chatId, messageId, text) {
8768
+ const token = this.settings.token;
8769
+ if (!token) {
8770
+ return;
8771
+ }
8772
+ await this.sendWithRateLimit(chatId);
8773
+ await this.callTelegramApi(
8774
+ token,
8775
+ "editMessageText",
8776
+ {
8777
+ chat_id: chatId,
8778
+ message_id: messageId,
8779
+ text
8780
+ },
8781
+ {
8782
+ retry429: true
8783
+ }
8784
+ );
8785
+ }
8786
+ extractTelegramMessageId(value) {
8787
+ const record = asRecord2(value);
8788
+ return asNumber(record.message_id ?? record.messageId);
8789
+ }
8790
+ async sendWithRateLimit(chatId) {
8791
+ const lastSend = this.lastSendAtByChatId.get(chatId) ?? 0;
8792
+ const now = nowMs();
8793
+ const waitMs = TELEGRAM_MIN_SEND_INTERVAL_MS - (now - lastSend);
8794
+ if (waitMs > 0) {
8795
+ await this.sleep(waitMs);
8796
+ }
8797
+ this.lastSendAtByChatId.set(chatId, nowMs());
8798
+ }
8799
+ async callTelegramApi(token, method, payload, options) {
8800
+ const url = `${TELEGRAM_API_BASE}/bot${token}/${method}`;
8801
+ const execute = async () => {
8802
+ const controller = new AbortController();
8803
+ const timeoutId = setTimeout(() => {
8804
+ controller.abort();
8805
+ }, TELEGRAM_API_TIMEOUT_MS);
8806
+ const signal = options?.signal;
8807
+ let externalAbortListener = null;
8808
+ if (signal) {
8809
+ if (signal.aborted) {
8810
+ clearTimeout(timeoutId);
8811
+ controller.abort();
8812
+ } else {
8813
+ externalAbortListener = () => controller.abort();
8814
+ signal.addEventListener("abort", externalAbortListener, { once: true });
8815
+ }
8816
+ }
8817
+ try {
8818
+ const response2 = await fetch(url, {
8819
+ method: "POST",
8820
+ headers: {
8821
+ "Content-Type": "application/json"
8822
+ },
8823
+ body: JSON.stringify(payload),
8824
+ signal: controller.signal
8825
+ });
8826
+ const json = await response2.json();
8827
+ return json;
8828
+ } finally {
8829
+ clearTimeout(timeoutId);
8830
+ if (signal && externalAbortListener) {
8831
+ signal.removeEventListener("abort", externalAbortListener);
8832
+ }
8833
+ }
8834
+ };
8835
+ const response = await execute();
8836
+ if (response.ok) {
8837
+ return response.result;
8838
+ }
8839
+ const retryAfterSeconds = response.parameters?.retry_after;
8840
+ if (options?.retry429 && response.error_code === 429 && typeof retryAfterSeconds === "number" && retryAfterSeconds > 0) {
8841
+ await this.sleep(retryAfterSeconds * 1e3);
8842
+ const retried = await execute();
8843
+ if (retried.ok) {
8844
+ return retried.result;
8845
+ }
8846
+ throw new Error(retried.description || `${method} failed (code ${retried.error_code ?? "unknown"})`);
8847
+ }
8848
+ throw new Error(response.description || `${method} failed (code ${response.error_code ?? "unknown"})`);
8849
+ }
8850
+ sleep(ms) {
8851
+ return new Promise((resolve) => {
8852
+ setTimeout(resolve, Math.max(0, Math.floor(ms)));
8853
+ });
8854
+ }
8855
+ buildRuntimeLockPath(token) {
8856
+ const tokenHash = (0, import_node_crypto4.createHash)("sha1").update(token).digest("hex").slice(0, 16);
8857
+ return import_node_path13.default.join(getUserDataDir(), `${TELEGRAM_BRIDGE_LOCK_FILE_PREFIX}-${tokenHash}.lock`);
8858
+ }
8859
+ async acquireRuntimeLock(token) {
8860
+ if (this.runtimeLockPath) {
8861
+ return;
8862
+ }
8863
+ const lockPath = this.buildRuntimeLockPath(token);
8864
+ await import_promises4.default.mkdir(import_node_path13.default.dirname(lockPath), { recursive: true });
8865
+ const payload = JSON.stringify(
8866
+ {
8867
+ pid: process.pid,
8868
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
8869
+ },
8870
+ null,
8871
+ 2
8872
+ );
8873
+ try {
8874
+ await import_promises4.default.writeFile(lockPath, payload, { encoding: "utf8", flag: "wx" });
8875
+ this.runtimeLockPath = lockPath;
8876
+ return;
8877
+ } catch (error) {
8878
+ const code = error.code;
8879
+ if (code !== "EEXIST") {
8880
+ throw error;
8881
+ }
8882
+ }
8883
+ const existingPid = await this.readLockPid(lockPath);
8884
+ if (existingPid && existingPid !== process.pid && this.isProcessAlive(existingPid)) {
8885
+ throw new Error(
8886
+ `Telegram bridge already running in another process (pid ${existingPid}). Stop the other instance to avoid duplicate replies.`
8887
+ );
8888
+ }
8889
+ await import_promises4.default.unlink(lockPath).catch(() => void 0);
8890
+ await import_promises4.default.writeFile(lockPath, payload, { encoding: "utf8", flag: "wx" });
8891
+ this.runtimeLockPath = lockPath;
8892
+ }
8893
+ async releaseRuntimeLock() {
8894
+ const lockPath = this.runtimeLockPath;
8895
+ this.runtimeLockPath = null;
8896
+ if (!lockPath) {
8897
+ return;
8898
+ }
8899
+ await import_promises4.default.unlink(lockPath).catch(() => void 0);
8900
+ }
8901
+ async readLockPid(lockPath) {
8902
+ try {
8903
+ const raw = await import_promises4.default.readFile(lockPath, "utf8");
8904
+ const parsed = JSON.parse(raw);
8905
+ const pid = asNumber(parsed.pid);
8906
+ if (pid === null || pid <= 0) {
8907
+ return null;
8908
+ }
8909
+ return Math.trunc(pid);
8910
+ } catch {
8911
+ return null;
8912
+ }
8913
+ }
8914
+ isProcessAlive(pid) {
8915
+ try {
8916
+ process.kill(pid, 0);
8917
+ return true;
8918
+ } catch (error) {
8919
+ const code = error.code;
8920
+ return code === "EPERM";
8921
+ }
8922
+ }
8923
+ };
8924
+ function createTelegramBridge(options) {
8925
+ return new TelegramBridge(options);
8926
+ }
8927
+
8928
+ // src/daemon.ts
8929
+ function hasFlag(args, flag) {
8930
+ return args.includes(flag);
8931
+ }
8932
+ function stripFlags(args) {
8933
+ return args.filter((arg) => !arg.startsWith("-"));
8934
+ }
8935
+ function parseStringFlag(flags, name) {
8936
+ const explicit = flags.find((flag) => flag.startsWith(`${name}=`));
8937
+ if (!explicit) {
8938
+ return null;
8939
+ }
8940
+ const value = explicit.slice(`${name}=`.length).trim();
8941
+ return value.length > 0 ? value : "";
8942
+ }
8943
+ function printDaemonHelp(version) {
8944
+ console.log(`CodexUse CLI v${version}
8945
+
8946
+ Usage:
8947
+ codexuse daemon start --telegram-bot-token=<token> [--project-path=/abs/path]
8948
+
8949
+ Environment:
8950
+ CODEXUSE_TELEGRAM_BOT_TOKEN Telegram bot token fallback
8951
+
8952
+ Notes:
8953
+ - Runs Codex app-server + Telegram bridge in headless mode (no desktop app).
8954
+ - --project-path auto-registers that path and makes it the default project for new chats.
8955
+ - Use on VPS/Linux with a process manager (systemd, pm2, supervisord).
8956
+ - Stop with Ctrl+C or SIGTERM.
8957
+ `);
8958
+ }
8959
+ async function ensureProjectRegistered(projectPath) {
8960
+ const resolvedPath = import_node_path14.default.resolve(projectPath);
8961
+ const projects = await listParityWorkspaces();
8962
+ const existing = projects.find((entry) => import_node_path14.default.resolve(entry.path) === resolvedPath) ?? null;
8963
+ if (!existing) {
8964
+ await assertProjectCreationAllowed(projects);
8965
+ const added = await addParityWorkspace(resolvedPath);
8966
+ console.log(`Added project: ${added.name} (${added.path})`);
8967
+ return added;
8968
+ }
8969
+ if (!existing.connected) {
8970
+ const connected = await connectParityWorkspace(existing.id);
8971
+ console.log(`Connected project: ${connected.name} (${connected.path})`);
8972
+ return connected;
8973
+ }
8974
+ return existing;
8975
+ }
8976
+ async function resolveProEnabled() {
8977
+ const cached = await licenseService.getCachedStatus().catch(() => null);
8978
+ if (cached?.isPro) {
8979
+ return true;
8980
+ }
8981
+ const refreshed = await licenseService.getStatus().catch(() => cached);
8982
+ return Boolean(refreshed?.isPro);
8983
+ }
8984
+ function parseProcessExitPayload(payload) {
8985
+ if (!payload || typeof payload !== "object") {
8986
+ return "";
8987
+ }
8988
+ const record = payload;
8989
+ const code = typeof record.code === "number" ? record.code : null;
8990
+ const signal = typeof record.signal === "string" ? record.signal : null;
8991
+ if (code !== null && signal) {
8992
+ return `code=${code}, signal=${signal}`;
8993
+ }
8994
+ if (code !== null) {
8995
+ return `code=${code}`;
8996
+ }
8997
+ if (signal) {
8998
+ return `signal=${signal}`;
8999
+ }
9000
+ return "";
9001
+ }
9002
+ async function runDaemonStart(options) {
9003
+ if (!options.telegramBotToken) {
9004
+ throw new Error(
9005
+ "Telegram bot token is required. Pass --telegram-bot-token=<token> or set CODEXUSE_TELEGRAM_BOT_TOKEN."
9006
+ );
9007
+ }
9008
+ let preferredProject = null;
9009
+ if (options.projectPath) {
9010
+ preferredProject = await ensureProjectRegistered(options.projectPath);
9011
+ }
9012
+ const appServer = new CodexAppServer();
9013
+ await appServer.initialize({
9014
+ name: "codexuse-cli-daemon",
9015
+ title: "CodexUse CLI Daemon",
9016
+ version: options.version
9017
+ });
9018
+ const bridge = createTelegramBridge({
9019
+ getAppServer: () => appServer,
9020
+ listProjects: () => listParityWorkspaces(),
9021
+ getProjectById: (id) => getParityWorkspaceById(id),
9022
+ isProEnabled: () => resolveProEnabled(),
9023
+ getDefaultProjectId: () => preferredProject?.id ?? null
9024
+ });
9025
+ await bridge.applyRuntimeSettings({
9026
+ telegramBridgeEnabled: true,
9027
+ telegramBotToken: options.telegramBotToken
9028
+ });
9029
+ const status = bridge.getStatus();
9030
+ if (!status.running) {
9031
+ await bridge.dispose().catch(() => void 0);
9032
+ await appServer.stop().catch(() => void 0);
9033
+ throw new Error(status.lastError || "Telegram bridge failed to start.");
9034
+ }
9035
+ const botLabel = status.botUsername ? `@${status.botUsername}` : "(unknown)";
9036
+ const projects = await listParityWorkspaces().catch(() => []);
9037
+ console.log(`Daemon started. Telegram bot: ${botLabel}`);
9038
+ console.log(`Streaming mode: ${status.streamMode}`);
9039
+ if (preferredProject) {
9040
+ console.log(`Default project: ${preferredProject.name} [${preferredProject.id}]`);
9041
+ }
9042
+ console.log(`Projects available: ${projects.length}`);
9043
+ console.log("Running until SIGINT/SIGTERM...");
9044
+ let stopping = false;
9045
+ let resolveWait = null;
9046
+ const stop = async (reason, exitCode) => {
9047
+ if (stopping) {
9048
+ return;
9049
+ }
9050
+ stopping = true;
9051
+ process.exitCode = exitCode;
9052
+ console.log(`Stopping daemon (${reason})...`);
9053
+ await bridge.dispose().catch((error) => {
9054
+ const message = error instanceof Error ? error.message : String(error);
9055
+ console.error(`Failed to stop Telegram bridge cleanly: ${message}`);
9056
+ });
9057
+ await appServer.stop().catch((error) => {
9058
+ const message = error instanceof Error ? error.message : String(error);
9059
+ console.error(`Failed to stop app-server cleanly: ${message}`);
9060
+ });
9061
+ resolveWait?.();
9062
+ };
9063
+ const onSigInt = () => {
9064
+ void stop("SIGINT", 0);
9065
+ };
9066
+ const onSigTerm = () => {
9067
+ void stop("SIGTERM", 0);
9068
+ };
9069
+ const onProcessExited = (payload) => {
9070
+ const suffix = parseProcessExitPayload(payload);
9071
+ const detail = suffix ? ` (${suffix})` : "";
9072
+ console.error(`Codex app-server exited unexpectedly${detail}.`);
9073
+ void stop("app-server-exited", 1);
9074
+ };
9075
+ process.once("SIGINT", onSigInt);
9076
+ process.once("SIGTERM", onSigTerm);
9077
+ appServer.once("codex:process-exited", onProcessExited);
9078
+ try {
9079
+ await new Promise((resolve) => {
9080
+ resolveWait = resolve;
9081
+ });
9082
+ } finally {
9083
+ process.off("SIGINT", onSigInt);
9084
+ process.off("SIGTERM", onSigTerm);
9085
+ appServer.off("codex:process-exited", onProcessExited);
9086
+ }
9087
+ }
9088
+ async function handleDaemonCommand(args, version) {
9089
+ const flags = args.filter((arg) => arg.startsWith("-"));
9090
+ const params = stripFlags(args);
9091
+ const sub = params[0];
9092
+ if (!sub || hasFlag(flags, "--help") || hasFlag(flags, "-h")) {
9093
+ printDaemonHelp(version);
9094
+ return;
9095
+ }
9096
+ switch (sub) {
9097
+ case "start": {
9098
+ const tokenFlag = parseStringFlag(flags, "--telegram-bot-token") ?? parseStringFlag(flags, "--bot-token");
9099
+ const tokenEnv = process.env.CODEXUSE_TELEGRAM_BOT_TOKEN?.trim() || null;
9100
+ const telegramBotToken = tokenFlag && tokenFlag.trim() || tokenEnv || "";
9101
+ const projectPath = parseStringFlag(flags, "--project-path") ?? parseStringFlag(flags, "--project") ?? null;
9102
+ await runDaemonStart({
9103
+ telegramBotToken,
9104
+ projectPath,
9105
+ version
9106
+ });
9107
+ return;
9108
+ }
9109
+ default:
9110
+ printDaemonHelp(version);
9111
+ return;
9112
+ }
9113
+ }
9114
+
9115
+ // src/index.ts
9116
+ var VERSION = true ? "2.5.5" : "0.0.0";
9117
+ var cliStorageReadyPromise = null;
9118
+ async function ensureCliStorageReady() {
9119
+ if (cliStorageReadyPromise) {
9120
+ return cliStorageReadyPromise;
9121
+ }
9122
+ cliStorageReadyPromise = (async () => {
9123
+ await initializeAppState(getUserDataDir());
9124
+ const migrated = await runStorageMigrationV1();
9125
+ if (migrated.migration.status === "pending_local_storage") {
9126
+ await importLegacyLocalStorageOnce({});
9127
+ }
9128
+ const state = await getAppState();
9129
+ if (state.migration.status !== "complete") {
9130
+ throw new Error(
9131
+ `Storage migration is not complete (status: ${state.migration.status}). CLI cannot continue until migration succeeds.`
9132
+ );
9133
+ }
9134
+ })().catch((error) => {
9135
+ cliStorageReadyPromise = null;
9136
+ throw error;
9137
+ });
9138
+ return cliStorageReadyPromise;
9139
+ }
9140
+ function printHelp() {
9141
+ console.log(`CodexUse CLI v${VERSION}
9142
+
9143
+ Usage:
9144
+ codexuse profile list [--no-usage] [--compact]
9145
+ codexuse profile current
9146
+ codexuse profile add <name> [--skip-login] [--device-auth] [--login=browser|device]
9147
+ codexuse profile refresh <name> [--skip-login] [--device-auth] [--login=browser|device]
9148
+ codexuse profile switch <name>
9149
+ codexuse profile autoroll [--threshold=50-100] [--dry-run] [--watch] [--interval=seconds]
9150
+ codexuse profile delete <name>
9151
+ codexuse profile rename <old> <new>
9152
+
9153
+ codexuse license status [--refresh]
9154
+ codexuse license activate <license-key>
9155
+
9156
+ codexuse sync status
9157
+ codexuse sync pull
9158
+ codexuse sync push
9159
+
9160
+ codexuse daemon start --telegram-bot-token=<token> [--project-path=/abs/path]
9161
+
9162
+ Flags:
9163
+ -h, --help Show help
9164
+ -v, --version Show version
9165
+ --no-usage Skip rate-limit usage fetch
9166
+ --compact Names only
9167
+ --device-auth Use device auth for Codex login
9168
+ --login=MODE Login mode: browser | device
9169
+ --threshold=NN Auto-roll switch threshold percent (50-100)
9170
+ --watch Keep checking and auto-switch when threshold is reached
9171
+ --interval=SEC Watch interval in seconds (default: 30)
9172
+ --dry-run Print planned switch without changing active profile
9173
+ --telegram-bot-token=TOKEN Telegram bot token for daemon mode
9174
+ --project-path=PATH Optional path to auto-register and set as default project in daemon mode
9175
+ `);
9176
+ }
9177
+ function hasFlag2(args, flag) {
9178
+ return args.includes(flag);
9179
+ }
9180
+ function stripFlags2(args) {
9181
+ return args.filter((arg) => !arg.startsWith("-"));
9182
+ }
9183
+ function parseLoginMode(flags) {
9184
+ if (flags.includes("--device-auth")) return "device";
9185
+ const explicit = flags.find((flag) => flag.startsWith("--login="));
9186
+ if (!explicit) return null;
9187
+ const value = explicit.split("=")[1];
9188
+ if (value === "browser" || value === "device") {
9189
+ return value;
9190
+ }
9191
+ return null;
9192
+ }
9193
+ var DEFAULT_AUTOROLL_INTERVAL_SECONDS = 30;
9194
+ var DEFAULT_AUTOROLL_THRESHOLD = 95;
9195
+ function parseNumericFlag(flags, name) {
9196
+ const explicit = flags.find((flag) => flag.startsWith(`${name}=`));
9197
+ if (!explicit) {
9198
+ return null;
9199
+ }
9200
+ const raw = explicit.slice(`${name}=`.length);
9201
+ const value = Number.parseFloat(raw);
9202
+ return Number.isFinite(value) ? value : Number.NaN;
9203
+ }
9204
+ function parseIntegerFlag(flags, name) {
9205
+ const explicit = flags.find((flag) => flag.startsWith(`${name}=`));
9206
+ if (!explicit) {
9207
+ return null;
9208
+ }
9209
+ const raw = explicit.slice(`${name}=`.length);
9210
+ const value = Number.parseInt(raw, 10);
9211
+ return Number.isFinite(value) ? value : Number.NaN;
9212
+ }
9213
+ function formatProfileLabel(name, displayName) {
9214
+ if (displayName && displayName.trim() && displayName !== name) {
9215
+ return `${displayName} (${name})`;
9216
+ }
9217
+ return name;
9218
+ }
9219
+ function toTitleCase(value) {
9220
+ return value.replace(/\w\S*/g, (word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase());
9221
+ }
9222
+ function formatShortDate(value) {
5142
9223
  if (!value) return null;
5143
9224
  const date = new Date(value);
5144
9225
  if (Number.isNaN(date.getTime())) return null;
@@ -5602,9 +9683,9 @@ function delay(ms) {
5602
9683
  }
5603
9684
  async function handleProfile(args) {
5604
9685
  const flags = args.filter((arg) => arg.startsWith("-"));
5605
- const params = stripFlags(args);
9686
+ const params = stripFlags2(args);
5606
9687
  const sub = params[0];
5607
- if (!sub || hasFlag(flags, "--help") || hasFlag(flags, "-h")) {
9688
+ if (!sub || hasFlag2(flags, "--help") || hasFlag2(flags, "-h")) {
5608
9689
  printHelp();
5609
9690
  return;
5610
9691
  }
@@ -5617,8 +9698,8 @@ async function handleProfile(args) {
5617
9698
  console.log("No profiles found.");
5618
9699
  return;
5619
9700
  }
5620
- const compact = hasFlag(flags, "--compact");
5621
- const withUsage = !hasFlag(flags, "--no-usage");
9701
+ const compact = hasFlag2(flags, "--compact");
9702
+ const withUsage = !hasFlag2(flags, "--no-usage");
5622
9703
  let usageMap = null;
5623
9704
  if (withUsage) {
5624
9705
  const codexPath = resolveCodexBinary();
@@ -5664,7 +9745,7 @@ async function handleProfile(args) {
5664
9745
  throw new Error("Profile name is required.");
5665
9746
  }
5666
9747
  await assertProfileCreationAllowed(manager);
5667
- if (!hasFlag(flags, "--skip-login")) {
9748
+ if (!hasFlag2(flags, "--skip-login")) {
5668
9749
  const loginMode = resolveLoginMode(parseLoginMode(flags));
5669
9750
  await runCodexLogin(loginMode);
5670
9751
  }
@@ -5678,7 +9759,7 @@ async function handleProfile(args) {
5678
9759
  if (!name) {
5679
9760
  throw new Error("Profile name is required.");
5680
9761
  }
5681
- if (!hasFlag(flags, "--skip-login")) {
9762
+ if (!hasFlag2(flags, "--skip-login")) {
5682
9763
  const loginMode = resolveLoginMode(parseLoginMode(flags));
5683
9764
  await runCodexLogin(loginMode);
5684
9765
  }
@@ -5698,8 +9779,8 @@ async function handleProfile(args) {
5698
9779
  }
5699
9780
  case "autoroll":
5700
9781
  case "auto-roll": {
5701
- const watch = hasFlag(flags, "--watch");
5702
- const dryRun = hasFlag(flags, "--dry-run");
9782
+ const watch = hasFlag2(flags, "--watch");
9783
+ const dryRun = hasFlag2(flags, "--dry-run");
5703
9784
  const settings = await getStoredAutoRollSettings().catch(() => null);
5704
9785
  const fallbackThreshold = typeof settings?.switchThreshold === "number" && Number.isFinite(settings.switchThreshold) ? Math.round(settings.switchThreshold) : DEFAULT_AUTOROLL_THRESHOLD;
5705
9786
  const threshold = parseAutoRollThreshold(flags, fallbackThreshold);
@@ -5754,15 +9835,15 @@ async function handleProfile(args) {
5754
9835
  }
5755
9836
  async function handleLicense(args) {
5756
9837
  const flags = args.filter((arg) => arg.startsWith("-"));
5757
- const params = stripFlags(args);
9838
+ const params = stripFlags2(args);
5758
9839
  const sub = params[0];
5759
- if (!sub || hasFlag(flags, "--help") || hasFlag(flags, "-h")) {
9840
+ if (!sub || hasFlag2(flags, "--help") || hasFlag2(flags, "-h")) {
5760
9841
  printHelp();
5761
9842
  return;
5762
9843
  }
5763
9844
  switch (sub) {
5764
9845
  case "status": {
5765
- const forceRefresh = hasFlag(flags, "--refresh");
9846
+ const forceRefresh = hasFlag2(flags, "--refresh");
5766
9847
  const status = await licenseService.getStatus({ forceRefresh });
5767
9848
  console.log(`Tier: ${status.tier}`);
5768
9849
  console.log(`State: ${status.state}`);
@@ -5811,9 +9892,9 @@ function printSyncResult(result) {
5811
9892
  }
5812
9893
  async function handleSync(args) {
5813
9894
  const flags = args.filter((arg) => arg.startsWith("-"));
5814
- const params = stripFlags(args);
9895
+ const params = stripFlags2(args);
5815
9896
  const sub = params[0];
5816
- if (!sub || hasFlag(flags, "--help") || hasFlag(flags, "-h")) {
9897
+ if (!sub || hasFlag2(flags, "--help") || hasFlag2(flags, "-h")) {
5817
9898
  printHelp();
5818
9899
  return;
5819
9900
  }
@@ -5866,11 +9947,15 @@ async function handleSync(args) {
5866
9947
  }
5867
9948
  async function main() {
5868
9949
  const args = process.argv.slice(2);
5869
- if (args.length === 0 || hasFlag(args, "--help") || hasFlag(args, "-h")) {
9950
+ if (args.length === 0) {
9951
+ printHelp();
9952
+ return;
9953
+ }
9954
+ if (args[0]?.startsWith("-") && (hasFlag2(args, "--help") || hasFlag2(args, "-h"))) {
5870
9955
  printHelp();
5871
9956
  return;
5872
9957
  }
5873
- if (hasFlag(args, "--version") || hasFlag(args, "-v")) {
9958
+ if (args[0]?.startsWith("-") && (hasFlag2(args, "--version") || hasFlag2(args, "-v"))) {
5874
9959
  console.log(VERSION);
5875
9960
  return;
5876
9961
  }
@@ -5889,6 +9974,10 @@ async function main() {
5889
9974
  await ensureCliStorageReady();
5890
9975
  await handleSync(rest);
5891
9976
  return;
9977
+ case "daemon":
9978
+ await ensureCliStorageReady();
9979
+ await handleDaemonCommand(rest, VERSION);
9980
+ return;
5892
9981
  default:
5893
9982
  printHelp();
5894
9983
  return;