@@ -49,6 +49,7 @@ import {
4949 GIT_CONFIG_DIGEST_LINE ,
5050 GIT_CONFIG_DIGEST_MARKER ,
5151 MAX_DIFF_BYTES ,
52+ MIN_PI_TIMEOUT_MS ,
5253 PI_TIMEOUT_MS ,
5354 PROMPT_PATH ,
5455 PUSH_ERROR_MAX ,
@@ -87,7 +88,7 @@ const MAX_CHANGED_FILES = 50
8788const MAX_FAILING_CHECKS_IN_PROMPT = 20
8889const MAX_REVIEW_PROMPT_BYTES = 250_000
8990const MAX_CHECK_PROMPT_BYTES = 400_000
90- const MIN_ROUND_BUDGET_MS = 5 * 60 * 1000
91+ const MIN_ROUND_BUDGET_MS = MIN_PI_TIMEOUT_MS
9192const ROUND_FINALIZATION_RESERVE_MS = 2 * FINALIZE_TIMEOUT_MS
9293const SANDBOX_KEEPALIVE_INTERVAL_MS = 4 * 60 * 1000
9394
@@ -175,6 +176,16 @@ interface RoundFinalize {
175176 diff : string
176177}
177178
179+ class BabysitFinalizeError extends Error {
180+ constructor (
181+ public readonly reason : BabysitStopReason ,
182+ message : string
183+ ) {
184+ super ( message )
185+ this . name = 'BabysitFinalizeError'
186+ }
187+ }
188+
178189function untrustedJson ( value : unknown ) : string {
179190 return JSON . stringify ( value ) . replace ( / [ < > & ] / g, ( character ) => {
180191 if ( character === '<' ) return '\\u003c'
@@ -416,14 +427,18 @@ async function finalizeRound(
416427 signal
417428 )
418429 if ( prepare . exitCode !== 0 ) {
419- throw new Error (
430+ throw new BabysitFinalizeError (
431+ 'agent_failure' ,
420432 `Babysit finalize refused repository state: ${ truncate ( prepare . stderr || prepare . stdout , PUSH_ERROR_MAX ) } `
421433 )
422434 }
423435 const noChanges = prepare . stdout . includes ( '__NO_CHANGES__=1' )
424436 const needsPush = prepare . stdout . includes ( '__NEEDS_PUSH__=1' )
425437 if ( noChanges === needsPush )
426- throw new Error ( 'Babysit finalize returned inconsistent change state' )
438+ throw new BabysitFinalizeError (
439+ 'agent_failure' ,
440+ 'Babysit finalize returned inconsistent change state'
441+ )
427442 if ( noChanges ) {
428443 return { commitPushed : false , newSha : roundBaseSha , changedFiles : [ ] , diff : '' }
429444 }
@@ -435,15 +450,25 @@ async function finalizeRound(
435450 const changedFiles = extractMarkerValues ( prepare . stdout , '__CHANGED__=' )
436451 const newSha = extractMarkerValues ( prepare . stdout , '__NEW_SHA__=' ) [ 0 ]
437452 if ( ! newSha || ! Number . isSafeInteger ( cumulativeDiffBytes ) ) {
438- throw new Error ( 'Babysit finalize omitted its commit or diff bounds' )
453+ throw new BabysitFinalizeError (
454+ 'agent_failure' ,
455+ 'Babysit finalize omitted its commit or diff bounds'
456+ )
439457 }
440458 if ( cumulativeChangedFiles . length > MAX_CHANGED_FILES || cumulativeDiffBytes > MAX_DIFF_BYTES ) {
441- throw new Error ( 'Babysit cumulative change bounds were exceeded' )
459+ throw new BabysitFinalizeError (
460+ 'bounds_exceeded' ,
461+ 'Babysit cumulative change bounds were exceeded'
462+ )
442463 }
443464 if ( cumulativeChangedFiles . some ( ( file ) => file === '.github' || file . startsWith ( '.github/' ) ) ) {
444- throw new Error ( 'Babysit refuses to push changes under .github/' )
465+ throw new BabysitFinalizeError (
466+ 'refused_content' ,
467+ 'Babysit refuses to push changes under .github/'
468+ )
445469 }
446470
471+ const diff = scrubPiSecrets ( await runner . readFile ( DIFF_PATH ) , secrets )
447472 assertBabysitPinned ( snapshot , await fetchBabysitSnapshot ( params , signal ) )
448473 const push = await raceAbort (
449474 runner . run ( BABYSIT_PUSH_SCRIPT , {
@@ -462,14 +487,14 @@ async function finalizeRound(
462487 signal
463488 )
464489 if ( ! push . stdout . includes ( '__PUSHED__=1' ) ) {
465- throw new Error (
490+ throw new BabysitFinalizeError (
491+ 'push_rejected' ,
466492 `git push failed: ${ truncate (
467493 scrubGitSecrets ( push . stderr || push . stdout || 'unknown error' , params . githubToken ) ,
468494 PUSH_ERROR_MAX
469495 ) } `
470496 )
471497 }
472- const diff = scrubPiSecrets ( await runner . readFile ( DIFF_PATH ) , secrets )
473498 return { commitPushed : true , newSha, changedFiles, diff }
474499}
475500
@@ -567,7 +592,7 @@ export async function runBabysitPiWithOptions(
567592 if ( lifetime < MIN_ROUND_BUDGET_MS ) {
568593 cancellation . cleanup ( )
569594 throw new Error (
570- 'Babysit needs at least five minutes of runtime; use an async trigger and a longer Pi sandbox lifetime.'
595+ 'Babysit needs at least one minute of runtime; use an async trigger and a longer Pi sandbox lifetime.'
571596 )
572597 }
573598
@@ -813,13 +838,9 @@ export async function runBabysitPiWithOptions(
813838 const reason : BabysitStopReason =
814839 error instanceof BabysitGitHubError
815840 ? error . reason
816- : message . includes ( '.github/' )
817- ? 'refused_content'
818- : message . includes ( 'bounds' )
819- ? 'bounds_exceeded'
820- : message . includes ( 'push failed' )
821- ? 'push_rejected'
822- : 'refused_content'
841+ : error instanceof BabysitFinalizeError
842+ ? error . reason
843+ : 'agent_failure'
823844 return resultFor ( totals , reason , progress , threadsClean , latestChecks ! . checksGreen )
824845 }
825846
0 commit comments