@roamcode.ai/server 1.0.18 → 1.0.19

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,47 @@ 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
+ /** Executable used by every Codex terminal and auxiliary service for this server process. */
2470
+ executable: string;
2471
+ /** User-selected executable before a managed macOS recovery copy is considered. */
2472
+ sourceExecutable: string;
2473
+ provenance: CodexInstallProvenance;
2474
+ /** True only when the verified private copy is active. The source installation is never modified. */
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
+ * macOS 26 can indefinitely block a valid OpenAI-signed Codex Homebrew Cask binary in `_dyld_start`.
2493
+ * A byte-identical executable at a fresh, non-quarantined path starts normally. On an observed timeout,
2494
+ * this resolver creates that private copy only after checking the embedded official OpenAI signature.
2495
+ * It never modifies the user's Codex installation and never applies the workaround to an unsigned binary.
2496
+ */
2497
+ declare function resolveCodexExecutable(options: ResolveCodexExecutableOptions): Promise<CodexExecutableResolution>;
2498
+
2458
2499
  declare const CODEX_OSC_MAX_CARRY: number;
2459
2500
  declare const CODEX_CLASSIFIER_TESTED_UP_TO = "0.144";
2460
2501
  interface CodexOscParser extends ProviderRuntimeSignalParser {
@@ -2467,4 +2508,4 @@ declare function codexClassifierVersionWarning(codexVersion: string | undefined)
2467
2508
 
2468
2509
  declare const SERVER_PACKAGE = "@roamcode.ai/server";
2469
2510
 
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 };
2511
+ 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
@@ -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.19" ? "1.0.19".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,220 @@ 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 {
8592
+ access,
8593
+ chmod,
8594
+ copyFile,
8595
+ lstat as lstat2,
8596
+ mkdir as mkdir2,
8597
+ readFile as readFile3,
8598
+ realpath as realpath3,
8599
+ rename as rename2,
8600
+ rm,
8601
+ stat as stat3,
8602
+ writeFile
8603
+ } from "fs/promises";
8604
+ import { randomUUID as randomUUID7 } from "crypto";
8605
+ import { delimiter, isAbsolute as isAbsolute5, join as join12, sep as sep5 } from "path";
8606
+ var CODEX_EXECUTABLE_PROBE_TIMEOUT_MS = 5e3;
8607
+ var OPENAI_CODE_SIGNING_TEAM_ID = "2DC432GLL2";
8608
+ var STATE_SCHEMA_VERSION = 1;
8609
+ var MAX_MANAGED_CODEX_BYTES = 1024 * 1024 * 1024;
8610
+ var MANAGED_DIRECTORY = "provider-bin";
8611
+ var MANAGED_EXECUTABLE = "codex-macos";
8612
+ var STATE_FILE = "codex-macos-source.json";
8613
+ var MAX_PROCESS_OUTPUT_BYTES = 16 * 1024;
8614
+ function runBounded(executable, args, options) {
8615
+ return new Promise((resolve7) => {
8616
+ execFile(
8617
+ executable,
8618
+ [...args],
8619
+ {
8620
+ ...options.env ? { env: options.env } : {},
8621
+ timeout: options.timeoutMs,
8622
+ maxBuffer: options.maxOutputBytes,
8623
+ windowsHide: true,
8624
+ encoding: "utf8"
8625
+ },
8626
+ (error, stdout, stderr) => {
8627
+ resolve7({
8628
+ ...error ? { error } : {},
8629
+ stdout: String(stdout),
8630
+ stderr: String(stderr)
8631
+ });
8632
+ }
8633
+ );
8634
+ });
8635
+ }
8636
+ async function defaultProbeCodexExecutable(executable, env) {
8637
+ const result = await runBounded(executable, ["--version"], {
8638
+ env,
8639
+ timeoutMs: CODEX_EXECUTABLE_PROBE_TIMEOUT_MS,
8640
+ maxOutputBytes: 1024
8641
+ });
8642
+ if (result.error) {
8643
+ if (result.error.killed || result.error.signal === "SIGTERM" || result.error.signal === "SIGKILL") {
8644
+ return { state: "timeout" };
8645
+ }
8646
+ return { state: "failed" };
8647
+ }
8648
+ try {
8649
+ return { state: "ready", version: parseCodexVersion(result.stdout) };
8650
+ } catch {
8651
+ return { state: "failed" };
8652
+ }
8653
+ }
8654
+ async function defaultResolveExecutable(command, env) {
8655
+ const candidates = isAbsolute5(command) || command.includes(sep5) ? [command] : (env.PATH ?? process.env.PATH ?? "").split(delimiter).filter(Boolean).map((entry) => join12(entry, command));
8656
+ for (const candidate of candidates) {
8657
+ try {
8658
+ await access(candidate, constants2.X_OK);
8659
+ return await realpath3(candidate);
8660
+ } catch {
8661
+ }
8662
+ }
8663
+ return void 0;
8664
+ }
8665
+ async function defaultVerifyOfficialSignature(executable) {
8666
+ const verified = await runBounded("/usr/bin/codesign", ["--verify", "--strict", executable], {
8667
+ timeoutMs: 5e3,
8668
+ maxOutputBytes: MAX_PROCESS_OUTPUT_BYTES
8669
+ });
8670
+ if (verified.error) return false;
8671
+ const details = await runBounded("/usr/bin/codesign", ["-d", "--verbose=4", executable], {
8672
+ timeoutMs: 5e3,
8673
+ maxOutputBytes: MAX_PROCESS_OUTPUT_BYTES
8674
+ });
8675
+ if (details.error) return false;
8676
+ return new RegExp(`^TeamIdentifier=${OPENAI_CODE_SIGNING_TEAM_ID}$`, "m").test(details.stderr) && /^Identifier=codex$/m.test(details.stderr);
8677
+ }
8678
+ async function defaultClearExtendedAttributes(executable) {
8679
+ const result = await runBounded("/usr/bin/xattr", ["-c", executable], {
8680
+ timeoutMs: 5e3,
8681
+ maxOutputBytes: MAX_PROCESS_OUTPUT_BYTES
8682
+ });
8683
+ return !result.error;
8684
+ }
8685
+ var defaultDeps = {
8686
+ platform: process.platform,
8687
+ resolveExecutable: defaultResolveExecutable,
8688
+ probe: defaultProbeCodexExecutable,
8689
+ verifyOfficialSignature: defaultVerifyOfficialSignature,
8690
+ clearExtendedAttributes: defaultClearExtendedAttributes
8691
+ };
8692
+ function provenanceFor(executable) {
8693
+ const normalized = executable.replaceAll("\\", "/");
8694
+ if (/\/Caskroom\/codex\//i.test(normalized)) return "homebrew";
8695
+ if (/\/Applications\/Codex\.app\//i.test(normalized)) return "chatgpt";
8696
+ if (/\/node_modules\/(?:@openai\/codex|@openai\/codex-[^/]+)\//i.test(normalized)) return "npm";
8697
+ return "unknown";
8698
+ }
8699
+ function identityFor(sourcePath, sourceStat) {
8700
+ return {
8701
+ sourcePath,
8702
+ device: String(sourceStat.dev),
8703
+ inode: String(sourceStat.ino),
8704
+ size: sourceStat.size,
8705
+ modifiedAtMs: sourceStat.mtimeMs
8706
+ };
8707
+ }
8708
+ function parseManagedState(raw) {
8709
+ try {
8710
+ const value = JSON.parse(raw);
8711
+ if (value.schemaVersion !== STATE_SCHEMA_VERSION || typeof value.sourcePath !== "string" || typeof value.device !== "string" || typeof value.inode !== "string" || typeof value.size !== "number" || !Number.isSafeInteger(value.size) || typeof value.modifiedAtMs !== "number" || !Number.isFinite(value.modifiedAtMs)) {
8712
+ return void 0;
8713
+ }
8714
+ return value;
8715
+ } catch {
8716
+ return void 0;
8717
+ }
8718
+ }
8719
+ function stateMatches(state, identity) {
8720
+ return state.sourcePath === identity.sourcePath && state.device === identity.device && state.inode === identity.inode && state.size === identity.size && state.modifiedAtMs === identity.modifiedAtMs;
8721
+ }
8722
+ async function usableManagedExecutable(target, env, deps) {
8723
+ try {
8724
+ const targetStat = await lstat2(target);
8725
+ if (!targetStat.isFile() || targetStat.isSymbolicLink()) return false;
8726
+ if (!await deps.verifyOfficialSignature(target)) return false;
8727
+ return (await deps.probe(target, env)).state === "ready";
8728
+ } catch {
8729
+ return false;
8730
+ }
8731
+ }
8732
+ async function resolveCodexExecutable(options) {
8733
+ const env = options.env ?? process.env;
8734
+ const deps = { ...defaultDeps, ...options.deps };
8735
+ const unresolved = {
8736
+ executable: options.codexBin,
8737
+ sourceExecutable: options.codexBin,
8738
+ provenance: "unknown",
8739
+ recovered: false
8740
+ };
8741
+ if (deps.platform !== "darwin") return unresolved;
8742
+ const source = await deps.resolveExecutable(options.codexBin, env);
8743
+ if (!source) return unresolved;
8744
+ const provenance = provenanceFor(source);
8745
+ const ordinary = {
8746
+ executable: source,
8747
+ sourceExecutable: source,
8748
+ provenance,
8749
+ recovered: false
8750
+ };
8751
+ let sourceStat;
8752
+ try {
8753
+ sourceStat = await stat3(source);
8754
+ } catch {
8755
+ return ordinary;
8756
+ }
8757
+ if (!sourceStat.isFile() || sourceStat.size <= 0 || sourceStat.size > MAX_MANAGED_CODEX_BYTES) return ordinary;
8758
+ const identity = identityFor(source, sourceStat);
8759
+ const managedDir = join12(options.dataDir, MANAGED_DIRECTORY);
8760
+ const target = join12(managedDir, MANAGED_EXECUTABLE);
8761
+ const statePath = join12(managedDir, STATE_FILE);
8762
+ try {
8763
+ const state = parseManagedState(await readFile3(statePath, "utf8"));
8764
+ if (state && stateMatches(state, identity) && await usableManagedExecutable(target, env, deps)) {
8765
+ return { executable: target, sourceExecutable: source, provenance, recovered: true };
8766
+ }
8767
+ } catch {
8768
+ }
8769
+ const sourceProbe = await deps.probe(source, env);
8770
+ if (sourceProbe.state !== "timeout") return ordinary;
8771
+ if (!await deps.verifyOfficialSignature(source)) return ordinary;
8772
+ const nonce = randomUUID7();
8773
+ const temporaryExecutable = join12(managedDir, `.codex-${nonce}.tmp`);
8774
+ const temporaryState = join12(managedDir, `.codex-state-${nonce}.tmp`);
8775
+ try {
8776
+ await mkdir2(managedDir, { recursive: true, mode: 448 });
8777
+ await chmod(managedDir, 448);
8778
+ await copyFile(source, temporaryExecutable, constants2.COPYFILE_EXCL);
8779
+ await chmod(temporaryExecutable, 448);
8780
+ if (!await deps.clearExtendedAttributes(temporaryExecutable)) return ordinary;
8781
+ if (!await deps.verifyOfficialSignature(temporaryExecutable)) return ordinary;
8782
+ if ((await deps.probe(temporaryExecutable, env)).state !== "ready") return ordinary;
8783
+ await rename2(temporaryExecutable, target);
8784
+ if (!await usableManagedExecutable(target, env, deps)) return ordinary;
8785
+ const state = { schemaVersion: STATE_SCHEMA_VERSION, ...identity };
8786
+ await writeFile(temporaryState, `${JSON.stringify(state)}
8787
+ `, { encoding: "utf8", mode: 384, flag: "wx" });
8788
+ await rename2(temporaryState, statePath);
8789
+ return { executable: target, sourceExecutable: source, provenance, recovered: true };
8790
+ } catch {
8791
+ return ordinary;
8792
+ } finally {
8793
+ await Promise.all([
8794
+ rm(temporaryExecutable, { force: true }).catch(() => void 0),
8795
+ rm(temporaryState, { force: true }).catch(() => void 0)
8796
+ ]);
8797
+ }
8798
+ }
8799
+
8800
+ // src/start.ts
8801
+ import { execFile as execFile2 } from "child_process";
8590
8802
  function providerPreflightWarning(name, availability) {
8591
8803
  if (availability.terminalAvailable) return void 0;
8592
8804
  return `
@@ -8637,7 +8849,18 @@ async function startServer(env = process.env) {
8637
8849
  config.accessToken = token;
8638
8850
  }
8639
8851
  assertConfigAllowsStart(config);
8640
- const store = openSessionStore({ dbPath: join12(config.dataDir, "sessions.db") });
8852
+ const codexExecutable = await resolveCodexExecutable({
8853
+ codexBin: config.codexBin,
8854
+ dataDir: config.dataDir,
8855
+ env
8856
+ });
8857
+ const codexBin = codexExecutable.executable;
8858
+ if (codexExecutable.recovered) {
8859
+ console.warn(
8860
+ "[roamcode] Recovered a macOS-blocked Codex CLI using a private verified copy of the official OpenAI-signed binary; the original installation was left unchanged."
8861
+ );
8862
+ }
8863
+ const store = openSessionStore({ dbPath: join13(config.dataDir, "sessions.db") });
8641
8864
  const storeMode = store.mode;
8642
8865
  if (storeMode === "memory-fallback") {
8643
8866
  console.warn(
@@ -8653,7 +8876,7 @@ async function startServer(env = process.env) {
8653
8876
  }).catch(() => {
8654
8877
  });
8655
8878
  const vapid = resolveVapidKeys({ dataDir: config.dataDir });
8656
- const pushStore = openPushStore({ dbPath: join12(config.dataDir, "push.db") });
8879
+ const pushStore = openPushStore({ dbPath: join13(config.dataDir, "push.db") });
8657
8880
  const vapidSubject = (env.ROAMCODE_VAPID_SUBJECT ?? env.REMOTE_CODER_VAPID_SUBJECT)?.trim() || "mailto:roamcode@localhost";
8658
8881
  let pushDispatcher;
8659
8882
  try {
@@ -8677,7 +8900,7 @@ async function startServer(env = process.env) {
8677
8900
  env
8678
8901
  })
8679
8902
  );
8680
- const codexClient = new CodexAppServerClient({ codexBin: config.codexBin, env });
8903
+ const codexClient = new CodexAppServerClient({ codexBin, env });
8681
8904
  const codexRpc = {
8682
8905
  request: async (method, params, schema) => {
8683
8906
  await codexClient.start();
@@ -8685,7 +8908,7 @@ async function startServer(env = process.env) {
8685
8908
  },
8686
8909
  onNotification: (listener) => codexClient.onNotification(listener)
8687
8910
  };
8688
- const profileClient = createCodexProfileClientLifecycle({ codexBin: config.codexBin, env });
8911
+ const profileClient = createCodexProfileClientLifecycle({ codexBin, env });
8689
8912
  const codexMetadata = new CodexMetadataService(codexRpc, {
8690
8913
  ...env.CODEX_HOME ? { codexHome: env.CODEX_HOME } : {},
8691
8914
  profileClient
@@ -8696,8 +8919,8 @@ async function startServer(env = process.env) {
8696
8919
  };
8697
8920
  const codexLatest = new CodexLatestService({
8698
8921
  runVersion: (args, options) => new Promise((resolve7, reject) => {
8699
- execFile(
8700
- config.codexBin,
8922
+ execFile2(
8923
+ codexBin,
8701
8924
  [...args],
8702
8925
  { env, timeout: options.timeoutMs, maxBuffer: options.maxOutputBytes, windowsHide: true },
8703
8926
  (error, stdout, stderr) => {
@@ -8714,7 +8937,7 @@ async function startServer(env = process.env) {
8714
8937
  }
8715
8938
  );
8716
8939
  }),
8717
- detectProvenance: () => "unknown",
8940
+ detectProvenance: () => codexExecutable.provenance,
8718
8941
  fetchNpmLatest: async (_packageName, options) => {
8719
8942
  const response = await fetch("https://registry.npmjs.org/@openai%2Fcodex/latest", {
8720
8943
  signal: AbortSignal.timeout(options.timeoutMs)
@@ -8740,7 +8963,7 @@ async function startServer(env = process.env) {
8740
8963
  }
8741
8964
  }),
8742
8965
  createCodexProvider({
8743
- codexBin: config.codexBin,
8966
+ codexBin,
8744
8967
  env,
8745
8968
  validateProfile: codexMetadata.validateProfile,
8746
8969
  probe: async () => {
@@ -8809,7 +9032,7 @@ async function startServer(env = process.env) {
8809
9032
  }
8810
9033
  function defaultWebDir() {
8811
9034
  const here = fileURLToPath2(new URL(".", import.meta.url));
8812
- return join12(here, "..", "..", "web", "dist");
9035
+ return join13(here, "..", "..", "web", "dist");
8813
9036
  }
8814
9037
  function installCrashGuards() {
8815
9038
  process.on("unhandledRejection", (reason) => {
@@ -8858,6 +9081,7 @@ export {
8858
9081
  CLAUDE_VERSION_CACHE_MS,
8859
9082
  CLAUDE_VERSION_TIMEOUT_MS,
8860
9083
  CODEX_CLASSIFIER_TESTED_UP_TO,
9084
+ CODEX_EXECUTABLE_PROBE_TIMEOUT_MS,
8861
9085
  CODEX_OSC_MAX_CARRY,
8862
9086
  ClaudeAuthService,
8863
9087
  ClaudeLatestService,
@@ -8869,6 +9093,7 @@ export {
8869
9093
  FETCH_TIMEOUT_MS,
8870
9094
  FsError,
8871
9095
  FsService,
9096
+ OPENAI_CODE_SIGNING_TEAM_ID,
8872
9097
  ProviderError,
8873
9098
  ProviderOptionsError,
8874
9099
  ProviderRegistry,
@@ -8922,6 +9147,7 @@ export {
8922
9147
  createWebPushSend,
8923
9148
  defaultFetchManifest,
8924
9149
  defaultFetchReleases,
9150
+ defaultProbeCodexExecutable,
8925
9151
  defaultRunClaudeVersion,
8926
9152
  defaultUpdaterFs,
8927
9153
  detectTerminalSupport,
@@ -8980,6 +9206,7 @@ export {
8980
9206
  renderSystemdUnit,
8981
9207
  resetCodexThreadResolutionCoordinatorForTests,
8982
9208
  resolveAccessToken,
9209
+ resolveCodexExecutable,
8983
9210
  resolveDataDir,
8984
9211
  resolveInstallRoot,
8985
9212
  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.19" ? "1.0.19".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,220 @@ 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 {
8053
+ access,
8054
+ chmod,
8055
+ copyFile,
8056
+ lstat as lstat2,
8057
+ mkdir as mkdir2,
8058
+ readFile as readFile3,
8059
+ realpath as realpath3,
8060
+ rename as rename2,
8061
+ rm,
8062
+ stat as stat3,
8063
+ writeFile
8064
+ } from "fs/promises";
8065
+ import { randomUUID as randomUUID7 } from "crypto";
8066
+ import { delimiter, isAbsolute as isAbsolute5, join as join12, sep as sep5 } from "path";
8067
+ var CODEX_EXECUTABLE_PROBE_TIMEOUT_MS = 5e3;
8068
+ var OPENAI_CODE_SIGNING_TEAM_ID = "2DC432GLL2";
8069
+ var STATE_SCHEMA_VERSION = 1;
8070
+ var MAX_MANAGED_CODEX_BYTES = 1024 * 1024 * 1024;
8071
+ var MANAGED_DIRECTORY = "provider-bin";
8072
+ var MANAGED_EXECUTABLE = "codex-macos";
8073
+ var STATE_FILE = "codex-macos-source.json";
8074
+ var MAX_PROCESS_OUTPUT_BYTES = 16 * 1024;
8075
+ function runBounded(executable, args, options) {
8076
+ return new Promise((resolve7) => {
8077
+ execFile(
8078
+ executable,
8079
+ [...args],
8080
+ {
8081
+ ...options.env ? { env: options.env } : {},
8082
+ timeout: options.timeoutMs,
8083
+ maxBuffer: options.maxOutputBytes,
8084
+ windowsHide: true,
8085
+ encoding: "utf8"
8086
+ },
8087
+ (error, stdout, stderr) => {
8088
+ resolve7({
8089
+ ...error ? { error } : {},
8090
+ stdout: String(stdout),
8091
+ stderr: String(stderr)
8092
+ });
8093
+ }
8094
+ );
8095
+ });
8096
+ }
8097
+ async function defaultProbeCodexExecutable(executable, env) {
8098
+ const result = await runBounded(executable, ["--version"], {
8099
+ env,
8100
+ timeoutMs: CODEX_EXECUTABLE_PROBE_TIMEOUT_MS,
8101
+ maxOutputBytes: 1024
8102
+ });
8103
+ if (result.error) {
8104
+ if (result.error.killed || result.error.signal === "SIGTERM" || result.error.signal === "SIGKILL") {
8105
+ return { state: "timeout" };
8106
+ }
8107
+ return { state: "failed" };
8108
+ }
8109
+ try {
8110
+ return { state: "ready", version: parseCodexVersion(result.stdout) };
8111
+ } catch {
8112
+ return { state: "failed" };
8113
+ }
8114
+ }
8115
+ async function defaultResolveExecutable(command, env) {
8116
+ const candidates = isAbsolute5(command) || command.includes(sep5) ? [command] : (env.PATH ?? process.env.PATH ?? "").split(delimiter).filter(Boolean).map((entry) => join12(entry, command));
8117
+ for (const candidate of candidates) {
8118
+ try {
8119
+ await access(candidate, constants2.X_OK);
8120
+ return await realpath3(candidate);
8121
+ } catch {
8122
+ }
8123
+ }
8124
+ return void 0;
8125
+ }
8126
+ async function defaultVerifyOfficialSignature(executable) {
8127
+ const verified = await runBounded("/usr/bin/codesign", ["--verify", "--strict", executable], {
8128
+ timeoutMs: 5e3,
8129
+ maxOutputBytes: MAX_PROCESS_OUTPUT_BYTES
8130
+ });
8131
+ if (verified.error) return false;
8132
+ const details = await runBounded("/usr/bin/codesign", ["-d", "--verbose=4", executable], {
8133
+ timeoutMs: 5e3,
8134
+ maxOutputBytes: MAX_PROCESS_OUTPUT_BYTES
8135
+ });
8136
+ if (details.error) return false;
8137
+ return new RegExp(`^TeamIdentifier=${OPENAI_CODE_SIGNING_TEAM_ID}$`, "m").test(details.stderr) && /^Identifier=codex$/m.test(details.stderr);
8138
+ }
8139
+ async function defaultClearExtendedAttributes(executable) {
8140
+ const result = await runBounded("/usr/bin/xattr", ["-c", executable], {
8141
+ timeoutMs: 5e3,
8142
+ maxOutputBytes: MAX_PROCESS_OUTPUT_BYTES
8143
+ });
8144
+ return !result.error;
8145
+ }
8146
+ var defaultDeps = {
8147
+ platform: process.platform,
8148
+ resolveExecutable: defaultResolveExecutable,
8149
+ probe: defaultProbeCodexExecutable,
8150
+ verifyOfficialSignature: defaultVerifyOfficialSignature,
8151
+ clearExtendedAttributes: defaultClearExtendedAttributes
8152
+ };
8153
+ function provenanceFor(executable) {
8154
+ const normalized = executable.replaceAll("\\", "/");
8155
+ if (/\/Caskroom\/codex\//i.test(normalized)) return "homebrew";
8156
+ if (/\/Applications\/Codex\.app\//i.test(normalized)) return "chatgpt";
8157
+ if (/\/node_modules\/(?:@openai\/codex|@openai\/codex-[^/]+)\//i.test(normalized)) return "npm";
8158
+ return "unknown";
8159
+ }
8160
+ function identityFor(sourcePath, sourceStat) {
8161
+ return {
8162
+ sourcePath,
8163
+ device: String(sourceStat.dev),
8164
+ inode: String(sourceStat.ino),
8165
+ size: sourceStat.size,
8166
+ modifiedAtMs: sourceStat.mtimeMs
8167
+ };
8168
+ }
8169
+ function parseManagedState(raw) {
8170
+ try {
8171
+ const value = JSON.parse(raw);
8172
+ if (value.schemaVersion !== STATE_SCHEMA_VERSION || typeof value.sourcePath !== "string" || typeof value.device !== "string" || typeof value.inode !== "string" || typeof value.size !== "number" || !Number.isSafeInteger(value.size) || typeof value.modifiedAtMs !== "number" || !Number.isFinite(value.modifiedAtMs)) {
8173
+ return void 0;
8174
+ }
8175
+ return value;
8176
+ } catch {
8177
+ return void 0;
8178
+ }
8179
+ }
8180
+ function stateMatches(state, identity) {
8181
+ return state.sourcePath === identity.sourcePath && state.device === identity.device && state.inode === identity.inode && state.size === identity.size && state.modifiedAtMs === identity.modifiedAtMs;
8182
+ }
8183
+ async function usableManagedExecutable(target, env, deps) {
8184
+ try {
8185
+ const targetStat = await lstat2(target);
8186
+ if (!targetStat.isFile() || targetStat.isSymbolicLink()) return false;
8187
+ if (!await deps.verifyOfficialSignature(target)) return false;
8188
+ return (await deps.probe(target, env)).state === "ready";
8189
+ } catch {
8190
+ return false;
8191
+ }
8192
+ }
8193
+ async function resolveCodexExecutable(options) {
8194
+ const env = options.env ?? process.env;
8195
+ const deps = { ...defaultDeps, ...options.deps };
8196
+ const unresolved = {
8197
+ executable: options.codexBin,
8198
+ sourceExecutable: options.codexBin,
8199
+ provenance: "unknown",
8200
+ recovered: false
8201
+ };
8202
+ if (deps.platform !== "darwin") return unresolved;
8203
+ const source = await deps.resolveExecutable(options.codexBin, env);
8204
+ if (!source) return unresolved;
8205
+ const provenance = provenanceFor(source);
8206
+ const ordinary = {
8207
+ executable: source,
8208
+ sourceExecutable: source,
8209
+ provenance,
8210
+ recovered: false
8211
+ };
8212
+ let sourceStat;
8213
+ try {
8214
+ sourceStat = await stat3(source);
8215
+ } catch {
8216
+ return ordinary;
8217
+ }
8218
+ if (!sourceStat.isFile() || sourceStat.size <= 0 || sourceStat.size > MAX_MANAGED_CODEX_BYTES) return ordinary;
8219
+ const identity = identityFor(source, sourceStat);
8220
+ const managedDir = join12(options.dataDir, MANAGED_DIRECTORY);
8221
+ const target = join12(managedDir, MANAGED_EXECUTABLE);
8222
+ const statePath = join12(managedDir, STATE_FILE);
8223
+ try {
8224
+ const state = parseManagedState(await readFile3(statePath, "utf8"));
8225
+ if (state && stateMatches(state, identity) && await usableManagedExecutable(target, env, deps)) {
8226
+ return { executable: target, sourceExecutable: source, provenance, recovered: true };
8227
+ }
8228
+ } catch {
8229
+ }
8230
+ const sourceProbe = await deps.probe(source, env);
8231
+ if (sourceProbe.state !== "timeout") return ordinary;
8232
+ if (!await deps.verifyOfficialSignature(source)) return ordinary;
8233
+ const nonce = randomUUID7();
8234
+ const temporaryExecutable = join12(managedDir, `.codex-${nonce}.tmp`);
8235
+ const temporaryState = join12(managedDir, `.codex-state-${nonce}.tmp`);
8236
+ try {
8237
+ await mkdir2(managedDir, { recursive: true, mode: 448 });
8238
+ await chmod(managedDir, 448);
8239
+ await copyFile(source, temporaryExecutable, constants2.COPYFILE_EXCL);
8240
+ await chmod(temporaryExecutable, 448);
8241
+ if (!await deps.clearExtendedAttributes(temporaryExecutable)) return ordinary;
8242
+ if (!await deps.verifyOfficialSignature(temporaryExecutable)) return ordinary;
8243
+ if ((await deps.probe(temporaryExecutable, env)).state !== "ready") return ordinary;
8244
+ await rename2(temporaryExecutable, target);
8245
+ if (!await usableManagedExecutable(target, env, deps)) return ordinary;
8246
+ const state = { schemaVersion: STATE_SCHEMA_VERSION, ...identity };
8247
+ await writeFile(temporaryState, `${JSON.stringify(state)}
8248
+ `, { encoding: "utf8", mode: 384, flag: "wx" });
8249
+ await rename2(temporaryState, statePath);
8250
+ return { executable: target, sourceExecutable: source, provenance, recovered: true };
8251
+ } catch {
8252
+ return ordinary;
8253
+ } finally {
8254
+ await Promise.all([
8255
+ rm(temporaryExecutable, { force: true }).catch(() => void 0),
8256
+ rm(temporaryState, { force: true }).catch(() => void 0)
8257
+ ]);
8258
+ }
8259
+ }
8260
+
8261
+ // src/start.ts
8262
+ import { execFile as execFile2 } from "child_process";
8051
8263
  function providerPreflightWarning(name, availability) {
8052
8264
  if (availability.terminalAvailable) return void 0;
8053
8265
  return `
@@ -8098,7 +8310,18 @@ async function startServer(env = process.env) {
8098
8310
  config.accessToken = token;
8099
8311
  }
8100
8312
  assertConfigAllowsStart(config);
8101
- const store = openSessionStore({ dbPath: join12(config.dataDir, "sessions.db") });
8313
+ const codexExecutable = await resolveCodexExecutable({
8314
+ codexBin: config.codexBin,
8315
+ dataDir: config.dataDir,
8316
+ env
8317
+ });
8318
+ const codexBin = codexExecutable.executable;
8319
+ if (codexExecutable.recovered) {
8320
+ console.warn(
8321
+ "[roamcode] Recovered a macOS-blocked Codex CLI using a private verified copy of the official OpenAI-signed binary; the original installation was left unchanged."
8322
+ );
8323
+ }
8324
+ const store = openSessionStore({ dbPath: join13(config.dataDir, "sessions.db") });
8102
8325
  const storeMode = store.mode;
8103
8326
  if (storeMode === "memory-fallback") {
8104
8327
  console.warn(
@@ -8114,7 +8337,7 @@ async function startServer(env = process.env) {
8114
8337
  }).catch(() => {
8115
8338
  });
8116
8339
  const vapid = resolveVapidKeys({ dataDir: config.dataDir });
8117
- const pushStore = openPushStore({ dbPath: join12(config.dataDir, "push.db") });
8340
+ const pushStore = openPushStore({ dbPath: join13(config.dataDir, "push.db") });
8118
8341
  const vapidSubject = (env.ROAMCODE_VAPID_SUBJECT ?? env.REMOTE_CODER_VAPID_SUBJECT)?.trim() || "mailto:roamcode@localhost";
8119
8342
  let pushDispatcher;
8120
8343
  try {
@@ -8138,7 +8361,7 @@ async function startServer(env = process.env) {
8138
8361
  env
8139
8362
  })
8140
8363
  );
8141
- const codexClient = new CodexAppServerClient({ codexBin: config.codexBin, env });
8364
+ const codexClient = new CodexAppServerClient({ codexBin, env });
8142
8365
  const codexRpc = {
8143
8366
  request: async (method, params, schema) => {
8144
8367
  await codexClient.start();
@@ -8146,7 +8369,7 @@ async function startServer(env = process.env) {
8146
8369
  },
8147
8370
  onNotification: (listener) => codexClient.onNotification(listener)
8148
8371
  };
8149
- const profileClient = createCodexProfileClientLifecycle({ codexBin: config.codexBin, env });
8372
+ const profileClient = createCodexProfileClientLifecycle({ codexBin, env });
8150
8373
  const codexMetadata = new CodexMetadataService(codexRpc, {
8151
8374
  ...env.CODEX_HOME ? { codexHome: env.CODEX_HOME } : {},
8152
8375
  profileClient
@@ -8157,8 +8380,8 @@ async function startServer(env = process.env) {
8157
8380
  };
8158
8381
  const codexLatest = new CodexLatestService({
8159
8382
  runVersion: (args, options) => new Promise((resolve7, reject) => {
8160
- execFile(
8161
- config.codexBin,
8383
+ execFile2(
8384
+ codexBin,
8162
8385
  [...args],
8163
8386
  { env, timeout: options.timeoutMs, maxBuffer: options.maxOutputBytes, windowsHide: true },
8164
8387
  (error, stdout, stderr) => {
@@ -8175,7 +8398,7 @@ async function startServer(env = process.env) {
8175
8398
  }
8176
8399
  );
8177
8400
  }),
8178
- detectProvenance: () => "unknown",
8401
+ detectProvenance: () => codexExecutable.provenance,
8179
8402
  fetchNpmLatest: async (_packageName, options) => {
8180
8403
  const response = await fetch("https://registry.npmjs.org/@openai%2Fcodex/latest", {
8181
8404
  signal: AbortSignal.timeout(options.timeoutMs)
@@ -8201,7 +8424,7 @@ async function startServer(env = process.env) {
8201
8424
  }
8202
8425
  }),
8203
8426
  createCodexProvider({
8204
- codexBin: config.codexBin,
8427
+ codexBin,
8205
8428
  env,
8206
8429
  validateProfile: codexMetadata.validateProfile,
8207
8430
  probe: async () => {
@@ -8270,7 +8493,7 @@ async function startServer(env = process.env) {
8270
8493
  }
8271
8494
  function defaultWebDir() {
8272
8495
  const here = fileURLToPath2(new URL(".", import.meta.url));
8273
- return join12(here, "..", "..", "web", "dist");
8496
+ return join13(here, "..", "..", "web", "dist");
8274
8497
  }
8275
8498
  function installCrashGuards() {
8276
8499
  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.19",
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.19"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/better-sqlite3": "^7.6.13",