11import { describe , expect , test } from "bun:test" ;
2- import { mkdtemp } from "node:fs/promises" ;
2+ import { mkdtemp , rm } from "node:fs/promises" ;
33import { tmpdir } from "node:os" ;
44import { join } from "node:path" ;
55import type { Agent , DirectorRegistry } from "@intx/agent" ;
@@ -13,11 +13,14 @@ import type {
1313import { withMockedModuleDuring } from "../../testkit/mock-module.js" ;
1414import type { ChatDirector } from "../agent/director.js" ;
1515import { authzParityDefinitions } from "../agent/tool-aliases.js" ;
16+ import { syncRunStateHandle , type RunStateHandle } from "./active-run.js" ;
1617import {
18+ commitIdlePromotionPrune ,
1719 createAdvertisedToolset ,
1820 loadSessionLocalSettings ,
1921 type ChatAgentWiring ,
2022} from "./assemble-runtime.js" ;
23+ import { loadState , saveState } from "./state.js" ;
2124
2225function def ( name : string ) : ToolDefinition {
2326 return {
@@ -182,6 +185,102 @@ describe("createAdvertisedToolset", () => {
182185 expect ( pruneIdlePromotions ( ) ) . toBe ( false ) ;
183186 } ) ;
184187
188+ test ( "fold prune persist omits activatedTools from run.json and the crash handle" , async ( ) => {
189+ const cwd = await mkdtemp ( join ( tmpdir ( ) , "fold-prune-persist-cwd-" ) ) ;
190+ const home = await mkdtemp ( join ( tmpdir ( ) , "fold-prune-persist-home-" ) ) ;
191+ try {
192+ const {
193+ activated,
194+ computeAdvertised,
195+ flushPromotions,
196+ pruneIdlePromotions,
197+ } = createAdvertisedToolset ( wiring ( ) ) ;
198+ const defs = [ def ( "read_file" ) , def ( "mcp__acme__do" ) ] ;
199+ activated . activate ( [ "mcp__acme__do" ] ) ;
200+ expect ( flushPromotions ( ) ) . toBe ( true ) ;
201+
202+ const sessionId = "fold-prune-persist" ;
203+ const base = {
204+ status : "running" as const ,
205+ turnsUsed : 2 ,
206+ task : "task" ,
207+ startedAt : 1 ,
208+ } ;
209+ await saveState (
210+ cwd ,
211+ sessionId ,
212+ { ...base , activatedTools : activated . list ( ) } ,
213+ home ,
214+ ) ;
215+ const handle : RunStateHandle = {
216+ sessionId,
217+ cwd,
218+ task : base . task ,
219+ startedAt : base . startedAt ,
220+ turnsUsed : base . turnsUsed ,
221+ activatedTools : activated . list ( ) ,
222+ } ;
223+
224+ let refreshed = false ;
225+ let persistWrite : Promise < void > | undefined ;
226+ expect (
227+ commitIdlePromotionPrune ( {
228+ pruneIdlePromotions,
229+ refreshAdvertised : ( ) => {
230+ refreshed = true ;
231+ expect ( computeAdvertised ( defs ) . map ( ( d ) => d . name ) ) . not . toContain (
232+ "mcp__acme__do" ,
233+ ) ;
234+ } ,
235+ persist : ( ) => {
236+ const tools = activated . list ( ) ;
237+ syncRunStateHandle ( handle , {
238+ turnsUsed : base . turnsUsed ,
239+ task : base . task ,
240+ startedAt : base . startedAt ,
241+ activatedTools : tools ,
242+ } ) ;
243+ persistWrite = saveState (
244+ cwd ,
245+ sessionId ,
246+ {
247+ ...base ,
248+ ...( tools . length > 0 ? { activatedTools : tools } : { } ) ,
249+ } ,
250+ home ,
251+ ) ;
252+ } ,
253+ } ) ,
254+ ) . toBe ( true ) ;
255+ expect ( refreshed ) . toBe ( true ) ;
256+ expect ( handle . activatedTools ) . toEqual ( [ ] ) ;
257+ if ( persistWrite === undefined ) {
258+ throw new Error ( "fold prune persist did not write" ) ;
259+ }
260+ await persistWrite ;
261+
262+ const loaded = await loadState ( cwd , sessionId , home ) ;
263+ expect ( loaded . kind ) . toBe ( "ok" ) ;
264+ if ( loaded . kind !== "ok" ) return ;
265+ expect ( loaded . state . activatedTools ) . toBeUndefined ( ) ;
266+
267+ expect (
268+ commitIdlePromotionPrune ( {
269+ pruneIdlePromotions,
270+ refreshAdvertised : ( ) => {
271+ throw new Error ( "idle prune must not refresh twice" ) ;
272+ } ,
273+ persist : ( ) => {
274+ throw new Error ( "idle prune must not persist twice" ) ;
275+ } ,
276+ } ) ,
277+ ) . toBe ( false ) ;
278+ } finally {
279+ await rm ( cwd , { recursive : true , force : true } ) ;
280+ await rm ( home , { recursive : true , force : true } ) ;
281+ }
282+ } ) ;
283+
185284 test ( "pinned tools are advertised before any activation and survive clear" , ( ) => {
186285 const { activated, computeAdvertised } = createAdvertisedToolset (
187286 wiring ( { pinnedTools : [ "mcp__linear__save_issue" ] } ) ,
0 commit comments