@@ -17,6 +17,7 @@ import type {
1717 TaskExecutionResult ,
1818 WorkspaceSpec ,
1919} from "@devagent-sdk/types" ;
20+ import { PROTOCOL_VERSION } from "@devagent-sdk/types" ;
2021
2122type RunMetadata = {
2223 runId : string ;
@@ -151,6 +152,24 @@ function safeWorkspaceName(workBranch: string): string {
151152 return normalized || "workspace" ;
152153}
153154
155+ function createTimeoutResult ( request : TaskExecutionRequest , startedAt : string ) : TaskExecutionResult {
156+ return {
157+ protocolVersion : PROTOCOL_VERSION ,
158+ taskId : request . taskId ,
159+ status : "failed" ,
160+ artifacts : [ ] ,
161+ metrics : {
162+ startedAt,
163+ finishedAt : new Date ( ) . toISOString ( ) ,
164+ durationMs : Date . now ( ) - new Date ( startedAt ) . getTime ( ) ,
165+ } ,
166+ error : {
167+ code : "EXECUTION_FAILED" ,
168+ message : `Task exceeded timeoutSec (${ request . constraints . timeoutSec } )` ,
169+ } ,
170+ } ;
171+ }
172+
154173export class FileSystemWorkspaceManager implements WorkspaceManager {
155174 async prepare ( spec : WorkspaceSpec ) : Promise < { workspacePath : string } > {
156175 const runnerRoot = workspaceRootFor ( spec . sourceRepoPath ) ;
@@ -282,9 +301,35 @@ export class LocalRunner implements RunnerClient {
282301 await writeFile ( join ( runsDir , `${ handle . id } .json` ) , JSON . stringify ( metadata , null , 2 ) ) ;
283302 this . knownRuns . set ( handle . id , metadata ) ;
284303
304+ const startedAt = new Date ( ) . toISOString ( ) ;
305+ const resultPromise = handle . wait ( ) ;
306+ const timedPromise = request . constraints . timeoutSec && request . constraints . timeoutSec > 0
307+ ? new Promise < TaskExecutionResult > ( ( resolve ) => {
308+ const timeoutMs = request . constraints . timeoutSec ! * 1000 ;
309+ const timer = setTimeout ( async ( ) => {
310+ try {
311+ await handle . cancel ( ) ;
312+ } catch {
313+ // Best-effort cancel.
314+ }
315+ const timeoutResult = createTimeoutResult ( request , startedAt ) ;
316+ onEvent ( {
317+ protocolVersion : PROTOCOL_VERSION ,
318+ type : "completed" ,
319+ at : timeoutResult . metrics . finishedAt ,
320+ taskId : request . taskId ,
321+ status : timeoutResult . status ,
322+ } ) ;
323+ resolve ( timeoutResult ) ;
324+ } , timeoutMs ) ;
325+
326+ void resultPromise . finally ( ( ) => clearTimeout ( timer ) ) ;
327+ } )
328+ : null ;
329+
285330 const wrappedHandle = new LocalRunHandle (
286331 handle . id ,
287- handle . wait ( ) . then ( async ( result : TaskExecutionResult ) => {
332+ Promise . race ( [ resultPromise , ... ( timedPromise ? [ timedPromise ] : [ ] ) ] ) . then ( async ( result : TaskExecutionResult ) => {
288333 metadata . status = result . status ;
289334 await writeFile ( resultPath , JSON . stringify ( result , null , 2 ) ) ;
290335 await writeFile ( join ( runsDir , `${ handle . id } .json` ) , JSON . stringify ( metadata , null , 2 ) ) ;
0 commit comments