@@ -1656,6 +1656,142 @@ it.layer(BaseTestLayer)("OrchestrationProjectionPipeline", (it) => {
16561656 } ) ,
16571657 ) ;
16581658
1659+ it . effect ( "clears stale pending user input from projected shell summaries" , ( ) =>
1660+ Effect . gen ( function * ( ) {
1661+ const projectionPipeline = yield * OrchestrationProjectionPipeline ;
1662+ const eventStore = yield * OrchestrationEventStore ;
1663+ const sql = yield * SqlClient . SqlClient ;
1664+ const appendAndProject = ( event : Parameters < typeof eventStore . append > [ 0 ] ) =>
1665+ eventStore
1666+ . append ( event )
1667+ . pipe ( Effect . flatMap ( ( savedEvent ) => projectionPipeline . projectEvent ( savedEvent ) ) ) ;
1668+
1669+ yield * appendAndProject ( {
1670+ type : "project.created" ,
1671+ eventId : EventId . make ( "evt-stale-user-input-1" ) ,
1672+ aggregateKind : "project" ,
1673+ aggregateId : ProjectId . make ( "project-stale-user-input" ) ,
1674+ occurredAt : "2026-02-26T12:35:00.000Z" ,
1675+ commandId : CommandId . make ( "cmd-stale-user-input-1" ) ,
1676+ causationEventId : null ,
1677+ correlationId : CorrelationId . make ( "cmd-stale-user-input-1" ) ,
1678+ metadata : { } ,
1679+ payload : {
1680+ projectId : ProjectId . make ( "project-stale-user-input" ) ,
1681+ title : "Project Stale User Input" ,
1682+ workspaceRoot : "/tmp/project-stale-user-input" ,
1683+ defaultModelSelection : null ,
1684+ scripts : [ ] ,
1685+ createdAt : "2026-02-26T12:35:00.000Z" ,
1686+ updatedAt : "2026-02-26T12:35:00.000Z" ,
1687+ } ,
1688+ } ) ;
1689+
1690+ yield * appendAndProject ( {
1691+ type : "thread.created" ,
1692+ eventId : EventId . make ( "evt-stale-user-input-2" ) ,
1693+ aggregateKind : "thread" ,
1694+ aggregateId : ThreadId . make ( "thread-stale-user-input" ) ,
1695+ occurredAt : "2026-02-26T12:35:01.000Z" ,
1696+ commandId : CommandId . make ( "cmd-stale-user-input-2" ) ,
1697+ causationEventId : null ,
1698+ correlationId : CorrelationId . make ( "cmd-stale-user-input-2" ) ,
1699+ metadata : { } ,
1700+ payload : {
1701+ threadId : ThreadId . make ( "thread-stale-user-input" ) ,
1702+ projectId : ProjectId . make ( "project-stale-user-input" ) ,
1703+ title : "Thread Stale User Input" ,
1704+ modelSelection : {
1705+ instanceId : ProviderInstanceId . make ( "codex" ) ,
1706+ model : "gpt-5-codex" ,
1707+ } ,
1708+ runtimeMode : "approval-required" ,
1709+ interactionMode : "default" ,
1710+ branch : null ,
1711+ worktreePath : null ,
1712+ createdAt : "2026-02-26T12:35:01.000Z" ,
1713+ updatedAt : "2026-02-26T12:35:01.000Z" ,
1714+ } ,
1715+ } ) ;
1716+
1717+ yield * appendAndProject ( {
1718+ type : "thread.activity-appended" ,
1719+ eventId : EventId . make ( "evt-stale-user-input-3" ) ,
1720+ aggregateKind : "thread" ,
1721+ aggregateId : ThreadId . make ( "thread-stale-user-input" ) ,
1722+ occurredAt : "2026-02-26T12:35:02.000Z" ,
1723+ commandId : CommandId . make ( "cmd-stale-user-input-3" ) ,
1724+ causationEventId : null ,
1725+ correlationId : CorrelationId . make ( "cmd-stale-user-input-3" ) ,
1726+ metadata : { } ,
1727+ payload : {
1728+ threadId : ThreadId . make ( "thread-stale-user-input" ) ,
1729+ activity : {
1730+ id : EventId . make ( "activity-stale-user-input-requested" ) ,
1731+ tone : "info" ,
1732+ kind : "user-input.requested" ,
1733+ summary : "User input requested" ,
1734+ payload : {
1735+ requestId : "user-input-request-stale-1" ,
1736+ questions : [
1737+ {
1738+ id : "sandbox_mode" ,
1739+ header : "Sandbox" ,
1740+ question : "Which mode should be used?" ,
1741+ options : [
1742+ {
1743+ label : "workspace-write" ,
1744+ description : "Allow workspace writes only" ,
1745+ } ,
1746+ ] ,
1747+ } ,
1748+ ] ,
1749+ } ,
1750+ turnId : null ,
1751+ createdAt : "2026-02-26T12:35:02.000Z" ,
1752+ } ,
1753+ } ,
1754+ } ) ;
1755+
1756+ yield * appendAndProject ( {
1757+ type : "thread.activity-appended" ,
1758+ eventId : EventId . make ( "evt-stale-user-input-4" ) ,
1759+ aggregateKind : "thread" ,
1760+ aggregateId : ThreadId . make ( "thread-stale-user-input" ) ,
1761+ occurredAt : "2026-02-26T12:35:03.000Z" ,
1762+ commandId : CommandId . make ( "cmd-stale-user-input-4" ) ,
1763+ causationEventId : null ,
1764+ correlationId : CorrelationId . make ( "cmd-stale-user-input-4" ) ,
1765+ metadata : { } ,
1766+ payload : {
1767+ threadId : ThreadId . make ( "thread-stale-user-input" ) ,
1768+ activity : {
1769+ id : EventId . make ( "activity-stale-user-input-failed" ) ,
1770+ tone : "error" ,
1771+ kind : "provider.user-input.respond.failed" ,
1772+ summary : "Provider user input response failed" ,
1773+ payload : {
1774+ requestId : "user-input-request-stale-1" ,
1775+ detail :
1776+ "Provider adapter request failed (codex) for item/tool/requestUserInput: Unknown pending Codex user input request: user-input-request-stale-1" ,
1777+ } ,
1778+ turnId : null ,
1779+ createdAt : "2026-02-26T12:35:03.000Z" ,
1780+ } ,
1781+ } ,
1782+ } ) ;
1783+
1784+ const threadRows = yield * sql < {
1785+ readonly pendingUserInputCount : number ;
1786+ } > `
1787+ SELECT pending_user_input_count AS "pendingUserInputCount"
1788+ FROM projection_threads
1789+ WHERE thread_id = 'thread-stale-user-input'
1790+ ` ;
1791+ assert . deepEqual ( threadRows , [ { pendingUserInputCount : 0 } ] ) ;
1792+ } ) ,
1793+ ) ;
1794+
16591795 it . effect ( "ignores non-stale provider approval response failures" , ( ) =>
16601796 Effect . gen ( function * ( ) {
16611797 const projectionPipeline = yield * OrchestrationProjectionPipeline ;
0 commit comments