11import { rmSync } from "node:fs"
22import { appendFile , mkdir , readFile , rm , writeFile } from "node:fs/promises"
33import { join , relative , resolve } from "node:path"
4+ import { pathToFileURL } from "node:url"
45import { spawn } from "node:child_process"
56import { materializeExternalNativePackage , materializeRuntimeSources , normalizeExternalPackageSource , normalizeRuntimeSources , parseExternalPackageSourcePolicy , validateRuntimeSourceModel } from "./materialize-external-native-package.mjs"
67import { readNativeResult } from "./native-result-file.mjs"
78import { assertNoRuntimeSourcePaths , sanitizeRuntimeSourceJson , sanitizeRuntimeSourceText , sanitizeRuntimeSourceValue } from "./runtime-source-sanitizer.mjs"
9+ import { publishRunnerWorkspace } from "./runner-workspace-publisher.mjs"
810
911const requestPath = process . env . AGENT_TASK_REQUEST_PATH || ".codebox/agent-task-request.json"
1012const workspace = resolve ( process . env . AGENT_TASK_WORKSPACE || process . cwd ( ) )
@@ -127,7 +129,7 @@ function accessFailure(request) {
127129 if ( ! allowed . includes ( target ) || ! tokenRepos . includes ( target ) ) return "Target repository is not explicitly authorized by allowed_repos and access_token_repos."
128130 if ( ! caller ) return "Caller repository is required for publication authorization."
129131 if ( target !== caller && process . env . EXPLICIT_ACCESS_TOKEN_CONFIGURED !== "true" ) return "An explicit ACCESS_TOKEN is required for cross-repository publication."
130- if ( ! string ( process . env . GITHUB_TOKEN ) ) return "No effective GitHub token is available for runner workspace tools."
132+ if ( ! string ( process . env . ACCESS_TOKEN || process . env . GITHUB_TOKEN ) ) return "No effective GitHub token is available for runner workspace tools."
131133 return ""
132134}
133135
@@ -276,11 +278,10 @@ process.once("exit", () => { if (privateRuntimeSourceRoot) rmSync(privateRuntime
276278for ( const signal of [ "SIGINT" , "SIGTERM" , "SIGHUP" ] ) {
277279 process . once ( signal , ( ) => { cleanupPrivateRuntimeSources ( ) . finally ( ( ) => process . exit ( 128 ) ) } )
278280}
279- const runnerWorkspaceTools = [
280- "workspace-read" , "workspace-ls" , "workspace-grep" , "workspace-write" , "workspace-edit" , "workspace-apply-patch" ,
281- "workspace-git-status" , "workspace-git-diff" , "workspace-git-add" , "workspace-git-commit" , "workspace-git-push" ,
282- "create-github-pull-request" , "create-github-issue" , "comment-github-pull-request" ,
283- ]
281+ const runnerWorkspaceTools = [
282+ "workspace_read" , "workspace_ls" , "workspace_grep" , "workspace_write" , "workspace_edit" , "workspace_apply_patch" ,
283+ "workspace_show" , "workspace_git_status" , "workspace_git_diff" ,
284+ ]
284285
285286function runtimeMetadataForExecutionLocation ( executionLocation ) {
286287 if ( executionLocation === "sandbox" ) return { environment : "runtime_local" , capability_scope : "runtime_local" }
@@ -297,10 +298,11 @@ if (accessError) {
297298 throw error
298299}
299300
300- const materializedPackage = request . run_agent && ! request . dry_run
301+ const skipMaterialization = process . env . NODE_ENV === "test" && process . env . WP_CODEBOX_TEST_SKIP_MATERIALIZATION === "true"
302+ const materializedPackage = request . run_agent && ! request . dry_run && ! skipMaterialization
301303 ? await materializeExternalNativePackage ( externalPackageSource , { policy : externalPackagePolicy } )
302304 : undefined
303- const materializedRuntimeSources = request . run_agent && ! request . dry_run
305+ const materializedRuntimeSources = request . run_agent && ! request . dry_run && ! skipMaterialization
304306 ? await materializeRuntimeSources ( runtimeSources , { policy : externalPackagePolicy , forbiddenRoots : [ workspace , artifactsPath ] } )
305307 : undefined
306308privateRuntimeSourceRoot = materializedRuntimeSources ?. root ?? ""
@@ -330,11 +332,11 @@ const taskInput = {
330332 structured_artifacts : request . artifacts ?. declarations || [ ] ,
331333 secret_env : [ "OPENAI_API_KEY" , "MODEL_PROVIDER_SECRET_1" , "MODEL_PROVIDER_SECRET_2" , "MODEL_PROVIDER_SECRET_3" , "MODEL_PROVIDER_SECRET_4" , "MODEL_PROVIDER_SECRET_5" ] . filter ( ( name ) => process . env [ name ] ) ,
332334 ...runtimeSourceInputs ,
333- allowed_tools : runnerWorkspaceTools ,
335+ allowed_tools : runnerWorkspaceTools ,
334336 sandbox_tool_policy : {
335337 schema : "wp-codebox/sandbox-tool-policy/v1" ,
336338 version : 1 ,
337- tools : runnerWorkspaceTools . map ( ( id ) => ( { id, runtime_tool_id : id , execution_location : "parent " , transport_visibility : "visible " , allowed : true , runtime : runtimeMetadataForExecutionLocation ( "parent " ) } ) ) ,
339+ tools : runnerWorkspaceTools . map ( ( id ) => ( { id, runtime_tool_id : id , execution_location : "sandbox " , transport_visibility : "sandbox " , allowed : true , runtime : runtimeMetadataForExecutionLocation ( "sandbox " ) } ) ) ,
338340 } ,
339341 max_turns : request . limits ?. max_turns ,
340342 task_timeout_seconds : Math . ceil ( Number ( request . limits ?. time_budget_ms || 0 ) / 1000 ) ,
@@ -357,16 +359,17 @@ const taskInput = {
357359 runner_workspace : { ...record ( request . runner_workspace ) , allowed_repos : request . access . allowed_repos } ,
358360 target_repo : request . target_repo ,
359361 writable_paths : request . writable_paths ,
360- runner_workspace_policy : { allowed_repos : request . access . allowed_repos } ,
362+ runner_workspace_policy : { allowed_repos : request . access . allowed_repos , writable_paths : request . writable_paths } ,
361363 } ,
362364 artifact_declarations : request . artifacts ?. declarations || [ ] ,
363365 required_artifacts : requiredArtifacts ( request . artifacts ?. declarations ) ,
364366 output_projections : [ ] ,
365367 metadata : { workload : request . workload , ...( materializedPackage ? { imported_agent : materializedPackage . identity } : { } ) , runtime_sources : materializedRuntimeSources ?. descriptors ?? [ ] } ,
366368 } ,
367369 } ,
368- } ,
369- }
370+ } ,
371+ workspaces : request . runner_workspace ?. enabled ? [ { target : "/workspace" , mode : "readwrite" , sourceMode : "repo-backed" , seed : { type : "directory" , source : workspace , excludePaths : [ ".git/**" , ".codebox/**" , "node_modules/**" , "vendor/**" , "dist/**" , "build/**" , "coverage/**" , ".cache/**" ] } } ] : [ ] ,
372+ }
370373
371374await writeFile ( executionInputPath , `${ JSON . stringify ( taskInput , null , 2 ) } \n` )
372375
@@ -384,6 +387,15 @@ const nativeRuntimeResult = request.run_agent && !request.dry_run
384387await rm ( nativeResultPath , { force : true } )
385388const runtimeResult = sanitizeRuntimeSourceValue ( nativeRuntimeResult , privateRuntimeSourceRootForSanitization )
386389assertNoRuntimeSourcePaths ( runtimeResult , privateRuntimeSourceRootForSanitization )
390+ let workspaceApply = { status : "no-op" , changedFiles : [ ] }
391+ let runnerWorkspaceCore = null
392+ if ( execution . code === 0 && runtimeResult . success === true && request . runner_workspace ?. enabled ) {
393+ runnerWorkspaceCore = await import ( pathToFileURL ( join ( codeboxRoot , "packages/runtime-core/dist/runner-workspace-apply.js" ) ) . href )
394+ const publicCore = await import ( pathToFileURL ( join ( codeboxRoot , "packages/runtime-core/dist/public.js" ) ) . href )
395+ const refs = publicCore . normalizePublicArtifactRefDTOs ( runtimeResult )
396+ . filter ( ( ref ) => ref . kind === "codebox-patch" || ref . kind === "codebox-changed-files" )
397+ workspaceApply = await runnerWorkspaceCore . applyRunnerWorkspacePatch ( { artifactRoot : artifactsPath , artifactRefs : refs , workspaceRoot : workspace , writablePaths : String ( request . writable_paths || "" ) . split ( "," ) . map ( ( value ) => value . trim ( ) ) . filter ( Boolean ) } )
398+ }
387399await cleanupPrivateRuntimeSources ( )
388400assertNoRuntimeSourcePaths ( runtimeResult , privateRuntimeSourceRootForSanitization )
389401
@@ -409,7 +421,19 @@ if (execution.code === 0 && request.run_agent && !request.dry_run) {
409421const verificationPassed = verification . every ( ( check ) => check . success )
410422const runtimeRecord = record ( runtimeResult )
411423const agentResult = record ( runtimeRecord . agent_task_run_result )
412- const publication = resultValue ( runtimeRecord , "outputs.artifact_result.result.outputs.runner_workspace_publication" )
424+ let publication = resultValue ( runtimeRecord , "metadata.runner_workspace_publication" )
425+ if ( execution . code === 0 && runtimeRecord . success === true && verificationPassed && workspaceApply . status === "applied" ) {
426+ await runnerWorkspaceCore . verifyRunnerWorkspaceIntegrity ( workspaceApply . integrity )
427+ const testPublisher = string ( process . env . WP_CODEBOX_TEST_PUBLISHER_MODULE )
428+ const publisher = testPublisher ? ( await import ( pathToFileURL ( resolve ( testPublisher ) ) . href ) ) . publishRunnerWorkspace : publishRunnerWorkspace
429+ const testHook = testPublisher && process . env . WP_CODEBOX_TEST_PUBLISHER_HOOK
430+ ? JSON . parse ( process . env . WP_CODEBOX_TEST_PUBLISHER_HOOK )
431+ : undefined
432+ publication = await publisher ( { request, changedFiles : workspaceApply . changedFiles , publicationFiles : workspaceApply . publicationFiles , token : process . env . ACCESS_TOKEN || process . env . GITHUB_TOKEN , ...( testHook ? { testHook } : { } ) } )
433+ runtimeRecord . metadata = { ...record ( runtimeRecord . metadata ) , runner_workspace_publication : publication }
434+ runtimeRecord . outputs = { ...record ( runtimeRecord . outputs ) , runner_workspace_publication : publication }
435+ }
436+ if ( request . success ?. requires_pr === true && workspaceApply . status === "no-op" && ! publication ) throw new Error ( "success_requires_pr cannot succeed for a no-op runner workspace task." )
413437let evaluatedProjections = { }
414438let projectionError = ""
415439try {
0 commit comments