fix(runner): refresh runtime context when restoring ACPX sessions

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-09-29 10:34:15 -05:00
1 parent 3d0c45920c
commit e08bb92aa2
2 files changed
+66

No files matched your search

@@ -22,6 +22,7 @@ import { fileURLToPath } from "node:url";
import { expect, it, vi } from "vitest";
import type { ControlPlanePort } from "../contracts/control-plane-port.js";
import { bindAcpxAgentFiles } from "../drivers/acpx/agent-files-binding.js";
import type { NativeExecutionInputV1 } from "../contracts/native-execution.js";
import type {
NativeSession,
@@ -485,6 +486,61 @@ it("carries the provider attachment seed across consecutive authority rotations"
});
});
it("restores ACPX with the current registered copy after the old run copy is collected", async () => {
const root = await mkdtemp(join(tmpdir(), "runnerd-acpx-context-rotation-"));
const priorRoot = join(root, "prior-run-copy");
const currentRoot = join(root, "current-run-copy");
await Promise.all([mkdir(priorRoot), mkdir(currentRoot)]);
const context = (rootPath: string) => ({
instructions: {
workingCopy: { kind: "agent_files", rootPath, entryPath: "AGENTS.md" },
},
}) as NativeRuntimeContextSnapshot;
const prior = context(priorRoot);
const current = context(currentRoot);
const desired = {
runnerInstanceId: "runner", environmentLeaseId: "lease", runId: "new-run",
normalizedSessionId: "same-session", turnId: "new-turn", itemId: "new-item",
};
const provider = {
kind: "acpx", agent: "copilot", runId: "old-run",
normalizedSessionId: "same-session", commandDigest: "sha256:immutable",
model: "explicit-model", runtimeContext: prior,
};
const state = {
runAttachTemplate: { provider, workspace: { cwd: root } },
commands: [{ type: "turn.start", payload: { text: "must not replay" } }],
};
try {
await rm(priorRoot, { recursive: true });
expect(() => bindAcpxAgentFiles(prior, [])).toThrowError(
expect.objectContaining({ code: "ENOENT" }),
);
const restored = runnerdRecoveryInternals.rotatedRunAttachPayload(
state, desired, null, undefined, current,
);
const restoredProvider = restored.provider as typeof provider;
expect(restoredProvider).toEqual({ ...provider, runId: desired.runId, runtimeContext: current });
expect(bindAcpxAgentFiles(restoredProvider.runtimeContext, [])?.root).toContain("current-run-copy");
expect(restored).not.toHaveProperty("text");
expect(state.runAttachTemplate.provider.runtimeContext).toBe(prior);
// A reopened provider can retain the current grant on another restoration.
const same = runnerdRecoveryInternals.rotatedRunAttachPayload(
{ runAttachTemplate: restored }, desired, null, undefined, current,
);
expect(same).toEqual(restored);
expect((same.provider as typeof provider).runtimeContext).not.toBe(current);
// Live warm attachment has no new provider process and must preserve its
// existing context until settlement, rather than replace its filesystem grant.
const live = runnerdRecoveryInternals.rotatedRunAttachPayload(
state, desired, null, undefined,
);
expect((live.provider as typeof provider).runtimeContext).toEqual(prior);
} finally {
await rm(root, { recursive: true, force: true });
}
});
it("replays the durable run attachment outcome and latest provider identity", () => {
expect(
runnerdRecoveryInternals.recoveredRunAttachment({
@@ -418,6 +418,7 @@ function rotatedRunAttachPayload(
authorizedTools: Record<string, unknown> | null,
completionContract:
{ revision: string; criterionIds: readonly string[] } | undefined,
runtimeContext?: NativeRuntimeContextSnapshot | null,
): Record<string, unknown> {
const commands = Array.isArray(state.commands)
? state.commands.map(record)
@@ -443,6 +444,7 @@ function rotatedRunAttachPayload(
desired,
authorizedTools,
completionContract,
runtimeContext,
);
}
@@ -452,12 +454,19 @@ function retargetRunAttachPayload(
authorizedTools: Record<string, unknown> | null,
completionContract:
{ revision: string; criterionIds: readonly string[] } | undefined,
runtimeContext?: NativeRuntimeContextSnapshot | null,
): Record<string, unknown> {
const payload = structuredClone(seedPayload);
const provider = record(payload.provider);
if (provider.kind === "acpx" || provider.provider === "acpx") {
provider.runId = desired.runId;
provider.normalizedSessionId = desired.normalizedSessionId;
// A run-scoped registered instruction copy is collected after shutdown.
// Restore the durable provider identity with the current authenticated
// context, never the prior run's now-stale filesystem grant.
if (runtimeContext !== undefined) {
provider.runtimeContext = structuredClone(runtimeContext);
}
payload.provider = provider;
}
if (authorizedTools !== null) payload.authorizedTools = authorizedTools;
@@ -5140,6 +5149,7 @@ class DurablePrpCodexTransport implements CodexAppServerTransport {
desiredIdentity,
this.#authorizedTools,
this.options.resumeCompletionContract,
runtimeContext,
);
if (provider === "codex") {
// These controller-owned, token-free paths belong to the new run.