@roamcode.ai/server 1.0.18 → 1.0.20

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.d.ts CHANGED
@@ -2455,6 +2455,46 @@ type CodexAttachContext = Pick<AttachSpawnOptions, "baseUrl" | "token" | "mcpScr
2455
2455
  declare function buildCodexArgs(context: ProviderProcessContext, attach?: CodexAttachContext): string[];
2456
2456
  declare function createCodexProvider(options: CreateCodexProviderOptions): AgentProvider;
2457
2457
 
2458
+ declare const CODEX_EXECUTABLE_PROBE_TIMEOUT_MS = 5000;
2459
+ declare const OPENAI_CODE_SIGNING_TEAM_ID = "2DC432GLL2";
2460
+ type CodexExecutableProbe = {
2461
+ state: "ready";
2462
+ version: string;
2463
+ } | {
2464
+ state: "timeout";
2465
+ } | {
2466
+ state: "failed";
2467
+ };
2468
+ interface CodexExecutableResolution {
2469
+ /** Stable user-selected command used by Codex terminals and auxiliary services. */
2470
+ executable: string;
2471
+ /** Resolved source installation inspected during boot. */
2472
+ sourceExecutable: string;
2473
+ provenance: CodexInstallProvenance;
2474
+ /** True only when a blocked OpenAI-signed Homebrew executable was atomically repaired in place. */
2475
+ recovered: boolean;
2476
+ }
2477
+ interface CodexExecutableDeps {
2478
+ platform: NodeJS.Platform;
2479
+ resolveExecutable(command: string, env: NodeJS.ProcessEnv): Promise<string | undefined>;
2480
+ probe(executable: string, env: NodeJS.ProcessEnv): Promise<CodexExecutableProbe>;
2481
+ verifyOfficialSignature(executable: string): Promise<boolean>;
2482
+ clearExtendedAttributes(executable: string): Promise<boolean>;
2483
+ }
2484
+ interface ResolveCodexExecutableOptions {
2485
+ codexBin: string;
2486
+ dataDir: string;
2487
+ env?: NodeJS.ProcessEnv;
2488
+ deps?: Partial<CodexExecutableDeps>;
2489
+ }
2490
+ declare function defaultProbeCodexExecutable(executable: string, env: NodeJS.ProcessEnv): Promise<CodexExecutableProbe>;
2491
+ /**
2492
+ * Probe the configured Codex CLI once at boot. Only a timed-out, officially signed Homebrew installation is
2493
+ * repaired, and the configured command remains the stable launch path so future Homebrew upgrades are not pinned
2494
+ * to an old Caskroom version.
2495
+ */
2496
+ declare function resolveCodexExecutable(options: ResolveCodexExecutableOptions): Promise<CodexExecutableResolution>;
2497
+
2458
2498
  declare const CODEX_OSC_MAX_CARRY: number;
2459
2499
  declare const CODEX_CLASSIFIER_TESTED_UP_TO = "0.144";
