Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,26 +47,55 @@ type PendingRequest = {

type RuntimeConfigReadOptions = Pick<
DeepScanPermissionProfilePreflightOptions,
"codexPath" | "cwd" | "configOverrides" | "env" | "signal" | "context"
"codexPath" | "cwd" | "configOverrides" | "env" | "signal"
> & {
readonly context?: PreflightContext;
readonly commandArgs?: readonly string[];
readonly configCwd?: string;
/** Full app-server invocation for callers that already constructed its arguments. */
readonly appServerArgs?: readonly string[];
readonly providerConfigOverrides?: readonly string[];
};

type PreflightContext = "helper" | "parent-config";

/** Read native effective settings, including managed provider requirements, without a turn. */
export async function readDeepScanRuntimeConfig(
options: RuntimeConfigReadOptions,
): Promise<JsonRecord> {
options: RuntimeConfigReadOptions & {
readonly plugin?: { marketplacePath: string; pluginName: string };
},
): Promise<JsonRecord | undefined> {
return withPreflightClient(options, async (client) => {
const response = await client.request("config/read", {
cwd: options.cwd,
cwd: options.configCwd ?? options.cwd,
includeLayers: false,
});
const config = record(response.config);
if (!config) throw malformedPreflightError(options.context);
return config;
if (!options.plugin) return config;
// Portable plugins expose their admitted server names through native discovery.
// Preserve native routing if the selected Codex cannot provide that metadata.
const detail = await client
.request("plugin/read", options.plugin)
.catch(() => undefined);
options.signal.throwIfAborted();
const plugin = record(detail?.plugin);
const summary = record(plugin?.summary);
const servers = plugin?.mcpServers;
if (
summary?.installed !== true ||
summary.enabled !== true ||
!Array.isArray(servers) ||
!servers.every((name) => typeof name === "string")
)
return undefined;
return {
...config,
mcp_servers: {
...Object.fromEntries(servers.map((name) => [name, {}])),
...record(config.mcp_servers),
},
};
});
}

Expand Down Expand Up @@ -266,7 +295,8 @@ async function withPreflightClient<T>(
options: RuntimeConfigReadOptions,
operation: (client: AppServerPreflightClient) => Promise<T>,
): Promise<T> {
if (options.signal.aborted) throw abortError(options.signal.reason);
if (options.signal.aborted)
throw abortError(options.signal.reason, options.context);
const client = new AppServerPreflightClient(options);
try {
await client.initialize();
Expand Down Expand Up @@ -320,6 +350,7 @@ class AppServerPreflightClient {
args.push("--config", override);
}
args.push("app-server", "--stdio");
if (options.configCwd !== undefined) args.push("--cd", options.configCwd);
}

this.child = spawn(executablePathForSpawn(options.codexPath), args, {
Expand Down Expand Up @@ -361,7 +392,7 @@ class AppServerPreflightClient {
});

const onAbort = () => {
this.fail(abortError(options.signal.reason));
this.fail(abortError(options.signal.reason, options.context));
this.stopChild();
};
options.signal.addEventListener("abort", onAbort, { once: true });
Expand Down Expand Up @@ -667,21 +698,28 @@ function profileCollisionError(context?: "helper"): DeepScanNonRetryableError {
);
}

function malformedPreflightError(context?: "helper"): Error {
function failedOperation(context?: PreflightContext): string {
return context === "parent-config"
? "Could not read the Deep Scan parent configuration"
: `${subject(context)} cannot safely verify its read-only worker permission profile`;
}

function malformedPreflightError(context?: PreflightContext): Error {
return new Error(
`${subject(context)} cannot safely verify its read-only worker permission profile with this Codex configuration. ${subject(context)} did not run.`,
context === "parent-config"
? `${failedOperation(context)}. Codex returned an invalid app-server response.`
: `${failedOperation(context)} with this Codex configuration. ${subject(context)} did not run.`,
);
}

function unsupportedCodexApiError(
codexPath: string,
api: string,
context?: "helper",
context?: PreflightContext,
): DeepScanNonRetryableError {
return new DeepScanNonRetryableError(
subject(context) +
" cannot safely verify its read-only worker permission profile because " +
"the selected Codex executable " +
failedOperation(context) +
" because the selected Codex executable " +
JSON.stringify(codexPath) +
" does not support the required " +
JSON.stringify(api) +
Expand All @@ -698,7 +736,7 @@ function jsonRpcPreflightError(
codexPath: string,
method: string,
value: unknown,
context?: "helper",
context?: PreflightContext,
): Error {
const error = record(value);
const code =
Expand All @@ -712,9 +750,8 @@ function jsonRpcPreflightError(
return failure;
}
return new Error(
subject(context) +
" cannot safely verify its read-only worker permission profile because " +
"the selected Codex executable " +
failedOperation(context) +
" because the selected Codex executable " +
JSON.stringify(codexPath) +
" returned an error for " +
JSON.stringify(method) +
Expand All @@ -730,7 +767,7 @@ function jsonRpcPreflightError(
function codexExecutableStartError(
codexPath: string,
error: Error,
context?: "helper",
context?: PreflightContext,
): Error {
const value = "code" in error ? error.code : undefined;
const code =
Expand All @@ -754,22 +791,24 @@ function codexExecutableExitError(
codexPath: string,
code: number | null,
signal: NodeJS.Signals | null,
context?: "helper",
context?: PreflightContext,
): Error {
const operation =
context === "parent-config"
? "the configuration read"
: "permission-profile verification";
const detail =
code !== null
? "exited before permission-profile verification completed with code " +
code
? `exited before ${operation} completed with code ${code}`
: signal !== null
? "was terminated before permission-profile verification completed by signal " +
signal
: "exited before permission-profile verification completed";
? `was terminated before ${operation} completed by signal ${signal}`
: `exited before ${operation} completed`;
return new Error(codexExecutableFailureMessage(codexPath, detail, context));
}

function codexExecutableStdioError(
codexPath: string,
context?: "helper",
context?: PreflightContext,
): Error {
return new Error(
codexExecutableFailureMessage(
Expand All @@ -783,8 +822,10 @@ function codexExecutableStdioError(
function codexExecutableFailureMessage(
codexPath: string,
detail: string,
context?: "helper",
context?: PreflightContext,
): string {
if (context === "parent-config")
return `${failedOperation(context)}. Codex executable ${JSON.stringify(codexPath)} ${detail}.`;
return (
subject(context) +
" cannot safely verify its read-only worker permission profile because " +
Expand Down Expand Up @@ -822,18 +863,20 @@ export function deepScanPermissionProfileFallbackError(
);
}

function subject(context?: "helper"): string {
function subject(context?: PreflightContext): string {
return context === "helper" ? "Read-only Codex helper" : "Deep Scan";
}

function isAbortError(error: unknown): boolean {
return error instanceof Error && error.name === "AbortError";
}

function abortError(reason: unknown): Error {
function abortError(reason: unknown, context?: PreflightContext): Error {
if (reason instanceof Error) return reason;
return new DOMException(
"Deep Scan worker permission profile preflight aborted.",
context === "parent-config"
? "Deep Scan parent configuration read aborted."
: "Deep Scan worker permission profile preflight aborted.",
"AbortError",
);
}
Loading
Loading