@@ -1242,13 +1242,10 @@ describe("updateToolDefinitions rewrites infer tools", () => {
12421242 expect ( JSON . stringify ( after ) ) . toBe ( JSON . stringify ( before ) ) ;
12431243 } ) ;
12441244
1245- // CL-7868 (direction A): the provider cache is a prefix cache keyed on the
1246- // tools array, so a tool_search turn that promotes a genuinely new tool must
1247- // not reshape the wire set mid-session. The call gate opens (the model
1248- // invokes the tool from the search result's schema) while the advertised
1249- // array holds steady; the growth event lands at the next cache-safe
1250- // boundary (compaction fold), appended after the untouched fixed prefix.
1251- test ( "a tool_search turn promoting a genuinely new tool leaves the wire byte-identical until the fold commits it" , async ( ) => {
1245+ // Promotion must open the call gate and commit the schema onto the next
1246+ // infer tools array. Holding growth until compaction left MCP names
1247+ // dispatchable in the runner but missing from the provider tools list.
1248+ test ( "a tool_search turn promoting a genuinely new tool commits it onto the next infer wire" , async ( ) => {
12521249 const linearTool = {
12531250 name : "mcp__linear__list_issues" ,
12541251 description : "list issues" ,
@@ -1289,42 +1286,26 @@ describe("updateToolDefinitions rewrites infer tools", () => {
12891286 expect ( advertised . activated . activate ( [ "mcp__linear__list_issues" ] ) ) . toBe (
12901287 true ,
12911288 ) ;
1289+ expect ( advertised . flushPromotions ( ) ) . toBe ( true ) ;
12921290 director . updateToolDefinitions (
12931291 advertised . computeAdvertised ( toolset . dynamicRunner . currentDefinitions ( ) ) ,
12941292 ) ;
12951293
1296- // Mid-session the wire is byte-identical — the hot prefix never grows —
1297- // while the gate is open so the model can invoke the match from the
1298- // result card's schema.
12991294 const after = await firstInferTools (
13001295 director ,
13011296 makeMessageReceivedEvent ( "continue" ) ,
13021297 ) ;
1303- expect ( JSON . stringify ( after ) ) . toBe ( JSON . stringify ( before ) ) ;
1298+ expect ( JSON . stringify ( after ) ) . not . toBe ( JSON . stringify ( before ) ) ;
13041299 expect ( advertised . isAdvertised ( "mcp__linear__list_issues" ) ) . toBe ( true ) ;
1305-
1306- // Cache-safe boundary (compaction fold): the pending promotion commits and
1307- // the next turn declares it after the untouched fixed prefix.
1308- expect ( advertised . flushPromotions ( ) ) . toBe ( true ) ;
1309- director . updateToolDefinitions (
1310- advertised . computeAdvertised ( toolset . dynamicRunner . currentDefinitions ( ) ) ,
1311- ) ;
1312- const folded = await firstInferTools (
1313- director ,
1314- makeMessageReceivedEvent ( "after fold" ) ,
1315- ) ;
1316- const foldedNames = ( folded as { name : string } [ ] ) . map ( ( t ) => t . name ) ;
1317- expect ( foldedNames ) . toContain ( "mcp__linear__list_issues" ) ;
1300+ const afterNames = ( after as { name : string } [ ] ) . map ( ( t ) => t . name ) ;
1301+ expect ( afterNames ) . toContain ( "mcp__linear__list_issues" ) ;
13181302 const beforeNames = ( before as { name : string } [ ] )
13191303 . map ( ( t ) => t . name )
13201304 . filter ( ( n ) => n !== "submit_output" ) ;
1321- const foldedPrefix = foldedNames . filter (
1305+ const afterPrefix = afterNames . filter (
13221306 ( n ) => n !== "submit_output" && n !== "mcp__linear__list_issues" ,
13231307 ) ;
1324- expect ( foldedPrefix ) . toEqual ( beforeNames ) ;
1325- expect ( foldedNames . indexOf ( "mcp__linear__list_issues" ) ) . toBe (
1326- beforeNames . length ,
1327- ) ;
1308+ expect ( afterPrefix ) . toEqual ( beforeNames ) ;
13281309
13291310 await toolset . dispose ( ) ;
13301311 } ) ;
@@ -1347,13 +1328,9 @@ describe("updateToolDefinitions rewrites infer tools", () => {
13471328 expect ( inferToolNames ( inferAction ) ) . toContain ( "submit_output" ) ;
13481329 } ) ;
13491330
1350- // End-to-end: tool_search matches an MCP tool; the runner's gate-only promote
1351- // wiring (mirrored here via createAdvertisedToolset + updateToolDefinitions)
1352- // keeps it off the wire the next turn — the hot prefix stays byte-identical
1353- // while the gate lets the model call it from the result card. The fold
1354- // commits the pending promotion with the tool's full definition, appended
1355- // after the untouched fixed prefix, and the array then holds steady.
1356- test ( "a tool_search match stays off the wire until the fold, then holds stable" , async ( ) => {
1331+ // tool_search + flush + updateToolDefinitions puts the match on the next
1332+ // infer wire; a further turn with no new discovery stays byte-identical.
1333+ test ( "a tool_search match is on the next infer wire and then holds stable" , async ( ) => {
13571334 const linearTool = {
13581335 name : "mcp__linear__list_issues" ,
13591336 description : "list issues" ,
@@ -1399,38 +1376,19 @@ describe("updateToolDefinitions rewrites infer tools", () => {
13991376 // Simulate the runner's gate-only promoter plus its boundary wire push:
14001377 // the gate opens but the wire recompute is byte-identical until the fold.
14011378 advertised . activated . activate ( [ "mcp__linear__list_issues" ] ) ;
1402- director . updateToolDefinitions (
1403- computeAdvertised ( toolset . dynamicRunner . currentDefinitions ( ) ) ,
1404- ) ;
1405-
1406- const gated = await firstInferTools (
1407- director ,
1408- makeMessageReceivedEvent ( "continue" ) ,
1409- ) ;
1410- expect ( ( gated as { name : string } [ ] ) . map ( ( t ) => t . name ) ) . not . toContain (
1411- "mcp__linear__list_issues" ,
1412- ) ;
1413- expect ( JSON . stringify ( gated ) ) . toBe ( beforeJson ) ;
1414-
1415- // Simulate the compaction fold: commit the pending promotion, push the
1416- // refreshed set, and the tool is declared — appended after the fixed
1417- // built-in prefix, which survives untouched ahead of it.
14181379 advertised . flushPromotions ( ) ;
14191380 director . updateToolDefinitions (
14201381 computeAdvertised ( toolset . dynamicRunner . currentDefinitions ( ) ) ,
14211382 ) ;
14221383
14231384 const after = await firstInferTools (
14241385 director ,
1425- makeMessageReceivedEvent ( "after fold " ) ,
1386+ makeMessageReceivedEvent ( "continue " ) ,
14261387 ) ;
14271388 const afterTools = after as { name : string } [ ] ;
14281389 const afterNames = afterTools . map ( ( t ) => t . name ) ;
14291390 expect ( afterNames ) . toContain ( "mcp__linear__list_issues" ) ;
1430- // submit_output rides along separately (see withCurrentTools), appended
1431- // after computeAdvertised's result every turn — strip it before comparing
1432- // the fixed built-in prefix, which must survive untouched ahead of the
1433- // newly appended MCP tool.
1391+ expect ( JSON . stringify ( after ) ) . not . toBe ( beforeJson ) ;
14341392 const beforePrefix = beforeNames . filter ( ( n ) => n !== "submit_output" ) ;
14351393 const afterPrefix = afterNames . filter (
14361394 ( n ) => n !== "submit_output" && n !== "mcp__linear__list_issues" ,
@@ -1440,13 +1398,11 @@ describe("updateToolDefinitions rewrites infer tools", () => {
14401398 beforePrefix . length ,
14411399 ) ;
14421400
1443- // A further turn with no new discovery stays byte-identical to `after`.
14441401 const stable = await firstInferTools (
14451402 director ,
14461403 makeMessageReceivedEvent ( "keep going" ) ,
14471404 ) ;
14481405 expect ( JSON . stringify ( stable ) ) . toBe ( JSON . stringify ( after ) ) ;
1449- expect ( JSON . stringify ( after ) ) . not . toBe ( beforeJson ) ;
14501406
14511407 await toolset . dispose ( ) ;
14521408 } ) ;
0 commit comments