2460
2500
  interface CodexOscParser extends ProviderRuntimeSignalParser {
@@ -2467,4 +2507,4 @@ declare function codexClassifierVersionWarning(codexVersion: string | undefined)
2467
2507
 
2468
2508
  declare const SERVER_PACKAGE = "@roamcode.ai/server";
2469
2509
 
2470
- export { API_PATH_DENYLIST, type AgentProvider, type AttachSpawnOptions, type AuthCheckResult, AuthGate, type AuthGateOptions, type Bar, CHECK_CACHE_MS, CLASSIFIER_TESTED_UP_TO, CLAUDE_LATEST_CACHE_MS, CLAUDE_VERSION_CACHE_MS, CLAUDE_VERSION_TIMEOUT_MS, CODEX_CLASSIFIER_TESTED_UP_TO, CODEX_OSC_MAX_CARRY, type CaptureOptions, type ChangelogEntry, type ClaudeAuthDeps, ClaudeAuthService, type ClaudeAuthStatus, type ClaudeAvailability, type ClaudeLatestDeps, ClaudeLatestService, type ClaudeMetadataRunner, ClaudeMetadataService, type ClaudeModelCatalogItem, type ClaudeSessionOptions, type ClaudeVersionProbe, type CodexAccount, CodexAppServerClient, type CodexAppServerClientOptions, type CodexDeviceLogin, type CodexInstallProvenance, CodexLatestService, type CodexLoginCompletion, type CodexLoginStatus, type CodexMetadataDiagnostics, type CodexMetadataRpc, CodexMetadataService, type CodexMetadataServiceOptions, type CodexModel, type CodexOscParser, type CodexProfileClientLifecycle, type CodexSessionOptions, type CodexSpawnLease, type CodexThreadInventoryEntry, type CodexThreadPersistence, CodexThreadResolver, type CodexThreadResolverOptions, type CodexUsage, type CodexVersionInfo, type CreateClaudeProviderOptions, type CreateCodexProfileClientLifecycleOptions, type CreateCodexProviderOptions, type CreatePushDispatcherDeps, type CreateServerDeps, type CreateServerResult, type CreateWebPushSendOptions, type DirEntry, type DirListing, type DirSearchResult, FETCH_TIMEOUT_MS, type FetchLatest, type FetchManifest, type FetchReleases, FsError, type FsErrorCode, FsService, type FsServiceOptions, type GitHubRelease, type HooksSettingsDocument, type IPty, type InstallServiceContext, type InstallServiceResult, type InstallationKind, type LaunchIntent, type ManagedInstallOptions, type ManagedInstallResult, type ManagedInstallStatus, type ManagedPaths, type McpConfigDocument, type ModelWeekBar, type OpenPushStoreOptions, type OpenSessionStoreOptions, type OriginCheckOptions, type PaneStatus, type ProcessSpec, type ProviderAvailability, ProviderError, type ProviderId, ProviderOptionsError, type ProviderProcessContext, ProviderRegistry, type ProviderRuntimeSignal, type ProviderRuntimeSignalParser, type ProviderSessionOptions, type PtySpawn, type PushDispatcher, type PushEvent, type PushEventKind, type PushPayload, type PushRecipient, type PushSendFn, type PushStore, type PushSubscriptionRecord, RUNNING_BUILD, RUNNING_VERSION, type RateLimitDecision, RateLimiter, type RateLimiterOptions, type RegisterStaticOptions, type ReleaseRecord, type RenderLaunchdOptions, type RenderSystemdOptions, type ResolveAccessTokenOptions, type ResolveCodexThreadOptions, type ResolveVapidKeysOptions, type RunClaudeVersion, type RunUsage, SEARCH_MAX_DEPTH, SEARCH_MAX_DIRS, SEARCH_MAX_RESULTS, SERVER_PACKAGE, SHELL_PATH_ALLOWLIST, type ServerConfig, type ServerRuntimeConfig, type ServiceRecord, type SessionDefaults, SessionDefaultsConflictError, type SessionFileDirection, type SessionFileKind, type SessionFileStorage, type SessionStore, type StoreMode, type StoredSession, type StoredSessionDefaults, type StoredSessionFile, type StoredStatus, TerminalManager, type TerminalManagerDeps, type TerminalMeta, TerminalProcess, type TerminalProcessOptions, type TerminalSub, USAGE_CACHE_MS, USAGE_TIMEOUT_MS, type UpdateAction, type UpdateState, type UpdateStatus, Updater, type UpdaterDeps, type UpdaterFs, type UsageInfo, UsageService, type UsageServiceDeps, type VapidKeys, type VersionInfo, WS_TICKET_TTL_MS, WsTicketStore, type WsTicketStoreOptions, assertConfigAllowsStart, buildCodexArgs, buildHooksSettingsDocument, buildMcpConfigDocument, buildPushPayload, buildServicePath, capturePane, classifierVersionWarning, classifyCodexPane, classifyPaneStatus, claudePreflightWarning, codexClassifierVersionWarning, compareVersions, computeBuildDrift, computeInstallDrift, createClaudeAuthService, createClaudeLatestService, createClaudeMetadataRunner, createClaudeProvider, createClaudeVersionProbe, createCodexOscParser, createCodexProfileClientLifecycle, createCodexProvider, createCodexThreadInventory, createCodexThreadPersistence, createPushDispatcher, createServer, createUpdater, createUsageRunner, createUsageService, createWebPushSend, defaultFetchManifest, defaultFetchReleases, defaultRunClaudeVersion, defaultUpdaterFs, detectTerminalSupport, enableService, ensureDataDir, extractBearerToken, extractLoginUrl, generateAccessToken, hasEncodedSep, hookAuthFileContent, hookAuthPathFor, hooksSettingsPathFor, installManagedRelease, installService, isCodexProfileClientLifecycle, isLoopbackAddress, isNewerMajorMinor, isOriginAllowed, isPublicForRequest, isPublicPath, isShellPath, isStableVersion, listTmuxSessions, loadConfig, loadServerConfig, looksLikeAssetRequest, managedPaths, mcpConfigPathFor, migrateServiceToLauncher, normalizeOrigin, normalizeProviderAvailability, normalizeRelease, normalizeSessionDefaults, openPushStore, openSessionStore, parseAllowedOrigins, parseAuthStatus, parseClaudeVersion, parseCodexOscNotifications, parseCodexVersion, parseLegacyClaudeArgs, parseNpmLatest, parseProviderOptions, parseReleaseNotes, parseUsage, pathForGate, persistAccessToken, providerPreflightWarning, readActiveVersion, readPreviousVersion, readServiceRecord, registerStatic, relativeWhen, renderLaunchdPlist, renderManagedLauncher, renderSystemdUnit, resetCodexThreadResolutionCoordinatorForTests, resolveAccessToken, resolveDataDir, resolveInstallRoot, resolveVapidKeys, restartService, runClaudePreflight, runProviderPreflight, stableReleases, startServer, tmuxSessionName, writeManagedLauncher };
2510
+ export { API_PATH_DENYLIST, type AgentProvider, type AttachSpawnOptions, type AuthCheckResult, AuthGate, type AuthGateOptions, type Bar, CHECK_CACHE_MS, CLASSIFIER_TESTED_UP_TO, CLAUDE_LATEST_CACHE_MS, CLAUDE_VERSION_CACHE_MS, CLAUDE_VERSION_TIMEOUT_MS, CODEX_CLASSIFIER_TESTED_UP_TO, CODEX_EXECUTABLE_PROBE_TIMEOUT_MS, CODEX_OSC_MAX_CARRY, type CaptureOptions, type ChangelogEntry, type ClaudeAuthDeps, ClaudeAuthService, type ClaudeAuthStatus, type ClaudeAvailability, type ClaudeLatestDeps, ClaudeLatestService, type ClaudeMetadataRunner, ClaudeMetadataService, type ClaudeModelCatalogItem, type ClaudeSessionOptions, type ClaudeVersionProbe, type CodexAccount, CodexAppServerClient, type CodexAppServerClientOptions, type CodexDeviceLogin, type CodexExecutableDeps, type CodexExecutableProbe, type CodexExecutableResolution, type CodexInstallProvenance, CodexLatestService, type CodexLoginCompletion, type CodexLoginStatus, type CodexMetadataDiagnostics, type CodexMetadataRpc, CodexMetadataService, type CodexMetadataServiceOptions, type CodexModel, type CodexOscParser, type CodexProfileClientLifecycle, type CodexSessionOptions, type CodexSpawnLease, type CodexThreadInventoryEntry, type CodexThreadPersistence, CodexThreadResolver, type CodexThreadResolverOptions, type CodexUsage, type CodexVersionInfo, type CreateClaudeProviderOptions, type CreateCodexProfileClientLifecycleOptions, type CreateCodexProviderOptions, type CreatePushDispatcherDeps, type CreateServerDeps, type CreateServerResult, type CreateWebPushSendOptions, type DirEntry, type DirListing, type DirSearchResult, FETCH_TIMEOUT_MS, type FetchLatest, type FetchManifest, type FetchReleases, FsError, type FsErrorCode, FsService, type FsServiceOptions, type GitHubRelease, type HooksSettingsDocument, type IPty, type InstallServiceContext, type InstallServiceResult, type InstallationKind, type LaunchIntent, type ManagedInstallOptions, type ManagedInstallResult, type ManagedInstallStatus, type ManagedPaths, type McpConfigDocument, type ModelWeekBar, OPENAI_CODE_SIGNING_TEAM_ID, type OpenPushStoreOptions, type OpenSessionStoreOptions, type OriginCheckOptions, type PaneStatus, type ProcessSpec, type ProviderAvailability, ProviderError, type ProviderId, ProviderOptionsError, type ProviderProcessContext, ProviderRegistry, type ProviderRuntimeSignal, type ProviderRuntimeSignalParser, type ProviderSessionOptions, type PtySpawn, type PushDispatcher, type PushEvent, type PushEventKind, type PushPayload, type PushRecipient, type PushSendFn, type PushStore, type PushSubscriptionRecord, RUNNING_BUILD, RUNNING_VERSION, type RateLimitDecision, RateLimiter, type RateLimiterOptions, type RegisterStaticOptions, type ReleaseRecord, type RenderLaunchdOptions, type RenderSystemdOptions, type ResolveAccessTokenOptions, type ResolveCodexExecutableOptions, type ResolveCodexThreadOptions, type ResolveVapidKeysOptions, type RunClaudeVersion, type RunUsage, SEARCH_MAX_DEPTH, SEARCH_MAX_DIRS, SEARCH_MAX_RESULTS, SERVER_PACKAGE, SHELL_PATH_ALLOWLIST, type ServerConfig, type ServerRuntimeConfig, type ServiceRecord, type SessionDefaults, SessionDefaultsConflictError, type SessionFileDirection, type SessionFileKind, type SessionFileStorage, type SessionStore, type StoreMode, type StoredSession, type StoredSessionDefaults, type StoredSessionFile, type StoredStatus, TerminalManager, type TerminalManagerDeps, type TerminalMeta, TerminalProcess, type TerminalProcessOptions, type TerminalSub, USAGE_CACHE_MS, USAGE_TIMEOUT_MS, type UpdateAction, type UpdateState, type UpdateStatus, Updater, type UpdaterDeps, type UpdaterFs, type UsageInfo, UsageService, type UsageServiceDeps, type VapidKeys, type VersionInfo, WS_TICKET_TTL_MS, WsTicketStore, type WsTicketStoreOptions, assertConfigAllowsStart, buildCodexArgs, buildHooksSettingsDocument, buildMcpConfigDocument, buildPushPayload, buildServicePath, capturePane, classifierVersionWarning, classifyCodexPane, classifyPaneStatus, claudePreflightWarning, codexClassifierVersionWarning, compareVersions, computeBuildDrift, computeInstallDrift, createClaudeAuthService, createClaudeLatestService, createClaudeMetadataRunner, createClaudeProvider, createClaudeVersionProbe, createCodexOscParser, createCodexProfileClientLifecycle, createCodexProvider, createCodexThreadInventory, createCodexThreadPersistence, createPushDispatcher, createServer, createUpdater, createUsageRunner, createUsageService, createWebPushSend, defaultFetchManifest, defaultFetchReleases, defaultProbeCodexExecutable, defaultRunClaudeVersion, defaultUpdaterFs, detectTerminalSupport, enableService, ensureDataDir, extractBearerToken, extractLoginUrl, generateAccessToken, hasEncodedSep, hookAuthFileContent, hookAuthPathFor, hooksSettingsPathFor, installManagedRelease, installService, isCodexProfileClientLifecycle, isLoopbackAddress, isNewerMajorMinor, isOriginAllowed, isPublicForRequest, isPublicPath, isShellPath, isStableVersion, listTmuxSessions, loadConfig, loadServerConfig, looksLikeAssetRequest, managedPaths, mcpConfigPathFor, migrateServiceToLauncher, normalizeOrigin, normalizeProviderAvailability, normalizeRelease, normalizeSessionDefaults, openPushStore, openSessionStore, parseAllowedOrigins, parseAuthStatus, parseClaudeVersion, parseCodexOscNotifications, parseCodexVersion, parseLegacyClaudeArgs, parseNpmLatest, parseProviderOptions, parseReleaseNotes, parseUsage, pathForGate, persistAccessToken, providerPreflightWarning, readActiveVersion, readPreviousVersion, readServiceRecord, registerStatic, relativeWhen, renderLaunchdPlist, renderManagedLauncher, renderSystemdUnit, resetCodexThreadResolutionCoordinatorForTests, resolveAccessToken, resolveCodexExecutable, resolveDataDir, resolveInstallRoot, resolveVapidKeys, restartService, runClaudePreflight, runProviderPreflight, stableReleases, startServer, tmuxSessionName, writeManagedLauncher };
package/dist/index.js CHANGED
@@ -2268,16 +2268,16 @@ async function smokeServer(serverEntry, dataDir, nodePath, log) {
2268
2268
  void dataDir;
2269
2269
  }
2270
2270
  }
