1- import { mkdir , readFile , writeFile } from "node:fs/promises"
1+ import { mkdir , readFile , rename , writeFile } from "node:fs/promises"
22import { join } from "node:path"
3- import { commandArgValue , executeBoundedRuntimePlan , parseCommandJsonObject , type BoundedRuntimePlan , type BoundedRuntimePlanResult , type ExecutionResult , type Runtime } from "@automattic/wp-codebox-core"
3+ import { commandArgValue , executeBoundedRuntimePlan , parseCommandJsonObject , type BoundedRuntimePlan , type BoundedRuntimePlanEntryResult , type BoundedRuntimePlanResult , type ExecutionResult , type Runtime } from "@automattic/wp-codebox-core"
44import { recipeExecutionSpec } from "./agent-sandbox.js"
55
66export interface BoundedRecipePlanExecutionOptions {
77 artifactRoot : string
88 recipeDirectory : string
99}
1010
11+ const BOUNDED_RUNTIME_PLAN_PROGRESS_SCHEMA = "wp-codebox/bounded-runtime-plan-progress/v1"
12+
1113export async function executeBoundedRecipePlan ( runtime : Runtime , plan : BoundedRuntimePlan , options : BoundedRecipePlanExecutionOptions ) : Promise < BoundedRuntimePlanResult > {
14+ const progressPath = join ( options . artifactRoot , "bounded-plan" , "progress.json" )
15+ const completed = new Map < string , BoundedRuntimePlanEntryResult > ( )
16+ let progressWrite = Promise . resolve ( )
17+ await mkdir ( join ( options . artifactRoot , "bounded-plan" ) , { recursive : true } )
18+ await writeBoundedPlanProgress ( progressPath , plan , completed , false )
1219 const aggregate = await executeBoundedRuntimePlan ( plan , {
1320 async materialize ( ) { return { workspace : options . recipeDirectory , runtime } } ,
1421 async startServices ( ) { return undefined } ,
@@ -50,10 +57,15 @@ export async function executeBoundedRecipePlan(runtime: Runtime, plan: BoundedRu
5057 } , null , 2 ) } \n`, "utf8" )
5158 return { success : exitCode === 0 , exitCode, message : stderr , stdoutRef, stderrRef, resultRef, artifactRefs }
5259 } ,
60+ async onEntryResult ( result ) {
61+ completed . set ( result . id , result )
62+ progressWrite = progressWrite . then ( async ( ) => writeBoundedPlanProgress ( progressPath , plan , completed , false ) )
63+ await progressWrite
64+ } ,
5365 async stopServices ( ) { } ,
5466 async dispose ( ) { } ,
5567 } )
56- await mkdir ( join ( options . artifactRoot , "bounded- plan" ) , { recursive : true } )
68+ await writeBoundedPlanProgress ( progressPath , plan , completed , true )
5769 await writeFile ( join ( options . artifactRoot , "bounded-plan/result.json" ) , `${ JSON . stringify ( aggregate , null , 2 ) } \n` , "utf8" )
5870 return aggregate
5971}
@@ -76,3 +88,27 @@ function runtimeArtifactRefs(execution: ExecutionResult | undefined): string[] {
7688 return typeof value === "string" && value ? [ value ] : [ ]
7789 } )
7890}
91+
92+ async function writeBoundedPlanProgress ( path : string , plan : BoundedRuntimePlan , completed : Map < string , BoundedRuntimePlanEntryResult > , complete : boolean ) : Promise < void > {
93+ const entries = plan . entries . flatMap ( ( entry ) => {
94+ const result = completed . get ( entry . id )
95+ return result ? [ result ] : [ ]
96+ } )
97+ const progress = {
98+ schema : BOUNDED_RUNTIME_PLAN_PROGRESS_SCHEMA ,
99+ complete,
100+ concurrency : Math . min ( plan . concurrency , plan . entries . length ) ,
101+ counts : {
102+ total : plan . entries . length ,
103+ succeeded : entries . filter ( ( entry ) => entry . status === "succeeded" ) . length ,
104+ failed : entries . filter ( ( entry ) => entry . status === "failed" ) . length ,
105+ timedOut : entries . filter ( ( entry ) => entry . status === "timed_out" ) . length ,
106+ cancelled : entries . filter ( ( entry ) => entry . status === "cancelled" ) . length ,
107+ unfinished : plan . entries . length - entries . length ,
108+ } ,
109+ entries,
110+ }
111+ const temporaryPath = `${ path } .tmp`
112+ await writeFile ( temporaryPath , `${ JSON . stringify ( progress , null , 2 ) } \n` , "utf8" )
113+ await rename ( temporaryPath , path )
114+ }
0 commit comments