@@ -1585,6 +1585,148 @@ describe("runTurn: real approval park + respondPermission resume", () => {
15851585 await env . destroy ( ) ;
15861586 } ) ;
15871587
1588+ it ( "creates and resolves a durable gate row without workflow context" , async ( ) => {
1589+ const posted : Array < { url : string ; body : Record < string , unknown > } > = [ ] ;
1590+ const fetchSpy = vi
1591+ . spyOn ( globalThis , "fetch" )
1592+ . mockImplementation ( async ( input , init ) => {
1593+ posted . push ( {
1594+ url : String ( input ) ,
1595+ body : JSON . parse ( init ?. body as string ) as Record < string , unknown > ,
1596+ } ) ;
1597+ return new Response ( JSON . stringify ( { ok : true } ) , { status : 200 } ) ;
1598+ } ) ;
1599+
1600+ try {
1601+ const { calls, deps, captured } = pausableHarness ( ) ;
1602+ deps . hydrateHarnessSessionFromDurable = async ( ) => { } ;
1603+ const noWorkflowRequest : AgentRunRequest = {
1604+ ...engineReq ,
1605+ ...auth ,
1606+ sessionId : "sess-no-workflow" ,
1607+ turnId : "turn-no-workflow-start" ,
1608+ } ;
1609+ const acquired = await acquireEnvironment ( noWorkflowRequest , deps ) ;
1610+ assert . equal ( acquired . ok , true ) ;
1611+ if ( ! acquired . ok ) return ;
1612+ const env = acquired . env ;
1613+
1614+ const firstTurn = runTurn (
1615+ env ,
1616+ noWorkflowRequest ,
1617+ undefined ,
1618+ undefined ,
1619+ { approvalParkMode : true } ,
1620+ ) ;
1621+ await flush ( ) ;
1622+ captured . onEvent ! (
1623+ updateEvent ( {
1624+ sessionUpdate : "tool_call" ,
1625+ toolCallId : "tc-no-workflow" ,
1626+ title : "commit" ,
1627+ rawInput : { message : "hi" } ,
1628+ } ) ,
1629+ ) ;
1630+ captured . onPermissionRequest ! ( {
1631+ id : "perm-no-workflow" ,
1632+ availableReplies : [ "once" , "reject" ] ,
1633+ toolCall : {
1634+ toolCallId : "tc-no-workflow" ,
1635+ name : "commit" ,
1636+ rawInput : { message : "hi" } ,
1637+ } ,
1638+ } ) ;
1639+ const paused = await firstTurn ;
1640+ assert . equal ( paused . stopReason , "paused" ) ;
1641+ await flush ( ) ;
1642+
1643+ const createCall = posted . find ( ( { url } ) =>
1644+ url . endsWith ( "/sessions/interactions/" ) ,
1645+ ) ;
1646+ assert . ok ( createCall ) ;
1647+ assert . equal ( createCall . body [ "session_id" ] , "sess-no-workflow" ) ;
1648+ assert . equal ( createCall . body [ "turn_id" ] , "turn-no-workflow-start" ) ;
1649+ const createData = createCall . body [ "data" ] as Record < string , unknown > ;
1650+ assert . equal ( "references" in createData , false ) ;
1651+
1652+ const parked = env . parkedApprovals . get ( "tc-no-workflow" ) ;
1653+ assert . ok ( parked ) ;
1654+ assert . ok ( parked . promptPromise ) ;
1655+ env . clearTurn ( ) ;
1656+ const resumedTurn = runTurn (
1657+ env ,
1658+ approveResume ( true , {
1659+ sessionId : "sess-no-workflow" ,
1660+ turnId : "turn-no-workflow-resume" ,
1661+ } ) ,
1662+ undefined ,
1663+ undefined ,
1664+ {
1665+ approvalParkMode : true ,
1666+ resume : {
1667+ decisions : [
1668+ {
1669+ permissionId : parked . permissionId ,
1670+ reply : "once" ,
1671+ toolCallId : parked . toolCallId ,
1672+ toolName : parked . toolName ,
1673+ args : parked . args ,
1674+ interactionToken : parked . interactionToken ,
1675+ promptPromise : parked . promptPromise ,
1676+ } ,
1677+ ] ,
1678+ carriedForward : [ ] ,
1679+ } ,
1680+ } ,
1681+ ) ;
1682+ for (
1683+ let attempt = 0 ;
1684+ attempt < 10 &&
1685+ ! posted . some ( ( { url } ) =>
1686+ url . endsWith ( "/sessions/interactions/transition" ) ,
1687+ ) ;
1688+ attempt += 1
1689+ ) {
1690+ await flush ( ) ;
1691+ }
1692+
1693+ const resolveCall = posted . find ( ( { url } ) =>
1694+ url . endsWith ( "/sessions/interactions/transition" ) ,
1695+ ) ;
1696+ assert . ok (
1697+ resolveCall ,
1698+ JSON . stringify ( { posted, permissionReplies : calls . permissionReplies } ) ,
1699+ ) ;
1700+ assert . deepEqual ( resolveCall . body , {
1701+ session_id : "sess-no-workflow" ,
1702+ token : parked . interactionToken ,
1703+ status : "resolved" ,
1704+ resolution : {
1705+ verdict : "approved" ,
1706+ tool_call_id : "tc-no-workflow" ,
1707+ } ,
1708+ } ) ;
1709+
1710+ captured . onEvent ! (
1711+ updateEvent ( {
1712+ sessionUpdate : "tool_call_update" ,
1713+ toolCallId : "tc-no-workflow" ,
1714+ status : "completed" ,
1715+ content : "committed" ,
1716+ } ) ,
1717+ ) ;
1718+ calls . resolvePrompt ! ( {
1719+ stopReason : "complete" ,
1720+ usage : { inputTokens : 1 , outputTokens : 1 } ,
1721+ } ) ;
1722+ const resumed = await resumedTurn ;
1723+ assert . equal ( resumed . ok , true ) ;
1724+ await env . destroy ( ) ;
1725+ } finally {
1726+ fetchSpy . mockRestore ( ) ;
1727+ }
1728+ } ) ;
1729+
15881730 it ( "preserves a denied call failed frame while a sibling gate is carried" , async ( ) => {
15891731 const { calls, deps, captured } = pausableHarness ( ) ;
15901732 const acquired = await acquireEnvironment ( engineReq , deps ) ;
0 commit comments