fix(runner): discover assigned tools when direct catalogs exceed limits (#14218)

Keep assigned app tools accessible when the combined Runner catalog exceeds
its operation or byte limits. Reserve task and completion tools, then expose
bounded discovery and call tools for large catalogs. Fetch oversized schemas
in reauthorized chunks without blocking later search results.

Retain task ownership, work-mode restrictions, pinned assignments, current
gateway authorization, approvals, and audit. Small catalogs stay direct.

Validation: 46 focused server tests, two Runner capacity tests, local
typecheck/build, 52 passing CI checks, and Greptile 5/5 with no open findings.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Devin FoleyandPaperclip authored and GitHub committed 2026-09-27 00:22:16 -07:00
1 parent 640dee1802
commit 5ee9e751fb
9 files changed
+319 -12

No files matched your search

+1
View File
@@ -52,6 +52,7 @@ export * from "./drivers/acpx/sidecar-protocol.js";
export * from "./drivers/runner-tool-bridge.js";
export {
createRunnerdCodexTransport,
runnerCodexDynamicToolsFit,
defaultCapabilityRunnerdBinary,
readRunnerdArtifactBinding,
drainRetainedRunnerdMaintenanceOperations,
@@ -0,0 +1,18 @@
import { expect, it } from "vitest";
import { runnerCodexDynamicToolsFit } from "./runnerd-codex-transport.js";
function tools(count: number, description = "Read a fixture") {
return Array.from({ length: count }, (_, i) => ({
name: `fixture_${i}`, description, inputSchema: { type: "object" },
}));
}
it("reserves completion tools at the runner's operation boundary", () => {
expect(runnerCodexDynamicToolsFit(tools(254))).toBe(true);
expect(runnerCodexDynamicToolsFit(tools(255))).toBe(false);
});
it("accounts for UTF-8 catalog bytes even below the operation limit", () => {
expect(runnerCodexDynamicToolsFit(tools(200, "x".repeat(1000)))).toBe(true);
expect(runnerCodexDynamicToolsFit(tools(200, "🙂".repeat(1000)))).toBe(false);
});
@@ -3045,6 +3045,17 @@ export function authorizedToolSetForProvider(
return authorizedToolSet(tools);
}
/** Include the completion tools that the Codex driver adds at session startup. */
export function runnerCodexDynamicToolsFit(
tools: readonly Readonly<Record<string, unknown>>[],
): boolean {
const supplied = [...tools, ...codexSemanticToolSpecs()];
// Keep these bounds aligned with runner-core/provider_bridge.rs. Project
// large optional catalogs before run.prepare; never raise the protocol caps.
return supplied.length <= 256 &&
Buffer.byteLength(JSON.stringify(authorizedToolSet(supplied)), "utf8") <= 768 * 1024;
}
/**
* Raw provider tracing is consumed by runnerd itself. The provider child still
* receives the narrower allowlist enforced by Rust's `SupervisedProcess`, so