@@ -14,6 +14,26 @@ interface Options {
1414 skipTelegram : boolean ;
1515}
1616
17+ export type RunOptions = {
18+ capture ?: boolean ;
19+ timeoutMs ?: number ;
20+ } ;
21+
22+ export type WorkflowRunInfo = {
23+ conclusion : string | null ;
24+ html_url : string ;
25+ status : string ;
26+ updated_at : string ;
27+ } ;
28+
29+ export type PollRunOptions = {
30+ pollIntervalMs ?: number ;
31+ readRun ?: ( repo : string , runId : string ) => WorkflowRunInfo ;
32+ sleep ?: ( ms : number ) => Promise < void > ;
33+ timeoutMs ?: number ;
34+ now ?: ( ) => number ;
35+ } ;
36+
1737function usage ( ) : string {
1838 return `Usage: pnpm release:beta-smoke -- --beta beta4 [options]
1939
@@ -88,15 +108,43 @@ function requireValue(argv: string[], index: number, flag: string): string {
88108}
89109
90110const CAPTURE_MAX_BUFFER_BYTES = 32 * 1024 * 1024 ;
111+ const DEFAULT_COMMAND_TIMEOUT_MS = readPositiveInt (
112+ process . env . OPENCLAW_RELEASE_BETA_SMOKE_COMMAND_MS ,
113+ 10 * 60_000 ,
114+ ) ;
115+ const TELEGRAM_POLL_INTERVAL_MS = readPositiveInt (
116+ process . env . OPENCLAW_RELEASE_BETA_SMOKE_POLL_INTERVAL_MS ,
117+ 30_000 ,
118+ ) ;
119+ const TELEGRAM_POLL_TIMEOUT_MS = readPositiveInt (
120+ process . env . OPENCLAW_RELEASE_BETA_SMOKE_POLL_TIMEOUT_MS ,
121+ 4 * 60 * 60_000 ,
122+ ) ;
123+
124+ function readPositiveInt ( raw : string | undefined , fallback : number ) : number {
125+ const text = ( raw ?? "" ) . trim ( ) ;
126+ if ( ! / ^ \d + $ / u. test ( text ) ) {
127+ return fallback ;
128+ }
129+ const parsed = Number ( text ) ;
130+ return Number . isInteger ( parsed ) && parsed > 0 ? parsed : fallback ;
131+ }
91132
92- function run ( command : string , args : string [ ] , input ?: { capture ?: boolean } ) : string {
133+ export function run ( command : string , args : string [ ] , input ?: RunOptions ) : string {
134+ const timeoutMs = input ?. timeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS ;
93135 const result = spawnSync ( command , args , {
94136 encoding : "utf8" ,
137+ killSignal : "SIGKILL" ,
95138 maxBuffer : CAPTURE_MAX_BUFFER_BYTES ,
96139 stdio : input ?. capture ? [ "ignore" , "pipe" , "pipe" ] : "inherit" ,
140+ timeout : timeoutMs ,
97141 } ) ;
98- if ( result . status !== 0 ) {
99- const reason = result . status ?? result . signal ?? result . error ?. message ?? "unknown" ;
142+ if ( result . error || result . status !== 0 ) {
143+ const errorCode = ( result . error as NodeJS . ErrnoException | undefined ) ?. code ;
144+ const reason =
145+ errorCode === "ETIMEDOUT"
146+ ? `timed out after ${ timeoutMs } ms`
147+ : ( result . status ?? result . signal ?? result . error ?. message ?? "unknown" ) ;
100148 const stderr = result . stderr ? `\n${ result . stderr } ` : "" ;
101149 throw new Error ( `${ command } ${ args . join ( " " ) } failed with ${ reason } ${ stderr } ` ) ;
102150 }
@@ -161,7 +209,7 @@ function runParallels(beta: string, model: string): void {
161209 "150m" ,
162210 ...forwarded . map ( shellQuote ) ,
163211 ] . join ( " " ) ;
164- run ( "bash" , [ "-lc" , command ] ) ;
212+ run ( "bash" , [ "-lc" , command ] , { timeoutMs : 155 * 60_000 } ) ;
165213}
166214
167215function ghJson ( repo : string , pathSuffix : string ) : unknown {
@@ -268,14 +316,22 @@ async function dispatchTelegram(options: Options, packageSpec: string): Promise<
268316 } ) ;
269317}
270318
271- async function pollRun ( repo : string , runId : string ) : Promise < void > {
319+ export async function pollRun (
320+ repo : string ,
321+ runId : string ,
322+ options : PollRunOptions = { } ,
323+ ) : Promise < void > {
324+ const started = ( options . now ?? Date . now ) ( ) ;
325+ const timeoutMs = Math . max ( 1 , options . timeoutMs ?? TELEGRAM_POLL_TIMEOUT_MS ) ;
326+ const pollIntervalMs = Math . max ( 1 , options . pollIntervalMs ?? TELEGRAM_POLL_INTERVAL_MS ) ;
327+ const sleep =
328+ options . sleep ?? ( ( ms : number ) => new Promise < void > ( ( resolve ) => setTimeout ( resolve , ms ) ) ) ;
329+ const readRun =
330+ options . readRun ??
331+ ( ( currentRepo : string , currentRunId : string ) =>
332+ ghJson ( currentRepo , `actions/runs/${ currentRunId } ` ) as WorkflowRunInfo ) ;
272333 for ( ; ; ) {
273- const info = ghJson ( repo , `actions/runs/${ runId } ` ) as {
274- conclusion : string | null ;
275- html_url : string ;
276- status : string ;
277- updated_at : string ;
278- } ;
334+ const info = readRun ( repo , runId ) ;
279335 console . log (
280336 `Telegram workflow ${ runId } : ${ info . status } ${ info . conclusion ? `/${ info . conclusion } ` : "" } updated=${ info . updated_at } ` ,
281337 ) ;
@@ -288,7 +344,11 @@ async function pollRun(repo: string, runId: string): Promise<void> {
288344 console . log ( info . html_url ) ;
289345 return ;
290346 }
291- await new Promise ( ( resolve ) => setTimeout ( resolve , 30_000 ) ) ;
347+ const elapsedMs = ( options . now ?? Date . now ) ( ) - started ;
348+ if ( elapsedMs >= timeoutMs ) {
349+ throw new Error ( `Telegram workflow ${ runId } did not complete within ${ timeoutMs } ms` ) ;
350+ }
351+ await sleep ( Math . min ( pollIntervalMs , timeoutMs - elapsedMs ) ) ;
292352 }
293353}
294354
0 commit comments