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