Files
openclaw/extensions/codex/src/app-server/plugin-app-cache-key.ts
Peter Steinberger f94a7dc183 feat(codex): supervise native Codex sessions (#104045)
* feat(codex): add native session supervision

* fix(codex): harden supervision integration

* fix(codex): preserve locked harness ownership

* fix(codex): fence native session archive

* fix(codex): revalidate archive binding ownership

* feat(codex): integrate supervision runtime

* feat(sessions): preserve harness-owned execution

* feat(sessions): persist harness ownership invariants

* feat(gateway): enforce harness-owned sessions

* feat(setup): enable detected Codex supervision

* feat(mac): expose supervised Codex sessions

* feat(ui): make Codex sessions actionable

* docs(codex): document session supervision

* test(codex): cover integration ownership

* chore(i18n): refresh supervision inventories

* fix(setup): finalize Codex activation atomically

* test(codex): narrow binding store update

* fix(sessions): preserve legacy model locks

* test(macos): serialize Codex catalog fixtures

* fix(sessions): preserve legacy lock admission

* chore(i18n): reconcile supervision metadata

* test(sessions): mark legacy lock fixture

* fix(macos): drain final Codex catalog frame

* docs: leave supervision note to release

* style(macos): satisfy Codex catalog type length

* chore: record session accessor seam owners

* fix(macos): honor configured Codex supervision

* fix(codex): preserve harness-owned model locks

* fix(codex): satisfy supervision lint gates

* chore(i18n): refresh native supervision inventory

* fix(codex): align supervision validation contracts

* fix(codex): close supervision boundary gaps

* fix(codex): preserve supervision activation contracts

* fix(codex): dispose standalone supervision runtime

* fix(codex): pin supervised source connection

* fix(plugins): bind delegated runs to exact session target

* fix(codex): scope supervised sessions to configured agents

* fix(codex): fingerprint effective supervision home

* fix(codex): normalize supervision plugin policy

* fix(codex): keep supervised bindings stable across upgrades

* fix(codex): guard all supervised binding connections

* fix(codex): preserve catalog filters and pending CAS identity

* fix(codex): preserve supervision identity for diagnostics

* fix(codex): bind uncertain commits to supervision connection

* fix(codex): satisfy supervision type boundaries

* fix(macos): reconcile current main validation

* fix(codex): handle absent runtime config in supervision

* fix(doctor): own local audio acceleration check

* fix(codex): satisfy integration lint gates

* fix(codex): satisfy lifecycle safety guards
2026-07-11 00:12:08 -07:00

158 lines
5.6 KiB
TypeScript

/**
* Builds stable Codex plugin/app inventory cache keys from app-server startup,
* auth, account, and version inputs without storing secret material.
*/
import { createHash } from "node:crypto";
import { createRequire } from "node:module";
import { OPENCLAW_VERSION } from "openclaw/plugin-sdk/agent-harness-runtime";
import { readPluginPackageVersion } from "openclaw/plugin-sdk/extension-shared";
import {
buildCodexAppInventoryCacheKey,
type CodexAppInventoryCacheKeyInput,
} from "./app-inventory-cache.js";
import { resolveCodexAppServerHomeDir } from "./auth-bridge.js";
import type { CodexAppServerRuntimeIdentity } from "./client.js";
import {
resolveCodexAppServerUserHomeDir,
type CodexAppServerRuntimeOptions,
type CodexAppServerStartOptions,
} from "./config.js";
const require = createRequire(import.meta.url);
const CODEX_PLUGIN_VERSION = readPluginPackageVersion({ require });
/** Inputs that identify the Codex app inventory cache scope for one runtime. */
export type CodexPluginAppCacheKeyParams = Omit<
CodexAppInventoryCacheKeyInput,
"codexHome" | "endpoint"
> & {
appServer: Pick<CodexAppServerRuntimeOptions, "start">;
agentDir?: string;
runtimeIdentity?: CodexAppServerRuntimeIdentity;
};
/** Builds the full app inventory cache key for Codex plugin/app discovery. */
export function buildCodexPluginAppCacheKey(params: CodexPluginAppCacheKeyParams): string {
return buildCodexAppInventoryCacheKey(
{
codexHome:
params.runtimeIdentity?.codexHome ??
resolveCodexPluginAppCacheCodexHome(params.appServer, params.agentDir),
endpoint: resolveCodexPluginAppCacheEndpoint(params.appServer),
authProfileId: params.authProfileId,
accountId: params.accountId,
envApiKeyFingerprint: params.envApiKeyFingerprint,
appServerVersion: params.appServerVersion ?? params.runtimeIdentity?.serverVersion,
runtimeIdentity: params.runtimeIdentity,
},
OPENCLAW_VERSION,
CODEX_PLUGIN_VERSION,
);
}
/** Builds a durable thread-binding fingerprint for one initialized app-server runtime. */
export function buildCodexAppServerRuntimeFingerprint(params: {
appServer: Pick<
CodexAppServerRuntimeOptions,
"start" | "connectionClass" | "remoteWorkspaceRoot"
>;
appServerVersion?: string;
runtimeIdentity?: CodexAppServerRuntimeIdentity;
}): string {
return JSON.stringify({
endpoint: resolveCodexPluginAppCacheEndpoint(params.appServer),
connectionClass: params.appServer.connectionClass,
remoteWorkspaceRoot: params.appServer.remoteWorkspaceRoot ?? null,
appServerVersion: params.appServerVersion ?? params.runtimeIdentity?.serverVersion ?? null,
runtimeIdentity: params.runtimeIdentity ?? null,
});
}
/** Fingerprints the configured connection that owns a supervised source thread. */
export function buildCodexAppServerConnectionFingerprint(
appServer: Pick<
CodexAppServerRuntimeOptions,
"start" | "connectionClass" | "remoteWorkspaceRoot"
>,
agentDir?: string,
): string {
return JSON.stringify({
endpoint: resolveCodexPluginAppCacheEndpoint(appServer),
connectionClass: appServer.connectionClass,
remoteWorkspaceRoot: appServer.remoteWorkspaceRoot ?? null,
homeScope: appServer.start.homeScope ?? null,
codexHome: resolveCodexAppServerConnectionHome(appServer.start, agentDir),
cwd: appServer.start.cwd ?? null,
});
}
function resolveCodexAppServerConnectionHome(
start: CodexAppServerStartOptions,
agentDir?: string,
): string | null {
const configured = start.env?.CODEX_HOME?.trim();
if (configured) {
return configured;
}
if (start.transport === "unix" && (!start.url || start.url === "unix://")) {
return resolveCodexAppServerUserHomeDir(start.env ?? process.env);
}
if (start.transport !== "stdio") {
return null;
}
if (start.homeScope === "user") {
return resolveCodexAppServerUserHomeDir(process.env);
}
return agentDir ? resolveCodexAppServerHomeDir(agentDir) : null;
}
/** Serializes app-server endpoint identity, including credential fingerprints. */
export function resolveCodexPluginAppCacheEndpoint(
appServer: Pick<CodexAppServerRuntimeOptions, "start">,
): string {
return JSON.stringify({
transport: appServer.start.transport,
command: appServer.start.command,
args: appServer.start.args,
url: appServer.start.url ?? null,
credentialFingerprint: fingerprintCodexPluginAppCacheCredentials(appServer.start),
});
}
/** Resolves the CODEX_HOME value that scopes local app-server inventory. */
export function resolveCodexPluginAppCacheCodexHome(
appServer: Pick<CodexAppServerRuntimeOptions, "start">,
agentDir?: string,
): string | undefined {
const configuredCodexHome = appServer.start.env?.CODEX_HOME?.trim();
if (configuredCodexHome) {
return configuredCodexHome;
}
return appServer.start.transport === "stdio" && agentDir
? resolveCodexAppServerHomeDir(agentDir)
: undefined;
}
function fingerprintCodexPluginAppCacheCredentials(
startOptions: CodexAppServerStartOptions,
): string | null {
const authToken = startOptions.authToken ?? "";
const headers = Object.entries(startOptions.headers)
.map(([key, value]) => [key.toLowerCase(), value] as const)
.toSorted(([left], [right]) => left.localeCompare(right));
if (!authToken && headers.length === 0) {
return null;
}
const hash = createHash("sha256");
hash.update("openclaw:codex:plugin-app-cache-credentials:v1");
hash.update("\0");
hash.update(authToken);
for (const [key, value] of headers) {
hash.update("\0");
hash.update(key);
hash.update("\0");
hash.update(value);
}
return `sha256:${hash.digest("hex")}`;
}