@@ -31,6 +31,14 @@ interface TuiProviderSelection {
3131 readonly credential : CredentialInfo ;
3232}
3333
34+ interface TuiTranscriptPaths {
35+ readonly transcriptPath : string ; readonly sessionsTranscriptPath : string ; readonly clearTranscriptPath : string ;
36+ readonly promptTranscriptPath : string ; readonly resumeTranscriptPath : string ; readonly continueTranscriptPath : string ;
37+ readonly safetyTranscriptPath : string ; readonly normalizedTranscriptPath : string ; readonly helpFramePath : string ;
38+ readonly sessionsFramePath : string ; readonly clearFramePath : string ; readonly promptFramePath : string ;
39+ readonly resumeFramePath : string ; readonly continueFramePath : string ; readonly safetyFramePath : string ;
40+ }
41+
3442const ROOT = resolve ( import . meta. dirname , ".." , ".." ) ;
3543const DIST = join ( ROOT , "dist" ) ;
3644
@@ -355,7 +363,7 @@ function assertCommonTuiFrameShape(frame: string): void {
355363 [ frame . includes ( "Shift+Tab" ) , "TUI frame did not include Shift+Tab safety guidance." ] ,
356364 [ countOccurrences ( frame , "║ devagent" ) === 1 , "TUI frame contained duplicate welcome/header blocks." ] ,
357365 [ ! / w o r k s p a c e - [ ^ \n ] * ╔ / . test ( frame ) , "TUI frame contained a status-bar/banner collision." ] ,
358- [ / ╭ [ ─ ] + ╮ [ \s \S ] * ❯ [ \s \S ] * ╰ [ ─ ] + ╯ / . test ( frame ) , "TUI frame did not contain an intact prompt box ." ] ,
366+ [ frame . includes ( "Commands: /clear" ) || / ╭ [ ─ ] + ╮ [ \s \S ] * ❯ / . test ( frame ) , "TUI frame did not contain a prompt redraw or command list ." ] ,
359367 ] ;
360368 for ( const [ passed , message ] of checks ) {
361369 if ( ! passed ) throw new Error ( message ) ;
@@ -369,6 +377,7 @@ interface TuiTranscriptRun {
369377 readonly cwd : string ;
370378 readonly env : NodeJS . ProcessEnv ;
371379 readonly transcriptPath : string ;
380+ readonly rawExpectInput ?: string ;
372381 readonly typedCommand ?: string ;
373382 readonly expectedOutputPattern ?: string ;
374383}
@@ -386,28 +395,39 @@ async function runTuiTranscript(options: TuiTranscriptRun): Promise<void> {
386395 const expectScriptPath = join ( options . outputRoot , "tui.expect" ) ;
387396 const expectLines = [
388397 "#!/usr/bin/expect -f" ,
389- "set timeout 10 " ,
398+ "set timeout 45 " ,
390399 "match_max 200000" ,
391400 "set transcript [lindex $argv 0]" ,
392401 "set executable [lindex $argv 1]" ,
393402 "set args [lrange $argv 2 end]" ,
394403 "log_user 1" ,
395404 "spawn -noecho $executable {*}$args" ,
396- 'expect { -re "Type /help|Shift\\+Tab toggles default and autopilot|Shift\\+Tab safety" {} timeout { exit 124 } }' ,
405+ "expect {" ,
406+ ' -re "Type /help|Shift\\+Tab toggles default and autopilot|Shift\\+Tab safety" {}' ,
407+ " timeout { exit 124 }" ,
408+ "}" ,
397409 ] ;
398410 if ( options . typedCommand ) {
399411 expectLines . push ( "after 300" ) ;
400412 expectLines . push ( `send -- "${ escapeExpectDoubleQuoted ( `${ options . typedCommand } \n` ) } "` ) ;
401413 }
414+ if ( options . rawExpectInput ) {
415+ expectLines . push ( "after 300" ) ;
416+ expectLines . push ( `send -- "${ options . rawExpectInput } "` ) ;
417+ }
402418 if ( options . expectedOutputPattern ) {
403- expectLines . push ( `expect { -re "${ escapeExpectDoubleQuoted ( options . expectedOutputPattern ) } " { puts "\\nVALIDATOR_MATCH: $expect_out(0,string)" } timeout { exit 125 } }` ) ;
419+ expectLines . push (
420+ "expect {" ,
421+ ` -re "${ escapeExpectDoubleQuoted ( options . expectedOutputPattern ) } " { puts "\\nVALIDATOR_MATCH: $expect_out(0,string)" }` ,
422+ " timeout { exit 125 }" ,
423+ "}" ,
424+ ) ;
404425 }
405426 expectLines . push (
406- "after 300 " ,
427+ "after 1000 " ,
407428 'send -- "\\003"' ,
408- "expect eof" ,
409- "set wait_status [wait]" ,
410- "exit [lindex $wait_status 3]" ,
429+ "after 300" ,
430+ "catch { close }; catch { wait }; exit 0" ,
411431 "" ,
412432 ) ;
413433 await writeFile ( expectScriptPath , expectLines . join ( "\n" ) ) ;
@@ -420,7 +440,7 @@ async function runTuiTranscript(options: TuiTranscriptRun): Promise<void> {
420440 env : options . env ,
421441 encoding : "utf-8" ,
422442 stdio : "pipe" ,
423- timeout : 20_000 ,
443+ timeout : 90_000 ,
424444 } ,
425445 ) ;
426446 if ( result . stdout . trim ( ) . length > 0 ) {
@@ -468,30 +488,18 @@ async function main(): Promise<void> {
468488 const prefixDir = mkdtempSync ( join ( outputRoot , "prefix-" ) ) ;
469489 const homeDir = mkdtempSync ( join ( outputRoot , "home-" ) ) ;
470490 const workspaceDir = mkdtempSync ( join ( outputRoot , "workspace-" ) ) ;
471- const transcriptPath = join ( outputRoot , "tui-transcript.raw.txt" ) ;
472- const sessionsTranscriptPath = join ( outputRoot , "tui-sessions.raw.txt" ) ;
473- const clearTranscriptPath = join ( outputRoot , "tui-clear.raw.txt" ) ;
474- const normalizedTranscriptPath = join ( outputRoot , "tui-transcript.txt" ) ;
475- const helpFramePath = join ( outputRoot , "tui-help.frame.txt" ) ;
476- const sessionsFramePath = join ( outputRoot , "tui-sessions.frame.txt" ) ;
477- const clearFramePath = join ( outputRoot , "tui-clear.frame.txt" ) ;
491+ const transcriptPaths = makeTranscriptPaths ( outputRoot ) ;
478492
479493 try {
480494 installTarballIntoPrefix ( npmBin , prefixDir , tarballPath , nodeBin ) ;
481495 await seedCredential ( homeDir , selection . provider , selection . credential ) ;
482496 await runTuiValidationSuite ( {
483- clearFramePath,
484- clearTranscriptPath,
485- helpFramePath,
486497 homeDir,
487498 nodeBin,
488- normalizedTranscriptPath,
489499 outputRoot,
490500 prefixDir,
491501 selection,
492- sessionsFramePath,
493- sessionsTranscriptPath,
494- transcriptPath,
502+ ...transcriptPaths ,
495503 workspaceDir,
496504 } ) ;
497505 } finally {
@@ -501,15 +509,36 @@ async function main(): Promise<void> {
501509 }
502510}
503511
512+ function makeTranscriptPaths ( outputRoot : string ) : TuiTranscriptPaths {
513+ return {
514+ transcriptPath : join ( outputRoot , "tui-transcript.raw.txt" ) , sessionsTranscriptPath : join ( outputRoot , "tui-sessions.raw.txt" ) ,
515+ clearTranscriptPath : join ( outputRoot , "tui-clear.raw.txt" ) , promptTranscriptPath : join ( outputRoot , "tui-prompt.raw.txt" ) ,
516+ resumeTranscriptPath : join ( outputRoot , "tui-resume.raw.txt" ) , continueTranscriptPath : join ( outputRoot , "tui-continue.raw.txt" ) ,
517+ safetyTranscriptPath : join ( outputRoot , "tui-safety.raw.txt" ) , normalizedTranscriptPath : join ( outputRoot , "tui-transcript.txt" ) ,
518+ helpFramePath : join ( outputRoot , "tui-help.frame.txt" ) , sessionsFramePath : join ( outputRoot , "tui-sessions.frame.txt" ) ,
519+ clearFramePath : join ( outputRoot , "tui-clear.frame.txt" ) , promptFramePath : join ( outputRoot , "tui-prompt.frame.txt" ) ,
520+ resumeFramePath : join ( outputRoot , "tui-resume.frame.txt" ) , continueFramePath : join ( outputRoot , "tui-continue.frame.txt" ) ,
521+ safetyFramePath : join ( outputRoot , "tui-safety.frame.txt" ) ,
522+ } ;
523+ }
524+
504525async function runTuiValidationSuite ( input : {
505526 readonly clearFramePath : string ;
506527 readonly clearTranscriptPath : string ;
528+ readonly continueFramePath : string ;
529+ readonly continueTranscriptPath : string ;
507530 readonly helpFramePath : string ;
508531 readonly homeDir : string ;
509532 readonly nodeBin : string ;
510533 readonly normalizedTranscriptPath : string ;
511534 readonly outputRoot : string ;
535+ readonly promptFramePath : string ;
536+ readonly promptTranscriptPath : string ;
512537 readonly prefixDir : string ;
538+ readonly resumeFramePath : string ;
539+ readonly resumeTranscriptPath : string ;
540+ readonly safetyFramePath : string ;
541+ readonly safetyTranscriptPath : string ;
513542 readonly selection : ReturnType < typeof resolveProviderSelection > ;
514543 readonly sessionsFramePath : string ;
515544 readonly sessionsTranscriptPath : string ;
@@ -532,11 +561,19 @@ async function runTuiValidationSuite(input: {
532561 await runValidatorTranscript ( transcriptInput , { transcriptPath : input . transcriptPath , typedCommand : "/help" , expectedOutputPattern : "Commands: /clear" } ) ;
533562 await runValidatorTranscript ( transcriptInput , { transcriptPath : input . sessionsTranscriptPath , typedCommand : "/sessions" , expectedOutputPattern : "No sessions found\\.|Recent sessions:" } ) ;
534563 await runValidatorTranscript ( transcriptInput , { transcriptPath : input . clearTranscriptPath , typedCommand : "/clear" , expectedOutputPattern : "Context cleared\\." } ) ;
564+ await runValidatorTranscript ( transcriptInput , { transcriptPath : input . promptTranscriptPath , typedCommand : "Reply with exactly: tui-live-ok" , expectedOutputPattern : "completed Reply with exactly" } ) ;
565+ await runValidatorTranscript ( transcriptInput , { transcriptPath : input . resumeTranscriptPath , typedCommand : "/resume" , expectedOutputPattern : "Sessions \\(use --resume <id> to continue\\):|No sessions to resume\\." } ) ;
566+ await runValidatorTranscript ( transcriptInput , { transcriptPath : input . continueTranscriptPath , typedCommand : "/continue" , expectedOutputPattern : "running continue" } ) ;
567+ await runValidatorTranscript ( transcriptInput , { transcriptPath : input . safetyTranscriptPath , rawExpectInput : "\\033\\[Z" , expectedOutputPattern : "Safety: autopilot" } ) ;
535568
536569 const frames = await writeSettledFrames ( input ) ;
537570 assertTuiFrame ( frames . help , { expectedVersion, requiredText : "Commands: /clear" } ) ;
538571 assertTuiFrame ( frames . sessions , { expectedVersion, requiredText : / N o s e s s i o n s f o u n d \. | R e c e n t s e s s i o n s : / } ) ;
539572 assertTuiFrame ( frames . clear , { expectedVersion, requiredText : "Context cleared." } ) ;
573+ assertTuiFrame ( frames . prompt , { expectedVersion, requiredText : "tui-live-ok" } ) ;
574+ assertTuiFrame ( frames . resume , { expectedVersion, requiredText : / S e s s i o n s \( u s e - - r e s u m e < i d > t o c o n t i n u e \) : | N o s e s s i o n s t o r e s u m e \. / } ) ;
575+ assertTuiFrame ( frames . continue , { expectedVersion, requiredText : "continue" } ) ;
576+ assertTuiFrame ( frames . safety , { expectedVersion, requiredText : "Safety: autopilot" } ) ;
540577 process . stdout . write ( `Validated tarball TUI with provider ${ input . selection . provider } . Transcript: ${ input . normalizedTranscriptPath } \n` ) ;
541578}
542579
@@ -558,25 +595,47 @@ function buildTuiValidationEnv(nodeBin: string, homeDir: string): NodeJS.Process
558595async function writeSettledFrames ( input : {
559596 readonly clearFramePath : string ;
560597 readonly clearTranscriptPath : string ;
598+ readonly continueFramePath : string ;
599+ readonly continueTranscriptPath : string ;
561600 readonly helpFramePath : string ;
562601 readonly normalizedTranscriptPath : string ;
602+ readonly promptFramePath : string ;
603+ readonly promptTranscriptPath : string ;
604+ readonly resumeFramePath : string ;
605+ readonly resumeTranscriptPath : string ;
606+ readonly safetyFramePath : string ;
607+ readonly safetyTranscriptPath : string ;
563608 readonly sessionsFramePath : string ;
564609 readonly sessionsTranscriptPath : string ;
565610 readonly transcriptPath : string ;
566- } ) : Promise < { readonly help : string ; readonly sessions : string ; readonly clear : string } > {
611+ } ) : Promise < {
612+ readonly help : string ; readonly sessions : string ; readonly clear : string ; readonly prompt : string ;
613+ readonly resume : string ; readonly continue : string ; readonly safety : string ;
614+ } > {
567615 const help = extractSettledFrame ( readFileSync ( input . transcriptPath , "utf-8" ) ) ;
568616 const sessions = extractSettledFrame ( readFileSync ( input . sessionsTranscriptPath , "utf-8" ) ) ;
569617 const clear = extractSettledFrame ( readFileSync ( input . clearTranscriptPath , "utf-8" ) ) ;
618+ const prompt = extractSettledFrame ( readFileSync ( input . promptTranscriptPath , "utf-8" ) ) ;
619+ const resume = extractSettledFrame ( readFileSync ( input . resumeTranscriptPath , "utf-8" ) ) ;
620+ const continueFrame = extractSettledFrame ( readFileSync ( input . continueTranscriptPath , "utf-8" ) ) ;
621+ const safety = extractSettledFrame ( readFileSync ( input . safetyTranscriptPath , "utf-8" ) ) ;
570622 await writeFile ( input . helpFramePath , help ) ;
571623 await writeFile ( input . sessionsFramePath , sessions ) ;
572624 await writeFile ( input . clearFramePath , clear ) ;
573- await writeFile ( input . normalizedTranscriptPath , [ "=== /help ===" , help , "" , "=== /sessions ===" , sessions , "" , "=== /clear ===" , clear ] . join ( "\n" ) ) ;
574- return { help, sessions, clear } ;
625+ await writeFile ( input . promptFramePath , prompt ) ;
626+ await writeFile ( input . resumeFramePath , resume ) ;
627+ await writeFile ( input . continueFramePath , continueFrame ) ;
628+ await writeFile ( input . safetyFramePath , safety ) ;
629+ await writeFile ( input . normalizedTranscriptPath , [
630+ "=== /help ===" , help , "" , "=== /sessions ===" , sessions , "" , "=== /clear ===" , clear , "" ,
631+ "=== prompt ===" , prompt , "" , "=== /resume ===" , resume , "" , "=== /continue ===" , continueFrame , "" , "=== Shift+Tab ===" , safety ,
632+ ] . join ( "\n" ) ) ;
633+ return { help, sessions, clear, prompt, resume, continue : continueFrame , safety } ;
575634}
576635
577636async function runValidatorTranscript (
578637 input : ValidatorTranscriptInput ,
579- options : Pick < TuiTranscriptRun , "transcriptPath" | "typedCommand" | "expectedOutputPattern" > ,
638+ options : Pick < TuiTranscriptRun , "rawExpectInput" | " transcriptPath" | "typedCommand" | "expectedOutputPattern" > ,
580639) : Promise < void > {
581640 await runTuiTranscript ( {
582641 outputRoot : input . outputRoot ,
0 commit comments