mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-21 08:22:20 +02:00
perf: overlap native GitHub and assigned-tool setup (#16620)
Prepare managed GitHub access and fresh assigned-MCP discovery concurrently for remote native Codex. Wait for both before provider construction and drain both on failure through the existing run cleanup. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
5f5602bba7
commit
0528cc7ee0
5 files changed
+195
-20
No files matched your search
@@ -1780,6 +1780,14 @@ cancellation remain enforced; the snapshot is removed when the provider exits.
|
||||
Native Codex is qualified only with `codexPermissionMode: "never"`. The create
|
||||
and edit surfaces do not offer `on-request` or `untrusted`, and a persisted
|
||||
unsupported value fails with remediation instead of being silently coerced.
|
||||
|
||||
Remote native Codex prepares managed GitHub access and assigned MCP tools
|
||||
concurrently after the backend claims its session scope and validates its launch
|
||||
binding. Provider startup waits for both preparations. A failed preparation waits
|
||||
for the other operation to settle before the existing run cleanup releases its
|
||||
GitHub access. Discovery remains fresh for each run, and tool calls still use the
|
||||
current gateway permissions. Local, custom-backend and other provider paths keep
|
||||
their existing preparation order.
|
||||
OpenCode defaults to `allow`, with explicit `ask` and `deny` options; ACPX
|
||||
defaults to `approve-all`, with explicit `approve-paperclip`, `approve-reads`,
|
||||
and `deny-all` options. Codex conversion keeps a non-empty model and
|
||||
|
||||
@@ -105,6 +105,15 @@ serial. This changes no API or agent grant. The
|
||||
[real-shell controls](../../server/src/services/native-runtime/native-artifact-preparation.test.ts)
|
||||
do not qualify the whole validation capability or a live response-time target.
|
||||
|
||||
Scoped backend-preparation source evidence: remote native Codex can prepare its
|
||||
managed GitHub transport during fresh assigned-MCP discovery. The session claim,
|
||||
wake binding, review assignment and complete gateway launch binding checks still
|
||||
precede that work. Provider startup waits for both results. Failures drain both
|
||||
operations before the existing run cleanup releases any created GitHub access.
|
||||
No grant, token, authority cache or provider dispatch bypass is added. The
|
||||
[executor controls](../../server/src/services/native-runtime/native-session-executor.test.ts)
|
||||
remain separate from live latency and permitted/denied agent acceptance.
|
||||
|
||||
For a linked Codex subscription with server-resolved managed credential provenance,
|
||||
setup checks the resolved owner and stages a private probe copy before local or
|
||||
remote isolation. Without a selected API key, an unbound linked `CODEX_HOME` is
|
||||
|
||||
@@ -72,6 +72,14 @@ failed runner probe and confirm the harness work has settled before failure
|
||||
returns. Providers without concurrent sync and explicit harness installs remain
|
||||
serial. The [real-shell artifact controls](../../server/src/services/native-runtime/native-artifact-preparation.test.ts)
|
||||
cover these source contracts; they do not establish live reply latency.
|
||||
For remote native Codex with managed GitHub access, inspect the controller
|
||||
preparation spans for overlapping GitHub setup and assigned MCP discovery.
|
||||
Provider startup must wait for both. Force either preparation to fail while the
|
||||
other is pending; confirm that the pending operation settles and run-owned access
|
||||
is released before failure returns. Repeat a warm follow-up and retain its actual
|
||||
reply time. The [executor controls](../../server/src/services/native-runtime/native-session-executor.test.ts)
|
||||
cover ordering, failure drain and credential projection; they do not qualify a
|
||||
live response-time target.
|
||||
Missing dependencies, failed authentication, invalid models and
|
||||
incompatible custom settings must remain actionable errors without silent runner
|
||||
switching. Saved unchanged execution can still be tested without applying new
|
||||
|
||||
@@ -10447,8 +10447,9 @@ describe("runnerd provider runtime wiring", () => {
|
||||
{ PAPERCLIP_NATIVE_MCP_URL: "http://127.0.0.1:3217/mcp/gateways/test" },
|
||||
{ PAPERCLIP_NATIVE_MCP_TOKEN: "private-run-token" },
|
||||
])("rejects partial remote assigned MCP bindings", async (runnerEnvironment) => {
|
||||
const prepareRunnerEnvironment = vi.fn(async () => runnerEnvironment);
|
||||
await expect(createRunnerdBackend({
|
||||
db: leaseDb(execution), execution, runnerInstanceId: "runner-partial-mcp", runnerEnvironment,
|
||||
db: leaseDb(execution), execution, runnerInstanceId: "runner-partial-mcp", runnerEnvironment, prepareRunnerEnvironment,
|
||||
runnerExecutionTarget: {
|
||||
kind: "remote", transport: "sandbox", providerKey: "daytona",
|
||||
leaseId: "lease-partial-mcp", remoteCwd: "/home/daytona/paperclip-workspace",
|
||||
@@ -10456,6 +10457,138 @@ describe("runnerd provider runtime wiring", () => {
|
||||
} as never,
|
||||
})).rejects.toThrow("assigned native MCP launch binding is incomplete");
|
||||
expect(state.createAssignedMcpTools).not.toHaveBeenCalled();
|
||||
expect(prepareRunnerEnvironment).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each(["success", "environment", "assigned", "both"] as const)("drains overlapping backend preparation before admission: %s", async failure => {
|
||||
let releaseEnvironment!: () => void;
|
||||
let releaseAssigned!: () => void;
|
||||
let assignedStarted!: () => void;
|
||||
const environmentGate = new Promise<void>(resolve => { releaseEnvironment = resolve; });
|
||||
const assignedGate = new Promise<void>(resolve => { releaseAssigned = resolve; });
|
||||
const assignedReady = new Promise<void>(resolve => { assignedStarted = resolve; });
|
||||
const runnerEnvironment = {
|
||||
PAPERCLIP_NATIVE_MCP_NAME: "paperclip-assigned",
|
||||
PAPERCLIP_NATIVE_MCP_URL: "http://127.0.0.1:3217/mcp/gateways/assigned-overlap",
|
||||
PAPERCLIP_NATIVE_MCP_TOKEN: "private-overlap-token",
|
||||
};
|
||||
const assignedMcpTools = { definitions: () => [], has: () => false, execute: vi.fn() };
|
||||
const prepareRunnerEnvironment = vi.fn(async () => {
|
||||
await environmentGate;
|
||||
if (failure === "environment" || failure === "both") throw new Error("environment failed");
|
||||
return { ...runnerEnvironment, PAPERCLIP_GITHUB_BROKER_TOKEN: "prepared-github-capability" };
|
||||
});
|
||||
state.createAssignedMcpTools.mockImplementationOnce(async () => {
|
||||
assignedStarted();
|
||||
await assignedGate;
|
||||
if (failure === "assigned" || failure === "both") throw new Error("assigned failed");
|
||||
return assignedMcpTools;
|
||||
});
|
||||
state.createBackend.mockClear();
|
||||
state.createTransport.mockClear();
|
||||
let settled = false;
|
||||
const preparation = createRunnerdBackend({
|
||||
db: leaseDb(execution), execution, runnerInstanceId: "runner-overlap",
|
||||
runnerEnvironment, prepareRunnerEnvironment,
|
||||
runnerExecutionTarget: {
|
||||
kind: "remote", transport: "sandbox", providerKey: "daytona",
|
||||
leaseId: "lease-overlap", remoteCwd: "/home/daytona/paperclip-workspace",
|
||||
runner: { execute: vi.fn() },
|
||||
} as never,
|
||||
}).then(value => ({ value }), error => ({ error })).finally(() => { settled = true; });
|
||||
try {
|
||||
await assignedReady;
|
||||
expect(prepareRunnerEnvironment).toHaveBeenCalledOnce();
|
||||
expect(settled).toBe(false);
|
||||
expect(state.createBackend).not.toHaveBeenCalled();
|
||||
if (failure === "assigned") releaseAssigned();
|
||||
else releaseEnvironment();
|
||||
await new Promise<void>(resolve => setImmediate(resolve));
|
||||
expect(settled).toBe(false);
|
||||
expect(state.createBackend).not.toHaveBeenCalled();
|
||||
releaseEnvironment();
|
||||
releaseAssigned();
|
||||
const result = await preparation;
|
||||
if (failure !== "success") {
|
||||
expect(result).toMatchObject({ error: { message: failure === "assigned" ? "assigned failed" : "environment failed" } });
|
||||
expect(state.createBackend).not.toHaveBeenCalled();
|
||||
} else {
|
||||
expect(result).not.toHaveProperty("error");
|
||||
expect(state.toolAuthorityDefinitions).toHaveBeenCalledWith(expect.objectContaining({ assignedMcpTools }));
|
||||
state.createBackend.mock.calls[0]![1].codexTransportFactory!();
|
||||
const options = state.createTransport.mock.calls[0]![0] as { environment: NodeJS.ProcessEnv };
|
||||
expect(options.environment.PAPERCLIP_GITHUB_BROKER_TOKEN).toBe("prepared-github-capability");
|
||||
expect(options.environment.PAPERCLIP_NATIVE_MCP_TOKEN).toBeUndefined();
|
||||
}
|
||||
} finally {
|
||||
releaseEnvironment();
|
||||
releaseAssigned();
|
||||
await preparation;
|
||||
}
|
||||
});
|
||||
|
||||
it.each(["github", "assigned"] as const)("cleans run-owned access after overlapping executor failure: %s", async failure => {
|
||||
const issueService = vi.spyOn(issueServiceModule, "issueService").mockReturnValue({
|
||||
update: vi.fn(async () => ({ status: "blocked", statusVersion: 1 })),
|
||||
} as unknown as ReturnType<typeof issueServiceModule.issueService>);
|
||||
let releaseGitHub!: () => void;
|
||||
let releaseAssigned!: () => void;
|
||||
const githubGate = new Promise<void>(resolve => { releaseGitHub = resolve; });
|
||||
const assignedGate = new Promise<void>(resolve => { releaseAssigned = resolve; });
|
||||
const deactivate = vi.fn();
|
||||
const stop = vi.fn(async () => undefined);
|
||||
const activate = vi.fn(() => deactivate);
|
||||
githubAccess.create.mockImplementationOnce(async () => {
|
||||
await githubGate;
|
||||
if (failure === "github") throw new Error("github setup failed");
|
||||
return { env: { PAPERCLIP_GITHUB_BROKER_TOKEN: "run-owned-capability" }, ready: true,
|
||||
duplexRequested: false, activate, stop };
|
||||
});
|
||||
state.createAssignedMcpTools.mockImplementationOnce(async () => {
|
||||
await assignedGate;
|
||||
if (failure === "assigned") throw new Error("assigned setup failed");
|
||||
return { definitions: () => [], has: () => false, execute: vi.fn() };
|
||||
});
|
||||
state.execute.mockClear();
|
||||
state.createBackend.mockClear();
|
||||
let settled = false;
|
||||
const running = executePaperclipNativeSession({
|
||||
db: leaseDb(execution), execution, runnerInstanceId: "runner-overlap-cleanup",
|
||||
useRunnerd: true, managedGitHub: true,
|
||||
runnerEnvironment: {
|
||||
PAPERCLIP_NATIVE_MCP_NAME: "paperclip-assigned",
|
||||
PAPERCLIP_NATIVE_MCP_URL: "http://127.0.0.1:3217/mcp/gateways/assigned-cleanup",
|
||||
PAPERCLIP_NATIVE_MCP_TOKEN: "private-cleanup-token",
|
||||
},
|
||||
runnerExecutionTarget: {
|
||||
kind: "remote", transport: "sandbox", providerKey: "daytona",
|
||||
leaseId: "lease-overlap-cleanup", remoteCwd: "/home/daytona/paperclip-workspace",
|
||||
runner: { execute: vi.fn() },
|
||||
} as never,
|
||||
}).then(value => ({ value }), error => ({ error })).finally(() => { settled = true; });
|
||||
try {
|
||||
await vi.waitFor(() => expect(state.createAssignedMcpTools).toHaveBeenCalledOnce());
|
||||
expect(githubAccess.create).toHaveBeenCalled();
|
||||
if (failure === "github") releaseGitHub();
|
||||
else releaseAssigned();
|
||||
await new Promise<void>(resolve => setImmediate(resolve));
|
||||
expect(settled).toBe(false);
|
||||
releaseGitHub();
|
||||
releaseAssigned();
|
||||
expect(await running).toMatchObject({ error: { message: `${failure} setup failed` } });
|
||||
expect(state.execute).not.toHaveBeenCalled();
|
||||
expect(state.createBackend).not.toHaveBeenCalled();
|
||||
if (failure === "assigned") {
|
||||
expect(activate).toHaveBeenCalledWith(execution.binding);
|
||||
expect(deactivate).toHaveBeenCalledOnce();
|
||||
expect(stop).toHaveBeenCalledOnce();
|
||||
}
|
||||
} finally {
|
||||
releaseGitHub();
|
||||
releaseAssigned();
|
||||
await running;
|
||||
issueService.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps assigned MCP credentials on the control plane for remote Codex", async () => {
|
||||
|
||||
@@ -8680,19 +8680,27 @@ async function executePaperclipNativeSessionWithinScope(
|
||||
controller,
|
||||
});
|
||||
try {
|
||||
if (input.managedGitHub && input.execution.schema !== "paperclip.native-execution-input.v6") {
|
||||
if (!input.execution.workspace.cwd) throw new Error("native_workspace_required");
|
||||
githubAccess ??= await timeController("github_access", () => createNativeGitHubAccess({
|
||||
scope: input.execution.binding,
|
||||
target: input.runnerExecutionTarget,
|
||||
cwd: input.execution.workspace.cwd!,
|
||||
env: resolveNativeProviderEnvironment(input.execution.provider, input.runnerEnvironment),
|
||||
resolveCredentials: (binding) => resolveGitHubOperationCredentials(input.db, binding),
|
||||
onLog: input.onLog,
|
||||
}));
|
||||
releaseGitHubRun = githubAccess.activate(input.execution.binding);
|
||||
input = { ...input, runnerEnvironment: { ...input.runnerEnvironment, ...githubAccess.env } };
|
||||
}
|
||||
const prepareRunnerEnvironment = async () => {
|
||||
if (input.managedGitHub && input.execution.schema !== "paperclip.native-execution-input.v6") {
|
||||
if (!input.execution.workspace.cwd) throw new Error("native_workspace_required");
|
||||
githubAccess ??= await timeController("github_access", () => createNativeGitHubAccess({
|
||||
scope: input.execution.binding,
|
||||
target: input.runnerExecutionTarget,
|
||||
cwd: input.execution.workspace.cwd!,
|
||||
env: resolveNativeProviderEnvironment(input.execution.provider, input.runnerEnvironment),
|
||||
resolveCredentials: (binding) => resolveGitHubOperationCredentials(input.db, binding),
|
||||
onLog: input.onLog,
|
||||
}));
|
||||
releaseGitHubRun = githubAccess.activate(input.execution.binding);
|
||||
input = { ...input, runnerEnvironment: { ...input.runnerEnvironment, ...githubAccess.env } };
|
||||
}
|
||||
return input.runnerEnvironment;
|
||||
};
|
||||
const overlapGitHubPreparation = input.managedGitHub && input.useRunnerd && input.backend === undefined
|
||||
&& input.runnerExecutionTarget?.kind === "remote" && input.execution.provider.kind === "codex"
|
||||
&& input.execution.schema !== "paperclip.native-execution-input.v6"
|
||||
&& input.execution.schema !== "paperclip.native-execution-input.v8";
|
||||
if (!overlapGitHubPreparation) await prepareRunnerEnvironment();
|
||||
const expectedCurrentWakeComments = await timeController("wake_binding", () => resolveCurrentWakeCommentsBinding(
|
||||
input.db,
|
||||
input.execution.binding,
|
||||
@@ -8702,6 +8710,7 @@ async function executePaperclipNativeSessionWithinScope(
|
||||
input.useRunnerd && input.backend === undefined
|
||||
? await timeController("backend_setup", () => createRunnerdBackend({
|
||||
...input,
|
||||
...(overlapGitHubPreparation ? { prepareRunnerEnvironment } : {}),
|
||||
// Durable scope and prior-run verification use the controller's
|
||||
// canonical workspace identity. createRunnerdBackend separately
|
||||
// projects remoteCwd into the provider execution boundary.
|
||||
@@ -11258,6 +11267,7 @@ export async function createRunnerdBackend(input: {
|
||||
}) => Promise<void>;
|
||||
dotWorkspaceRoot?: string;
|
||||
runnerEnvironment?: NodeJS.ProcessEnv;
|
||||
prepareRunnerEnvironment?: () => Promise<NodeJS.ProcessEnv | undefined>;
|
||||
/** Private grant materialization; never a user-configured host path. */
|
||||
managedAiCredentialHome?: string;
|
||||
runnerExecutionTarget?: AdapterExecutionTarget | null;
|
||||
@@ -11485,14 +11495,21 @@ async function createRunnerdBackendWithinSessionClaim(
|
||||
if (relayAssignedMcp && hasAssignedMcp && !assignedGatewayPublicId) {
|
||||
throw new Error("assigned native MCP gateway path is invalid");
|
||||
}
|
||||
const assignedMcpTools = relayAssignedMcp && !nativeReview && assignedMcpUrl && assignedMcpToken
|
||||
? await timeController("assigned_mcp", () => createAssignedMcpTools({
|
||||
const [preparedEnvironment, preparedAssignedMcp] = await Promise.allSettled([
|
||||
Promise.resolve().then(() => input.prepareRunnerEnvironment?.() ?? input.runnerEnvironment),
|
||||
Promise.resolve().then(() => relayAssignedMcp && !nativeReview && assignedMcpUrl && assignedMcpToken
|
||||
? timeController("assigned_mcp", () => createAssignedMcpTools({
|
||||
gateway: getAssignedMcpGateway(input.db),
|
||||
gatewayPublicId: assignedGatewayPublicId!,
|
||||
bearerToken: assignedMcpToken,
|
||||
workMode: input.execution.task.workMode,
|
||||
}))
|
||||
: undefined;
|
||||
: undefined),
|
||||
]);
|
||||
if (preparedEnvironment.status === "rejected") throw preparedEnvironment.reason;
|
||||
if (preparedAssignedMcp.status === "rejected") throw preparedAssignedMcp.reason;
|
||||
const runnerEnvironment = preparedEnvironment.value;
|
||||
const assignedMcpTools = preparedAssignedMcp.value;
|
||||
const dotAttachmentActor = input.execution.schema === "paperclip.native-execution-input.v6"
|
||||
? await input.db.select({ config: agents.adapterConfig }).from(agents).where(and(
|
||||
eq(agents.id, input.execution.binding.agentId), eq(agents.companyId, input.execution.binding.companyId),
|
||||
@@ -12726,8 +12743,8 @@ async function createRunnerdBackendWithinSessionClaim(
|
||||
const isGrok = input.execution.provider.kind === "acpx" && input.execution.provider.agent === "grok";
|
||||
let effectiveRunnerEnvironmentBase: NodeJS.ProcessEnv = {
|
||||
...(isGrok
|
||||
? input.runnerEnvironment ?? {}
|
||||
: resolveNativeProviderEnvironment(input.execution.provider, input.runnerEnvironment)),
|
||||
? runnerEnvironment ?? {}
|
||||
: resolveNativeProviderEnvironment(input.execution.provider, runnerEnvironment)),
|
||||
};
|
||||
const grokCredential = isGrok ? await prepareGrokRunnerCredentials({
|
||||
companyId: input.execution.binding.companyId, environment: effectiveRunnerEnvironmentBase, remote: Boolean(remoteTarget),
|
||||
@@ -13019,7 +13036,7 @@ async function createRunnerdBackendWithinSessionClaim(
|
||||
runnerBinary: controllerRunnerBinary,
|
||||
codexCommand: remoteCodexBinary ?? undefined,
|
||||
sourceCodexHome: remoteTarget
|
||||
? resolveSourceCodexHome(resolveNativeProviderEnvironment(input.execution.provider, input.runnerEnvironment))
|
||||
? resolveSourceCodexHome(resolveNativeProviderEnvironment(input.execution.provider, runnerEnvironment))
|
||||
: undefined,
|
||||
runnerReconnectGraceMs: remoteTarget ? 120_000 : undefined,
|
||||
environment: effectiveRunnerEnvironment,
|
||||
|
||||
Reference in new issue
Block a user