@@ -6,10 +6,21 @@ import {
66 createActivatedToolTracker ,
77 advertisedTools ,
88 advertisedToolNamesForSessionMode ,
9+ coreToolNamesForSessionMode ,
910 CORE_TOOL_NAMES ,
1011 CATALOG_TOOL_NAMES ,
12+ type ToolAvailability ,
1113} from "./tool-search.js" ;
1214
15+ const FULL_AVAILABILITY : ToolAvailability = {
16+ hasGoalAtLaunch : true ,
17+ languageServerAvailable : true ,
18+ } ;
19+ const NO_AVAILABILITY : ToolAvailability = {
20+ hasGoalAtLaunch : false ,
21+ languageServerAvailable : false ,
22+ } ;
23+
1324const defs : ToolDefinition [ ] = [
1425 { name : "read_file" , description : "read a file" , inputSchema : { type : "object" , properties : { } , required : [ ] } } ,
1526 { name : "web_search" , description : "search the web for pages" , inputSchema : { type : "object" , properties : { } , required : [ ] } } ,
@@ -55,10 +66,49 @@ describe("createToolIndex", () => {
5566 } ) ;
5667
5768 test ( "orchestrator mode advertises task and search_agents; single mode omits them" , ( ) => {
58- expect ( advertisedToolNamesForSessionMode ( "orchestrator" ) ) . toContain ( "task" ) ;
59- expect ( advertisedToolNamesForSessionMode ( "orchestrator" ) ) . toContain ( "search_agents" ) ;
60- expect ( advertisedToolNamesForSessionMode ( "single" ) ) . not . toContain ( "task" ) ;
61- expect ( advertisedToolNamesForSessionMode ( "single" ) ) . not . toContain ( "search_agents" ) ;
69+ expect ( advertisedToolNamesForSessionMode ( "orchestrator" , FULL_AVAILABILITY ) ) . toContain ( "task" ) ;
70+ expect ( advertisedToolNamesForSessionMode ( "orchestrator" , FULL_AVAILABILITY ) ) . toContain ( "search_agents" ) ;
71+ expect ( advertisedToolNamesForSessionMode ( "single" , FULL_AVAILABILITY ) ) . not . toContain ( "task" ) ;
72+ expect ( advertisedToolNamesForSessionMode ( "single" , FULL_AVAILABILITY ) ) . not . toContain ( "search_agents" ) ;
73+ } ) ;
74+
75+ test ( "manage_tasks is advertised in both session modes regardless of availability" , ( ) => {
76+ expect ( coreToolNamesForSessionMode ( "single" , NO_AVAILABILITY ) ) . toContain ( "manage_tasks" ) ;
77+ expect ( coreToolNamesForSessionMode ( "orchestrator" , NO_AVAILABILITY ) ) . toContain ( "manage_tasks" ) ;
78+ } ) ;
79+
80+ test ( "present is never in the advertised core set — discovered via tool_search only" , ( ) => {
81+ expect ( CORE_TOOL_NAMES ) . not . toContain ( "present" ) ;
82+ expect ( coreToolNamesForSessionMode ( "orchestrator" , FULL_AVAILABILITY ) ) . not . toContain ( "present" ) ;
83+ } ) ;
84+
85+ test ( "manage_goal is advertised only when the session starts with a goal" , ( ) => {
86+ expect (
87+ coreToolNamesForSessionMode ( "orchestrator" , { hasGoalAtLaunch : true , languageServerAvailable : true } ) ,
88+ ) . toContain ( "manage_goal" ) ;
89+ expect (
90+ coreToolNamesForSessionMode ( "orchestrator" , { hasGoalAtLaunch : false , languageServerAvailable : true } ) ,
91+ ) . not . toContain ( "manage_goal" ) ;
92+ } ) ;
93+
94+ test ( "lsp is advertised only when a language server was detected at startup" , ( ) => {
95+ expect (
96+ coreToolNamesForSessionMode ( "orchestrator" , { hasGoalAtLaunch : false , languageServerAvailable : true } ) ,
97+ ) . toContain ( "lsp" ) ;
98+ expect (
99+ coreToolNamesForSessionMode ( "orchestrator" , { hasGoalAtLaunch : false , languageServerAvailable : false } ) ,
100+ ) . not . toContain ( "lsp" ) ;
101+ } ) ;
102+
103+ test ( "ask_operator is advertised regardless of session mode or availability" , ( ) => {
104+ expect ( coreToolNamesForSessionMode ( "single" , NO_AVAILABILITY ) ) . toContain ( "ask_operator" ) ;
105+ expect ( coreToolNamesForSessionMode ( "orchestrator" , NO_AVAILABILITY ) ) . toContain ( "ask_operator" ) ;
106+ } ) ;
107+
108+ test ( "the advertised set is deterministic — repeat calls with the same inputs are identical" , ( ) => {
109+ const first = coreToolNamesForSessionMode ( "orchestrator" , NO_AVAILABILITY ) ;
110+ const second = coreToolNamesForSessionMode ( "orchestrator" , NO_AVAILABILITY ) ;
111+ expect ( second ) . toEqual ( first ) ;
62112 } ) ;
63113
64114 test ( "returns nothing for an empty query" , ( ) => {
@@ -122,9 +172,11 @@ describe("advertisedTools", () => {
122172 ] ;
123173
124174 test ( "single session mode omits multi-agent tools from the wire prefix" , ( ) => {
125- const names = advertisedTools ( registry , [ ] , advertisedToolNamesForSessionMode ( "single" ) ) . map (
126- ( d ) => d . name ,
127- ) ;
175+ const names = advertisedTools (
176+ registry ,
177+ [ ] ,
178+ advertisedToolNamesForSessionMode ( "single" , FULL_AVAILABILITY ) ,
179+ ) . map ( ( d ) => d . name ) ;
128180 expect ( names ) . not . toContain ( "task" ) ;
129181 expect ( names ) . not . toContain ( "search_agents" ) ;
130182 expect ( names ) . toContain ( "read_file" ) ;
@@ -186,6 +238,23 @@ describe("advertisedTools", () => {
186238 expect ( names . slice ( tailIdx ) ) . toEqual ( [ "mcp__acme__do" , "mcp__linear__create_issue" ] ) ;
187239 } ) ;
188240
241+ test ( "the built-in prefix is byte-identical across repeated turns of the same session" , ( ) => {
242+ // Session-start availability is computed once and must never be
243+ // re-evaluated per turn — simulate several turns by calling with the same
244+ // captured prefix and confirm the wire array never drifts.
245+ const prefix = advertisedToolNamesForSessionMode ( "orchestrator" , {
246+ hasGoalAtLaunch : false ,
247+ languageServerAvailable : true ,
248+ } ) ;
249+ const turn1 = JSON . stringify ( advertisedTools ( registry , [ ] , prefix ) ) ;
250+ const turn2 = JSON . stringify ( advertisedTools ( registry , [ ] , prefix ) ) ;
251+ const turn3 = JSON . stringify ( advertisedTools ( registry , [ "mcp__linear__create_issue" ] , prefix ) ) ;
252+ expect ( turn2 ) . toBe ( turn1 ) ;
253+ // Growth from a mid-session discovery only appends — the prefix itself
254+ // (everything before the activated tail) still matches turn 1 exactly.
255+ expect ( turn3 . startsWith ( turn1 . slice ( 0 , - 1 ) ) ) . toBe ( true ) ;
256+ } ) ;
257+
189258 test ( "tool_search never returns an already-advertised built-in" , ( ) => {
190259 for ( const name of [ ...CORE_TOOL_NAMES , ...CATALOG_TOOL_NAMES ] ) {
191260 expect ( index . search ( name ) ) . not . toContain ( name ) ;
0 commit comments