@roamcode.ai/server 1.0.17 → 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 +42 -1
- package/dist/index.js +251 -24
- package/dist/start.js +247 -24
- package/package.json +2 -2
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.
|
|
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
|
|
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
|
|
7400
|
-
if (!
|
|
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() &&
|
|
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
|
|
7433
|
-
if (!
|
|
7434
|
-
if (
|
|
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() ||
|
|
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:
|
|
7446
|
-
ino:
|
|
7447
|
-
size:
|
|
7448
|
-
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/
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
8700
|
-
|
|
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: () =>
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
6861
|
-
if (!
|
|
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() &&
|
|
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
|
|
6894
|
-
if (!
|
|
6895
|
-
if (
|
|
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() ||
|
|
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:
|
|
6907
|
-
ino:
|
|
6908
|
-
size:
|
|
6909
|
-
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/
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
8161
|
-
|
|
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: () =>
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
43
|
+
"@roamcode.ai/web": "1.0.19"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/better-sqlite3": "^7.6.13",
|