fix(native): use current authority for warm plugin completion (#17444)

This commit is contained in:
Nicky Leach authored and GitHub committed 2026-10-24 10:54:40 -07:00
1 parent 988fe0180b
commit 410e5c6eed
2 files changed
+28 -4

No files matched your search

@@ -1,5 +1,6 @@
import * as environmentTaskRuntime from "../environment-task-runtime.js";
import * as pluginTaskRuntime from "./plugin-runner-task.js";
import * as completionFeedback from "./native-completion-feedback.js";
import { environmentTaskOperationSchema } from "@paperclipai/plugin-sdk";
import { trace } from "@opentelemetry/api";
import { InMemorySpanExporter, SimpleSpanProcessor, TracerProvider } from "@opentelemetry/sdk-trace";
@@ -119,6 +120,7 @@ type BackendFactoryOptions = {
};
}) => unknown;
dynamicToolHandler?: (call: unknown) => Promise<unknown>;
completionFeedback?: (result: Parameters<typeof completionFeedback.nativeCompletionFeedback>[2]) => Promise<string>;
onSpawn?: (meta: {
pid: number;
processGroupId: number | null;
@@ -10521,15 +10523,32 @@ describe("runnerd provider runtime wiring", () => {
await writeRunnerAuthority(root, identity, "suspended");
}) };
const nextSession = { close: vi.fn(async () => undefined) };
const result = { result: { summary: "done" }, terminal: { runTerminalState: "succeeded" },
const validated = validatePrpStructuredRunResult({ schema: "paperclip.run_result.v1", reportedWorkDisposition: "done",
summary: "done", completionClaim: { contractRevision: first.completionContract.contract.revision, objectiveSatisfied: true,
criteria: [], remainingWork: [] }, evidence: [], verification: [], artifacts: [], attentionRequests: [] });
if (!validated.ok) throw new Error("Invalid completion fixture");
const result = { result: validated.result, terminal: { runTerminalState: "succeeded" },
turnId: "turn", normalizedSessionId: first.session.normalizedSessionId,
providerSessionId: "provider", driverKind: "test", driverVersion: "1",
nativeEventCount: 1, highestContiguousSourceSeq: 1, usage: null };
const feedback = vi.spyOn(completionFeedback, "nativeCompletionFeedback").mockResolvedValue("accepted");
const firstDb = leaseDb(first);
let retainedFeedback: BackendFactoryOptions["completionFeedback"];
state.execute.mockReset().mockImplementationOnce(async options => {
retainedFeedback = state.createBackend.mock.calls.at(-1)![1].completionFeedback;
await options.onSession?.(firstSession);
return result;
}).mockImplementationOnce(async options => {
order.push("execute-next");
if (reusable) {
await expect(retainedFeedback!(result.result)).resolves.toBe("accepted");
expect(feedback).toHaveBeenLastCalledWith(firstDb, next.binding.runId, result.result);
expect(state.toolAuthorityDefinitions).toHaveBeenLastCalledWith(expect.objectContaining({ runId: next.binding.runId }));
feedback.mockClear();
state.toolAuthorityDefinitions.mockRejectedValueOnce(new Error("current authority denied"));
await expect(retainedFeedback!(result.result)).rejects.toThrow("current authority denied");
expect(feedback).not.toHaveBeenCalled();
}
expect(options.existingSession).toBe(reusable ? firstSession : undefined);
await options.onSession?.(reusable ? firstSession : nextSession);
return result;
@@ -10542,7 +10561,7 @@ describe("runnerd provider runtime wiring", () => {
? priorNativeRunDb(scenario === "stale-owner" ? older : first, scenario === "active-owner" ? "running" : "succeeded").select(fields as never)
: ordinarySelect(fields as never)) as Db["select"];
try {
await executePaperclipNativeSession({ db: leaseDb(first), execution: first,
await executePaperclipNativeSession({ db: firstDb, execution: first,
runnerInstanceId: identity.runnerInstanceId, useRunnerd: true, runnerExecutionTarget: pluginTarget as never,...pluginOptions });
if (scenario === "stale-owner") await writeRunnerAuthority(root, { ...identity, runId: older.binding.runId }, "ready");
if (scenario === "missing-owner") {
@@ -10598,7 +10617,7 @@ describe("runnerd provider runtime wiring", () => {
}
} finally {
failRetirement = false;
resume?.mockRestore();owners?.mockRestore();
resume?.mockRestore();owners?.mockRestore();feedback.mockRestore();
await closeWarmNativeSessionsForEnvironment({ environmentId, reason: "test cleanup" });
}
});
@@ -11916,7 +11916,12 @@ async function createRunnerdBackendWithinSessionClaim(
// its host environment out of the plugin and records the prepared directory.
runnerInstanceId: prior?.runnerInstanceId ?? effectiveRunnerInstanceId, environment: {},
workingDirectoryAuthority: "runner_prepared_launch", dynamicTools, dynamicToolHandler: executeTool,
completionFeedback: async result => { await authorityEpoch.definitions(); return nativeCompletionFeedback(input.db, input.execution.binding.runId, result); },
completionFeedback: async result => {
const current = sessionToolAuthorityEpochs.get(sessionScopeId);
if (!current) throw new Error("native_session_tool_authority_unavailable");
await current.definitions();
return nativeCompletionFeedback(input.db, current.runId, result);
},
codexTransportFactory: recovery => createRunnerdCodexTransport({ provider: provider.kind, stateDirectory: root,
...owner.driver, runnerArtifact: description.runner,
...(provider.kind === "acpx" ? { acpxAgent: provider.agent,