From 06d4fe3121e57ca834b533e757bd0f540fadabc5 Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Mon, 19 Oct 2026 14:29:22 -0500 Subject: [PATCH] perf(runner): preload essential Claude task tools (#16885) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The Runner exposes assigned tools to Claude through an authenticated MCP bridge. > - Claude can defer these tools and search for them before a simple task can finish. > - Title and terminal tools are common requirements for a task turn. > - This pull request marks those three tools for immediate loading by Claude. > - The remaining tools keep the SDK deferral policy and the existing access checks. ## Linked Issues or Issue Description Refs #15993, #15994, #15997. **What existing behavior does this improve?** Claude Runner tool discovery during task execution. **Current behavior** Claude can use ToolSearch to discover the mandatory title and terminal tools before it answers a simple task. **Proposed behavior** Expose `set_task_title`, `paperclip_finish`, and `paperclip_block` in the first Claude request through per-tool `anthropic/alwaysLoad` metadata. Keep other tools deferred under the SDK policy. **Reason and benefit** Remove an avoidable tool discovery step without changing the model, effort, or authorization. Repeated product measurements will check the latency benefit. ## What Changed - Add closed metadata for the three essential tools only in Claude runtime hosts. - Keep incoming metadata discarded and private tools hidden. Keep other providers unchanged. - Test real bridge catalogs and provider routing. Check the pinned Claude SDK request against a loopback fixture with no paid calls. - Advance the pending Copilot source binding because it attests the shared bridge file. Preserve historical profile readers and qualification limits. ## Verification - Focused bridge, admission, profile, and effort controls: 185 passed, one skipped. Three provider-routing cases pass. - Runner TypeScript compilation passes. - The production SDK fixture loads exactly three essential tools with the change and zero in the controlled baseline. The document tool stays deferred. - Six real API-key tasks used Opus 5.5 / High in alternating order A B B A A B. Median first assistant receipt was 22.021 s baseline vs 17.060 s candidate (22.5% lower). Median total was 24.119 s vs 19.150 s (20.6% lower). ToolSearch was 3/3 baseline vs 0/3 candidate. All six tasks saved a title and accepted finish. A separate candidate follow-up resumed the same provider session and finished without ToolSearch. - Median startup was 4.786 s baseline vs 5.381 s candidate. This change does not improve startup. Three samples per version give directional evidence only. - Estimated cost for the six tasks was $0.850900800. Candidate total was $0.450341800 vs baseline $0.400559000; the first candidate wrote a new cache prefix. These rate-card estimates are not invoices. - The benchmark used source `5aed3c6a083cf8e6246815455abde3b29ecbbbac`. Rebuilding both bundles at master-reconciled head `d2da07259048c0a2bebd210c35cc0ebb3ff42c67` produced identical SHA-256 values. Baseline bundle SHA-256: `10a92c373bba4fb630bf4fefeab953d850a77cf8258bc2e94711410a73a78ecd`. Candidate: `40348fc67a3b1ae5cc1f27f933f369981d31bcdf5d1bdc7b1797968ddfe53938`. Baseline differs only by compiling the Claude preload selector as false. Fresh task sessions avoid checkpoint changes. - Full local runtime-host runs exposed credential lease collisions in existing cases. A later complete host run passed 67 cases and timed out in an existing admission-abort case; that case passes in isolation. All failures are retained. Focused controls pass. Complete Runner suites, repository typecheck, tests, browser checks, and build pass in cloud CI on this head. ## Risks - These three tool schemas add input to Claude requests. Measurements must include token usage and cost. - Copilot source identity changes because it includes the bridge source hash. It remains pending qualification. Its catalog and executable closure do not change. - The test fixture proves request shape. It does not prove real-provider latency or platform package qualification. ## Model Used OpenAI Codex, GPT-6 family, with reasoning, tool use, and code execution. The exact serving model ID and context window are not exposed in this session. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass (focused controls and isolated cancellation check; broad local failures are retained above) - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip --- .dockerignore | 1 + doc/architecture/runner-pi-capabilities.md | 7 +-- .../runner-rich-acp-capabilities.md | 7 +-- packages/paperclip-runner/acpx-profiles.json | 4 +- .../docs/protocol-compatibility.md | 14 ++++++ .../src/generated_acpx_profiles.rs | 2 +- .../drivers/acpx/copilot-installation.test.ts | 20 +++++++-- .../src/drivers/acpx/generated-profiles.ts | 4 +- .../src/drivers/acpx/profile-compatibility.ts | 2 +- .../src/drivers/acpx/runtime-host.test.ts | 25 +++++++++++ .../src/drivers/acpx/runtime-host.ts | 7 ++- .../src/drivers/runner-tool-bridge.test.ts | 21 +++++++++ .../src/drivers/runner-tool-bridge.ts | 10 +++++ .../copilot-profile-v23-identity.json | 44 +++++++++++++++++++ 14 files changed, 151 insertions(+), 17 deletions(-) create mode 100644 packages/paperclip-runner/test/fixtures/copilot-profile-v23-identity.json diff --git a/.dockerignore b/.dockerignore index a863f36141c..cb547de2a60 100644 --- a/.dockerignore +++ b/.dockerignore @@ -76,6 +76,7 @@ packages/grok-acp/bin !packages/paperclip-runner/test-fixtures/pi-acp/profile-v26-identity.json !packages/paperclip-runner/test/fixtures/copilot-profile-v19-claude-qualification-identity.json !packages/paperclip-runner/test/fixtures/copilot-profile-v22-identity.json +!packages/paperclip-runner/test/fixtures/copilot-profile-v23-identity.json !packages/paperclip-runner/test-fixtures/pi-acp/profile-v24-claude-historical-identity.json diff --git a/doc/architecture/runner-pi-capabilities.md b/doc/architecture/runner-pi-capabilities.md index c367ce6acd3..365f7d3d303 100644 --- a/doc/architecture/runner-pi-capabilities.md +++ b/doc/architecture/runner-pi-capabilities.md @@ -4,10 +4,11 @@ The composed Claude source declares **Pi profile 27** (`sha256:b1b75a1ae3ac498eb07b95b96bebf4da86227b83d09276076dd5c222e3964dba`) -and **Copilot profile 22**, still pending qualification -(`sha256:9027ee2bdf9180e3267b7d05b56b0703943251a29713c393f7a3f2ef1072efd2`). +and **Copilot profile 23**, still pending qualification +(`sha256:6ca1e2c9efa23887ba6f08d89980c91d13114640ac8a1a79f00f27aa8cd39d63`). These identities bind the shared Claude effort and lifecycle changes plus -master's OpenRouter attribution. Provider executable closures, model/effort +master's OpenRouter attribution. Copilot's shared bridge binding also includes +Claude-only essential tool metadata; its own tool catalog is unchanged. Provider executable closures, model/effort options and qualification boundaries are unchanged. Current package and source checks remain separate from retained provider execution results, which keep their literal profile/source identities. Earlier immutable fixtures remain diff --git a/doc/architecture/runner-rich-acp-capabilities.md b/doc/architecture/runner-rich-acp-capabilities.md index a1d611b7423..275d5804a54 100644 --- a/doc/architecture/runner-rich-acp-capabilities.md +++ b/doc/architecture/runner-rich-acp-capabilities.md @@ -4,10 +4,11 @@ The composed Claude source declares **Pi profile 27** (`sha256:b1b75a1ae3ac498eb07b95b96bebf4da86227b83d09276076dd5c222e3964dba`) -and **Copilot profile 22**, still pending qualification -(`sha256:9027ee2bdf9180e3267b7d05b56b0703943251a29713c393f7a3f2ef1072efd2`). +and **Copilot profile 23**, still pending qualification +(`sha256:6ca1e2c9efa23887ba6f08d89980c91d13114640ac8a1a79f00f27aa8cd39d63`). These identities bind the shared Claude effort and lifecycle changes plus -master's OpenRouter attribution. Provider executable closures, model/effort +master's OpenRouter attribution. Copilot's shared bridge binding also includes +Claude-only essential tool metadata; its own tool catalog is unchanged. Provider executable closures, model/effort options and qualification boundaries are unchanged. Current package and source checks remain separate from retained provider execution results, which keep their literal profile/source identities. Earlier immutable fixtures remain diff --git a/packages/paperclip-runner/acpx-profiles.json b/packages/paperclip-runner/acpx-profiles.json index e483929ad19..a8881081ca8 100644 --- a/packages/paperclip-runner/acpx-profiles.json +++ b/packages/paperclip-runner/acpx-profiles.json @@ -32,12 +32,12 @@ "requiresProviderPolicy": true }, "copilot": { - "agentProfileVersion": 22, + "agentProfileVersion": 23, "agentServerPackage": "@github/copilot", "agentServerVersion": "1.0.88", "agentRuntimePackage": null, "agentRuntimeVersion": null, - "commandDigest": "sha256:9027ee2bdf9180e3267b7d05b56b0703943251a29713c393f7a3f2ef1072efd2", + "commandDigest": "sha256:6ca1e2c9efa23887ba6f08d89980c91d13114640ac8a1a79f00f27aa8cd39d63", "requiresProviderPolicy": true, "qualificationStatus": "pending" }, diff --git a/packages/paperclip-runner/docs/protocol-compatibility.md b/packages/paperclip-runner/docs/protocol-compatibility.md index cafc8ddb618..175d5b3b681 100644 --- a/packages/paperclip-runner/docs/protocol-compatibility.md +++ b/packages/paperclip-runner/docs/protocol-compatibility.md @@ -317,3 +317,17 @@ including master's OpenRouter attribution. The prior branch profile 24 is preserved separately from master's published profile 24. Provider executable bytes remain unchanged. These new source identities require fresh compatibility checks; earlier provider runs retain their original identities. + +## Claude essential task tools + +Claude's runner-owned MCP bridge marks `set_task_title`, `paperclip_finish` and +`paperclip_block` with `_meta["anthropic/alwaysLoad"]: true`. The pinned SDK can +include these definitions in its first request without a ToolSearch round trip. +Other public tools keep their existing deferral policy. Private operations stay +undiscoverable, and incoming tool definitions cannot set this metadata themselves. +The host enables this only for Claude; Codex and Copilot catalogs are unchanged. +No server-wide tool preloading, model change or permission change is introduced. + +The shared bridge's source binding advances pending Copilot profile 22 to 23. +Profile 22 remains decodable and its fixture is immutable. Copilot remains pending; +the revision does not qualify a new provider or change its executable closure. diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/generated_acpx_profiles.rs b/packages/paperclip-runner/runner/crates/runner-core/src/generated_acpx_profiles.rs index 1a72ae27b75..7ad167ce577 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/generated_acpx_profiles.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/generated_acpx_profiles.rs @@ -47,7 +47,7 @@ pub(crate) fn acpx_release_profile(agent: &str) -> Option { agent_runtime_package: None, agent_runtime_version: None, command_digest: - "sha256:9027ee2bdf9180e3267b7d05b56b0703943251a29713c393f7a3f2ef1072efd2", + "sha256:6ca1e2c9efa23887ba6f08d89980c91d13114640ac8a1a79f00f27aa8cd39d63", requires_provider_policy: true, }, "claude" => AcpxReleaseProfile { diff --git a/packages/paperclip-runner/src/drivers/acpx/copilot-installation.test.ts b/packages/paperclip-runner/src/drivers/acpx/copilot-installation.test.ts index 0a81202310b..12ba72ccf45 100644 --- a/packages/paperclip-runner/src/drivers/acpx/copilot-installation.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/copilot-installation.test.ts @@ -211,8 +211,22 @@ describe("Copilot build-owned installation", () => { expect(QUALIFIED_ACPX_PROFILES.copilot.qualificationStatus).toBe("pending"); }); - it("admits the current v22 declaration and binds its source policy, receipts and patch hashes", () => { - const identity = JSON.parse(readFileSync(new URL("../../../test/fixtures/copilot-profile-v22-identity.json", import.meta.url), "utf8")); + it("preserves v22 history while binding the Claude-only preload metadata branch", async () => { + const prior = JSON.parse(readFileSync(new URL("../../../test/fixtures/copilot-profile-v22-identity.json", import.meta.url), "utf8")); + const current = JSON.parse(readFileSync(new URL("../../../test/fixtures/copilot-profile-v23-identity.json", import.meta.url), "utf8")); + expect(Object.keys(current.declaration).filter(key => current.declaration[key] !== prior.declaration[key]).sort()) + .toEqual(["agentProfileVersion", "semanticBridgeSourceSha256"]); + expect(Object.keys(current.declaration).sort()).toEqual(Object.keys(prior.declaration).sort()); + expect(isSupportedAcpxProfileVersion("copilot", 22)).toBe(true); + const sorted = Object.fromEntries(Object.entries(prior.declaration).sort(([a], [b]) => a.localeCompare(b))); + expect(`sha256:${createHash("sha256").update(JSON.stringify(sorted)).digest("hex")}`).toBe(prior.commandDigest); + await expect(verifyCopilotInstallation({ ...QUALIFIED_ACPX_PROFILES.copilot, + agentProfileVersion: 22, commandDigest: prior.commandDigest })).rejects.toThrow("exact pinned"); + expect(QUALIFIED_ACPX_PROFILES.copilot.qualificationStatus).toBe("pending"); + }); + + it("admits the current v23 declaration and binds its source policy, receipts and patch hashes", () => { + const identity = JSON.parse(readFileSync(new URL("../../../test/fixtures/copilot-profile-v23-identity.json", import.meta.url), "utf8")); expect(identity.declaration.systemInstructionDelivery).toBe(COPILOT_SYSTEM_INSTRUCTION_DELIVERY); expect(identity.declaration.sharedRuntimeContract).toBe("paperclip.acpx-runtime-contract.v1"); expect(identity.declaration.permissionContextContract).toBe(COPILOT_PERMISSION_CONTEXT_CONTRACT); @@ -243,7 +257,7 @@ describe("Copilot build-owned installation", () => { expect([...closure].filter(path => !bound.has(path))).toEqual(["negative-control:unbound-policy"]); }); it.each([...PERMISSION_POLICY_SOURCES, ...SEMANTIC_RECEIPT_SOURCES])("changing %s changes the candidate identity", (_path, field) => { - const identity = JSON.parse(readFileSync(new URL("../../../test/fixtures/copilot-profile-v22-identity.json", import.meta.url), "utf8")); + const identity = JSON.parse(readFileSync(new URL("../../../test/fixtures/copilot-profile-v23-identity.json", import.meta.url), "utf8")); identity.declaration[field] = "0".repeat(64); const sorted = Object.fromEntries(Object.entries(identity.declaration).sort(([a], [b]) => a.localeCompare(b))); expect(`sha256:${createHash("sha256").update(JSON.stringify(sorted)).digest("hex")}`).not.toBe(identity.commandDigest); diff --git a/packages/paperclip-runner/src/drivers/acpx/generated-profiles.ts b/packages/paperclip-runner/src/drivers/acpx/generated-profiles.ts index 58740700729..bf7d198f0b9 100644 --- a/packages/paperclip-runner/src/drivers/acpx/generated-profiles.ts +++ b/packages/paperclip-runner/src/drivers/acpx/generated-profiles.ts @@ -48,12 +48,12 @@ export const QUALIFIED_ACPX_PROFILE_DATA = { "protocolVersion": 1, "acpxVersion": "0.13.1", "agent": "copilot", - "agentProfileVersion": 22, + "agentProfileVersion": 23, "agentServerPackage": "@github/copilot", "agentServerVersion": "1.0.88", "agentRuntimePackage": null, "agentRuntimeVersion": null, - "commandDigest": "sha256:9027ee2bdf9180e3267b7d05b56b0703943251a29713c393f7a3f2ef1072efd2", + "commandDigest": "sha256:6ca1e2c9efa23887ba6f08d89980c91d13114640ac8a1a79f00f27aa8cd39d63", "qualificationStatus": "pending", "permissionPolicy": "interactive" }, diff --git a/packages/paperclip-runner/src/drivers/acpx/profile-compatibility.ts b/packages/paperclip-runner/src/drivers/acpx/profile-compatibility.ts index a38fc6fed8b..213dd87a81d 100644 --- a/packages/paperclip-runner/src/drivers/acpx/profile-compatibility.ts +++ b/packages/paperclip-runner/src/drivers/acpx/profile-compatibility.ts @@ -8,7 +8,7 @@ export type AcpxProfileVersion = HistoricalAcpxProfileVersion | CurrentAcpxProfi const historicalVersions: Readonly> = { pi: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26], claude: [1, 2, 3, 4, 5], codex: [1, 2, 3, 4, 5], - grok: [1, 2, 3, 4, 5], copilot: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21], + grok: [1, 2, 3, 4, 5], copilot: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22], cursor: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], }; diff --git a/packages/paperclip-runner/src/drivers/acpx/runtime-host.test.ts b/packages/paperclip-runner/src/drivers/acpx/runtime-host.test.ts index ba93401fde0..20bc11c713a 100644 --- a/packages/paperclip-runner/src/drivers/acpx/runtime-host.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/runtime-host.test.ts @@ -906,6 +906,31 @@ describe("ACPX runtime host", () => { expect(fixture.commandClose).toHaveBeenCalledOnce(); }); + it.each(["claude", "codex", "copilot"] as const)("preloads essential task tools only for Claude through the host (%s)", async agent => { + const fixture = await hostFixture(); + const model = agent === "claude" ? "claude-opus-5-5" : "gpt-5.6-sol"; + let bridge: Extract | undefined; + const environment = agent === "claude" ? { ANTHROPIC_API_KEY: "fixture-not-a-real-credential" } + : agent === "copilot" ? { COPILOT_GITHUB_TOKEN: "fixture-not-a-real-credential" } + : { PAPERCLIP_ACPX_CODEX_AUTH_JSON_SECRET: "{}" }; + const host = await AcpxRuntimeHost.open({ + ...fixture.options, agent, model, permissionMode: "deny-all", environment, + semanticTools: { tools: [{ name: "set_task_title", inputSchema: { type: "object", properties: {} } }], handler: async () => ({}) }, + }, fixture.dependencies({ openRuntime: async options => { + bridge = options.mcpServers.find((server): server is NonNullable => "url" in server && server.runnerOwned); + return runtimePort({ getStatus: async () => ({ models: { currentModelId: model, availableModelIds: [model] } }) }); + } })); + try { + const response = await fetch(bridge!.url, { + method: "POST", headers: { Authorization: `Bearer ${bridge!.bearerToken}`, "Content-Type": "application/json" }, + body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list" }), + }); + const listed = await response.json() as { result: { tools: Array<{ name: string; _meta?: Record }> } }; + expect(listed.result.tools.filter(tool => tool._meta?.["anthropic/alwaysLoad"] === true).map(({ name }) => name)) + .toEqual(agent === "claude" ? ["set_task_title", "paperclip_finish", "paperclip_block"] : []); + } finally { await host.close({ reason: "tool preload verified" }); } + }); + it("forwards semantic receipt capture through the real runtime host and authenticated bridge", async () => { const fixture = await hostFixture(); const events: CanonicalProviderEvent[] = []; diff --git a/packages/paperclip-runner/src/drivers/acpx/runtime-host.ts b/packages/paperclip-runner/src/drivers/acpx/runtime-host.ts index 070dbe8d4d1..af13be285f4 100644 --- a/packages/paperclip-runner/src/drivers/acpx/runtime-host.ts +++ b/packages/paperclip-runner/src/drivers/acpx/runtime-host.ts @@ -194,7 +194,7 @@ export type AcpxMcpServerBinding = { name: string; command: string; args: string[]; env: Record; runnerOwned: false; }; -export type AcpxSemanticToolSession = Omit; +export type AcpxSemanticToolSession = Omit; export interface AcpxRetainedCleanupFailure { resource: @@ -582,7 +582,10 @@ export class AcpxRuntimeHost { toolBridge = options.semanticTools ? await acquireAbortableAdmissionResource({ signal: options.signal, - acquire: () => startRunnerToolBridge(options.semanticTools!), + acquire: () => startRunnerToolBridge({ + ...options.semanticTools!, + preloadClaudeTaskTools: options.agent === "claude", + }), resource: "tool_bridge", releaseLate: (lateToolBridge) => lateToolBridge.close(), reportFailure: (failure) => diff --git a/packages/paperclip-runner/src/drivers/runner-tool-bridge.test.ts b/packages/paperclip-runner/src/drivers/runner-tool-bridge.test.ts index b61d2c32f3a..152b8a9b6ea 100644 --- a/packages/paperclip-runner/src/drivers/runner-tool-bridge.test.ts +++ b/packages/paperclip-runner/src/drivers/runner-tool-bridge.test.ts @@ -38,6 +38,27 @@ async function rpc( } describe("runner semantic MCP bridge", () => { + it.each([false, true])("preloads only essential public Claude task tools when opted in (%s)", async preloadClaudeTaskTools => { + const bridge = await startRunnerToolBridge({ + preloadClaudeTaskTools, + tools: [tool("paperclip__set_task_title"), { + ...tool("documents.read"), _meta: { "anthropic/alwaysLoad": true }, + }], + privateTools: [tool("__paperclip_permission")], + handler: async () => ({ ok: true }), + }); + bridges.push(bridge); + const listed = await (await rpc(bridge, { id: 1, method: "tools/list" })).json() as { + result: { tools: Array<{ name: string; _meta?: Record }> }; + }; + expect(listed.result.tools.map(({ name }) => name)).toEqual([ + "set_task_title", "documents.read", "paperclip_finish", "paperclip_block", + ]); + expect(listed.result.tools.filter(tool => tool._meta?.["anthropic/alwaysLoad"] === true).map(({ name }) => name)) + .toEqual(preloadClaudeTaskTools ? ["set_task_title", "paperclip_finish", "paperclip_block"] : []); + expect(listed.result.tools.find(tool => tool.name === "documents.read")?._meta).toBeUndefined(); + }); + it("binds to loopback, requires authentication, and exposes a closed catalog", async () => { const bridge = await startRunnerToolBridge({ secret: "session-secret", diff --git a/packages/paperclip-runner/src/drivers/runner-tool-bridge.ts b/packages/paperclip-runner/src/drivers/runner-tool-bridge.ts index 06c59a8a8db..4c3a49a873a 100644 --- a/packages/paperclip-runner/src/drivers/runner-tool-bridge.ts +++ b/packages/paperclip-runner/src/drivers/runner-tool-bridge.ts @@ -22,6 +22,7 @@ export interface RunnerToolDefinition { name: string; description?: string; inputSchema: Record; + _meta?: { "anthropic/alwaysLoad": true }; } export interface RunnerToolCall { @@ -36,6 +37,8 @@ export interface RunnerToolCall { export interface RunnerToolBridgeOptions { tools?: readonly Readonly>[]; + /** Claude-only: avoid tool search for task naming and terminal disposition. */ + preloadClaudeTaskTools?: boolean; /** Runner-owned operations that are callable but never model-visible. */ privateTools?: readonly Readonly>[]; handler(call: RunnerToolCall): Promise; @@ -118,6 +121,13 @@ export async function startRunnerToolBridge( } } const visibleTools = [...tools, ...structuredClone(RESERVED_TOOLS)]; + if (options.preloadClaudeTaskTools) { + for (const tool of visibleTools) { + if (tool.name === "set_task_title" || tool.name === PRP_COMPLETION_TOOL_NAME || tool.name === PRP_BLOCK_TOOL_NAME) { + tool._meta = { "anthropic/alwaysLoad": true }; + } + } + } const admittedTools = [...visibleTools, ...privateTools]; const validators = compileValidators(admittedTools); const calls = new Map(); diff --git a/packages/paperclip-runner/test/fixtures/copilot-profile-v23-identity.json b/packages/paperclip-runner/test/fixtures/copilot-profile-v23-identity.json new file mode 100644 index 00000000000..abf8d388cc9 --- /dev/null +++ b/packages/paperclip-runner/test/fixtures/copilot-profile-v23-identity.json @@ -0,0 +1,44 @@ +{ + "commandDigest": "sha256:6ca1e2c9efa23887ba6f08d89980c91d13114640ac8a1a79f00f27aa8cd39d63", + "declaration": { + "schema": "paperclip.acpx_profile_declaration.v1", + "agent": "copilot", + "agentProfileVersion": 23, + "acpxVersion": "0.13.1", + "agentServerVersion": "1.0.88", + "protocolPolicyRevision": "copilot-agent-manual-v1", + "acpxPatchSha256": "00d6af17216cb0cad902b48633eb19bf0ec6b05837a984cb24661fca593f09c1", + "policySha256": "a4be69be43db84052560420a3ca546a665c9165573c24d543eb33d144ba138a3", + "distributionSourceSha256": "c0a65e247fc47b263bd369a3d5ab505c4b323d4e6493be8a9ecbeb0e9dbb9107", + "agentFilesBinding": "registered-runtime-context-v1", + "systemInstructionDelivery": "COPILOT_HOME/copilot-instructions.md:replace-under-lifetime-lease-before-launch:v1", + "sharedRuntimeContract": "paperclip.acpx-runtime-contract.v1", + "innerDistributionSourceSha256": "9bb08347eed01f1ab44e065c8b06ead9be3a25973d6e6329dc4c0a099096279a", + "upstreamAppSha256": "7b48282a19b5b0814a0c96ad5e3a125173d792cc55f9525b2effea962f6063c0", + "patchedAppSha256": "61f8eebe0c30cc03b311c6818b2acb158a6d16ca58c8bfac5ebd42db095c2679", + "messageIdentityContract": "copilot-native-message-id-v1", + "ownedDistributionDelivery": "COPILOT_CLI_DIST_DIR:lease-owned-guarded-inner-distribution:v1", + "permissionContextContract": "copilot-edit-permission-context-v1", + "permissionContextSourceSha256": "fe1bb611bea289d81b1c7c973fbb01b93de133a7caa8fe78b7ff4ee1f485b224", + "permissionAdapterSourceSha256": "49421a779c76e286ce65d75052026eab9630b1995af9ccf045a2d43d284633ce", + "permissionLocationsSourceSha256": "f39a81da395b6e23aa2a1a45572a28846e5e81bfd3d764d9a456f74a79c752e9", + "permissionRedactionSourceSha256": "ad6770c69269087951790e2e87e22ac088e3d432769adf17ebc865ed512e6c4f", + "permissionClassifierSourceSha256": "1161eabd82e26133e3b3db3ab567f65d52808b03f176e93497e02c8330349fb4", + "permissionIdentitySourceSha256": "770b47305e571a81f344883057d0e4a482a1cd59ac77d045836593a8e739deb1", + "semanticToolReceiptContract": "paperclip.semantic_tool_receipt.v2", + "semanticReceiptSourceSha256": "b2795726b2234a663fec1a6ca11f0551ae69882fe9e82aa39686e952d036e791", + "semanticBridgeSourceSha256": "89e3dde25ff35bfd4d7f520fd9d14af3cb9027f182a2d51431d298f1015be580", + "toolEvidenceSourceSha256": "49d7d09eb6957e596a173b0afd763544afe582f0c1f05cc0992fe51916a8fdb3", + "semanticNormalizedInputContract": "validated-forwarded-input-commit-v1", + "semanticSidecarSourceSha256": "ddd98aabe20f4d40b04bb58126bda548a9d85b8234a3f9ea84b6db414e9b4814", + "semanticDirectDriverSourceSha256": "72d5134a9567ced685bc942adf14ec5dfba04bcd6b0eeabe77eac0cec5c434c3", + "semanticValidationSourceSha256": "02fbb92681c78c5de5d2f1aae25aabc25a0e5df6b0ff4baccac652192a8da975", + "semanticNormalizationSourceSha256": "8b628b82e8eacc91cdcc6fdca2210ba4cf282f01e9c6f74e95ff271655b5047e", + "semanticCompletionContractSourceSha256": "a104278d4d0e063a69bb464938db117dc27691b5bd4590c5a359e11f4917f623", + "semanticValidatorsSourceSha256": "38ae5379d47df633564730ec1359a16d21c0d0a68addb5e488b58c697f13362c", + "semanticSchemaBundleSourceSha256": "5e957fe0f0ba164441d6a5b8a276e52cf6f2ffaf99741faca99cff05e539221c", + "semanticSidecarProtocolSourceSha256": "03c0c95b900f9dc18f29f6be27b8c79bc0bcab29780190d46a74e95328081236", + "semanticSidecarReceiptCommitContract": "correlated-tool-resolve-v1", + "semanticContinuationSourceSha256": "eb877719ef32a935afa4ec93f0f1c43d58d55bc4e1042965febcac4c45ed851c" + } +}