fix(runner): verify ACPX model from durable state
This commit is contained in:
parent
d60306420d
commit
d49c011403
|
|
@ -199,26 +199,39 @@ describe("Codex ACPX runtime adapter", () => {
|
|||
expect(assertWorkspaceHeld).toHaveBeenCalledOnce();
|
||||
expect(command.spawn).not.toHaveBeenCalled();
|
||||
});
|
||||
it("maps status, model selection, and state-preserving close", async () => {
|
||||
it("reads verified status from durable state without draining live updates", async () => {
|
||||
const runtime = fakeRuntime();
|
||||
vi.mocked(runtime.getStatus!).mockResolvedValue({
|
||||
models: {
|
||||
currentModelId: "gpt-5.6-sol",
|
||||
availableModelIds: ["gpt-5.6-sol"],
|
||||
vi.mocked(runtime.getStatus!).mockReturnValue(new Promise(() => {}));
|
||||
const durableRecord = {
|
||||
acpxRecordId: "record-1",
|
||||
acpSessionId: "backend-1",
|
||||
agentSessionId: "agent-1",
|
||||
acpx: {
|
||||
current_model_id: "gpt-5.6-sol",
|
||||
available_models: ["gpt-5.6-sol"],
|
||||
},
|
||||
});
|
||||
} as never;
|
||||
const durableStore: AcpSessionStore = {
|
||||
load: vi.fn(async () => structuredClone(durableRecord)),
|
||||
save: vi.fn(),
|
||||
};
|
||||
const port = await openCodexAcpxRuntime(openOptions(fakeCommand()), {
|
||||
createRegistry: () => registry(),
|
||||
createStore: () => store(),
|
||||
createStore: () => durableStore,
|
||||
createRuntime: () => runtime,
|
||||
});
|
||||
|
||||
expect(await port.getStatus()).toEqual({
|
||||
expect(await port.getStatus()).toMatchObject({
|
||||
acpxRecordId: "record-1",
|
||||
backendSessionId: "backend-1",
|
||||
agentSessionId: "agent-1",
|
||||
models: {
|
||||
currentModelId: "gpt-5.6-sol",
|
||||
availableModelIds: ["gpt-5.6-sol"],
|
||||
},
|
||||
});
|
||||
expect(durableStore.load).toHaveBeenCalledWith("record-1");
|
||||
expect(runtime.getStatus).not.toHaveBeenCalled();
|
||||
await port.setModel?.("gpt-5.6-sol");
|
||||
expect(runtime.setConfigOption).toHaveBeenCalledWith({
|
||||
handle: HANDLE,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import {
|
|||
awaitVerifiedAcpxProviderExit,
|
||||
awaitVerifiedAcpxProviderOwnership,
|
||||
} from "./installation-integrity.js";
|
||||
import type { AcpxModelStatus } from "./model-verification.js";
|
||||
import { decideAcpxPermission } from "./permission-policy.js";
|
||||
|
||||
const VERIFIED_COMMAND_SENTINEL = "paperclip-verified-acpx-command";
|
||||
|
|
@ -366,6 +367,7 @@ export async function openQualifiedAcpxRuntime(
|
|||
runtime,
|
||||
handle,
|
||||
requireIdentity(handle),
|
||||
baseStore,
|
||||
children,
|
||||
runtimeCloseTimeoutMs,
|
||||
);
|
||||
|
|
@ -774,6 +776,7 @@ function runtimePort(
|
|||
runtime: AcpRuntime,
|
||||
handle: AcpRuntimeHandle,
|
||||
identity: AcpxRuntimePortIdentity,
|
||||
sessionStore: AcpSessionStore,
|
||||
children: SpawnedChildSet,
|
||||
runtimeCloseTimeoutMs: number,
|
||||
): AcpxRuntimePort {
|
||||
|
|
@ -1004,10 +1007,7 @@ function runtimePort(
|
|||
return structuredClone(identity);
|
||||
},
|
||||
async getStatus() {
|
||||
if (!runtime.getStatus) {
|
||||
throw new Error("The pinned ACPX runtime cannot report session status");
|
||||
}
|
||||
return structuredClone(await runtime.getStatus({ handle }));
|
||||
return await persistedRuntimeStatus(sessionStore, handle, identity);
|
||||
},
|
||||
...(runtime.setConfigOption
|
||||
? {
|
||||
|
|
@ -1035,6 +1035,50 @@ function runtimePort(
|
|||
return port;
|
||||
}
|
||||
|
||||
async function persistedRuntimeStatus(
|
||||
sessionStore: AcpSessionStore,
|
||||
handle: AcpRuntimeHandle,
|
||||
identity: AcpxRuntimePortIdentity,
|
||||
): Promise<AcpxModelStatus> {
|
||||
const recordId = handle.acpxRecordId ?? handle.sessionKey;
|
||||
const record = await sessionStore.load(recordId);
|
||||
if (!record) {
|
||||
throw new Error(
|
||||
"The pinned ACPX runtime omitted its persisted session record",
|
||||
);
|
||||
}
|
||||
if (
|
||||
record.acpxRecordId !== identity.acpxRecordId ||
|
||||
record.acpSessionId !== identity.backendSessionId ||
|
||||
record.agentSessionId !== identity.agentSessionId
|
||||
) {
|
||||
throw new Error(
|
||||
"The persisted ACPX session identity changed after admission",
|
||||
);
|
||||
}
|
||||
const currentModelId = record.acpx?.current_model_id;
|
||||
const availableModelIds = record.acpx?.available_models;
|
||||
return {
|
||||
summary: [
|
||||
`session=${record.acpxRecordId}`,
|
||||
`backendSessionId=${record.acpSessionId}`,
|
||||
`agentSessionId=${record.agentSessionId}`,
|
||||
record.closed === true ? "closed" : "open",
|
||||
].join(" "),
|
||||
acpxRecordId: record.acpxRecordId,
|
||||
backendSessionId: record.acpSessionId,
|
||||
agentSessionId: record.agentSessionId,
|
||||
...(currentModelId === undefined && !availableModelIds?.length
|
||||
? {}
|
||||
: {
|
||||
models: {
|
||||
...(currentModelId === undefined ? {} : { currentModelId }),
|
||||
availableModelIds: availableModelIds ? [...availableModelIds] : [],
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function runtimeCloseOutcome(
|
||||
runtime: AcpRuntime,
|
||||
input: Parameters<AcpRuntime["close"]>[0],
|
||||
|
|
|
|||
Loading…
Reference in New Issue