2271
- function replaceSymlink(link, target) {
2272
- const temp = `${link}.${process.pid}.${randomUUID2()}.new`;
2271
+ function replaceSymlink(link2, target) {
2272
+ const temp = `${link2}.${process.pid}.${randomUUID2()}.new`;
2273
2273
  try {
2274
2274
  unlinkSync(temp);
2275
2275
  } catch {
2276
2276
  }
2277
- const parent = realpathSync(dirname2(link));
2277
+ const parent = realpathSync(dirname2(link2));
2278
2278
  const canonicalTarget = realpathSync(target);
2279
2279
  symlinkSync(relative(parent, canonicalTarget), temp, "dir");
2280
- renameSync(temp, link);
2280
+ renameSync(temp, link2);
2281
2281
  }
2282
2282
  function trimLog(log) {
2283
2283
  return log.split("\n").slice(-20).join("\n").slice(-8e3);
@@ -2429,7 +2429,7 @@ async function installManagedRelease(opts) {
2429
2429
  }
2430
2430
 
2431
2431
  // src/updater.ts
2432
- var RUNNING_VERSION = "1.0.18" ? "1.0.18".replace(/^v/, "") : packageVersion();
2432
+ var RUNNING_VERSION = "1.0.20" ? "1.0.20".replace(/^v/, "") : packageVersion();
2433
2433
  var RUNNING_BUILD = RUNNING_VERSION;
2434
2434
  var RELEASES_API = "https://api.github.com/repos/burakgon/roamcode/releases?per_page=100";
2435
2435
  var RELEASE_MANIFEST_ASSET = "roamcode-release.json";
@@ -5828,7 +5828,7 @@ function createServer(config, deps = {}) {
5828
5828
  availability = { terminalAvailable: false, metadataAvailable: false };
5829
5829
  }
5830
5830
  availability = normalizeProviderAvailability(terminalAvailable, availability);
5831
- if (provider.id === "codex") {
5831
+ if (provider.id === "codex" && availability.terminalAvailable) {
5832
5832
  let metadataAvailable = false;
5833
5833
  try {
5834
5834
  metadataAvailable = Boolean(
@@ -6386,7 +6386,7 @@ function contentDisposition(filename, disposition = "attachment") {
6386
6386
 
6387
6387
  // src/start.ts
6388
6388
  import { pathToFileURL, fileURLToPath as fileURLToPath2 } from "url";
6389
- import { join as join12 } from "path";
6389
+ import { join as join13 } from "path";
6390
6390
  import { existsSync as existsSync6 } from "fs";
6391
6391
 
6392
6392
  // src/push-store.ts
@@ -7396,11 +7396,11 @@ async function resolveSecureCodexHome(configured) {
7396
7396
  if (!configured || !isAbsolute3(configured)) return void 0;
7397
7397
  try {
7398
7398
  if (await hasSymlinkComponent(configured)) return void 0;
7399
- const stat3 = await lstat(configured, { bigint: true });
7400
- if (!stat3.isDirectory() || stat3.isSymbolicLink()) return void 0;
7399
+ const stat4 = await lstat(configured, { bigint: true });
7400
+ if (!stat4.isDirectory() || stat4.isSymbolicLink()) return void 0;
7401
7401
  const canonical = await realpath2(configured);
7402
7402
  const after = await lstat(configured, { bigint: true });
7403
- return canonical === resolve6(configured) && after.isDirectory() && !after.isSymbolicLink() && stat3.dev === after.dev && stat3.ino === after.ino ? canonical : void 0;
7403
+ return canonical === resolve6(configured) && after.isDirectory() && !after.isSymbolicLink() && stat4.dev === after.dev && stat4.ino === after.ino ? canonical : void 0;
7404
7404
  } catch {
7405
7405
  return void 0;
7406
7406
  }
@@ -7429,23 +7429,23 @@ async function captureProfileFingerprint(codexHome, profile) {
7429
7429
  async function captureConfigFileFingerprint(codexHome, path) {
7430
7430
  try {
7431
7431
  if (relative2(codexHome, path).startsWith("..")) return void 0;
7432
- const stat3 = await lstat(path, { bigint: true });
7433
- if (!stat3.isFile() || stat3.isSymbolicLink()) return void 0;
7434
- if (stat3.size > BigInt(MAX_CONFIG_BYTES)) return void 0;
7432
+ const stat4 = await lstat(path, { bigint: true });
7433
+ if (!stat4.isFile() || stat4.isSymbolicLink()) return void 0;
7434
+ if (stat4.size > BigInt(MAX_CONFIG_BYTES)) return void 0;
7435
7435
  const realPath = await realpath2(path);
7436
7436
  if (realPath !== path) return void 0;
7437
7437
  const contents = await readFile2(path);
7438
7438
  const after = await lstat(path, { bigint: true });
7439
- if (!after.isFile() || after.isSymbolicLink() || stat3.dev !== after.dev || stat3.ino !== after.ino || stat3.size !== after.size || stat3.mtimeNs !== after.mtimeNs) {
7439
+ if (!after.isFile() || after.isSymbolicLink() || stat4.dev !== after.dev || stat4.ino !== after.ino || stat4.size !== after.size || stat4.mtimeNs !== after.mtimeNs) {
7440
7440
  return void 0;
7441
7441
  }
7442
7442
  return {
7443
7443
  path,
7444
7444
  realPath,
7445
- dev: stat3.dev,
7446
- ino: stat3.ino,
7447
- size: stat3.size,
7448
- mtimeNs: stat3.mtimeNs,
7445
+ dev: stat4.dev,
7446
+ ino: stat4.ino,
7447
+ size: stat4.size,
7448
+ mtimeNs: stat4.mtimeNs,
7449
7449
  digest: createHash("sha256").update(contents).digest("hex")
7450
7450
  };
7451
7451
  } catch {
@@ -8585,8 +8585,199 @@ var CodexLatestService = class {
8585
8585
  }
8586
8586
  };
8587
8587
 
8588
- // src/start.ts
8588
+ // src/providers/codex-executable.ts
8589
8589
  import { execFile } from "child_process";
8590
+ import { constants as constants2 } from "fs";
8591
+ import { access, chmod, copyFile, link, realpath as realpath3, rename as rename2, rm, rmdir as rmdir2, stat as stat3 } from "fs/promises";
8592
+ import { randomUUID as randomUUID7 } from "crypto";
8593
+ import { basename as basename3, delimiter, dirname as dirname5, isAbsolute as isAbsolute5, join as join12, sep as sep5 } from "path";
8594
+ var CODEX_EXECUTABLE_PROBE_TIMEOUT_MS = 5e3;
8595
+ var OPENAI_CODE_SIGNING_TEAM_ID = "2DC432GLL2";
8596
+ var MAX_CODEX_BYTES = 1024 * 1024 * 1024;
8597
+ var MAX_PROCESS_OUTPUT_BYTES = 16 * 1024;
8598
+ var LEGACY_MANAGED_DIRECTORY = "provider-bin";
8599
+ var LEGACY_MANAGED_EXECUTABLE = "codex-macos";
8600
+ var LEGACY_MANAGED_STATE = "codex-macos-source.json";
8601
+ function runBounded(executable, args, options) {
8602
+ return new Promise((resolve7) => {
8603
+ execFile(
8604
+ executable,
8605
+ [...args],
8606
+ {
8607
+ ...options.env ? { env: options.env } : {},
8608
+ timeout: options.timeoutMs,
8609
+ maxBuffer: options.maxOutputBytes,
8610
+ windowsHide: true,
8611
+ encoding: "utf8"
8612
+ },
8613
+ (error, stdout, stderr) => {
8614
+ resolve7({
8615
+ ...error ? { error } : {},
8616
+ stdout: String(stdout),
8617
+ stderr: String(stderr)
8618
+ });
8619
+ }
8620
+ );
8621
+ });
8622
+ }
8623
+ async function defaultProbeCodexExecutable(executable, env) {
8624
+ const result = await runBounded(executable, ["--version"], {
8625
+ env,
8626
+ timeoutMs: CODEX_EXECUTABLE_PROBE_TIMEOUT_MS,
8627
+ maxOutputBytes: 1024
8628
+ });
8629
+ if (result.error) {
8630
+ if (result.error.killed || result.error.signal === "SIGTERM" || result.error.signal === "SIGKILL") {
8631
+ return { state: "timeout" };
8632
+ }
8633
+ return { state: "failed" };
8634
+ }
8635
+ try {
8636
+ return { state: "ready", version: parseCodexVersion(result.stdout) };
8637
+ } catch {
8638
+ return { state: "failed" };
8639
+ }
8640
+ }
8641
+ async function defaultResolveExecutable(command, env) {
8642
+ const candidates = isAbsolute5(command) || command.includes(sep5) ? [command] : (env.PATH ?? process.env.PATH ?? "").split(delimiter).filter(Boolean).map((entry) => join12(entry, command));
8643
+ for (const candidate of candidates) {
8644
+ try {
8645
+ await access(candidate, constants2.X_OK);
8646
+ return await realpath3(candidate);
8647
+ } catch {
8648
+ }
8649
+ }
8650
+ return void 0;
8651
+ }
8652
+ async function defaultVerifyOfficialSignature(executable) {
8653
+ const verified = await runBounded("/usr/bin/codesign", ["--verify", "--strict", executable], {
8654
+ timeoutMs: 5e3,
8655
+ maxOutputBytes: MAX_PROCESS_OUTPUT_BYTES
8656
+ });
8657
+ if (verified.error) return false;
8658
+ const details = await runBounded("/usr/bin/codesign", ["-d", "--verbose=4", executable], {
8659
+ timeoutMs: 5e3,
8660
+ maxOutputBytes: MAX_PROCESS_OUTPUT_BYTES
8661
+ });
8662
+ if (details.error) return false;
8663
+ return new RegExp(`^TeamIdentifier=${OPENAI_CODE_SIGNING_TEAM_ID}$`, "m").test(details.stderr) && /^Identifier=codex$/m.test(details.stderr);
8664
+ }
8665
+ async function defaultClearExtendedAttributes(executable) {
8666
+ const result = await runBounded("/usr/bin/xattr", ["-c", executable], {
8667
+ timeoutMs: 5e3,
8668
+ maxOutputBytes: MAX_PROCESS_OUTPUT_BYTES
8669
+ });
8670
+ return !result.error;
8671
+ }
8672
+ var defaultDeps = {
8673
+ platform: process.platform,
8674
+ resolveExecutable: defaultResolveExecutable,
8675
+ probe: defaultProbeCodexExecutable,
8676
+ verifyOfficialSignature: defaultVerifyOfficialSignature,
8677
+ clearExtendedAttributes: defaultClearExtendedAttributes
8678
+ };
8679
+ function provenanceFor(executable) {
8680
+ const normalized = executable.replaceAll("\\", "/");
8681
+ if (/\/Caskroom\/codex\//i.test(normalized)) return "homebrew";
8682
+ if (/\/Applications\/Codex\.app\//i.test(normalized)) return "chatgpt";
8683
+ if (/\/node_modules\/(?:@openai\/codex|@openai\/codex-[^/]+)\//i.test(normalized)) return "npm";
8684
+ return "unknown";
8685
+ }
8686
+ function validSource(sourceStat) {
8687
+ return sourceStat.isFile() && sourceStat.size > 0 && sourceStat.size <= MAX_CODEX_BYTES;
8688
+ }
8689
+ function sameSource(left, right) {
8690
+ return left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mtimeMs === right.mtimeMs;
8691
+ }
8692
+ async function removeLegacyManagedCopy(dataDir) {
8693
+ const directory = join12(dataDir, LEGACY_MANAGED_DIRECTORY);
8694
+ await Promise.all([
8695
+ rm(join12(directory, LEGACY_MANAGED_EXECUTABLE), { force: true }).catch(() => void 0),
8696
+ rm(join12(directory, LEGACY_MANAGED_STATE), { force: true }).catch(() => void 0)
8697
+ ]);
8698
+ await rmdir2(directory).catch(() => void 0);
8699
+ }
8700
+ async function repairHomebrewExecutable(source, sourceStat, env, deps) {
8701
+ if (!await deps.verifyOfficialSignature(source)) return false;
8702
+ const nonce = randomUUID7();
8703
+ const directory = dirname5(source);
8704
+ const name = basename3(source);
8705
+ const temporary = join12(directory, `.${name}.roamcode-repair-${nonce}`);
8706
+ const backup = join12(directory, `.${name}.roamcode-backup-${nonce}`);
8707
+ let backupCreated = false;
8708
+ let sourceReplaced = false;
8709
+ let committed = false;
8710
+ let preserveBackup = false;
8711
+ try {
8712
+ await copyFile(source, temporary, constants2.COPYFILE_EXCL);
8713
+ await chmod(temporary, sourceStat.mode & 511);
8714
+ if (!await deps.clearExtendedAttributes(temporary)) return false;
8715
+ if (!await deps.verifyOfficialSignature(temporary)) return false;
8716
+ if ((await deps.probe(temporary, env)).state !== "ready") return false;
8717
+ const currentStat = await stat3(source);
8718
+ if (!sameSource(sourceStat, currentStat)) return false;
8719
+ await link(source, backup);
8720
+ backupCreated = true;
8721
+ await rename2(temporary, source);
8722
+ sourceReplaced = true;
8723
+ if (!await deps.verifyOfficialSignature(source)) return false;
8724
+ if ((await deps.probe(source, env)).state !== "ready") return false;
8725
+ committed = true;
8726
+ return true;
8727
+ } catch {
8728
+ return false;
8729
+ } finally {
8730
+ if (sourceReplaced && !committed && backupCreated) {
8731
+ try {
8732
+ await rename2(backup, source);
8733
+ backupCreated = false;
8734
+ } catch {
8735
+ preserveBackup = true;
8736
+ }
8737
+ }
8738
+ await rm(temporary, { force: true }).catch(() => void 0);
8739
+ if (!preserveBackup) await rm(backup, { force: true }).catch(() => void 0);
8740
+ }
8741
+ }
8742
+ async function resolveCodexExecutable(options) {
8743
+ const env = options.env ?? process.env;
8744
+ const deps = { ...defaultDeps, ...options.deps };
8745
+ const unresolved = {
8746
+ executable: options.codexBin,
8747
+ sourceExecutable: options.codexBin,
8748
+ provenance: "unknown",
8749
+ recovered: false
8750
+ };
8751
+ if (deps.platform !== "darwin") return unresolved;
8752
+ const source = await deps.resolveExecutable(options.codexBin, env);
8753
+ if (!source) return unresolved;
8754
+ const provenance = provenanceFor(source);
8755
+ const ordinary = {
8756
+ executable: options.codexBin,
8757
+ sourceExecutable: source,
8758
+ provenance,
8759
+ recovered: false
8760
+ };
8761
+ let sourceStat;
8762
+ try {
8763
+ sourceStat = await stat3(source);
8764
+ } catch {
8765
+ return ordinary;
8766
+ }
8767
+ if (!validSource(sourceStat)) return ordinary;
8768
+ const sourceProbe = await deps.probe(source, env);
8769
+ if (sourceProbe.state === "ready") {
8770
+ await removeLegacyManagedCopy(options.dataDir);
8771
+ return ordinary;
8772
+ }
8773
+ if (sourceProbe.state !== "timeout" || provenance !== "homebrew") return ordinary;
8774
+ if (!await repairHomebrewExecutable(source, sourceStat, env, deps)) return ordinary;
8775
+ await removeLegacyManagedCopy(options.dataDir);
8776
+ return { ...ordinary, recovered: true };
8777
+ }
8778
+
8779
+ // src/start.ts
8780
+ import { execFile as execFile2 } from "child_process";
8590
8781
  function providerPreflightWarning(name, availability) {
8591
8782
  if (availability.terminalAvailable) return void 0;
8592
8783
  return `
@@ -8637,7 +8828,18 @@ async function startServer(env = process.env) {
8637
8828
  config.accessToken = token;
8638
8829
  }
8639
8830
  assertConfigAllowsStart(config);
8640
- const store = openSessionStore({ dbPath: join12(config.dataDir, "sessions.db") });
8831
+ const codexExecutable = await resolveCodexExecutable({
8832
+ codexBin: config.codexBin,
8833
+ dataDir: config.dataDir,
8834
+ env
8835
+ });
8836
+ const codexBin = codexExecutable.executable;
8837
+ if (codexExecutable.recovered) {
8838
+ console.warn(
8839
+ "[roamcode] Repaired a macOS-blocked Homebrew Codex CLI in place using a verified, byte-identical OpenAI-signed replacement; no private binary was retained."
8840
+ );
8841
+ }
8842
+ const store = openSessionStore({ dbPath: join13(config.dataDir, "sessions.db") });
8641
8843
  const storeMode = store.mode;
8642
8844
  if (storeMode === "memory-fallback") {
8643
8845
  console.warn(
@@ -8653,7 +8855,7 @@ async function startServer(env = process.env) {
8653
8855
  }).catch(() => {
8654
8856
  });
8655
8857
  const vapid = resolveVapidKeys({ dataDir: config.dataDir });
8656
- const pushStore = openPushStore({ dbPath: join12(config.dataDir, "push.db") });
8858
+ const pushStore = openPushStore({ dbPath: join13(config.dataDir, "push.db") });
8657
8859
  const vapidSubject = (env.ROAMCODE_VAPID_SUBJECT ?? env.REMOTE_CODER_VAPID_SUBJECT)?.trim() || "mailto:roamcode@localhost";
8658
8860
  let pushDispatcher;
8659
8861
  try {
@@ -8677,7 +8879,7 @@ async function startServer(env = process.env) {
8677
8879
  env
8678
8880
  })
8679
8881
  );
8680
- const codexClient = new CodexAppServerClient({ codexBin: config.codexBin, env });
8882
+ const codexClient = new CodexAppServerClient({ codexBin, env });
8681
8883
  const codexRpc = {
8682
8884
  request: async (method, params, schema) => {
8683
8885
  await codexClient.start();
@@ -8685,7 +8887,7 @@ async function startServer(env = process.env) {
8685
8887
  },
8686
8888
  onNotification: (listener) => codexClient.onNotification(listener)
8687
8889
  };
8688
- const profileClient = createCodexProfileClientLifecycle({ codexBin: config.codexBin, env });
8890
+ const profileClient = createCodexProfileClientLifecycle({ codexBin, env });
8689
8891
  const codexMetadata = new CodexMetadataService(codexRpc, {
8690
8892
  ...env.CODEX_HOME ? { codexHome: env.CODEX_HOME } : {},
8691
8893
  profileClient
@@ -8696,8 +8898,8 @@ async function startServer(env = process.env) {
8696
8898
  };
8697
8899
  const codexLatest = new CodexLatestService({
8698
8900
  runVersion: (args, options) => new Promise((resolve7, reject) => {
8699
- execFile(
8700
- config.codexBin,
8901
+ execFile2(
8902
+ codexBin,
8701
8903
  [...args],
8702
8904
  { env, timeout: options.timeoutMs, maxBuffer: options.maxOutputBytes, windowsHide: true },
8703
8905
  (error, stdout, stderr) => {
@@ -8714,7 +8916,7 @@ async function startServer(env = process.env) {
8714
8916
  }
8715
8917
  );
8716
8918
  }),
8717
- detectProvenance: () => "unknown",
8919
+ detectProvenance: () => codexExecutable.provenance,
8718
8920
  fetchNpmLatest: async (_packageName, options) => {
8719
8921
  const response = await fetch("https://registry.npmjs.org/@openai%2Fcodex/latest", {
8720
8922
  signal: AbortSignal.timeout(options.timeoutMs)
@@ -8740,7 +8942,7 @@ async function startServer(env = process.env) {
8740
8942
  }
8741
8943
  }),
8742
8944
  createCodexProvider({
8743
- codexBin: config.codexBin,
8945
+ codexBin,
8744
8946
  env,
8745
8947
  validateProfile: codexMetadata.validateProfile,
8746
8948
  probe: async () => {
@@ -8809,7 +9011,7 @@ async function startServer(env = process.env) {
8809
9011
  }
8810
9012
  function defaultWebDir() {
8811
9013
  const here = fileURLToPath2(new URL(".", import.meta.url));
8812
- return join12(here, "..", "..", "web", "dist");
9014
+ return join13(here, "..", "..", "web", "dist");
8813
9015
  }
8814
9016
  function installCrashGuards() {
8815
9017
  process.on("unhandledRejection", (reason) => {
@@ -8858,6 +9060,7 @@ export {
8858
9060
  CLAUDE_VERSION_CACHE_MS,
8859
9061
  CLAUDE_VERSION_TIMEOUT_MS,
8860
9062
  CODEX_CLASSIFIER_TESTED_UP_TO,
9063
+ CODEX_EXECUTABLE_PROBE_TIMEOUT_MS,
8861
9064
  CODEX_OSC_MAX_CARRY,
8862
9065
  ClaudeAuthService,
8863
9066
  ClaudeLatestService,
@@ -8869,6 +9072,7 @@ export {
8869
9072
  FETCH_TIMEOUT_MS,
8870
9073
  FsError,
8871
9074
  FsService,
9075
+ OPENAI_CODE_SIGNING_TEAM_ID,
8872
9076
  ProviderError,
8873
9077
  ProviderOptionsError,
8874
9078
  ProviderRegistry,
@@ -8922,6 +9126,7 @@ export {
8922
9126
  createWebPushSend,
8923
9127
  defaultFetchManifest,
8924
9128
  defaultFetchReleases,
9129
+ defaultProbeCodexExecutable,
8925
9130
  defaultRunClaudeVersion,
8926
9131
  defaultUpdaterFs,
8927
9132
  detectTerminalSupport,
@@ -8980,6 +9185,7 @@ export {
8980
9185
  renderSystemdUnit,
8981
9186
  resetCodexThreadResolutionCoordinatorForTests,
8982
9187
  resolveAccessToken,
9188
+ resolveCodexExecutable,
8983
9189
  resolveDataDir,
8984
9190
  resolveInstallRoot,
8985
9191
  resolveVapidKeys,
package/dist/start.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/start.ts
4
4
  import { pathToFileURL, fileURLToPath as fileURLToPath2 } from "url";
5
- import { join as join12 } from "path";
5
+ import { join as join13 } from "path";
6
6
  import { existsSync as existsSync6 } from "fs";
7
7
 
8
8
  // src/transport.ts
@@ -871,7 +871,7 @@ function readPreviousVersion(root) {
871
871
  }
872
872
 
873
873
  // src/updater.ts
874
- var RUNNING_VERSION = "1.0.18" ? "1.0.18".replace(/^v/, "") : packageVersion();
874
+ var RUNNING_VERSION = "1.0.20" ? "1.0.20".replace(/^v/, "") : packageVersion();
875
875
  var RELEASES_API = "https://api.github.com/repos/burakgon/roamcode/releases?per_page=100";
876
876
  var RELEASE_MANIFEST_ASSET = "roamcode-release.json";
877
877
  var CHECK_CACHE_MS = 15 * 6e4;
@@ -5215,7 +5215,7 @@ function createServer(config, deps = {}) {
5215
5215
  availability = { terminalAvailable: false, metadataAvailable: false };
5216
5216
  }
5217
5217
  availability = normalizeProviderAvailability(terminalAvailable, availability);
5218
- if (provider.id === "codex") {
5218
+ if (provider.id === "codex" && availability.terminalAvailable) {
5219
5219
  let metadataAvailable = false;
5220
5220
  try {
5221
5221
  metadataAvailable = Boolean(
@@ -6857,11 +6857,11 @@ async function resolveSecureCodexHome(configured) {
6857
6857
  if (!configured || !isAbsolute3(configured)) return void 0;
6858
6858
  try {
6859
6859
  if (await hasSymlinkComponent(configured)) return void 0;
6860
- const stat3 = await lstat(configured, { bigint: true });
6861
- if (!stat3.isDirectory() || stat3.isSymbolicLink()) return void 0;
6860
+ const stat4 = await lstat(configured, { bigint: true });
6861
+ if (!stat4.isDirectory() || stat4.isSymbolicLink()) return void 0;
6862
6862
  const canonical = await realpath2(configured);
6863
6863
  const after = await lstat(configured, { bigint: true });
6864
- return canonical === resolve6(configured) && after.isDirectory() && !after.isSymbolicLink() && stat3.dev === after.dev && stat3.ino === after.ino ? canonical : void 0;
6864
+ return canonical === resolve6(configured) && after.isDirectory() && !after.isSymbolicLink() && stat4.dev === after.dev && stat4.ino === after.ino ? canonical : void 0;
6865
6865
  } catch {
6866
6866
  return void 0;
6867
6867
  }
@@ -6890,23 +6890,23 @@ async function captureProfileFingerprint(codexHome, profile) {
6890
6890
  async function captureConfigFileFingerprint(codexHome, path) {
6891
6891
  try {
6892
6892
  if (relative2(codexHome, path).startsWith("..")) return void 0;
6893
- const stat3 = await lstat(path, { bigint: true });
6894
- if (!stat3.isFile() || stat3.isSymbolicLink()) return void 0;
6895
- if (stat3.size > BigInt(MAX_CONFIG_BYTES)) return void 0;
6893
+ const stat4 = await lstat(path, { bigint: true });
6894
+ if (!stat4.isFile() || stat4.isSymbolicLink()) return void 0;
6895
+ if (stat4.size > BigInt(MAX_CONFIG_BYTES)) return void 0;
6896
6896
  const realPath = await realpath2(path);
6897
6897
  if (realPath !== path) return void 0;
6898
6898
  const contents = await readFile2(path);
6899
6899
  const after = await lstat(path, { bigint: true });
6900
- if (!after.isFile() || after.isSymbolicLink() || stat3.dev !== after.dev || stat3.ino !== after.ino || stat3.size !== after.size || stat3.mtimeNs !== after.mtimeNs) {
6900
+ if (!after.isFile() || after.isSymbolicLink() || stat4.dev !== after.dev || stat4.ino !== after.ino || stat4.size !== after.size || stat4.mtimeNs !== after.mtimeNs) {
6901
6901
  return void 0;
6902
6902
  }
6903
6903
  return {
6904
6904
  path,
6905
6905
  realPath,
6906
- dev: stat3.dev,
6907
- ino: stat3.ino,
6908
- size: stat3.size,
6909
- mtimeNs: stat3.mtimeNs,
6906
+ dev: stat4.dev,
6907
+ ino: stat4.ino,
6908
+ size: stat4.size,
6909
+ mtimeNs: stat4.mtimeNs,
6910
6910
  digest: createHash("sha256").update(contents).digest("hex")
6911
6911
  };
6912
6912
  } catch {
@@ -8046,8 +8046,199 @@ var CodexLatestService = class {
8046
8046
  }
8047
8047
  };
8048
8048
 
8049
- // src/start.ts
8049
+ // src/providers/codex-executable.ts
8050
8050
  import { execFile } from "child_process";
8051
+ import { constants as constants2 } from "fs";
8052
+ import { access, chmod, copyFile, link, realpath as realpath3, rename as rename2, rm, rmdir as rmdir2, stat as stat3 } from "fs/promises";
8053
+ import { randomUUID as randomUUID7 } from "crypto";
8054
+ import { basename as basename3, delimiter, dirname as dirname5, isAbsolute as isAbsolute5, join as join12, sep as sep5 } from "path";
8055
+ var CODEX_EXECUTABLE_PROBE_TIMEOUT_MS = 5e3;
8056
+ var OPENAI_CODE_SIGNING_TEAM_ID = "2DC432GLL2";
8057
+ var MAX_CODEX_BYTES = 1024 * 1024 * 1024;
8058
+ var MAX_PROCESS_OUTPUT_BYTES = 16 * 1024;
8059
+ var LEGACY_MANAGED_DIRECTORY = "provider-bin";
8060
+ var LEGACY_MANAGED_EXECUTABLE = "codex-macos";
8061
+ var LEGACY_MANAGED_STATE = "codex-macos-source.json";
8062
+ function runBounded(executable, args, options) {
8063
+ return new Promise((resolve7) => {
8064
+ execFile(
8065
+ executable,
8066
+ [...args],
8067
+ {
8068
+ ...options.env ? { env: options.env } : {},
8069
+ timeout: options.timeoutMs,
8070
+ maxBuffer: options.maxOutputBytes,
8071
+ windowsHide: true,
8072
+ encoding: "utf8"
8073
+ },
8074
+ (error, stdout, stderr) => {
8075
+ resolve7({
8076
+ ...error ? { error } : {},
8077
+ stdout: String(stdout),
8078
+ stderr: String(stderr)
8079
+ });
8080
+ }
8081
+ );
8082
+ });
8083
+ }
8084
+ async function defaultProbeCodexExecutable(executable, env) {
8085
+ const result = await runBounded(executable, ["--version"], {
8086
+ env,
8087
+ timeoutMs: CODEX_EXECUTABLE_PROBE_TIMEOUT_MS,
8088
+ maxOutputBytes: 1024
8089
+ });
8090
+ if (result.error) {
8091
+ if (result.error.killed || result.error.signal === "SIGTERM" || result.error.signal === "SIGKILL") {
8092
+ return { state: "timeout" };
8093
+ }
8094
+ return { state: "failed" };
8095
+ }
8096
+ try {
8097
+ return { state: "ready", version: parseCodexVersion(result.stdout) };
8098
+ } catch {
8099
+ return { state: "failed" };
8100
+ }
8101
+ }
8102
+ async function defaultResolveExecutable(command, env) {
8103
+ const candidates = isAbsolute5(command) || command.includes(sep5) ? [command] : (env.PATH ?? process.env.PATH ?? "").split(delimiter).filter(Boolean).map((entry) => join12(entry, command));
8104
+ for (const candidate of candidates) {
8105
+ try {
8106
+ await access(candidate, constants2.X_OK);
8107
+ return await realpath3(candidate);
8108
+ } catch {
8109
+ }
8110
+ }
8111
+ return void 0;
8112
+ }
8113
+ async function defaultVerifyOfficialSignature(executable) {
8114
+ const verified = await runBounded("/usr/bin/codesign", ["--verify", "--strict", executable], {
8115
+ timeoutMs: 5e3,
8116
+ maxOutputBytes: MAX_PROCESS_OUTPUT_BYTES
8117
+ });
8118
+ if (verified.error) return false;
8119
+ const details = await runBounded("/usr/bin/codesign", ["-d", "--verbose=4", executable], {
8120
+ timeoutMs: 5e3,
8121
+ maxOutputBytes: MAX_PROCESS_OUTPUT_BYTES
8122
+ });
8123
+ if (details.error) return false;
8124
+ return new RegExp(`^TeamIdentifier=${OPENAI_CODE_SIGNING_TEAM_ID}$`, "m").test(details.stderr) && /^Identifier=codex$/m.test(details.stderr);
8125
+ }
8126
+ async function defaultClearExtendedAttributes(executable) {
8127
+ const result = await runBounded("/usr/bin/xattr", ["-c", executable], {
8128
+ timeoutMs: 5e3,
8129
+ maxOutputBytes: MAX_PROCESS_OUTPUT_BYTES
8130
+ });
8131
+ return !result.error;
8132
+ }
8133
+ var defaultDeps = {
8134
+ platform: process.platform,
8135
+ resolveExecutable: defaultResolveExecutable,
8136
+ probe: defaultProbeCodexExecutable,
8137
+ verifyOfficialSignature: defaultVerifyOfficialSignature,
8138
+ clearExtendedAttributes: defaultClearExtendedAttributes
8139
+ };
8140
+ function provenanceFor(executable) {
8141
+ const normalized = executable.replaceAll("\\", "/");
8142
+ if (/\/Caskroom\/codex\//i.test(normalized)) return "homebrew";
8143
+ if (/\/Applications\/Codex\.app\//i.test(normalized)) return "chatgpt";
8144
+ if (/\/node_modules\/(?:@openai\/codex|@openai\/codex-[^/]+)\//i.test(normalized)) return "npm";
8145
+ return "unknown";
8146
+ }
8147
+ function validSource(sourceStat) {
8148
+ return sourceStat.isFile() && sourceStat.size > 0 && sourceStat.size <= MAX_CODEX_BYTES;
8149
+ }
8150
+ function sameSource(left, right) {
8151
+ return left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mtimeMs === right.mtimeMs;
8152
+ }
8153
+ async function removeLegacyManagedCopy(dataDir) {
8154
+ const directory = join12(dataDir, LEGACY_MANAGED_DIRECTORY);
8155
+ await Promise.all([
8156
+ rm(join12(directory, LEGACY_MANAGED_EXECUTABLE), { force: true }).catch(() => void 0),
8157
+ rm(join12(directory, LEGACY_MANAGED_STATE), { force: true }).catch(() => void 0)
8158
+ ]);
8159
+ await rmdir2(directory).catch(() => void 0);
8160
+ }
8161
+ async function repairHomebrewExecutable(source, sourceStat, env, deps) {
8162
+ if (!await deps.verifyOfficialSignature(source)) return false;
8163
+ const nonce = randomUUID7();
8164
+ const directory = dirname5(source);
8165
+ const name = basename3(source);
8166
+ const temporary = join12(directory, `.${name}.roamcode-repair-${nonce}`);
8167
+ const backup = join12(directory, `.${name}.roamcode-backup-${nonce}`);
8168
+ let backupCreated = false;
8169
+ let sourceReplaced = false;
8170
+ let committed = false;
8171
+ let preserveBackup = false;
8172
+ try {
8173
+ await copyFile(source, temporary, constants2.COPYFILE_EXCL);
8174
+ await chmod(temporary, sourceStat.mode & 511);
8175
+ if (!await deps.clearExtendedAttributes(temporary)) return false;
8176
+ if (!await deps.verifyOfficialSignature(temporary)) return false;
8177
+ if ((await deps.probe(temporary, env)).state !== "ready") return false;
8178
+ const currentStat = await stat3(source);
8179
+ if (!sameSource(sourceStat, currentStat)) return false;
8180
+ await link(source, backup);
8181
+ backupCreated = true;
8182
+ await rename2(temporary, source);
8183
+ sourceReplaced = true;
8184
+ if (!await deps.verifyOfficialSignature(source)) return false;
8185
+ if ((await deps.probe(source, env)).state !== "ready") return false;
8186
+ committed = true;
8187
+ return true;
8188
+ } catch {
8189
+ return false;
8190
+ } finally {
8191
+ if (sourceReplaced && !committed && backupCreated) {
8192
+ try {
8193
+ await rename2(backup, source);
8194
+ backupCreated = false;
8195
+ } catch {
8196
+ preserveBackup = true;
8197
+ }
8198
+ }
8199
+ await rm(temporary, { force: true }).catch(() => void 0);
8200
+ if (!preserveBackup) await rm(backup, { force: true }).catch(() => void 0);
8201
+ }
8202
+ }
8203
+ async function resolveCodexExecutable(options) {
8204
+ const env = options.env ?? process.env;
8205
+ const deps = { ...defaultDeps, ...options.deps };
8206
+ const unresolved = {
8207
+ executable: options.codexBin,
8208
+ sourceExecutable: options.codexBin,
8209
+ provenance: "unknown",
8210
+ recovered: false
8211
+ };
8212
+ if (deps.platform !== "darwin") return unresolved;
8213
+ const source = await deps.resolveExecutable(options.codexBin, env);
8214
+ if (!source) return unresolved;
8215
+ const provenance = provenanceFor(source);
8216
+ const ordinary = {
8217
+ executable: options.codexBin,
8218
+ sourceExecutable: source,
8219
+ provenance,
8220
+ recovered: false
8221
+ };
8222
+ let sourceStat;
8223
+ try {
8224
+ sourceStat = await stat3(source);
8225
+ } catch {
8226
+ return ordinary;
8227
+ }
8228
+ if (!validSource(sourceStat)) return ordinary;
8229
+ const sourceProbe = await deps.probe(source, env);
8230
+ if (sourceProbe.state === "ready") {
8231
+ await removeLegacyManagedCopy(options.dataDir);
8232
+ return ordinary;
8233
+ }
8234
+ if (sourceProbe.state !== "timeout" || provenance !== "homebrew") return ordinary;
8235
+ if (!await repairHomebrewExecutable(source, sourceStat, env, deps)) return ordinary;
8236
+ await removeLegacyManagedCopy(options.dataDir);
8237
+ return { ...ordinary, recovered: true };
8238
+ }
8239
+
8240
+ // src/start.ts
8241
+ import { execFile as execFile2 } from "child_process";
8051
8242
  function providerPreflightWarning(name, availability) {
8052
8243
  if (availability.terminalAvailable) return void 0;
8053
8244
  return `
@@ -8098,7 +8289,18 @@ async function startServer(env = process.env) {
8098
8289
  config.accessToken = token;
8099
8290
  }
8100
8291
  assertConfigAllowsStart(config);
8101
- const store = openSessionStore({ dbPath: join12(config.dataDir, "sessions.db") });
8292
+ const codexExecutable = await resolveCodexExecutable({
8293
+ codexBin: config.codexBin,
8294
+ dataDir: config.dataDir,
8295
+ env
8296
+ });
8297
+ const codexBin = codexExecutable.executable;
8298
+ if (codexExecutable.recovered) {
8299
+ console.warn(
8300
+ "[roamcode] Repaired a macOS-blocked Homebrew Codex CLI in place using a verified, byte-identical OpenAI-signed replacement; no private binary was retained."
8301
+ );
8302
+ }
8303
+ const store = openSessionStore({ dbPath: join13(config.dataDir, "sessions.db") });
8102
8304
  const storeMode = store.mode;
8103
8305
  if (storeMode === "memory-fallback") {
8104
8306
  console.warn(
@@ -8114,7 +8316,7 @@ async function startServer(env = process.env) {
8114
8316
  }).catch(() => {
8115
8317
  });
8116
8318
  const vapid = resolveVapidKeys({ dataDir: config.dataDir });
8117
- const pushStore = openPushStore({ dbPath: join12(config.dataDir, "push.db") });
8319
+ const pushStore = openPushStore({ dbPath: join13(config.dataDir, "push.db") });
8118
8320
  const vapidSubject = (env.ROAMCODE_VAPID_SUBJECT ?? env.REMOTE_CODER_VAPID_SUBJECT)?.trim() || "mailto:roamcode@localhost";
8119
8321
  let pushDispatcher;
8120
8322
  try {
@@ -8138,7 +8340,7 @@ async function startServer(env = process.env) {
8138
8340
  env
8139
8341
  })
8140
8342
  );
8141
- const codexClient = new CodexAppServerClient({ codexBin: config.codexBin, env });
8343
+ const codexClient = new CodexAppServerClient({ codexBin, env });
8142
8344
  const codexRpc = {
8143
8345
  request: async (method, params, schema) => {
8144
8346
  await codexClient.start();
@@ -8146,7 +8348,7 @@ async function startServer(env = process.env) {
8146
8348
  },
8147
8349
  onNotification: (listener) => codexClient.onNotification(listener)
8148
8350
  };
8149
- const profileClient = createCodexProfileClientLifecycle({ codexBin: config.codexBin, env });
8351
+ const profileClient = createCodexProfileClientLifecycle({ codexBin, env });
8150
8352
  const codexMetadata = new CodexMetadataService(codexRpc, {
8151
8353
  ...env.CODEX_HOME ? { codexHome: env.CODEX_HOME } : {},
8152
8354
  profileClient
@@ -8157,8 +8359,8 @@ async function startServer(env = process.env) {
8157
8359
  };
8158
8360
  const codexLatest = new CodexLatestService({
8159
8361
  runVersion: (args, options) => new Promise((resolve7, reject) => {
8160
- execFile(
8161
- config.codexBin,
8362
+ execFile2(
8363
+ codexBin,
8162
8364
  [...args],
8163
8365
  { env, timeout: options.timeoutMs, maxBuffer: options.maxOutputBytes, windowsHide: true },
8164
8366
  (error, stdout, stderr) => {
@@ -8175,7 +8377,7 @@ async function startServer(env = process.env) {
8175
8377
  }
8176
8378
  );
8177
8379
  }),
8178
- detectProvenance: () => "unknown",
8380
+ detectProvenance: () => codexExecutable.provenance,
8179
8381
  fetchNpmLatest: async (_packageName, options) => {
8180
8382
  const response = await fetch("https://registry.npmjs.org/@openai%2Fcodex/latest", {
8181
8383
  signal: AbortSignal.timeout(options.timeoutMs)
@@ -8201,7 +8403,7 @@ async function startServer(env = process.env) {
8201
8403
  }
8202
8404
  }),
8203
8405
  createCodexProvider({
8204
- codexBin: config.codexBin,
8406
+ codexBin,
8205
8407
  env,
8206
8408
  validateProfile: codexMetadata.validateProfile,
8207
8409
  probe: async () => {
@@ -8270,7 +8472,7 @@ async function startServer(env = process.env) {
8270
8472
  }
8271
8473
  function defaultWebDir() {
8272
8474
  const here = fileURLToPath2(new URL(".", import.meta.url));
8273
- return join12(here, "..", "..", "web", "dist");
8475
+ return join13(here, "..", "..", "web", "dist");
8274
8476
  }
8275
8477
  function installCrashGuards() {
8276
8478
  process.on("unhandledRejection", (reason) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roamcode.ai/server",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
4
4
  "description": "Host-native server for RoamCode",
5
5
  "homepage": "https://roamcode.ai",
6
6
  "bugs": "https://github.com/burakgon/roamcode/issues",
@@ -40,7 +40,7 @@
40
40
  "node-pty": "1.1.0",
41
41
  "web-push": "^3.6.7",
42
42
  "zod": "^4.4.3",
43
- "@roamcode.ai/web": "1.0.18"
43
+ "@roamcode.ai/web": "1.0.20"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/better-sqlite3": "^7.6.13",