11/** Tests cron before_agent_reply gating at the CLI runner entrypoint. */
22import { afterEach , beforeAll , beforeEach , describe , expect , it , vi } from "vitest" ;
33import { SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js" ;
4+ import type { CliOutput } from "./cli-output.js" ;
45import { cliBackendLog } from "./cli-runner/log.js" ;
56
67// vi.mock factories are hoisted above imports, so any references inside them
@@ -28,9 +29,9 @@ const {
2829 runBeforeAgentReplyMock : vi . fn < ( event : unknown , ctx : unknown ) => Promise < BeforeAgentReplyResult > > (
2930 async ( ) => undefined ,
3031 ) ,
31- executePreparedCliRunMock : vi . fn ( async ( _context : unknown , _cliSessionIdToUse ?: string ) => ( {
32- text : "" ,
33- } ) ) ,
32+ executePreparedCliRunMock : vi . fn <
33+ ( _context : unknown , _cliSessionIdToUse ?: string ) => Promise < CliOutput >
34+ > ( async ( ) => ( { text : "" } ) ) ,
3435 prepareCliRunContextMock : vi . fn ( ) ,
3536 closeClaudeLiveSessionForContextMock : vi . fn ( ) ,
3637 closeMcpLoopbackServerMock : vi . fn ( ) ,
@@ -262,6 +263,35 @@ describe("runCliAgent cron before_agent_reply seam", () => {
262263 expect ( executePreparedCliRunMock ) . toHaveBeenCalledTimes ( 1 ) ;
263264 } ) ;
264265
266+ it ( "reports confirmed CLI messaging delivery evidence without leaking it to later invocations" , async ( ) => {
267+ executePreparedCliRunMock . mockResolvedValueOnce ( {
268+ text : "sent" ,
269+ didSendViaMessagingTool : true ,
270+ messagingToolSentTargets : [
271+ {
272+ tool : "message" ,
273+ provider : "telegram" ,
274+ to : "chat123" ,
275+ } ,
276+ ] ,
277+ } ) ;
278+ executePreparedCliRunMock . mockResolvedValueOnce ( { text : "later" } ) ;
279+
280+ const firstResult = await runCliAgent ( baseRunParams ) ;
281+ expect ( firstResult . didSendViaMessagingTool ) . toBe ( true ) ;
282+ expect ( firstResult . messagingToolSentTargets ) . toEqual ( [
283+ expect . objectContaining ( {
284+ tool : "message" ,
285+ provider : "telegram" ,
286+ to : "chat123" ,
287+ } ) ,
288+ ] ) ;
289+
290+ const laterResult = await runCliAgent ( baseRunParams ) ;
291+ expect ( laterResult . didSendViaMessagingTool ) . toBeUndefined ( ) ;
292+ expect ( laterResult . messagingToolSentTargets ) . toBeUndefined ( ) ;
293+ } ) ;
294+
265295 it ( "can close temporary CLI live sessions after a run" , async ( ) => {
266296 executePreparedCliRunMock . mockResolvedValue ( { text : "real reply" } ) ;
267297
@@ -282,4 +312,27 @@ describe("runCliAgent cron before_agent_reply seam", () => {
282312 expect ( executePreparedCliRunMock ) . toHaveBeenCalledTimes ( 1 ) ;
283313 expect ( closeMcpLoopbackServerMock ) . toHaveBeenCalledTimes ( 1 ) ;
284314 } ) ;
315+
316+ it ( "preserves confirmed delivery when bundle MCP cleanup fails" , async ( ) => {
317+ executePreparedCliRunMock . mockResolvedValue ( {
318+ text : "" ,
319+ didSendViaMessagingTool : true ,
320+ } ) ;
321+ closeMcpLoopbackServerMock . mockRejectedValue ( new Error ( "loopback cleanup failed" ) ) ;
322+
323+ await expect (
324+ runCliAgent ( { ...baseRunParams , cleanupBundleMcpOnRunEnd : true } ) ,
325+ ) . resolves . toMatchObject ( {
326+ didSendViaMessagingTool : true ,
327+ } ) ;
328+ } ) ;
329+
330+ it ( "surfaces bundle MCP cleanup failures when nothing was delivered" , async ( ) => {
331+ executePreparedCliRunMock . mockResolvedValue ( { text : "real reply" } ) ;
332+ closeMcpLoopbackServerMock . mockRejectedValue ( new Error ( "loopback cleanup failed" ) ) ;
333+
334+ await expect ( runCliAgent ( { ...baseRunParams , cleanupBundleMcpOnRunEnd : true } ) ) . rejects . toThrow (
335+ "loopback cleanup failed" ,
336+ ) ;
337+ } ) ;
285338} ) ;
0 commit comments