mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-23 09:07:46 +02:00
Fix isolated ACPX admission for assigned PRP tools (#17149)
## Thinking Path - Paperclip uses Runner to execute agent tasks under Core's selected policy. - The ACPX sidecar can receive assigned MCP tools in its PRP catalog. - Isolated admission still requires a separate native MCP endpoint for those assignments. - This check rejects a valid PRP route before the provider can start. - This PR identifies the sidecar's PRP transport at the runtime-host boundary. - The benefit is that assigned tools work with the existing isolated permission policy. ## Linked Issues or Issue Description **What happened?** An isolated Claude task with an assigned MCP binding fails with `assigned native MCP launch binding is unavailable` when Core delivers its tools through PRP. **Expected behavior** The sidecar must accept its authenticated PRP tool bridge as the assigned-tool route. Callers that use the native gateway must still supply its complete binding. **Steps to reproduce** 1. Open an isolated Claude sidecar session with `deny-all` permissions and a read-only policy. 2. Supply an assigned MCP binding in the runtime context and its tools in the PRP catalog. 3. Omit native gateway environment variables. Admission fails before provider startup. **Paperclip version or commit** Reproduced against `92cfd701c39bb1ea394a796023481a21decb2532`. **Deployment mode** Runner with an ACPX sidecar and controller-owned tool delivery. Related contract work: #16978. The search found no existing PR or issue for this failure. ## What Changed - Let runtime-host callers select PRP delivery when they supply the semantic tool bridge. - Select that route in the sidecar. Keep native gateway validation and direct callers' default behavior. - Preserve exact controller operation names in PRP mode, including the compact assigned-MCP catalog. Keep existing aliases for other bridge callers. - Preserve the selected permission and read-only policies. - Add one process-level sidecar regression and one host admission regression. The host test calls the real authenticated MCP bridge. - Document the route and refresh the generated Copilot source hashes. The provider and profile versions stay the same. ## Verification - Passed 11 focused tests after the rebase across the runtime host, sidecar, tool bridge, and Copilot identity tests. They cover both compact catalog operations, restricted and approve-all policies, legacy aliases, missing and invalid gateway bindings, and the existing direct gateway path. - The process-level test crosses the real sidecar dispatcher and stops at workspace validation before provider startup. The host test uses real ownership sockets and MCP HTTP, with a fake provider port. - The generated identity tests match the current source hashes. The historical fixture stays unchanged. - The standard review passed on the rebased commit. [Apex passed the required 5/5 threshold](https://github.com/paperclipai/paperclip/runs/119513362214) on `e63a223`, with 11 files reviewed and no findings. - Rebased onto master `734c265`. The conflicts affected generated source identities and their test expectations. All reviewed behavior files remain byte-for-byte unchanged. The user authorized retaining the Apex result for this rebase. [CI passed on `3facef5`](https://github.com/paperclipai/paperclip/actions/runs/39685163239). The final PR checks contain 56 passes and four expected skips. The branch is current with master, has no conflicts, and has no unresolved review comments. - `node packages/paperclip-runner/scripts/copilot-profile-identity.mjs --check` passed. - Initial local runs exposed missing test dependencies and sandbox denial of loopback sockets. After the scoped dependency install and socket permission, the selected tests passed. - Broad typecheck, build, and test verification runs in GitHub CI, as requested. No live provider or model request was made. ## Risks - PRP callers must supply the assigned tools in the controller-owned catalog. The existing bridge still enforces its closed catalog and authentication. - An explicit PRP selection without a semantic bridge still fails. Invalid native gateway bindings still fail. - No database, protocol-frame, provider-version, or credential-storage change is required. ## Model Used OpenAI GPT-6 through Codex, with source inspection, editing, and command execution. The exact served model ID and context-window size 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 - [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 <noreply@paperclip.ing>
This commit is contained in:
1 parent
734c265339
commit
0081863463
11 files changed
+87
-14
No files matched your search
@@ -192,6 +192,14 @@ and terminal settlement at the process boundary. Copilot and Pi remain gated.
|
||||
Verified distributions are build-owned; no provider accepts an arbitrary executable.
|
||||
See [the rich ACP capability report](../../doc/architecture/runner-rich-acp-capabilities.md).
|
||||
|
||||
If the controller sends assigned MCP tools in the PRP catalog, the sidecar uses
|
||||
its authenticated tool bridge.
|
||||
Isolated provider admission accepts this route without a separate native MCP endpoint.
|
||||
Direct runtime-host callers must supply the native gateway binding unless they select
|
||||
PRP tool delivery and supply the semantic tool bridge.
|
||||
PRP delivery keeps the controller's exact operation names, including compact MCP catalog tools.
|
||||
The selected permission and read-only policies still apply.
|
||||
|
||||
Install Cursor explicitly with `paperclipai runtime setup cursor`; npm installation
|
||||
does not download it. Run setup as the OS user that runs Paperclip (the service
|
||||
account for a managed service). Setup writes to that account's
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"agentServerVersion": "1.0.88",
|
||||
"agentRuntimePackage": null,
|
||||
"agentRuntimeVersion": null,
|
||||
"commandDigest": "sha256:6f49e1a37bcde897703701b638dddf010a0b8e2c92c211a30d4a0bd41e48e77d",
|
||||
"commandDigest": "sha256:7fbf0b3a48250c0c1852503d0dfd0af238c402d19c53573b573cc81f07c6016f",
|
||||
"requiresProviderPolicy": true
|
||||
},
|
||||
"claude": {
|
||||
|
||||
@@ -47,7 +47,7 @@ pub(crate) fn acpx_release_profile(agent: &str) -> Option<AcpxReleaseProfile> {
|
||||
agent_runtime_package: None,
|
||||
agent_runtime_version: None,
|
||||
command_digest:
|
||||
"sha256:6f49e1a37bcde897703701b638dddf010a0b8e2c92c211a30d4a0bd41e48e77d",
|
||||
"sha256:7fbf0b3a48250c0c1852503d0dfd0af238c402d19c53573b573cc81f07c6016f",
|
||||
requires_provider_policy: true,
|
||||
},
|
||||
"claude" => AcpxReleaseProfile {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { canonicalNativeRuntimeContextDigest } from "../contracts/runtime-context.js";
|
||||
import { acpxProfileActivity } from "../drivers/acpx/profile-activity.js";
|
||||
import { redactObservabilityDiagnostic } from "../drivers/agent-observability.js";
|
||||
import { appendSemanticToolReceipt, readNativeSemanticReceipt, semanticInputSha256 } from "../drivers/semantic-tool-receipt.js";
|
||||
@@ -1129,6 +1130,23 @@ describe("qualified ACPX runtime sidecar", () => {
|
||||
},
|
||||
);
|
||||
|
||||
it("routes assigned MCP through PRP in the actual restricted Claude open dispatcher", async () => {
|
||||
const sidecar = startSidecar(), model = "claude-sonnet-5", digest = "a".repeat(64);
|
||||
const snapshot = { prompt: { revision: "fixture", text: "Instructions", digest: createHash("sha256").update("Instructions").digest("hex") },
|
||||
instructions: { entryPath: "AGENTS.md", bundle: { schema: "paperclip.runtime-asset.v1" as const,
|
||||
rootPath: "/unused", digest, manifestDigest: digest, fileCount: 0, totalBytes: 0 } },
|
||||
skills: [], mcp: { assignmentSetId: "assigned", digest, bindingId: "assigned-run" } };
|
||||
sidecar.write(initializeRequest(1, "claude", model));
|
||||
expect(await sidecar.next(frame => frame.id === 1)).toMatchObject({ ok: true });
|
||||
sidecar.write({ protocolVersion: ACPX_SIDECAR_PROTOCOL_VERSION, id: 2, command: "session.open",
|
||||
params: { runtimeDirectory: "/unused", workingDirectory: "/", normalizedSessionId: "assigned-open",
|
||||
agent: "claude", model, permissionMode: "deny-all", providerPolicy: { readOnly: true }, systemInstructions: "Instructions",
|
||||
runtimeContext: { ...snapshot, aggregateDigest: canonicalNativeRuntimeContextDigest(snapshot) },
|
||||
tools: [{ name: "assigned_lookup", inputSchema: { type: "object" } }] } });
|
||||
expect(await sidecar.next(frame => frame.id === 2)).toMatchObject({ ok: false,
|
||||
error: { message: "ACPX working directory must be a non-root directory" } });
|
||||
});
|
||||
|
||||
it.each(["off", "low", "high", "max"])(
|
||||
"accepts Pi thinking level %s through the actual session.open dispatcher",
|
||||
async (piThinkingLevel) => {
|
||||
|
||||
@@ -307,6 +307,7 @@ async function dispatch(
|
||||
runtimeContext: params.runtimeContext,
|
||||
environment: createAcpxSidecarHostEnvironment(process.env, params.agent, params.normalizedSessionId),
|
||||
expectedIdentity: params.expectedIdentity,
|
||||
assignedMcpTransport: "prp",
|
||||
semanticTools: {
|
||||
tools: params.tools,
|
||||
handler: waitForTool,
|
||||
|
||||
@@ -89,14 +89,14 @@ describe("Copilot build-owned installation", () => {
|
||||
it("admits the current profile with environment launch preparation", () => {
|
||||
expect(QUALIFIED_ACPX_PROFILES.copilot.agentProfileVersion).toBe(58);
|
||||
expect(QUALIFIED_ACPX_PROFILES.copilot.qualificationStatus).toBeUndefined();
|
||||
expect(QUALIFIED_ACPX_PROFILES.copilot.commandDigest).toBe("sha256:6f49e1a37bcde897703701b638dddf010a0b8e2c92c211a30d4a0bd41e48e77d");
|
||||
expect(QUALIFIED_ACPX_PROFILES.copilot.commandDigest).toBe("sha256:7fbf0b3a48250c0c1852503d0dfd0af238c402d19c53573b573cc81f07c6016f");
|
||||
});
|
||||
|
||||
it("preserves v57 while binding the shared Pi setup and launch repairs in v58", () => {
|
||||
const prior = JSON.parse(readFileSync(new URL("../../../test/fixtures/copilot-profile-v57-identity.json", import.meta.url), "utf8"));
|
||||
const current = JSON.parse(readFileSync(new URL("../../../test/fixtures/copilot-profile-v58-identity.json", import.meta.url), "utf8"));
|
||||
expect(Object.keys(current.declaration).filter(key => current.declaration[key] !== prior.declaration[key]).sort()).toEqual([
|
||||
"agentProfileVersion", "guardedInstallationSourceSha256", "profileCompatibilitySourceSha256", "profileInstallationSourceSha256", "runtimeRequestBridgeSourceSha256",
|
||||
"agentProfileVersion", "guardedInstallationSourceSha256", "profileCompatibilitySourceSha256", "profileInstallationSourceSha256", "runtimeHostSourceSha256", "runtimeRequestBridgeSourceSha256", "semanticBridgeSourceSha256", "semanticSidecarSourceSha256",
|
||||
]);
|
||||
expect(isSupportedAcpxProfileVersion("copilot", 57)).toBe(true);
|
||||
expect(current.commandDigest).not.toBe(prior.commandDigest);
|
||||
|
||||
@@ -53,7 +53,7 @@ export const QUALIFIED_ACPX_PROFILE_DATA = {
|
||||
"agentServerVersion": "1.0.88",
|
||||
"agentRuntimePackage": null,
|
||||
"agentRuntimeVersion": null,
|
||||
"commandDigest": "sha256:6f49e1a37bcde897703701b638dddf010a0b8e2c92c211a30d4a0bd41e48e77d",
|
||||
"commandDigest": "sha256:7fbf0b3a48250c0c1852503d0dfd0af238c402d19c53573b573cc81f07c6016f",
|
||||
"permissionPolicy": "interactive"
|
||||
},
|
||||
"claude": {
|
||||
|
||||
@@ -379,6 +379,45 @@ describe("ACPX runtime host", () => {
|
||||
await nextOwner.close({ reason: "cancelled initializer ownership released" });
|
||||
});
|
||||
|
||||
it.each(["deny-all", "approve-all"] as const)("admits the compact assigned MCP catalog through PRP with %s policy", async permissionMode => {
|
||||
const fixture = await hostFixture();
|
||||
const runtimeContext = { skills: [], mcp: { bindingId: "assigned-run" } } as unknown as NativeRuntimeContextSnapshot;
|
||||
const catalog = ["paperclip_search_assigned_tools", "paperclip_call_assigned_tool"].map(name => ({ name, inputSchema: { type: "object" } }));
|
||||
const readOnly = permissionMode === "deny-all";
|
||||
const handler = vi.fn(async () => ({ value: "assigned result" }));
|
||||
let bridge: { url: string; bearerToken: string } | undefined;
|
||||
const options = { ...fixture.options, agent: "claude" as const, model: "claude-sonnet-5",
|
||||
permissionMode, providerPolicy: { readOnly }, runtimeContext,
|
||||
semanticTools: { tools: catalog, handler } };
|
||||
const openRuntime = vi.fn(async (launch: AcpxRuntimePortOpenOptions) => {
|
||||
expect(launch.permissionMode).toBe(permissionMode);
|
||||
expect(launch.launchEnvironment.PAPERCLIP_RUNNER_HOME_MODE).toBeUndefined();
|
||||
expect(launch.mcpServers.map(server => server.name)).toEqual(["paperclip"]);
|
||||
if (!("url" in launch.mcpServers[0])) throw new Error("missing tool bridge");
|
||||
bridge = launch.mcpServers[0];
|
||||
return runtimePort({ getStatus: async () => ({ models: { currentModelId: "claude-sonnet-5" } }) });
|
||||
});
|
||||
const dependencies = fixture.dependencies({ openRuntime });
|
||||
const prepare = dependencies.prepareSandbox!;
|
||||
dependencies.prepareSandbox = input => { expect(input.providerPolicy?.readOnly).toBe(readOnly); return prepare(input); };
|
||||
await expect(AcpxRuntimeHost.open(options, dependencies)).rejects.toThrow("assigned native MCP launch binding is unavailable");
|
||||
await expect(AcpxRuntimeHost.open({ ...options, assignedMcpTransport: "prp", semanticTools: undefined }, dependencies)).rejects.toThrow("assigned native MCP launch binding is unavailable");
|
||||
expect(openRuntime).not.toHaveBeenCalled();
|
||||
const host = await AcpxRuntimeHost.open({ ...options, assignedMcpTransport: "prp" }, dependencies);
|
||||
try {
|
||||
for (const tool of catalog) {
|
||||
const response = await fetch(bridge!.url, { method: "POST", headers: {
|
||||
Authorization: `Bearer ${bridge!.bearerToken}`, "Content-Type": "application/json",
|
||||
}, body: JSON.stringify({ jsonrpc: "2.0", id: tool.name, method: "tools/call", params: { name: tool.name, arguments: {} } }) });
|
||||
expect(response.status).toBe(200);
|
||||
expect(await response.json()).toMatchObject({ id: tool.name, result: { content: expect.any(Array) } });
|
||||
expect(handler).toHaveBeenLastCalledWith(expect.objectContaining({ tool: tool.name, arguments: {} }));
|
||||
}
|
||||
expect(handler).toHaveBeenCalledTimes(catalog.length);
|
||||
} finally { await host.close({ reason: "PRP assignment verified" }); }
|
||||
expect(openRuntime).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("registers the assigned connection gateway alongside the Claude task bridge", async () => {
|
||||
const fixture = await hostFixture();
|
||||
let servers: AcpxRuntimePortOpenOptions["mcpServers"] = [];
|
||||
|
||||
@@ -254,6 +254,7 @@ export interface OpenAcpxRuntimeHostOptions {
|
||||
/** Abort admission without admitting resources that resolve afterward. */
|
||||
signal?: AbortSignal;
|
||||
semanticTools?: AcpxSemanticToolSession;
|
||||
assignedMcpTransport?: "prp";
|
||||
onGoalUpdate?: (goal: AcpxRuntimeGoalSnapshot | null) => void;
|
||||
}
|
||||
|
||||
@@ -349,7 +350,8 @@ export class AcpxRuntimeHost {
|
||||
if (nativeMcp?.name === "paperclip") {
|
||||
throw new Error("assigned native MCP name conflicts with the task bridge");
|
||||
}
|
||||
if (!preconfigured && options.runtimeContext?.mcp.bindingId && !nativeMcp) {
|
||||
if (!preconfigured && options.runtimeContext?.mcp.bindingId && !nativeMcp
|
||||
&& !(options.assignedMcpTransport === "prp" && options.semanticTools)) {
|
||||
throw new Error("assigned native MCP launch binding is unavailable");
|
||||
}
|
||||
const profile = resolveQualifiedAcpxProfile(options.agent, options.model);
|
||||
@@ -588,6 +590,7 @@ export class AcpxRuntimeHost {
|
||||
signal: options.signal,
|
||||
acquire: () => startRunnerToolBridge({
|
||||
...options.semanticTools!,
|
||||
preserveToolNames: options.assignedMcpTransport === "prp",
|
||||
preloadClaudeTaskTools: options.agent === "claude",
|
||||
}),
|
||||
resource: "tool_bridge",
|
||||
|
||||
@@ -39,6 +39,7 @@ export interface RunnerToolBridgeOptions {
|
||||
tools?: readonly Readonly<Record<string, unknown>>[];
|
||||
/** Claude-only: avoid tool search for task naming and terminal disposition. */
|
||||
preloadClaudeTaskTools?: boolean;
|
||||
preserveToolNames?: boolean;
|
||||
/** Runner-owned operations that are callable but never model-visible. */
|
||||
privateTools?: readonly Readonly<Record<string, unknown>>[];
|
||||
handler(call: RunnerToolCall): Promise<unknown>;
|
||||
@@ -110,8 +111,8 @@ export async function startRunnerToolBridge(
|
||||
const secret = options.secret ?? randomBytes(32).toString("base64url");
|
||||
if (secret.length === 0)
|
||||
throw new Error("Runner tool bridge secret is empty");
|
||||
const tools = normalizeTools(options.tools ?? [], "public");
|
||||
const privateTools = normalizeTools(options.privateTools ?? [], "private");
|
||||
const tools = normalizeTools(options.tools ?? [], "public", options.preserveToolNames);
|
||||
const privateTools = normalizeTools(options.privateTools ?? [], "private", options.preserveToolNames);
|
||||
const publicNames = new Set(tools.map((tool) => tool.name));
|
||||
for (const tool of privateTools) {
|
||||
if (publicNames.has(tool.name)) {
|
||||
@@ -134,6 +135,7 @@ export async function startRunnerToolBridge(
|
||||
const controllers = new Map<string, AbortController>();
|
||||
const context = {
|
||||
secret,
|
||||
preserveToolNames: options.preserveToolNames === true,
|
||||
visibleTools,
|
||||
admittedTools: new Map(admittedTools.map((tool) => [tool.name, tool])),
|
||||
validators,
|
||||
@@ -204,6 +206,7 @@ async function handleRequest(
|
||||
response: ServerResponse,
|
||||
context: {
|
||||
secret: string;
|
||||
preserveToolNames: boolean;
|
||||
visibleTools: RunnerToolDefinition[];
|
||||
admittedTools: Map<string, RunnerToolDefinition>;
|
||||
validators: Map<string, ValidateFunction>;
|
||||
@@ -289,7 +292,7 @@ async function handleRequest(
|
||||
|
||||
const params = isRecord(message.params) ? message.params : {};
|
||||
const rawName = typeof params.name === "string" ? params.name : "";
|
||||
const tool = canonicalRunnerToolName(rawName);
|
||||
const tool = context.preserveToolNames ? rawName : canonicalRunnerToolName(rawName);
|
||||
if (!context.admittedTools.has(tool)) {
|
||||
writeToolError(response, id, "Unsupported tool.");
|
||||
return;
|
||||
@@ -413,13 +416,14 @@ async function handleRequest(
|
||||
function normalizeTools(
|
||||
input: readonly Readonly<Record<string, unknown>>[],
|
||||
visibility: "public" | "private",
|
||||
preserveNames = false,
|
||||
): RunnerToolDefinition[] {
|
||||
const tools: RunnerToolDefinition[] = [];
|
||||
const seen = new Set<string>();
|
||||
const reservedNames = new Set(RESERVED_TOOLS.map((tool) => tool.name));
|
||||
for (const raw of input) {
|
||||
const name =
|
||||
typeof raw.name === "string" ? canonicalRunnerToolName(raw.name) : "";
|
||||
typeof raw.name === "string" ? (preserveNames ? raw.name : canonicalRunnerToolName(raw.name)) : "";
|
||||
if (!/^[A-Za-z0-9_.:-]{1,256}$/.test(name)) {
|
||||
throw new Error(`Runner ${visibility} tool has an invalid name`);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"commandDigest": "sha256:6f49e1a37bcde897703701b638dddf010a0b8e2c92c211a30d4a0bd41e48e77d",
|
||||
"commandDigest": "sha256:7fbf0b3a48250c0c1852503d0dfd0af238c402d19c53573b573cc81f07c6016f",
|
||||
"declaration": {
|
||||
"schema": "paperclip.acpx_profile_declaration.v1",
|
||||
"agent": "copilot",
|
||||
@@ -26,10 +26,10 @@
|
||||
"permissionClassifierSourceSha256": "1161eabd82e26133e3b3db3ab567f65d52808b03f176e93497e02c8330349fb4",
|
||||
"semanticToolReceiptContract": "paperclip.semantic_tool_receipt.v2",
|
||||
"semanticReceiptSourceSha256": "b2795726b2234a663fec1a6ca11f0551ae69882fe9e82aa39686e952d036e791",
|
||||
"semanticBridgeSourceSha256": "89e3dde25ff35bfd4d7f520fd9d14af3cb9027f182a2d51431d298f1015be580",
|
||||
"semanticBridgeSourceSha256": "8823475a90f5da3477e86eb2d4d5a221733f1dda7117d0f6ff00e3b6bebabf6b",
|
||||
"toolEvidenceSourceSha256": "6eef5039afc75bb851eaf9cd82a6f099e4bd398ca2deb59dd66f5b2a81a7a706",
|
||||
"semanticNormalizedInputContract": "validated-forwarded-input-commit-v1",
|
||||
"semanticSidecarSourceSha256": "cc90b59bdfe6a82f7fa3e819e8671f71f7b14047e68942e8086618ffbaacf59f",
|
||||
"semanticSidecarSourceSha256": "6165e9c3e544c6991b2703ba406f83617d3a939d988355da9d46eded09c4679a",
|
||||
"semanticDirectDriverSourceSha256": "ff572638f2cb5d9f8249ff8e2f412311c1e3af209ef0bf7e5ea9f10a21d91103",
|
||||
"semanticValidationSourceSha256": "02fbb92681c78c5de5d2f1aae25aabc25a0e5df6b0ff4baccac652192a8da975",
|
||||
"semanticNormalizationSourceSha256": "8b628b82e8eacc91cdcc6fdca2210ba4cf282f01e9c6f74e95ff271655b5047e",
|
||||
@@ -75,7 +75,7 @@
|
||||
"metadataProbeSourceSha256": "be4c8416edd27061109bc1f33d0c26071b4facbb3f651beaae6b3c0275cce727",
|
||||
"permissionToolIdentitySourceSha256": "770b47305e571a81f344883057d0e4a482a1cd59ac77d045836593a8e739deb1",
|
||||
"semanticContinuationSourceSha256": "d248e47a0fe7f2536d1e64cea0b0b410f34104800cd643d995b4473104ecc8e0",
|
||||
"runtimeHostSourceSha256": "60e5454f030e87f2afa8b1fb120b26adb126b6b17d0e5e82f49c77f76f466cbb",
|
||||
"runtimeHostSourceSha256": "94a73749267f5a80b5df19413a65b4b9263c00a9bf7e98fda00f00b8f9fd32ec",
|
||||
"runtimeAdapterSourceSha256": "e4c764c2c3c7dd655a965b39472bd1ad8d577d1e7258b25594b171c9429752b8",
|
||||
"runtimeSandboxSourceSha256": "92dfa53f5a3ed0062913ae417eeccec243b430d444495efde2a8e9c4c02e78ea",
|
||||
"cancellationSourceSha256": "a07d1a9e982dec0ae130c1ad1efa5bc75c6a39e013b730ba3ee45256f09ceeca",
|
||||
|
||||
Reference in new issue
Block a user