@@ -1513,11 +1513,10 @@ describe("sessions tools", () => {
15131513 expect ( calls . find ( ( call ) => call . method === "send" ) ) . toBeUndefined ( ) ;
15141514 } ) ;
15151515
1516- it ( "sessions_send reroutes run-scoped active deliveries when transcript steering is rejected " , async ( ) => {
1516+ it ( "sessions_send reports active-run queue rejection without durable-session fallback " , async ( ) => {
15171517 const calls : Array < { method ?: string ; params ?: unknown } > = [ ] ;
15181518 const requesterKey = "agent:re-portal:main" ;
15191519 const runScopedCallerKey = "agent:leasing-ops:cron:monthly-utility:run:run-fast" ;
1520- const durableCallerKey = "agent:leasing-ops:cron:monthly-utility" ;
15211520 const queueMessage = vi . fn ( async ( _text : string , _options ?: unknown ) => {
15221521 throw new Error ( "active session ended before queued steering message was committed" ) ;
15231522 } ) ;
@@ -1539,13 +1538,6 @@ describe("sessions tools", () => {
15391538 if ( request . method === "agent" ) {
15401539 return { runId : "fallback-run" , status : "accepted" , acceptedAt : 2000 } ;
15411540 }
1542- if ( request . method === "agent.wait" ) {
1543- const params = request . params as { runId ?: string } | undefined ;
1544- return { runId : params ?. runId ?? "fallback-run" , status : "ok" } ;
1545- }
1546- if ( request . method === "chat.history" ) {
1547- return { messages : [ ] } ;
1548- }
15491541 return { } ;
15501542 } ) ;
15511543
@@ -1570,9 +1562,11 @@ describe("sessions tools", () => {
15701562 timeoutSeconds : 0 ,
15711563 } ) ;
15721564 const details = sessionsSendDetails ( result . details ) ;
1573- expect ( details . status ) . toBe ( "accepted " ) ;
1565+ expect ( details . status ) . toBe ( "error " ) ;
15741566 expect ( details . sessionKey ) . toBe ( runScopedCallerKey ) ;
1575- expect ( details . delivery ?. status ) . toBe ( "pending" ) ;
1567+ expect ( details . error ) . toContain ( "queue_message_failed reason=runtime_rejected" ) ;
1568+ expect ( details . error ) . toContain ( "caller-active-session" ) ;
1569+ expect ( details . error ) . not . toContain ( "fallback_failed" ) ;
15761570 const queuedText = queueMessage . mock . calls [ 0 ] ?. [ 0 ] ;
15771571 expect ( queuedText ) . toContain ( "[Inter-session message]" ) ;
15781572 expect ( queuedText ) . toContain ( "[TASK-COMPLETE] re-portal occupancy ready" ) ;
@@ -1583,47 +1577,233 @@ describe("sessions tools", () => {
15831577 waitForTranscriptCommit : true ,
15841578 sourceReplyDeliveryMode : "message_tool_only" ,
15851579 } ) ;
1580+ expect ( calls . some ( ( call ) => call . method === "agent" ) ) . toBe ( false ) ;
1581+ } ) ;
15861582
1587- await vi . waitFor ( ( ) => {
1588- const fallbackCall = calls . find (
1589- ( call ) =>
1590- call . method === "agent" &&
1591- ( call . params as { sessionKey ?: string } | undefined ) ?. sessionKey === durableCallerKey ,
1592- ) ;
1593- expect ( fallbackCall ) . toBeDefined ( ) ;
1583+ it ( "sessions_send reports source reply delivery mode mismatch without durable-session fallback" , async ( ) => {
1584+ const calls : Array < { method ?: string ; params ?: unknown } > = [ ] ;
1585+ const runScopedCallerKey = "agent:leasing-ops:cron:monthly-utility:run:run-fast" ;
1586+ const queueMessage = vi . fn ( async ( ) => { } ) ;
1587+ setActiveEmbeddedRun (
1588+ "caller-active-session" ,
1589+ {
1590+ queueMessage,
1591+ isStreaming : ( ) => true ,
1592+ isCompacting : ( ) => false ,
1593+ supportsTranscriptCommitWait : true ,
1594+ sourceReplyDeliveryMode : "automatic" ,
1595+ abort : ( ) => { } ,
1596+ } ,
1597+ runScopedCallerKey ,
1598+ ) ;
1599+ callGatewayMock . mockImplementation ( async ( opts : unknown ) => {
1600+ const request = opts as { method ?: string ; params ?: unknown } ;
1601+ calls . push ( request ) ;
1602+ if ( request . method === "agent" ) {
1603+ return { runId : "fallback-run" , status : "accepted" , acceptedAt : 2000 } ;
1604+ }
1605+ return { } ;
15941606 } ) ;
15951607
1608+ const tool = createOpenClawTools ( {
1609+ agentSessionKey : "agent:re-portal:main" ,
1610+ agentChannel : "telegram" ,
1611+ config : {
1612+ ...TEST_CONFIG ,
1613+ session : {
1614+ ...TEST_CONFIG . session ,
1615+ agentToAgent : { maxPingPongTurns : 0 } ,
1616+ } ,
1617+ } ,
1618+ } ) . find ( ( candidate ) => candidate . name === "sessions_send" ) ;
1619+ if ( ! tool ) {
1620+ throw new Error ( "missing sessions_send tool" ) ;
1621+ }
1622+
1623+ const result = await tool . execute ( "call-run-scoped-caller" , {
1624+ sessionKey : runScopedCallerKey ,
1625+ message : "[TASK-COMPLETE] re-portal occupancy ready" ,
1626+ timeoutSeconds : 0 ,
1627+ } ) ;
1628+
1629+ const details = sessionsSendDetails ( result . details ) ;
1630+ expect ( details . status ) . toBe ( "error" ) ;
1631+ expect ( details . sessionKey ) . toBe ( runScopedCallerKey ) ;
1632+ expect ( details . error ) . toContain (
1633+ "queue_message_failed reason=source_reply_delivery_mode_mismatch" ,
1634+ ) ;
1635+ expect ( queueMessage ) . not . toHaveBeenCalled ( ) ;
1636+ expect ( calls . some ( ( call ) => call . method === "agent" ) ) . toBe ( false ) ;
1637+ } ) ;
1638+
1639+ it ( "sessions_send keeps ordinary active session targets on the gateway agent path" , async ( ) => {
1640+ const calls : Array < { method ?: string ; params ?: unknown } > = [ ] ;
1641+ const ordinaryActiveKey = "agent:main:main" ;
1642+ const queueMessage = vi . fn ( async ( ) => { } ) ;
1643+ setActiveEmbeddedRun (
1644+ "ordinary-active-session" ,
1645+ {
1646+ queueMessage,
1647+ isStreaming : ( ) => true ,
1648+ isCompacting : ( ) => false ,
1649+ supportsTranscriptCommitWait : true ,
1650+ sourceReplyDeliveryMode : "automatic" ,
1651+ abort : ( ) => { } ,
1652+ } ,
1653+ ordinaryActiveKey ,
1654+ ) ;
1655+ callGatewayMock . mockImplementation ( async ( opts : unknown ) => {
1656+ const request = opts as { method ?: string ; params ?: unknown } ;
1657+ calls . push ( request ) ;
1658+ if ( request . method === "agent" ) {
1659+ return { runId : "ordinary-agent-run" , status : "accepted" , acceptedAt : 2000 } ;
1660+ }
1661+ return { } ;
1662+ } ) ;
1663+
1664+ const tool = createOpenClawTools ( {
1665+ agentSessionKey : "agent:re-portal:main" ,
1666+ agentChannel : "telegram" ,
1667+ config : {
1668+ ...TEST_CONFIG ,
1669+ session : {
1670+ ...TEST_CONFIG . session ,
1671+ agentToAgent : { maxPingPongTurns : 0 } ,
1672+ } ,
1673+ } ,
1674+ } ) . find ( ( candidate ) => candidate . name === "sessions_send" ) ;
1675+ if ( ! tool ) {
1676+ throw new Error ( "missing sessions_send tool" ) ;
1677+ }
1678+
1679+ const result = await tool . execute ( "call-ordinary-active" , {
1680+ sessionKey : ordinaryActiveKey ,
1681+ message : "ordinary active target should stay gateway routed" ,
1682+ timeoutSeconds : 0 ,
1683+ } ) ;
1684+
1685+ const details = sessionsSendDetails ( result . details ) ;
1686+ expect ( details . status ) . toBe ( "accepted" ) ;
1687+ expect ( details . runId ) . toBe ( "ordinary-agent-run" ) ;
1688+ expect ( details . sessionKey ) . toBe ( ordinaryActiveKey ) ;
1689+ expect ( queueMessage ) . not . toHaveBeenCalled ( ) ;
15961690 const agentCalls = calls . filter ( ( call ) => call . method === "agent" ) ;
1597- expect (
1598- agentCalls . some (
1599- ( call ) =>
1600- ( call . params as { sessionKey ?: string } | undefined ) ?. sessionKey === runScopedCallerKey ,
1601- ) ,
1602- ) . toBe ( false ) ;
1603- const fallbackParams = agentCalls . find (
1604- ( call ) =>
1605- ( call . params as { sessionKey ?: string } | undefined ) ?. sessionKey === durableCallerKey ,
1606- ) ?. params as { inputProvenance ?: { sourceSessionKey ?: string } ; message ?: string } | undefined ;
1607- expect ( fallbackParams ?. message ) . toContain ( "[Inter-session message]" ) ;
1608- expect ( fallbackParams ?. message ) . toContain ( "[TASK-COMPLETE] re-portal occupancy ready" ) ;
1609- expect ( fallbackParams ?. inputProvenance ?. sourceSessionKey ) . toBe ( requesterKey ) ;
1610-
1611- await vi . waitFor ( ( ) => {
1612- const waitCall = calls . find (
1613- ( call ) =>
1614- call . method === "agent.wait" &&
1615- ( call . params as { runId ?: string } | undefined ) ?. runId === "fallback-run" ,
1616- ) ;
1617- expect ( waitCall ) . toBeDefined ( ) ;
1691+ expect ( agentCalls ) . toHaveLength ( 1 ) ;
1692+ expect ( agentParams ( agentCalls [ 0 ] ?? { } ) . sessionKey ) . toBe ( ordinaryActiveKey ) ;
1693+ } ) ;
1694+
1695+ it ( "sessions_send falls back from stranded cron run key to durable cron parent" , async ( ) => {
1696+ const calls : Array < { method ?: string ; params ?: unknown } > = [ ] ;
1697+ const runScopedCallerKey = "agent:leasing-ops:cron:monthly-utility:run:run-fast" ;
1698+ const durableCronCallerKey = "agent:leasing-ops:cron:monthly-utility" ;
1699+ const queueMessage = vi . fn ( async ( ) => { } ) ;
1700+ setActiveEmbeddedRun (
1701+ "caller-active-session" ,
1702+ {
1703+ queueMessage,
1704+ isStreaming : ( ) => false ,
1705+ isCompacting : ( ) => false ,
1706+ supportsTranscriptCommitWait : true ,
1707+ sourceReplyDeliveryMode : "message_tool_only" ,
1708+ abort : ( ) => { } ,
1709+ } ,
1710+ runScopedCallerKey ,
1711+ ) ;
1712+ callGatewayMock . mockImplementation ( async ( opts : unknown ) => {
1713+ const request = opts as { method ?: string ; params ?: unknown } ;
1714+ calls . push ( request ) ;
1715+ if ( request . method === "agent" ) {
1716+ return { runId : "durable-fallback-run" , status : "accepted" , acceptedAt : 2000 } ;
1717+ }
1718+ return { } ;
16181719 } ) ;
1619- await vi . waitFor ( ( ) => {
1620- const historyCall = calls . find (
1621- ( call ) =>
1622- call . method === "chat.history" &&
1623- ( call . params as { sessionKey ?: string } | undefined ) ?. sessionKey === durableCallerKey ,
1624- ) ;
1625- expect ( historyCall ) . toBeDefined ( ) ;
1720+
1721+ const tool = createOpenClawTools ( {
1722+ agentSessionKey : "agent:re-portal:main" ,
1723+ agentChannel : "telegram" ,
1724+ config : {
1725+ ...TEST_CONFIG ,
1726+ session : {
1727+ ...TEST_CONFIG . session ,
1728+ agentToAgent : { maxPingPongTurns : 0 } ,
1729+ } ,
1730+ } ,
1731+ } ) . find ( ( candidate ) => candidate . name === "sessions_send" ) ;
1732+ if ( ! tool ) {
1733+ throw new Error ( "missing sessions_send tool" ) ;
1734+ }
1735+
1736+ const result = await tool . execute ( "call-run-scoped-caller" , {
1737+ sessionKey : runScopedCallerKey ,
1738+ message : "[TASK-COMPLETE] re-portal occupancy ready" ,
1739+ timeoutSeconds : 0 ,
16261740 } ) ;
1741+
1742+ const details = sessionsSendDetails ( result . details ) ;
1743+ expect ( details . status ) . toBe ( "accepted" ) ;
1744+ expect ( details . runId ) . toBe ( "durable-fallback-run" ) ;
1745+ expect ( details . sessionKey ) . toBe ( runScopedCallerKey ) ;
1746+ expect ( queueMessage ) . not . toHaveBeenCalled ( ) ;
1747+ const agentCalls = calls . filter ( ( call ) => call . method === "agent" ) ;
1748+ expect ( agentCalls ) . toHaveLength ( 1 ) ;
1749+ const params = agentParams ( agentCalls [ 0 ] ?? { } ) ;
1750+ expect ( params . sessionKey ) . toBe ( durableCronCallerKey ) ;
1751+ expect ( params . message ) . toContain ( "[Inter-session message]" ) ;
1752+ expect ( params . message ) . toContain ( "[TASK-COMPLETE] re-portal occupancy ready" ) ;
1753+ } ) ;
1754+
1755+ it ( "sessions_send rejects non-cron run-looking keys without durable-session fallback" , async ( ) => {
1756+ const calls : Array < { method ?: string ; params ?: unknown } > = [ ] ;
1757+ const runScopedCallerKey = "agent:leasing-ops:slack:channel:c-room:run:run-fast" ;
1758+ const queueMessage = vi . fn ( async ( ) => { } ) ;
1759+ setActiveEmbeddedRun (
1760+ "caller-active-session" ,
1761+ {
1762+ queueMessage,
1763+ isStreaming : ( ) => false ,
1764+ isCompacting : ( ) => false ,
1765+ supportsTranscriptCommitWait : true ,
1766+ sourceReplyDeliveryMode : "message_tool_only" ,
1767+ abort : ( ) => { } ,
1768+ } ,
1769+ runScopedCallerKey ,
1770+ ) ;
1771+ callGatewayMock . mockImplementation ( async ( opts : unknown ) => {
1772+ const request = opts as { method ?: string ; params ?: unknown } ;
1773+ calls . push ( request ) ;
1774+ if ( request . method === "agent" ) {
1775+ return { runId : "durable-fallback-run" , status : "accepted" , acceptedAt : 2000 } ;
1776+ }
1777+ return { } ;
1778+ } ) ;
1779+
1780+ const tool = createOpenClawTools ( {
1781+ agentSessionKey : "agent:re-portal:main" ,
1782+ agentChannel : "telegram" ,
1783+ config : {
1784+ ...TEST_CONFIG ,
1785+ session : {
1786+ ...TEST_CONFIG . session ,
1787+ agentToAgent : { maxPingPongTurns : 0 } ,
1788+ } ,
1789+ } ,
1790+ } ) . find ( ( candidate ) => candidate . name === "sessions_send" ) ;
1791+ if ( ! tool ) {
1792+ throw new Error ( "missing sessions_send tool" ) ;
1793+ }
1794+
1795+ const result = await tool . execute ( "call-run-scoped-caller" , {
1796+ sessionKey : runScopedCallerKey ,
1797+ message : "[TASK-COMPLETE] re-portal occupancy ready" ,
1798+ timeoutSeconds : 0 ,
1799+ } ) ;
1800+
1801+ const details = sessionsSendDetails ( result . details ) ;
1802+ expect ( details . status ) . toBe ( "error" ) ;
1803+ expect ( details . sessionKey ) . toBe ( runScopedCallerKey ) ;
1804+ expect ( details . error ) . toContain ( "queue_message_failed reason=not_streaming" ) ;
1805+ expect ( queueMessage ) . not . toHaveBeenCalled ( ) ;
1806+ expect ( calls . some ( ( call ) => call . method === "agent" ) ) . toBe ( false ) ;
16271807 } ) ;
16281808
16291809 it ( "sessions_send preserves active delivery when transcript commit wait is unsupported" , async ( ) => {
@@ -1677,7 +1857,7 @@ describe("sessions tools", () => {
16771857 expect ( calls . some ( ( call ) => call . method === "agent" ) ) . toBe ( false ) ;
16781858 } ) ;
16791859
1680- it ( "sessions_send reports run-scoped fallback admission failures" , async ( ) => {
1860+ it ( "sessions_send reports run-scoped queue admission failures without gateway fallback " , async ( ) => {
16811861 const runScopedCallerKey = "agent:leasing-ops:cron:monthly-utility:run:run-fast" ;
16821862 const queueMessage = vi . fn ( async ( ) => {
16831863 throw new Error ( "active session ended before queued steering message was committed" ) ;
@@ -1720,7 +1900,12 @@ describe("sessions tools", () => {
17201900 expect ( details . status ) . toBe ( "error" ) ;
17211901 expect ( details . sessionKey ) . toBe ( runScopedCallerKey ) ;
17221902 expect ( details . error ) . toContain ( "queue_message_failed reason=runtime_rejected" ) ;
1723- expect ( details . error ) . toContain ( "fallback_failed error=gateway request timeout for agent" ) ;
1903+ expect ( details . error ) . not . toContain ( "fallback_failed" ) ;
1904+ expect (
1905+ callGatewayMock . mock . calls . some (
1906+ ( call ) => ( call [ 0 ] as { method ?: string } | undefined ) ?. method === "agent" ,
1907+ ) ,
1908+ ) . toBe ( false ) ;
17241909 } ) ;
17251910
17261911 it ( "sessions_send preserves terminal timeouts without starting A2A" , async ( ) => {
0 commit comments