@@ -1877,6 +1877,142 @@ it.layer(BaseTestLayer)("OrchestrationProjectionPipeline", (it) => {
18771877 } ) ,
18781878 ) ;
18791879
1880+ it . effect ( "clears stale pending user input from projected shell summaries" , ( ) =>
1881+ Effect . gen ( function * ( ) {
1882+ const projectionPipeline = yield * OrchestrationProjectionPipeline ;
1883+ const eventStore = yield * OrchestrationEventStore ;
1884+ const sql = yield * SqlClient . SqlClient ;
1885+ const appendAndProject = ( event : Parameters < typeof eventStore . append > [ 0 ] ) =>
1886+ eventStore
1887+ . append ( event )
1888+ . pipe ( Effect . flatMap ( ( savedEvent ) => projectionPipeline . projectEvent ( savedEvent ) ) ) ;
1889+
1890+ yield * appendAndProject ( {
1891+ type : "project.created" ,
1892+ eventId : EventId . make ( "evt-stale-user-input-1" ) ,
1893+ aggregateKind : "project" ,
1894+ aggregateId : ProjectId . make ( "project-stale-user-input" ) ,
1895+ occurredAt : "2026-02-26T12:35:00.000Z" ,
1896+ commandId : CommandId . make ( "cmd-stale-user-input-1" ) ,
1897+ causationEventId : null ,
1898+ correlationId : CorrelationId . make ( "cmd-stale-user-input-1" ) ,
1899+ metadata : { } ,
1900+ payload : {
1901+ projectId : ProjectId . make ( "project-stale-user-input" ) ,
1902+ title : "Project Stale User Input" ,
1903+ workspaceRoot : "/tmp/project-stale-user-input" ,
1904+ defaultModelSelection : null ,
1905+ scripts : [ ] ,
1906+ createdAt : "2026-02-26T12:35:00.000Z" ,
1907+ updatedAt : "2026-02-26T12:35:00.000Z" ,
1908+ } ,
1909+ } ) ;
1910+
1911+ yield * appendAndProject ( {
1912+ type : "thread.created" ,
1913+ eventId : EventId . make ( "evt-stale-user-input-2" ) ,
1914+ aggregateKind : "thread" ,
1915+ aggregateId : ThreadId . make ( "thread-stale-user-input" ) ,
1916+ occurredAt : "2026-02-26T12:35:01.000Z" ,
1917+ commandId : CommandId . make ( "cmd-stale-user-input-2" ) ,
1918+ causationEventId : null ,
1919+ correlationId : CorrelationId . make ( "cmd-stale-user-input-2" ) ,
1920+ metadata : { } ,
1921+ payload : {
1922+ threadId : ThreadId . make ( "thread-stale-user-input" ) ,
1923+ projectId : ProjectId . make ( "project-stale-user-input" ) ,
1924+ title : "Thread Stale User Input" ,
1925+ modelSelection : {
1926+ instanceId : ProviderInstanceId . make ( "codex" ) ,
1927+ model : "gpt-5-codex" ,
1928+ } ,
1929+ runtimeMode : "approval-required" ,
1930+ interactionMode : "default" ,
1931+ branch : null ,
1932+ worktreePath : null ,
1933+ createdAt : "2026-02-26T12:35:01.000Z" ,
1934+ updatedAt : "2026-02-26T12:35:01.000Z" ,
1935+ } ,
1936+ } ) ;
1937+
1938+ yield * appendAndProject ( {
1939+ type : "thread.activity-appended" ,
1940+ eventId : EventId . make ( "evt-stale-user-input-3" ) ,
1941+ aggregateKind : "thread" ,
1942+ aggregateId : ThreadId . make ( "thread-stale-user-input" ) ,
1943+ occurredAt : "2026-02-26T12:35:02.000Z" ,
1944+ commandId : CommandId . make ( "cmd-stale-user-input-3" ) ,
1945+ causationEventId : null ,
1946+ correlationId : CorrelationId . make ( "cmd-stale-user-input-3" ) ,
1947+ metadata : { } ,
1948+ payload : {
1949+ threadId : ThreadId . make ( "thread-stale-user-input" ) ,
1950+ activity : {
1951+ id : EventId . make ( "activity-stale-user-input-requested" ) ,
1952+ tone : "info" ,
1953+ kind : "user-input.requested" ,
1954+ summary : "User input requested" ,
1955+ payload : {
1956+ requestId : "user-input-request-stale-1" ,
1957+ questions : [
1958+ {
1959+ id : "sandbox_mode" ,
1960+ header : "Sandbox" ,
1961+ question : "Which mode should be used?" ,
1962+ options : [
1963+ {
1964+ label : "workspace-write" ,
1965+ description : "Allow workspace writes only" ,
1966+ } ,
1967+ ] ,
1968+ } ,
1969+ ] ,
1970+ } ,
1971+ turnId : null ,
1972+ createdAt : "2026-02-26T12:35:02.000Z" ,
1973+ } ,
1974+ } ,
1975+ } ) ;
1976+
1977+ yield * appendAndProject ( {
1978+ type : "thread.activity-appended" ,
1979+ eventId : EventId . make ( "evt-stale-user-input-4" ) ,
1980+ aggregateKind : "thread" ,
1981+ aggregateId : ThreadId . make ( "thread-stale-user-input" ) ,
1982+ occurredAt : "2026-02-26T12:35:03.000Z" ,
1983+ commandId : CommandId . make ( "cmd-stale-user-input-4" ) ,
1984+ causationEventId : null ,
1985+ correlationId : CorrelationId . make ( "cmd-stale-user-input-4" ) ,
1986+ metadata : { } ,
1987+ payload : {
1988+ threadId : ThreadId . make ( "thread-stale-user-input" ) ,
1989+ activity : {
1990+ id : EventId . make ( "activity-stale-user-input-failed" ) ,
1991+ tone : "error" ,
1992+ kind : "provider.user-input.respond.failed" ,
1993+ summary : "Provider user input response failed" ,
1994+ payload : {
1995+ requestId : "user-input-request-stale-1" ,
1996+ detail :
1997+ "Provider adapter request failed (codex) for item/tool/requestUserInput: Unknown pending Codex user input request: user-input-request-stale-1" ,
1998+ } ,
1999+ turnId : null ,
2000+ createdAt : "2026-02-26T12:35:03.000Z" ,
2001+ } ,
2002+ } ,
2003+ } ) ;
2004+
2005+ const threadRows = yield * sql < {
2006+ readonly pendingUserInputCount : number ;
2007+ } > `
2008+ SELECT pending_user_input_count AS "pendingUserInputCount"
2009+ FROM projection_threads
2010+ WHERE thread_id = 'thread-stale-user-input'
2011+ ` ;
2012+ assert . deepEqual ( threadRows , [ { pendingUserInputCount : 0 } ] ) ;
2013+ } ) ,
2014+ ) ;
2015+
18802016 it . effect ( "ignores non-stale provider approval response failures" , ( ) =>
18812017 Effect . gen ( function * ( ) {
18822018 const projectionPipeline = yield * OrchestrationProjectionPipeline ;
0 commit comments