fix(runner): preserve ACPX completion receipts and scrub failed Grok admissions

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-09-22 16:14:59 -05:00
1 parent 78eb3f19f3
commit 52ba6b564a
8 files changed
+46 -6

No files matched your search

@@ -9,6 +9,7 @@ import {
describe("ACPX driver profile", () => {
it.each([
["codex", "available"],
["grok", "available"],
["claude", "available"],
["pi", "unsupported"],
] as const)(
@@ -48,6 +49,7 @@ describe("ACPX driver profile", () => {
it.each([
["claude", "claude-sonnet-5"],
["codex", "gpt-5.6-sol"],
["grok", "grok-4.7"],
] as const)("accepts the exact qualified %s model", (agent, model) => {
expect(validateAcpxDriverConfig({ agent, model })).toEqual({
ok: true,
@@ -12,6 +12,7 @@ export const GROK_AUTH_REFRESH_FILE = "auth-refresh.json";
export async function stageManagedGrokCredential(input: {
agentHomeDirectory: string;
environment?: NodeJS.ProcessEnv;
retainRefresh?: () => boolean;
}): Promise<AcpxProviderLifetimeLease> {
const source = input.environment ?? {};
const inline = source.PAPERCLIP_ACPX_GROK_AUTH_JSON_SECRET;
@@ -51,7 +52,7 @@ export async function stageManagedGrokCredential(input: {
// The runtime host calls close only after verified provider exit. Retain
// a private refresh handoff for the controller's existing Grok merge
// predicate, then scrub auth.json before releasing the process fence.
if (inline) {
if (inline && (input.retainRefresh?.() ?? true)) {
const auth = await open(join(input.agentHomeDirectory, "auth.json"), constants.O_RDONLY | constants.O_NOFOLLOW).catch((error) => {
if (error.code === "ENOENT") return null;
throw error;
@@ -7,7 +7,7 @@ import {
describe("qualified ACPX profiles", () => {
it("binds each agent to one immutable package and model declaration", () => {
for (const agent of ["pi", "claude", "codex"] as const) {
for (const agent of ["pi", "claude", "codex", "grok"] as const) {
const profile = QUALIFIED_ACPX_PROFILES[agent];
expect(profile.agent).toBe(agent);
expect(profile.commandDigest).toMatch(/^sha256:[a-f0-9]{64}$/);
@@ -828,6 +828,25 @@ describe("ACPX runtime host", () => {
expect(openRuntime).not.toHaveBeenCalled();
});
it.each(["expired credential", "provider process died"])("cleans Grok credentials after failed initialization: %s", async (failure) => {
const fixture = await hostFixture();
let home = "";
await expect(AcpxRuntimeHost.open({
...fixture.options, agent: "grok", model: "grok-4.7", permissionMode: "approve-reads",
environment: { PAPERCLIP_ACPX_GROK_AUTH_JSON_SECRET: JSON.stringify({ "https://accounts.x.ai::11111111-1111-4111-8111-111111111111": { key: "test-expired", refresh_token: "test-refresh" } }) },
}, fixture.dependencies({ openRuntime: async (options) => {
home = options.launchEnvironment.GROK_HOME!;
expect(options.permissionMode).toBe("approve-reads");
expect(options.launchEnvironment).not.toHaveProperty("PAPERCLIP_ACPX_GROK_AUTH_JSON_SECRET");
expect(await readFile(join(home, "auth.json"), "utf8")).toContain("test-expired");
throw new Error(failure);
} }))).rejects.toThrow(failure);
for (const filename of ["auth.json", "auth-refresh.json"]) {
await expect(readFile(join(home, filename))).rejects.toMatchObject({ code: "ENOENT" });
}
expect(fixture.commandClose).toHaveBeenCalledOnce();
});
it("cleans credentials and command leases when provider open fails", async () => {
const fixture = await hostFixture();
let authPath = "";
@@ -338,6 +338,7 @@ export class AcpxRuntimeHost {
if (installation.commandDigest !== profile.commandDigest) {
throw new Error("Verified ACPX installation does not match its profile");
}
let admissionSucceeded = false;
let command: VerifiedAcpxCommandLease | null = null;
let credential: AcpxProviderLifetimeLease | null = null;
let toolBridge: RunnerToolBridge | null = null;
@@ -408,7 +409,7 @@ export class AcpxRuntimeHost {
} else if (options.agent === "grok") {
credential = await acquireAbortableAdmissionResource({
signal: options.signal,
acquire: () => stageManagedGrokCredential({ agentHomeDirectory: sandbox.agentHomeDirectory, environment: options.environment }),
acquire: () => stageManagedGrokCredential({ agentHomeDirectory: sandbox.agentHomeDirectory, environment: options.environment, retainRefresh: () => admissionSucceeded }),
resource: "credential", releaseLate: (lease) => lease.close(),
reportFailure: (failure) => dependencies.reportRetainedCleanupFailure(failure),
});
@@ -550,6 +551,7 @@ export class AcpxRuntimeHost {
verifyExpectedAcpxIdentity(options.expectedIdentity, binding, identity);
}
options.signal?.throwIfAborted();
admissionSucceeded = true;
return new AcpxRuntimeHost({
runtime,
binding,
@@ -428,7 +428,7 @@ export async function prepareAcpxRuntimeSandbox(input: {
XDG_CACHE_HOME: cacheDirectory,
PAPERCLIP_ACPX_PROFILE: input.agent,
PAPERCLIP_ACPX_ISOLATED_CONTEXT: "1",
...(input.agent === "grok" ? { GROK_HOME: agentHomeDirectory, NO_BROWSER: "1" } : {}),
...(input.agent === "grok" ? { GROK_HOME: agentHomeDirectory, NO_BROWSER: "1", GROK_DISABLE_AUTOUPDATER: "1" } : {}),
...(input.agent === "pi"
? {
PI_CODING_AGENT_DIR: agentHomeDirectory,
@@ -3254,7 +3254,7 @@ export function createRunnerdCodexAppServerArgs(input: {
);
}
function unwrapToolResponse(response: Record<string, unknown>): {
export function unwrapToolResponse(response: Record<string, unknown>, preserveEnvelope = false): {
readonly __paperclipSemanticToolOutcome: true;
readonly result: unknown;
readonly isError: boolean;
@@ -3265,7 +3265,7 @@ function unwrapToolResponse(response: Record<string, unknown>): {
const value = record(items[0]).text;
let result: unknown = response;
try {
if (typeof value === "string") result = JSON.parse(value);
if (!preserveEnvelope && typeof value === "string") result = JSON.parse(value);
} catch {
result = response;
}
@@ -5461,6 +5461,8 @@ class DurablePrpCodexTransport implements CodexAppServerTransport {
}
: {}),
}),
this.options.provider === "acpx" &&
(call.operationId === "paperclip_finish" || call.operationId === "paperclip_block"),
);
if (call.operationId === "call_api") {
const result = record(outcome.result);
@@ -0,0 +1,14 @@
import { describe, expect, it } from "vitest";
import { unwrapToolResponse } from "./runnerd-codex-transport.js";
describe("runnerd semantic tool response", () => {
it("preserves the validated ACPX terminal acknowledgement envelope", () => {
const response = { success: true, contentItems: [{ type: "inputText", text: JSON.stringify({ ok: true, accepted: true }) }] };
expect(unwrapToolResponse(response, true)).toEqual({ __paperclipSemanticToolOutcome: true, result: response, isError: false });
expect(unwrapToolResponse(response)).toEqual({ __paperclipSemanticToolOutcome: true, result: { ok: true, accepted: true }, isError: false });
});
it("keeps denial feedback and does not promote rejected terminal reports to success", () => {
const response = { success: false, contentItems: [{ type: "inputText", text: JSON.stringify({ ok: false, denial: { code: "stale_completion_contract" } }) }] };
expect(unwrapToolResponse(response, true)).toMatchObject({ isError: true, result: { success: false } });
});
});