diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs index e4ecef1f6c..edd2c630db 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs @@ -23,10 +23,7 @@ use crate::durable::{ AcpxLaunchProfile, Command, CommandExecution, CommandExecutor, DurableRunnerConfig, DurableRunnerError, EventPriority, PolledEvent, }; -use crate::process_supervisor::{ - is_node_interpreter, verified_node_esm_loader_arguments, VerifiedProcessArgument, - VerifiedProcessLaunch, VERIFIED_NODE_ESM_LOADER, VERIFIED_NODE_EVAL_ARG, -}; +use crate::process_supervisor::{VerifiedProcessArgument, VerifiedProcessLaunch}; use crate::provider_bridge::{ authorized_tool_catalog_digest, AuthorizedToolSet, ToolResult, TOOL_SET_SCHEMA, }; @@ -213,7 +210,7 @@ impl AcpxProviderDescriptor { "ACPX runner launch profile does not authenticate its command", ) })?; - let mut verified_args = launch_profile + let verified_args = launch_profile .args .iter() .map(|argument| { @@ -232,13 +229,6 @@ impl AcpxProviderDescriptor { }) }) .collect::, _>>()?; - // Verified scripts are exposed to Node through an immutable descriptor - // path (for example /proc/self/fd/12), which intentionally has no .js - // suffix. A runner-owned eval loader reads that exact sealed descriptor - // and imports it as ESM without mutating the qualified launch artifact. - if is_node_interpreter(&launch_profile.command) { - verified_args.splice(0..0, verified_node_esm_loader_arguments()); - } Ok(AcpxSidecarTransportConfig { command: launch_profile.command.clone(), args: launch_profile.args.clone(), @@ -1409,7 +1399,7 @@ mod tests { fn binds_sidecar_paths_arguments_and_contents_to_the_runner_profile() { let directory = temporary_directory("launch-binding"); let command = directory.join("node"); - let sidecar = directory.join("sidecar.js"); + let sidecar = directory.join("sidecar.cjs"); write_artifact(&command, b"qualified node", true); write_artifact(&sidecar, b"qualified sidecar", false); let args = vec![sidecar.to_string_lossy().into_owned()]; @@ -1429,16 +1419,6 @@ mod tests { let verified_launch = transport.verified_launch.as_ref().unwrap(); assert!(matches!( verified_launch.arguments().first(), - Some(VerifiedProcessArgument::Literal(argument)) - if argument == VERIFIED_NODE_EVAL_ARG - )); - assert!(matches!( - verified_launch.arguments().get(1), - Some(VerifiedProcessArgument::Literal(argument)) - if argument == VERIFIED_NODE_ESM_LOADER - )); - assert!(matches!( - verified_launch.arguments().get(2), Some(VerifiedProcessArgument::Artifact(_)) )); diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/bin/fake-codex-app-server.rs b/packages/paperclip-runner/runner/crates/runner-core/src/bin/fake-codex-app-server.rs index a2e73441de..6c77d15241 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/bin/fake-codex-app-server.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/bin/fake-codex-app-server.rs @@ -514,6 +514,9 @@ fn run() -> Result<(), Box> { .iter() .any(|value| value == "--finish-turn-with-pending-tool"); let require_dynamic_tool = args.iter().any(|value| value == "--require-dynamic-tool"); + let require_completion_contract = args + .iter() + .any(|value| value == "--require-completion-contract"); let expected_canonical_task_context = argument(&args, "--expected-canonical-task-context") .map(|value| serde_json::from_str::(&value)) .transpose()?; @@ -766,6 +769,15 @@ fn run() -> Result<(), Box> { if require_dynamic_tool && !has_task_context_tool(&message) { return Err("thread/start omitted the authorized dynamic tool".into()); } + if require_completion_contract + && message.pointer("/params/completionContract") + != Some(&json!({ + "revision": "revision-1", + "criterionIds": ["criterion-1"], + })) + { + return Err("thread/start omitted the durable completion contract".into()); + } state.thread_id = "codex-thread-1".to_owned(); state.active_turn_id = None; save_state(&state_path, &state)?; @@ -784,6 +796,15 @@ fn run() -> Result<(), Box> { if require_dynamic_tool && !has_task_context_tool(&message) { return Err("thread/resume omitted the authorized dynamic tool".into()); } + if require_completion_contract + && message.pointer("/params/completionContract") + != Some(&json!({ + "revision": "revision-1", + "criterionIds": ["criterion-1"], + })) + { + return Err("thread/resume omitted the durable completion contract".into()); + } let unowned_turn_marker = state_path.with_file_name("resume-unowned-turn"); if resume_unowned_turn_when_marked && unowned_turn_marker.exists() { state.active_turn_id = Some("provider-turn-unowned".to_owned()); diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs b/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs index cded391568..b6002a8cd5 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs @@ -15,9 +15,8 @@ use crate::durable::QualifiedLaunchArtifact; use crate::durable::{redact_text, OpenCodeLaunchProfile}; use crate::local_runner::LocalRunnerError; use crate::process_supervisor::{ - is_node_interpreter, verified_node_esm_loader_arguments, BoundedLogBuffer, ProcessOutput, - SupervisedProcess, VerifiedProcessArgument, VerifiedProcessLaunch, VERIFIED_NODE_ESM_LOADER, - VERIFIED_NODE_EVAL_ARG, + BoundedLogBuffer, ProcessOutput, SupervisedProcess, VerifiedProcessArgument, + VerifiedProcessLaunch, }; use crate::provider_bridge::{AuthorizedTool, DurableReplayFilter, ToolResult}; use crate::provider_events::normalized_codex_terminal_event_type; @@ -55,6 +54,12 @@ pub(crate) const MAX_SETTLED_PROVIDER_TURN_IDS: usize = 4_096; type QuestionOptionLabels = BTreeMap>; type QuestionSetMapping = (String, Value, QuestionOptionLabels); +#[derive(Clone)] +struct ProviderCompletionContract { + revision: String, + criterion_ids: Vec, +} + fn base64_encode(input: &[u8]) -> String { const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; let mut encoded = String::with_capacity(input.len().div_ceil(3) * 4); @@ -525,6 +530,7 @@ pub struct CodexProvider { trace: Option, last_trace_frame_id: Option, opencode_launch_profile: Option, + completion_contract: Option, } impl CodexProvider { @@ -532,7 +538,14 @@ impl CodexProvider { config: &CodexProviderConfig, resume_thread_id: Option<&str>, ) -> Result { - Self::start_with_tools_for_generation(config, std::iter::empty(), resume_thread_id, 1, None) + Self::start_with_tools_for_generation( + config, + std::iter::empty(), + resume_thread_id, + 1, + None, + None, + ) } pub fn start_with_tools( @@ -540,7 +553,14 @@ impl CodexProvider { authorized_tools: impl IntoIterator, resume_thread_id: Option<&str>, ) -> Result { - Self::start_with_tools_for_generation(config, authorized_tools, resume_thread_id, 1, None) + Self::start_with_tools_for_generation( + config, + authorized_tools, + resume_thread_id, + 1, + None, + None, + ) } pub(crate) fn start_with_tools_for_generation( @@ -549,6 +569,7 @@ impl CodexProvider { resume_thread_id: Option<&str>, process_generation: u64, opencode_launch_profile: Option<&OpenCodeLaunchProfile>, + completion_contract: Option<(&str, &[String])>, ) -> Result { config.validate()?; if process_generation == 0 { @@ -649,6 +670,12 @@ impl CodexProvider { trace: ProviderTraceSink::from_environment(), last_trace_frame_id: None, opencode_launch_profile: opencode_launch_profile.cloned(), + completion_contract: completion_contract.map(|(revision, criterion_ids)| { + ProviderCompletionContract { + revision: revision.to_owned(), + criterion_ids: criterion_ids.to_vec(), + } + }), }; let initialized = provider.request( "initialize", @@ -678,6 +705,17 @@ impl CodexProvider { let params_object = params .as_object_mut() .expect("Codex thread parameters are an object"); + if config.provider == "opencode" { + if let Some(contract) = provider.completion_contract.as_ref() { + params_object.insert( + "completionContract".to_owned(), + json!({ + "revision": contract.revision, + "criterionIds": contract.criterion_ids, + }), + ); + } + } let method = if let Some(thread_id) = resume_thread_id { params_object.insert("threadId".to_owned(), json!(thread_id)); "thread/resume" @@ -837,6 +875,7 @@ impl CodexProvider { let completed_turn_authority = self.completed_turn_authority.clone(); let completion_reconciliation_pending = self.completion_reconciliation_pending; let durable_tool_call_replays = self.durable_tool_call_replays; + let completion_contract = self.completion_contract.clone(); // Exact turn identities may be forgotten only after the provider // process that could emit them is gone. Resume the same thread in a @@ -849,6 +888,12 @@ impl CodexProvider { Some(&thread_id), next_generation, self.opencode_launch_profile.as_ref(), + completion_contract.as_ref().map(|contract| { + ( + contract.revision.as_str(), + contract.criterion_ids.as_slice(), + ) + }), )?; replacement.durable_tool_call_replays = durable_tool_call_replays; if replacement.active_provider_turn_id.is_some() { @@ -1949,18 +1994,11 @@ fn verified_opencode_launch( .map_err(|error| LocalRunnerError::invalid(error.to_string()))?; let executable = verify_launch_artifact(&profile.executable, "OpenCode provider executable") .map_err(|error| LocalRunnerError::invalid(error.to_string()))?; - let mut args = vec![ + let args = vec![ VerifiedProcessArgument::Artifact(proxy), VerifiedProcessArgument::Literal(TRUSTED_OPENCODE_EXECUTABLE_ARG.to_owned()), VerifiedProcessArgument::ExecutableArtifact(executable), ]; - if is_node_interpreter(&profile.command.path) { - // The immutable verified proxy path is extensionless. Load its exact - // sealed bytes as an ESM data URL while leaving process.argv[1] bound - // to that descriptor, so the proxy retains its ordinary CLI contract. - // Qualified non-Node test/provider commands retain their arguments. - args.splice(0..0, verified_node_esm_loader_arguments()); - } Ok(VerifiedProcessLaunch::new(command, args)) } @@ -2683,7 +2721,7 @@ mod tests { } #[test] - fn verified_opencode_proxy_retains_esm_semantics_for_extensionless_snapshot() { + fn verified_opencode_proxy_executes_the_descriptor_safe_commonjs_bundle() { let nonce = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() @@ -2694,10 +2732,10 @@ mod tests { )); fs::create_dir_all(&directory).unwrap(); let command = directory.join("node"); - let proxy = directory.join("proxy.js"); + let proxy = directory.join("proxy.cjs"); let executable = directory.join("opencode"); fs::write(&command, b"qualified node").unwrap(); - fs::write(&proxy, b"export {};\n").unwrap(); + fs::write(&proxy, b"module.exports = {};\n").unwrap(); fs::write(&executable, b"qualified opencode").unwrap(); let profile = OpenCodeLaunchProfile { command: qualified_artifact(&command), @@ -2708,25 +2746,15 @@ mod tests { let launch = verified_opencode_launch(&profile).unwrap(); assert!(matches!( launch.arguments().first(), - Some(VerifiedProcessArgument::Literal(argument)) - if argument == VERIFIED_NODE_EVAL_ARG + Some(VerifiedProcessArgument::Artifact(_)) )); assert!(matches!( launch.arguments().get(1), - Some(VerifiedProcessArgument::Literal(argument)) - if argument == VERIFIED_NODE_ESM_LOADER - )); - assert!(matches!( - launch.arguments().get(2), - Some(VerifiedProcessArgument::Artifact(_)) - )); - assert!(matches!( - launch.arguments().get(3), Some(VerifiedProcessArgument::Literal(argument)) if argument == TRUSTED_OPENCODE_EXECUTABLE_ARG )); assert!(matches!( - launch.arguments().get(4), + launch.arguments().get(2), Some(VerifiedProcessArgument::ExecutableArtifact(_)) )); fs::remove_dir_all(directory).unwrap(); diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/process_supervisor.rs b/packages/paperclip-runner/runner/crates/runner-core/src/process_supervisor.rs index 415c9473ec..1e4e8bb1d5 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/process_supervisor.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/process_supervisor.rs @@ -26,14 +26,6 @@ use sha2::{Digest, Sha256}; use crate::local_runner::LocalRunnerError; const PROCESS_OUTPUT_QUEUE_CAPACITY: usize = 256; -pub(crate) const VERIFIED_NODE_EVAL_ARG: &str = "--eval"; -pub(crate) const VERIFIED_NODE_ESM_LOADER: &str = r#"const fs=require("node:fs");const source=fs.readFileSync(process.argv[1]);import("data:text/javascript;base64,"+source.toString("base64")).catch((error)=>{console.error(error instanceof Error?error.message:String(error));process.exitCode=1;});"#; - -pub(crate) fn is_node_interpreter(path: &Path) -> bool { - path.file_name() - .and_then(|name| name.to_str()) - .is_some_and(|name| matches!(name, "node" | "nodejs" | "node.exe")) -} #[derive(Clone, Debug)] pub struct VerifiedProcessArtifact { @@ -196,13 +188,6 @@ pub enum VerifiedProcessArgument { ExecutableArtifact(VerifiedProcessArtifact), } -pub(crate) fn verified_node_esm_loader_arguments() -> [VerifiedProcessArgument; 2] { - [ - VerifiedProcessArgument::Literal(VERIFIED_NODE_EVAL_ARG.to_owned()), - VerifiedProcessArgument::Literal(VERIFIED_NODE_ESM_LOADER.to_owned()), - ] -} - #[derive(Clone, Debug)] pub struct VerifiedProcessLaunch { program: VerifiedProcessArtifact, diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/provider_backend.rs b/packages/paperclip-runner/runner/crates/runner-core/src/provider_backend.rs index 6151c8cced..4156973225 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/provider_backend.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/provider_backend.rs @@ -1001,6 +1001,12 @@ impl CodexCommandExecutor { Some(&thread_id), process_generation, self.opencode_launch_profile.as_ref(), + state.completion_contract.as_ref().map(|contract| { + ( + contract.revision.as_str(), + contract.criterion_ids.as_slice(), + ) + }), ) .map_err(|error| { DurableRunnerError::invalid(format!( @@ -1395,6 +1401,12 @@ impl CodexCommandExecutor { state.thread_id.as_deref(), process_generation, self.opencode_launch_profile.as_ref(), + state.completion_contract.as_ref().map(|contract| { + ( + contract.revision.as_str(), + contract.criterion_ids.as_slice(), + ) + }), ) .map_err(|error| { DurableRunnerError::invalid(format!("failed to start Codex provider: {error}")) diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/native_provider_backend.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/native_provider_backend.rs index 815eedea69..9c3f2cde3f 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/native_provider_backend.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/native_provider_backend.rs @@ -95,7 +95,7 @@ fn opencode_config(state_dir: &Path) -> DurableRunnerConfig { fs::write( &proxy_script, format!( - "#!/bin/sh\nexec '{}' --state-file '{}' --call-log '{}'\n", + "#!/bin/sh\nexec '{}' --state-file '{}' --call-log '{}' --require-completion-contract\n", env!("CARGO_BIN_EXE_fake-codex-app-server"), state_dir.join("fake-opencode-state.json").display(), state_dir.join("fake-opencode-calls.log").display(), diff --git a/packages/paperclip-runner/scripts/build-provider-pack.mjs b/packages/paperclip-runner/scripts/build-provider-pack.mjs index bb5f6e4869..b4703dbaf3 100644 --- a/packages/paperclip-runner/scripts/build-provider-pack.mjs +++ b/packages/paperclip-runner/scripts/build-provider-pack.mjs @@ -187,8 +187,8 @@ try { ); } - const opencodeProxyPath = "dist/cli/opencode-app-server-proxy.js"; - const acpxSidecarPath = "dist/cli/acpx-runtime-sidecar.js"; + const opencodeProxyPath = "dist/cli/opencode-app-server-proxy.cjs"; + const acpxSidecarPath = "dist/cli/acpx-runtime-sidecar.cjs"; const opencodeCommand = "node_modules/.bin/opencode"; const opencodeExecutable = "node_modules/opencode-ai/bin/opencode.exe"; const nodeCommand = "node_modules/node/bin/node"; diff --git a/packages/paperclip-runner/scripts/build-verified-provider-entrypoints.mjs b/packages/paperclip-runner/scripts/build-verified-provider-entrypoints.mjs index 52daaa3a62..caabd4f7e8 100644 --- a/packages/paperclip-runner/scripts/build-verified-provider-entrypoints.mjs +++ b/packages/paperclip-runner/scripts/build-verified-provider-entrypoints.mjs @@ -12,11 +12,16 @@ export const verifiedProviderEntrypoints = Object.freeze([ name: "acpx-runtime-sidecar", source: resolve(packageRoot, "src/cli/acpx-runtime-sidecar.ts"), output: resolve(packageRoot, "dist/cli/acpx-runtime-sidecar.js"), + verifiedOutput: resolve(packageRoot, "dist/cli/acpx-runtime-sidecar.cjs"), }), Object.freeze({ name: "opencode-app-server-proxy", source: resolve(packageRoot, "src/cli/opencode-app-server-proxy.ts"), output: resolve(packageRoot, "dist/cli/opencode-app-server-proxy.js"), + verifiedOutput: resolve( + packageRoot, + "dist/cli/opencode-app-server-proxy.cjs", + ), }), ]); @@ -47,27 +52,35 @@ function assertSelfContainedBundle(entrypoint, result) { export async function bundleVerifiedProviderEntrypoints({ write = true } = {}) { const results = []; for (const entrypoint of verifiedProviderEntrypoints) { - const result = await build({ - entryPoints: [entrypoint.source], - outfile: entrypoint.output, - bundle: true, - platform: "node", - format: "esm", - target: "node24", - packages: "bundle", - splitting: false, - sourcemap: false, - legalComments: "none", - metafile: true, - treeShaking: true, - write, - logLevel: "silent", - }); - assertSelfContainedBundle(entrypoint, result); + const buildBundle = async (outfile, format) => { + const result = await build({ + entryPoints: [entrypoint.source], + outfile, + bundle: true, + platform: "node", + format, + target: "node24", + packages: "bundle", + splitting: false, + sourcemap: false, + legalComments: "none", + metafile: true, + treeShaking: true, + write, + logLevel: "silent", + }); + assertSelfContainedBundle(entrypoint, result); + return result; + }; + const result = await buildBundle(entrypoint.output, "esm"); + const verifiedResult = await buildBundle(entrypoint.verifiedOutput, "cjs"); if (write && process.platform !== "win32") { - await chmod(entrypoint.output, 0o755); + await Promise.all([ + chmod(entrypoint.output, 0o755), + chmod(entrypoint.verifiedOutput, 0o755), + ]); } - results.push({ entrypoint, result }); + results.push({ entrypoint, result, verifiedResult }); } return results; } diff --git a/packages/paperclip-runner/scripts/build-verified-provider-entrypoints.test.mjs b/packages/paperclip-runner/scripts/build-verified-provider-entrypoints.test.mjs index 46a93c646e..f582e62883 100644 --- a/packages/paperclip-runner/scripts/build-verified-provider-entrypoints.test.mjs +++ b/packages/paperclip-runner/scripts/build-verified-provider-entrypoints.test.mjs @@ -7,13 +7,15 @@ import { verifiedProviderEntrypoints, } from "./build-verified-provider-entrypoints.mjs"; -test("verified JS provider entrypoints bundle into one descriptor-safe file", async () => { +test("provider entrypoints include self-contained ESM and descriptor-safe CommonJS bundles", async () => { const bundles = await bundleVerifiedProviderEntrypoints({ write: false }); assert.equal(bundles.length, verifiedProviderEntrypoints.length); - for (const { entrypoint, result } of bundles) { - assert.equal(result.outputFiles?.length, 1, entrypoint.name); - const source = result.outputFiles[0].text; - assert.match(source, /^#!\/usr\/bin\/env node\n/); + for (const { entrypoint, result, verifiedResult } of bundles) { + for (const bundle of [result, verifiedResult]) { + assert.equal(bundle.outputFiles?.length, 1, entrypoint.name); + const source = bundle.outputFiles[0].text; + assert.match(source, /^#!\/usr\/bin\/env node\n/); + } } }); @@ -24,8 +26,10 @@ test("written provider entrypoints satisfy qualified launch permissions", async } await bundleVerifiedProviderEntrypoints(); for (const entrypoint of verifiedProviderEntrypoints) { - const mode = (await stat(entrypoint.output)).mode; - assert.equal(mode & 0o022, 0, entrypoint.name); - assert.notEqual(mode & 0o100, 0, entrypoint.name); + for (const output of [entrypoint.output, entrypoint.verifiedOutput]) { + const mode = (await stat(output)).mode; + assert.equal(mode & 0o022, 0, entrypoint.name); + assert.notEqual(mode & 0o100, 0, entrypoint.name); + } } }); diff --git a/packages/paperclip-runner/src/live/runnerd-codex-transport.test.ts b/packages/paperclip-runner/src/live/runnerd-codex-transport.test.ts index 029db6f680..087f145529 100644 --- a/packages/paperclip-runner/src/live/runnerd-codex-transport.test.ts +++ b/packages/paperclip-runner/src/live/runnerd-codex-transport.test.ts @@ -108,7 +108,7 @@ it("rejects caller-selected local ACPX artifacts even when they are self-hashed" } }); -it.each(["acpx-runtime-sidecar.js", "opencode-app-server-proxy.js"] as const)( +it.each(["acpx-runtime-sidecar.cjs", "opencode-app-server-proxy.cjs"] as const)( "resolves the %s local provider artifact from verified build-owned output", async (artifact) => { const directory = await mkdtemp( @@ -143,12 +143,12 @@ it.each(["acpx-runtime-sidecar.js", "opencode-app-server-proxy.js"] as const)( it("derives the ACPX package root only from the verified dist/cli layout", () => { expect( runnerdLaunchProfileInternals.acpxProviderPackageRoot( - "/provider-pack/dist/cli/acpx-runtime-sidecar.js", + "/provider-pack/dist/cli/acpx-runtime-sidecar.cjs", ), ).toBe("/provider-pack"); expect(() => runnerdLaunchProfileInternals.acpxProviderPackageRoot( - "/unverified/acpx-runtime-sidecar.js", + "/unverified/acpx-runtime-sidecar.cjs", ), ).toThrow("ACPX sidecar must use the provider package dist/cli layout"); }); @@ -462,7 +462,7 @@ it.each([ runtimeContextPath: "/isolated/runtime-context.json", hasRuntimeContext: true, acpxSidecarPath: - "/verified/provider-pack/dist/cli/acpx-runtime-sidecar.js", + "/verified/provider-pack/dist/cli/acpx-runtime-sidecar.cjs", }); expect(environment).toMatchObject({ diff --git a/packages/paperclip-runner/src/live/runnerd-codex-transport.ts b/packages/paperclip-runner/src/live/runnerd-codex-transport.ts index 3148217a7f..e36e9a65c1 100644 --- a/packages/paperclip-runner/src/live/runnerd-codex-transport.ts +++ b/packages/paperclip-runner/src/live/runnerd-codex-transport.ts @@ -1089,7 +1089,7 @@ function approvedRunnerArtifact(runnerBinaryPath: string): { } type BuildOwnedCliArtifact = - "acpx-runtime-sidecar.js" | "opencode-app-server-proxy.js"; + "acpx-runtime-sidecar.cjs" | "opencode-app-server-proxy.cjs"; function buildOwnedCliArtifactCandidates( artifact: BuildOwnedCliArtifact, @@ -1114,7 +1114,7 @@ function resolveBuildOwnedCliArtifact( function acpxProviderPackageRoot(sidecarScript: string): string { const cliDirectory = dirname(sidecarScript); if ( - basename(sidecarScript) !== "acpx-runtime-sidecar.js" || + basename(sidecarScript) !== "acpx-runtime-sidecar.cjs" || basename(cliDirectory) !== "cli" || basename(dirname(cliDirectory)) !== "dist" ) { @@ -1141,7 +1141,7 @@ function acpxRunnerLaunchProfile( if (!options.runnerFilesystemRoot) { const buildCommand = process.execPath; const buildSidecarCandidates = buildOwnedCliArtifactCandidates( - "acpx-runtime-sidecar.js", + "acpx-runtime-sidecar.cjs", ); if ( options.providerNodeCommand !== undefined || @@ -1157,7 +1157,7 @@ function acpxRunnerLaunchProfile( ); } const buildSidecar = resolveBuildOwnedCliArtifact( - "acpx-runtime-sidecar.js", + "acpx-runtime-sidecar.cjs", buildSidecarCandidates, ); if (sidecarScript !== buildSidecar) { @@ -1336,7 +1336,7 @@ export function createCapabilityRunnerdProviderEnvironment(input: { const sidecarPath = input.acpxSidecarPath ?? input.options.acpxSidecarPath ?? - resolve(packageRoot, "dist", "cli", "acpx-runtime-sidecar.js"); + resolve(packageRoot, "dist", "cli", "acpx-runtime-sidecar.cjs"); return { ...createSanitizedAcpxSpawnInput( input.options.environment, @@ -2099,16 +2099,16 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { const opencodeProxyPath = this.options.opencodeProxyPath ?? (provider === "opencode" && !this.options.runnerFilesystemRoot - ? resolveBuildOwnedCliArtifact("opencode-app-server-proxy.js") + ? resolveBuildOwnedCliArtifact("opencode-app-server-proxy.cjs") : fileURLToPath( - new URL("../cli/opencode-app-server-proxy.js", import.meta.url), + new URL("../cli/opencode-app-server-proxy.cjs", import.meta.url), )); const acpxSidecarPath = this.options.acpxSidecarPath ?? (provider === "acpx" && !this.options.runnerFilesystemRoot - ? resolveBuildOwnedCliArtifact("acpx-runtime-sidecar.js") + ? resolveBuildOwnedCliArtifact("acpx-runtime-sidecar.cjs") : fileURLToPath( - new URL("../cli/acpx-runtime-sidecar.js", import.meta.url), + new URL("../cli/acpx-runtime-sidecar.cjs", import.meta.url), )); const providerNodeCommand = this.options.providerNodeCommand ?? process.execPath; @@ -2589,16 +2589,16 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { const opencodeProxyPath = this.options.opencodeProxyPath ?? (provider === "opencode" && !this.options.runnerFilesystemRoot - ? resolveBuildOwnedCliArtifact("opencode-app-server-proxy.js") + ? resolveBuildOwnedCliArtifact("opencode-app-server-proxy.cjs") : fileURLToPath( - new URL("../cli/opencode-app-server-proxy.js", import.meta.url), + new URL("../cli/opencode-app-server-proxy.cjs", import.meta.url), )); const acpxSidecarPath = this.options.acpxSidecarPath ?? (provider === "acpx" && !this.options.runnerFilesystemRoot - ? resolveBuildOwnedCliArtifact("acpx-runtime-sidecar.js") + ? resolveBuildOwnedCliArtifact("acpx-runtime-sidecar.cjs") : fileURLToPath( - new URL("../cli/acpx-runtime-sidecar.js", import.meta.url), + new URL("../cli/acpx-runtime-sidecar.cjs", import.meta.url), )); const providerNodeCommand = this.options.providerNodeCommand ?? process.execPath; 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 eba23196cd..b7d7658df1 100644 --- a/server/src/services/native-runtime/native-session-executor.test.ts +++ b/server/src/services/native-runtime/native-session-executor.test.ts @@ -250,11 +250,11 @@ describe("remote provider pack manifest", () => { const opencodeCommand = "#!/bin/sh\n"; const opencodeExecutable = "opencode-binary\n"; await writeFile( - join(root, "dist", "cli", "opencode-app-server-proxy.js"), + join(root, "dist", "cli", "opencode-app-server-proxy.cjs"), proxy, ); await writeFile( - join(root, "dist", "cli", "acpx-runtime-sidecar.js"), + join(root, "dist", "cli", "acpx-runtime-sidecar.cjs"), sidecar, ); await writeFile(join(root, "node_modules", "node", "bin", "node"), node); @@ -305,11 +305,11 @@ describe("remote provider pack manifest", () => { sha256: digest(opencodeExecutable), }, opencodeProxy: { - path: "dist/cli/opencode-app-server-proxy.js", + path: "dist/cli/opencode-app-server-proxy.cjs", sha256: proxySha, }, acpxSidecar: { - path: "dist/cli/acpx-runtime-sidecar.js", + path: "dist/cli/acpx-runtime-sidecar.cjs", sha256: sidecarSha, }, }, @@ -352,14 +352,14 @@ describe("remote provider pack manifest", () => { } await writeManifest(); await writeFile( - join(root, "dist", "cli", "opencode-app-server-proxy.js"), + join(root, "dist", "cli", "opencode-app-server-proxy.cjs"), "tampered\n", ); expect(() => readRemoteProviderPackManifest(root)).toThrow( "OpenCode proxy digest mismatch", ); await writeFile( - join(root, "dist", "cli", "opencode-app-server-proxy.js"), + join(root, "dist", "cli", "opencode-app-server-proxy.cjs"), proxy, ); await writeFile( diff --git a/server/src/services/native-runtime/native-session-executor.ts b/server/src/services/native-runtime/native-session-executor.ts index 242d415ff0..4fbf847c92 100644 --- a/server/src/services/native-runtime/native-session-executor.ts +++ b/server/src/services/native-runtime/native-session-executor.ts @@ -4448,8 +4448,8 @@ const REMOTE_PROVIDER_PACK_ARTIFACT_PATHS = { productionLock: "pnpm-lock.yaml", opencodeCommand: "node_modules/.bin/opencode", opencodeExecutable: "node_modules/opencode-ai/bin/opencode.exe", - opencodeProxy: "dist/cli/opencode-app-server-proxy.js", - acpxSidecar: "dist/cli/acpx-runtime-sidecar.js", + opencodeProxy: "dist/cli/opencode-app-server-proxy.cjs", + acpxSidecar: "dist/cli/acpx-runtime-sidecar.cjs", } as const; type RemoteProviderPackManifest = {