diff --git a/doc/observability.md b/doc/observability.md index 324273807ec..e8004fe4f93 100644 --- a/doc/observability.md +++ b/doc/observability.md @@ -354,6 +354,14 @@ phase names, wall timestamps, and success/failure, with a 1 MiB request limit, 128 retained measurements, and 16 measurements per phase. No operator OTLP endpoint or ingestion credential is passed to OpenCode. +For fresh remote diagnostic runs, the selected pack must contain the current +build's OpenCode proxy. An otherwise compatible image pack can predate the +collector. Such a pack cannot satisfy the diagnostic request. The existing +verified pack preparation path selects or stages the current proxy; it can add +startup time or fail admission when matching runtime assets are unavailable. +Ordinary runs retain compatible image-pack reuse. Recovery retains the pack +identity already admitted for that session. + Available phases cover context/instruction loading, skill discovery, tool and model resolution, model streaming (`ai.streamText`, including stream consumption), Git discovery/default branch, snapshots, dependency installation, and plugin startup. diff --git a/server/src/services/company-skills.ts b/server/src/services/company-skills.ts index d2819de5714..1a874631923 100644 --- a/server/src/services/company-skills.ts +++ b/server/src/services/company-skills.ts @@ -6385,6 +6385,7 @@ export function companySkillService(db: Db, options: { actor?: AuthorizationActo database: DbOrTransaction = db, ): Promise { const out: CompanySkill[] = []; + const bundledFolders = new Map>>(); for (const skill of imported) { assertImportedSkillKeyAllowed(skill); assertImportedSkillSourceAllowed(skill); @@ -6412,9 +6413,11 @@ export function companySkillService(db: Db, options: { actor?: AuthorizationActo const parsed = parseFrontmatterMarkdown(skill.markdown); const storeMetadata = readSkillStoreMetadata(parsed.frontmatter, metadata); const bundledCategory = paperclipBundledFolderCategory(skill.key, incomingMeta); - const bundledFolder = bundledCategory - ? await folderSvc.ensureBundledCategory(companyId, bundledFolderLabel(bundledCategory)) - : null; + let bundledFolder = bundledCategory ? bundledFolders.get(bundledCategory) : undefined; + if (bundledCategory && !bundledFolder) { + bundledFolder = await folderSvc.ensureBundledCategory(companyId, bundledFolderLabel(bundledCategory)); + bundledFolders.set(bundledCategory, bundledFolder); + } const projectId = asString(incomingMeta.projectId); const projectName = asString(incomingMeta.projectName); const projectFolder = !existing && incomingKind === "project_scan" && projectId && projectName diff --git a/server/src/services/native-runtime/native-session-executor.test.ts b/server/src/services/native-runtime/native-session-executor.test.ts index d78b215d16c..57420904e29 100644 --- a/server/src/services/native-runtime/native-session-executor.test.ts +++ b/server/src/services/native-runtime/native-session-executor.test.ts @@ -1659,9 +1659,11 @@ describe("remote provider pack manifest", () => { return JSON.stringify(value); }; - it.each(["opencode", "cursor", "claude-x64", "claude-arm64"] as const)("uses the bundled Linux image authority for remote %s without controller overrides", async provider => { + it.each(["opencode", "opencode-diagnostics", "opencode-diagnostics-stale", "cursor", "claude-x64", "claude-arm64"] as const)("uses the bundled Linux image authority for remote %s without controller overrides", async provider => { const architecture = provider === "claude-arm64" ? "arm64" : "x64"; const claude = provider.startsWith("claude-"); + const opencode = provider.startsWith("opencode"); + const diagnostics = provider.startsWith("opencode-diagnostics"); const root = await mkdtemp(join(tmpdir(), "paperclip-bundled-image-")); const digest = "sha256:" + "a".repeat(64); const artifacts = Object.fromEntries(Object.entries({ @@ -1669,7 +1671,7 @@ describe("remote provider pack manifest", () => { productionLock: "pnpm-lock.yaml", opencodeCommand: "node_modules/.bin/opencode", opencodeExecutable: "node_modules/opencode-ai/bin/opencode.exe", opencodeProxy: "dist/cli/opencode-app-server-proxy.cjs", acpxSidecar: "dist/cli/acpx-runtime-sidecar.cjs", - }).map(([key, path]) => [key, { path, sha256: digest }])) as Record<"nodeCommand" | "opencodeCommand", { path: string; sha256: string }>; + }).map(([key, path]) => [key, { path, sha256: digest }])) as Record<"nodeCommand" | "opencodeCommand" | "opencodeProxy", { path: string; sha256: string }>; const payload = { compatibility: { opencode: 1, acpx: 1 }, target: { platform: "linux", architecture }, runnerSourceRevision: "a".repeat(40), @@ -1695,16 +1697,17 @@ describe("remote provider pack manifest", () => { daemon.writeUInt16LE(architecture === "arm64" ? 183 : 62, 18); await writeFile(state.bundledRunnerBinary, daemon); if (claude) state.resolveRemoteRunnerBinary.mockResolvedValue(state.bundledRunnerBinary); - const remoteExecution = { ...execution, provider: provider === "opencode" + const remoteExecution = { ...execution, provider: opencode ? { kind: "opencode", model: null } : { kind: "acpx", agent: claude ? "claude" : "cursor", model: null }, workspace: { ...execution.workspace, cwd: root }, - session: { ...execution.session, normalizedSessionId: `bundled-${provider}-${randomUUID()}`, driverKind: provider === "opencode" ? "opencode_server" : "acpx_runtime" }, + session: { ...execution.session, normalizedSessionId: `bundled-${provider}-${randomUUID()}`, driverKind: opencode ? "opencode_server" : "acpx_runtime" }, } as unknown as NativeExecutionInputV1; const syncIn = vi.fn(async (_operations: { files: { kind: string }[] }[]) => undefined); const installed = structuredClone(manifest); - if (provider === "opencode") { + if (opencode) { installed.payload.runnerSourceRevision = "c".repeat(40); installed.payload.artifacts.nodeCommand.sha256 = "sha256:" + "d".repeat(64); + if (provider !== "opencode-diagnostics") installed.payload.artifacts.opencodeProxy.sha256 = "sha256:" + "e".repeat(64); installed.digest = "sha256:" + createHash("sha256").update(canonical(installed.payload)).digest("hex"); } const execute = vi.fn(async (command: { command: string; args?: string[] }) => ({ @@ -1717,12 +1720,18 @@ describe("remote provider pack manifest", () => { }) : "", })); try { - await createRunnerdBackend({ db: leaseDb(remoteExecution), execution: remoteExecution, + const prepare = createRunnerdBackend({ db: leaseDb(remoteExecution), execution: remoteExecution, runnerInstanceId: "bundled-image-runner", runnerIngressAuthorized: true, - ...(provider === "opencode" ? { runnerEnvironment: { PAPERCLIP_NATIVE_MCP_NAME: "assigned-fixture", PAPERCLIP_NATIVE_MCP_URL: "http://127.0.0.1/mcp/gateways/assigned-fixture", PAPERCLIP_NATIVE_MCP_TOKEN: "private-fixture" } } : {}), + ...(opencode ? { runnerEnvironment: { ...configuredEnvironmentProjection(diagnostics ? { PAPERCLIP_OPENCODE_PERFORMANCE_DIAGNOSTICS: "1" } : {}), PAPERCLIP_NATIVE_MCP_NAME: "assigned-fixture", PAPERCLIP_NATIVE_MCP_URL: "http://127.0.0.1/mcp/gateways/assigned-fixture", PAPERCLIP_NATIVE_MCP_TOKEN: "private-fixture" } } : {}), runnerExecutionTarget: { kind: "remote", transport: "sandbox", remoteCwd: "/workspace", environmentId: "environment", leaseId: "lease", providerKey: "daytona", effectiveCapabilities: { runnerWebSocketIngress: true }, runner: { execute, syncIn } } as never }); + if (provider === "opencode-diagnostics-stale") { + await expect(prepare).rejects.toThrow("image provider pack did not match the bundled release identity"); + expect(state.createBackend).not.toHaveBeenCalled(); + return; + } + await prepare; state.createTransport.mockClear(); state.createBackend.mock.calls.at(-1)![1].codexTransportFactory!(); const transport = state.createTransport.mock.calls.at(-1)![0] as RunnerTransportOptions & { @@ -1730,9 +1739,10 @@ describe("remote provider pack manifest", () => { environment?: NodeJS.ProcessEnv; }; expect(transport.runnerBinary).toBe(state.bundledRunnerBinary); - if (provider === "opencode") { + if (opencode) { expect(state.createAssignedMcpTools).toHaveBeenCalledWith(expect.objectContaining({ gatewayPublicId: "assigned-fixture", bearerToken: "private-fixture", delivery: "on_demand" })); for (const name of ["PAPERCLIP_NATIVE_MCP_NAME", "PAPERCLIP_NATIVE_MCP_URL", "PAPERCLIP_NATIVE_MCP_TOKEN"]) expect(transport.environment?.[name]).toBeUndefined(); + expect(transport.environment?.PAPERCLIP_OPENCODE_PERFORMANCE_DIAGNOSTICS).toBe(diagnostics ? "1" : undefined); } if (claude) { expect(state.resolveRemoteRunnerBinary).toHaveBeenCalledWith(expect.objectContaining({ remoteCwd: "/workspace", configuredPath: undefined })); @@ -1750,7 +1760,8 @@ describe("remote provider pack manifest", () => { expect(execute.mock.calls.filter(([call]) => call.args?.[0] === "--build-metadata")).toHaveLength(1); expect(execute.mock.calls.some(([call]) => call.args?.[0] === "-e" || call.args?.[0] === "--version")).toBe(false); expect(syncIn.mock.calls.every(([operations]) => operations.every(operation => operation.files.every(file => file.kind === "file")))).toBe(true); - if (provider === "claude-x64") { + if (provider === "claude-x64" || provider === "opencode") { + const admitted = opencode ? installed : manifest; const identity = transport.prpIdentity!; const artifact = { version: "0.0.0", digest: "sha256:" + createHash("sha256").update(daemon).digest("hex") }; resolveRemoteProviderPackAuthority({ stateDirectory: transport.stateDirectory!, @@ -1758,7 +1769,7 @@ describe("remote provider pack manifest", () => { workspaceId: remoteExecution.binding.executionWorkspaceId, sessionId: remoteExecution.session.normalizedSessionId!, runnerInstanceId: identity.runnerInstanceId, environmentLeaseId: identity.environmentLeaseId, remoteCwd: "/workspace", providerDigest: nativeSha256(remoteExecution.provider) }, - reattach: false, current: () => manifest, runnerArtifact: () => artifact }).verified(); + reattach: false, current: () => admitted, runnerArtifact: () => artifact }).verified(); await mkdir(join(transport.stateDirectory!, "control-plane"), { recursive: true }); await writeFile(join(transport.stateDirectory!, "control-plane", "control-plane-state.json"), JSON.stringify(durableControlPlaneState(identity))); const next = structuredClone(manifest); @@ -1771,7 +1782,7 @@ describe("remote provider pack manifest", () => { schema: "paperclip-runner/runnerd-build-metadata/v1", binaryName: "paperclip-runnerd", packageName: "@paperclipai/paperclip-runner", binaryContractVersion: 2, durableSessionCapabilities: ["unlimited_runtime", "connection_lease_renewal"], prpTransportModes: ["listen_ws"], - providerPacks: [{ root: "/opt/paperclip-runner/provider-pack", manifest, immutable: true }], + providerPacks: [{ root: "/opt/paperclip-runner/provider-pack", manifest: admitted, immutable: true }], }) }; if (request.args[0] === "-e" || request.args[0] === "--version") return { exitCode: 0, timedOut: false, stderr: "", stdout: request.args[0] === "--version" ? "1.18.34" : "" }; const script = request.args[1]; @@ -1781,6 +1792,7 @@ describe("remote provider pack manifest", () => { }); await createRunnerdBackend({ db: leaseDb(remoteExecution), execution: remoteExecution, runnerInstanceId: identity.runnerInstanceId, runnerIngressAuthorized: true, + ...(opencode ? { runnerEnvironment: configuredEnvironmentProjection({ PAPERCLIP_OPENCODE_PERFORMANCE_DIAGNOSTICS: "1" }) } : {}), runnerExecutionTarget: { kind: "remote", transport: "sandbox", remoteCwd: "/workspace", environmentId: "environment", leaseId: "lease", providerKey: "daytona", effectiveCapabilities: { runnerWebSocketIngress: true }, runner: { execute: recoveryExecute }, sandboxLeaseAcquisition: { outcome: "resumed", providerLeaseId: "sandbox" } } as never, @@ -1790,7 +1802,7 @@ describe("remote provider pack manifest", () => { }); state.createBackend.mock.calls.at(-1)![1].codexTransportFactory!(); const recovered = state.createTransport.mock.calls.at(-1)![0]; - expect(recovered).toMatchObject({ providerPackAuthorityDigest: manifest.digest, runnerArtifact: artifact, adoptExistingRunner: { pid: 123 } }); + expect(recovered).toMatchObject({ providerPackAuthorityDigest: admitted.digest, runnerArtifact: artifact, adoptExistingRunner: { pid: 123 } }); } } finally { state.bundledPackManifestPath = null; state.bundledRunnerBinary = null; diff --git a/server/src/services/native-runtime/native-session-executor.ts b/server/src/services/native-runtime/native-session-executor.ts index b1cb4ccdade..630027317ab 100644 --- a/server/src/services/native-runtime/native-session-executor.ts +++ b/server/src/services/native-runtime/native-session-executor.ts @@ -12444,6 +12444,8 @@ async function createRunnerdBackendWithinSessionClaim( manifest: expected, installation, provider: input.execution.provider as { kind: "opencode" } | { kind: "acpx"; agent: string }, requireExactIdentity: reattachProviderPack || explicitProviderPackSelection, + requireCurrentOpenCodeProxy: !reattachProviderPack + && runnerEnvironment?.PAPERCLIP_OPENCODE_PERFORMANCE_DIAGNOSTICS === "1", })); if (!reattachProviderPack) providerPackAuthority!.select(manifest); expectedProviderPackManifest = manifest; diff --git a/server/src/services/native-runtime/remote-provider-pack-verification.test.ts b/server/src/services/native-runtime/remote-provider-pack-verification.test.ts index e5c8f742329..70f110045f2 100644 --- a/server/src/services/native-runtime/remote-provider-pack-verification.test.ts +++ b/server/src/services/native-runtime/remote-provider-pack-verification.test.ts @@ -61,11 +61,12 @@ describe("installed provider pack compatibility", () => { } }); const reseal = (pack: ProviderPackIdentity) => ({ ...pack, digest: sha(canonicalProviderPackJson(pack.payload)) }); - const check = (installed: ProviderPackIdentity, options: { immutable?: boolean; requireExactIdentity?: boolean } = {}) => { + const check = (installed: ProviderPackIdentity, options: { immutable?: boolean; requireExactIdentity?: boolean; requireCurrentOpenCodeProxy?: boolean } = {}) => { const execute = vi.fn(); return verifyRemoteProviderPack({ runner: { execute }, cwd: "/workspace", packRoot: "/opt/paperclip-runner/provider-pack", manifest: manifest("b"), installation: { root: "/opt/paperclip-runner/provider-pack", manifest: installed, immutable: options.immutable ?? true }, - provider: { kind: "opencode" }, requireExactIdentity: options.requireExactIdentity }); + provider: { kind: "opencode" }, requireExactIdentity: options.requireExactIdentity, + requireCurrentOpenCodeProxy: options.requireCurrentOpenCodeProxy }); }; it.each(["interface", "platform", "schema", "artifact_path", "digest", "legacy"])("rejects an incompatible %s", async field => { let pack = manifest("a"); @@ -85,6 +86,15 @@ describe("installed provider pack compatibility", () => { await expect(check(manifest("a"), { requireExactIdentity: true })).rejects.toThrow("incompatible"); await expect(check(manifest("b"), { requireExactIdentity: true })).resolves.toEqual(manifest("b")); }); + it("requires the current proxy for requested OpenCode diagnostics", async () => { + const installed = manifest("a"); + await expect(check(installed)).resolves.toEqual(installed); + await expect(check(installed, { requireCurrentOpenCodeProxy: true })) + .rejects.toThrow("requested OpenCode diagnostics"); + installed.payload.artifacts.opencodeProxy = manifest("b").payload.artifacts.opencodeProxy; + await expect(check(reseal(installed), { requireCurrentOpenCodeProxy: true })) + .resolves.toEqual(reseal(installed)); + }); it("ignores unrelated provider and dependency changes for OpenCode", async () => { const pack = manifest("a"); pack.payload.compatibility!.acpx += 1; diff --git a/server/src/services/native-runtime/remote-provider-pack-verification.ts b/server/src/services/native-runtime/remote-provider-pack-verification.ts index 68170e23089..64f0bf0d649 100644 --- a/server/src/services/native-runtime/remote-provider-pack-verification.ts +++ b/server/src/services/native-runtime/remote-provider-pack-verification.ts @@ -34,7 +34,7 @@ export type PackProvider = { kind: "opencode" } | { kind: "acpx"; agent: string */ function compatibleInstallation( installation: ProviderPackInstallation, expected: T, packRoot: string, - provider: PackProvider, requireExactIdentity: boolean, + provider: PackProvider, requireExactIdentity: boolean, requireCurrentOpenCodeProxy: boolean, ): T { const actual = installation.manifest as T | null; const payload = actual?.payload; @@ -56,6 +56,10 @@ function compatibleInstallation( throw new Error("runner_remote_provider_artifact_incompatible: installed provider pack artifact identity is invalid"); } } + if (provider.kind === "opencode" && requireCurrentOpenCodeProxy + && payload.artifacts.opencodeProxy?.sha256 !== expected.payload.artifacts.opencodeProxy?.sha256) { + throw new Error("runner_remote_provider_artifact_incompatible: installed proxy does not match the requested OpenCode diagnostics"); + } if (provider.kind === "acpx") { const agent = provider.agent; const currentProfile = expected.payload.acpxProfileDigests?.[agent]; @@ -72,7 +76,7 @@ function compatibleInstallation( return structuredClone(actual); } -export async function verifyRemoteProviderPack({ runner, cwd, packRoot, manifest, installation, provider, requireExactIdentity = false }: { +export async function verifyRemoteProviderPack({ runner, cwd, packRoot, manifest, installation, provider, requireExactIdentity = false, requireCurrentOpenCodeProxy = false }: { runner: CommandManagedRuntimeRunner; cwd: string; packRoot: string; @@ -80,10 +84,11 @@ export async function verifyRemoteProviderPack({ installation?: ProviderPackInstallation; provider?: PackProvider; requireExactIdentity?: boolean; + requireCurrentOpenCodeProxy?: boolean; }): Promise { if (installation) { if (!provider) throw new Error("runner_remote_provider_artifact_incompatible: provider interface is required"); - return compatibleInstallation(installation, manifest, packRoot, provider, requireExactIdentity); + return compatibleInstallation(installation, manifest, packRoot, provider, requireExactIdentity, requireCurrentOpenCodeProxy); } const expected = Buffer.from( canonicalProviderPackJson(manifest),