@@ -29,12 +29,24 @@ import {
2929 resolvePingPongTurns ,
3030} from "./sessions-send-helpers.js" ;
3131
32- const SessionsSendToolSchema = Type . Object ( {
33- sessionKey : Type . Optional ( Type . String ( ) ) ,
34- label : Type . Optional ( Type . String ( ) ) ,
35- message : Type . String ( ) ,
36- timeoutSeconds : Type . Optional ( Type . Integer ( { minimum : 0 } ) ) ,
37- } ) ;
32+ const SessionsSendToolSchema = Type . Union ( [
33+ Type . Object (
34+ {
35+ sessionKey : Type . String ( ) ,
36+ message : Type . String ( ) ,
37+ timeoutSeconds : Type . Optional ( Type . Integer ( { minimum : 0 } ) ) ,
38+ } ,
39+ { additionalProperties : false } ,
40+ ) ,
41+ Type . Object (
42+ {
43+ label : Type . String ( ) ,
44+ message : Type . String ( ) ,
45+ timeoutSeconds : Type . Optional ( Type . Integer ( { minimum : 0 } ) ) ,
46+ } ,
47+ { additionalProperties : false } ,
48+ ) ,
49+ ] ) ;
3850
3951export function createSessionsSendTool ( opts ?: {
4052 agentSessionKey ?: string ;
@@ -49,83 +61,124 @@ export function createSessionsSendTool(opts?: {
4961 parameters : SessionsSendToolSchema ,
5062 execute : async ( _toolCallId , args ) => {
5163 const params = args as Record < string , unknown > ;
52- let sessionKey = readStringParam ( params , "sessionKey" ) ;
53- const labelParam = readStringParam ( params , "label" ) ;
5464 const message = readStringParam ( params , "message" , { required : true } ) ;
5565 const cfg = loadConfig ( ) ;
66+ const { mainKey, alias } = resolveMainSessionAlias ( cfg ) ;
67+ const visibility =
68+ cfg . agents ?. defaults ?. sandbox ?. sessionToolsVisibility ?? "spawned" ;
69+ const requesterInternalKey =
70+ typeof opts ?. agentSessionKey === "string" && opts . agentSessionKey . trim ( )
71+ ? resolveInternalSessionKey ( {
72+ key : opts . agentSessionKey ,
73+ alias,
74+ mainKey,
75+ } )
76+ : undefined ;
77+ const restrictToSpawned =
78+ opts ?. sandboxed === true &&
79+ visibility === "spawned" &&
80+ requesterInternalKey &&
81+ ! isSubagentSessionKey ( requesterInternalKey ) ;
5682
57- // Lookup by label if sessionKey not provided
58- if ( ! sessionKey && labelParam ) {
59- const listResult = ( await callGateway ( {
83+ const sessionKeyParam = readStringParam ( params , "sessionKey" ) ;
84+ const labelParam = readStringParam ( params , "label" ) ;
85+ if ( sessionKeyParam && labelParam ) {
86+ return jsonResult ( {
87+ runId : crypto . randomUUID ( ) ,
88+ status : "error" ,
89+ error : "Provide either sessionKey or label (not both)." ,
90+ } ) ;
91+ }
92+
93+ const listSessions = async ( listParams : Record < string , unknown > ) => {
94+ const result = ( await callGateway ( {
6095 method : "sessions.list" ,
61- params : { activeMinutes : 1440 } , // Last 24h
96+ params : listParams ,
6297 timeoutMs : 10_000 ,
63- } ) ) as { sessions ?: Array < { key : string ; label ?: string } > } ;
64- const match = listResult . sessions ?. find (
65- ( s ) => s . label === labelParam ,
66- ) ;
67- if ( ! match ) {
98+ } ) ) as { sessions ?: Array < Record < string , unknown > > } ;
99+ return Array . isArray ( result ?. sessions ) ? result . sessions : [ ] ;
100+ } ;
101+
102+ const activeMinutes = 24 * 60 ;
103+ const visibleSessions = restrictToSpawned
104+ ? await listSessions ( {
105+ activeMinutes,
106+ includeGlobal : false ,
107+ includeUnknown : false ,
108+ limit : 500 ,
109+ spawnedBy : requesterInternalKey ,
110+ } )
111+ : undefined ;
112+
113+ let sessionKey = sessionKeyParam ;
114+ if ( ! sessionKey && labelParam ) {
115+ const sessions =
116+ visibleSessions ??
117+ ( await listSessions ( {
118+ activeMinutes,
119+ includeGlobal : false ,
120+ includeUnknown : false ,
121+ limit : 500 ,
122+ } ) ) ;
123+ const matches = sessions . filter ( ( entry ) => {
124+ const label =
125+ typeof entry ?. label === "string" ? entry . label : undefined ;
126+ return label === labelParam ;
127+ } ) ;
128+ if ( matches . length === 0 ) {
129+ if ( restrictToSpawned ) {
130+ return jsonResult ( {
131+ runId : crypto . randomUUID ( ) ,
132+ status : "forbidden" ,
133+ error : `Session not visible from this sandboxed agent session: label=${ labelParam } ` ,
134+ } ) ;
135+ }
68136 return jsonResult ( {
137+ runId : crypto . randomUUID ( ) ,
69138 status : "error" ,
70139 error : `No session found with label: ${ labelParam } ` ,
71140 } ) ;
72141 }
73- sessionKey = match . key ;
142+ if ( matches . length > 1 ) {
143+ const keys = matches
144+ . map ( ( entry ) => ( typeof entry ?. key === "string" ? entry . key : "" ) )
145+ . filter ( Boolean )
146+ . join ( ", " ) ;
147+ return jsonResult ( {
148+ runId : crypto . randomUUID ( ) ,
149+ status : "error" ,
150+ error : `Multiple sessions found with label: ${ labelParam } ${ keys ? ` (${ keys } )` : "" } ` ,
151+ } ) ;
152+ }
153+ const key = matches [ 0 ] ?. key ;
154+ if ( typeof key !== "string" || ! key . trim ( ) ) {
155+ return jsonResult ( {
156+ runId : crypto . randomUUID ( ) ,
157+ status : "error" ,
158+ error : `Invalid session entry for label: ${ labelParam } ` ,
159+ } ) ;
160+ }
161+ sessionKey = key ;
74162 }
75163
76164 if ( ! sessionKey ) {
77165 return jsonResult ( {
166+ runId : crypto . randomUUID ( ) ,
78167 status : "error" ,
79168 error : "Either sessionKey or label is required" ,
80169 } ) ;
81170 }
82- const { mainKey, alias } = resolveMainSessionAlias ( cfg ) ;
83- const visibility =
84- cfg . agents ?. defaults ?. sandbox ?. sessionToolsVisibility ?? "spawned" ;
85- const requesterInternalKey =
86- typeof opts ?. agentSessionKey === "string" && opts . agentSessionKey . trim ( )
87- ? resolveInternalSessionKey ( {
88- key : opts . agentSessionKey ,
89- alias,
90- mainKey,
91- } )
92- : undefined ;
171+
93172 const resolvedKey = resolveInternalSessionKey ( {
94173 key : sessionKey ,
95174 alias,
96175 mainKey,
97176 } ) ;
98- const restrictToSpawned =
99- opts ?. sandboxed === true &&
100- visibility === "spawned" &&
101- requesterInternalKey &&
102- ! isSubagentSessionKey ( requesterInternalKey ) ;
177+
103178 if ( restrictToSpawned ) {
104- try {
105- const list = ( await callGateway ( {
106- method : "sessions.list" ,
107- params : {
108- includeGlobal : false ,
109- includeUnknown : false ,
110- limit : 500 ,
111- spawnedBy : requesterInternalKey ,
112- } ,
113- } ) ) as { sessions ?: Array < Record < string , unknown > > } ;
114- const sessions = Array . isArray ( list ?. sessions ) ? list . sessions : [ ] ;
115- const ok = sessions . some ( ( entry ) => entry ?. key === resolvedKey ) ;
116- if ( ! ok ) {
117- return jsonResult ( {
118- runId : crypto . randomUUID ( ) ,
119- status : "forbidden" ,
120- error : `Session not visible from this sandboxed agent session: ${ sessionKey } ` ,
121- sessionKey : resolveDisplaySessionKey ( {
122- key : sessionKey ,
123- alias,
124- mainKey,
125- } ) ,
126- } ) ;
127- }
128- } catch {
179+ const sessions = visibleSessions ?? [ ] ;
180+ const ok = sessions . some ( ( entry ) => entry ?. key === resolvedKey ) ;
181+ if ( ! ok ) {
129182 return jsonResult ( {
130183 runId : crypto . randomUUID ( ) ,
131184 status : "forbidden" ,
0 commit comments