Files
openclaw/extensions/codex/src/command-handler-scope.ts
T
Peter Steinberger 45dd558d92 fix(codex): prevent session handoffs and native tasks from losing thread ownership (#120740)
* fix(codex): preserve thread ownership across session lifecycles

* test(codex): keep canonical context-engine session fixtures

* fix(codex): fence replacement thread rollback by ownership

* fix(codex): fence thread handoffs by physical client ownership

* fix(codex): derive conversation privacy from its source session

* fix(codex): claim active native subagents at their lifecycle owner

* fix(codex): preserve conversation ownership when detach fails

* fix(codex): fence stale native child close notifications

* fix(codex): preserve active child ownership during rollback

* test(codex): keep lifecycle fixtures aligned with public contracts

* fix(codex): preserve rollback failure causes across ownership recovery

* fix(codex): retain aggregate rollback causes through lint analysis

* fix(codex): release native children when idle retention fails
2026-08-08 16:20:16 -07:00

107 lines
3.7 KiB
TypeScript

import { resolveAgentDir, resolveSessionAgentIds } from "openclaw/plugin-sdk/agent-runtime";
import type { PluginCommandContext } from "openclaw/plugin-sdk/plugin-entry";
import { resolveCodexAppServerAuthProfileIdForAgent } from "./app-server/auth-bridge.js";
import { resolveCodexBindingAppServerConnection } from "./app-server/binding-connection.js";
import {
sessionBindingIdentity,
type CodexAppServerBindingIdentity,
} from "./app-server/session-binding.js";
import type { CodexCommandDeps } from "./command-handler-deps.js";
import type { CodexControlRequestOptions } from "./command-rpc.js";
import { readCodexConversationBindingData } from "./conversation-binding-data.js";
type CodexConversationControlTarget = {
identity: CodexAppServerBindingIdentity;
agentId: string;
agentDir: string;
requestedAuthProfileId?: string;
};
export async function resolveControlTarget(
ctx: PluginCommandContext,
): Promise<CodexConversationControlTarget | undefined> {
const binding = await ctx.getCurrentConversationBinding();
const data = readCodexConversationBindingData(binding);
const scope = resolveCodexConversationControlScope(ctx);
if (data?.kind === "codex-app-server-session") {
return {
identity: conversationBindingIdentity(data.bindingId),
agentId: data.agentId ?? scope.agentId,
agentDir: data.agentDir ?? scope.agentDir,
requestedAuthProfileId: data.start?.authProfileId,
};
}
return ctx.sessionId
? {
identity: sessionBindingIdentity({
sessionId: ctx.sessionId,
sessionKey: ctx.sessionKey,
agentId: scope.agentId,
config: ctx.config,
}),
agentId: scope.agentId,
agentDir: scope.agentDir,
}
: undefined;
}
type CommandAppServerScope = Pick<
CodexControlRequestOptions,
"authProfileId" | "sessionId" | "sessionKey" | "startOptions"
> & { agentId: string; agentDir: string };
export async function resolveCommandAppServerScope(
deps: CodexCommandDeps,
ctx: PluginCommandContext,
pluginConfig: unknown,
): Promise<CommandAppServerScope> {
const target = await resolveControlTarget(ctx);
const fallback = resolveCodexConversationControlScope(ctx);
const agentDir = target?.agentDir ?? fallback.agentDir;
const binding = target ? await deps.bindingStore.read(target.identity) : undefined;
const authProfileId =
binding?.connectionScope === "supervision"
? undefined
: resolveCodexAppServerAuthProfileIdForAgent({
authProfileId: binding?.authProfileId ?? target?.requestedAuthProfileId,
agentDir,
config: ctx.config,
});
const connection = resolveCodexBindingAppServerConnection({
binding,
authProfileId,
pluginConfig,
});
return {
agentId: target?.agentId ?? fallback.agentId,
agentDir,
...(connection.clientAuthProfileId !== undefined
? { authProfileId: connection.clientAuthProfileId }
: {}),
...(connection.usesSupervisionConnection ? { startOptions: connection.appServer.start } : {}),
...(ctx.sessionKey ? { sessionKey: ctx.sessionKey } : {}),
...(ctx.sessionId ? { sessionId: ctx.sessionId } : {}),
};
}
export function conversationBindingIdentity(
bindingId: string,
): Extract<CodexAppServerBindingIdentity, { kind: "conversation" }> {
return { kind: "conversation", bindingId };
}
export function resolveCodexConversationControlScope(ctx: PluginCommandContext): {
agentId: string;
agentDir: string;
} {
const { sessionAgentId } = resolveSessionAgentIds({
sessionKey: ctx.sessionKey,
agentId: ctx.agentId,
config: ctx.config,
});
return {
agentId: sessionAgentId,
agentDir: resolveAgentDir(ctx.config, sessionAgentId),
};
}