Skip to content

Commit 524cf68

Browse files
author
NGUYEN Duc Trung
committed
fix: address PR review comments for subagent custom tools
- Fix onDestroy comment to reflect actual call site (Disconnect, not Destroy) - Return slice element pointer directly in getAgentConfig to avoid copy - Guard against empty toolCallID in onSubagentStarted to prevent map key collision - Use non-blocking channel send in E2E test to prevent deadlock on retries - Strengthen unsupported-tool test assertion (require non-nil content)
1 parent c826cfa commit 524cf68

File tree

3 files changed

+37
-29
lines changed

3 files changed

+37
-29
lines changed

go/client.go

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,14 @@ type subagentInstance struct {
8181
// }
8282
// defer client.Stop()
8383
type Client struct {
84-
options ClientOptions
85-
process *exec.Cmd
86-
client *jsonrpc2.Client
87-
actualPort int
88-
actualHost string
89-
state ConnectionState
90-
sessions map[string]*Session
91-
sessionsMux sync.Mutex
84+
options ClientOptions
85+
process *exec.Cmd
86+
client *jsonrpc2.Client
87+
actualPort int
88+
actualHost string
89+
state ConnectionState
90+
sessions map[string]*Session
91+
sessionsMux sync.Mutex
9292

9393
// childToParent maps childSessionID → parentSessionID.
9494
// Populated exclusively from authoritative protocol signals.
@@ -151,16 +151,16 @@ func NewClient(options *ClientOptions) *Client {
151151
}
152152

153153
client := &Client{
154-
options: opts,
155-
state: StateDisconnected,
154+
options: opts,
155+
state: StateDisconnected,
156156
sessions: make(map[string]*Session),
157157
childToParent: make(map[string]string),
158158
childToAgent: make(map[string]string),
159159
subagentInstances: make(map[string]map[string]*subagentInstance),
160160
actualHost: "localhost",
161-
isExternalServer: false,
162-
useStdio: true,
163-
autoStart: true, // default
161+
isExternalServer: false,
162+
useStdio: true,
163+
autoStart: true, // default
164164
}
165165

166166
if options != nil {
@@ -1569,15 +1569,19 @@ func (c *Client) onSubagentStarted(parentSessionID string, event SessionEvent) {
15691569
c.sessionsMux.Lock()
15701570
defer c.sessionsMux.Unlock()
15711571

1572-
// Track instance by toolCallID (unique per launch)
1573-
if c.subagentInstances[parentSessionID] == nil {
1574-
c.subagentInstances[parentSessionID] = make(map[string]*subagentInstance)
1575-
}
1576-
c.subagentInstances[parentSessionID][toolCallID] = &subagentInstance{
1577-
agentName: agentName,
1578-
toolCallID: toolCallID,
1579-
childSessionID: childSessionID,
1580-
startedAt: event.Timestamp,
1572+
// Track instance by toolCallID (unique per launch).
1573+
// Skip tracking when toolCallID is empty — multiple launches would collide
1574+
// on the empty-string key and overwrite each other.
1575+
if toolCallID != "" {
1576+
if c.subagentInstances[parentSessionID] == nil {
1577+
c.subagentInstances[parentSessionID] = make(map[string]*subagentInstance)
1578+
}
1579+
c.subagentInstances[parentSessionID][toolCallID] = &subagentInstance{
1580+
agentName: agentName,
1581+
toolCallID: toolCallID,
1582+
childSessionID: childSessionID,
1583+
startedAt: event.Timestamp,
1584+
}
15811585
}
15821586

15831587
// Eagerly map child→parent and child→agent

go/internal/e2e/subagent_tool_test.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ func TestSubagentCustomTools(t *testing.T) {
3939
func(params struct {
4040
Result string `json:"result" jsonschema:"The result to save"`
4141
}, inv copilot.ToolInvocation) (string, error) {
42-
toolInvoked <- params.Result
42+
select {
43+
case toolInvoked <- params.Result:
44+
default:
45+
}
4346
return "saved: " + params.Result, nil
4447
}),
4548
},
@@ -127,9 +130,11 @@ func TestSubagentCustomTools(t *testing.T) {
127130
if err != nil {
128131
t.Fatalf("Failed to get assistant message: %v", err)
129132
}
130-
if answer.Data.Content != nil {
131-
t.Logf("Response: %s", *answer.Data.Content)
133+
if answer.Data.Content == nil {
134+
t.Fatal("Expected assistant message content but got nil")
132135
}
133-
// The restricted_tool handler should NOT have been called (assertion in handler above)
136+
t.Logf("Response: %s", *answer.Data.Content)
137+
// The primary assertion is the t.Error inside restricted_tool's handler above.
138+
// We don't assert on response text because LLM output is non-deterministic.
134139
})
135140
}

go/session.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ type Session struct {
6666
hooksMux sync.RWMutex
6767
transformCallbacks map[string]SectionTransformFn
6868
transformMu sync.Mutex
69-
onDestroy func() // set by Client when session is created; called by Destroy()
69+
onDestroy func() // set by Client when session is created; called by Disconnect() (Destroy() delegates to Disconnect())
7070
customAgents []CustomAgentConfig // agent configs from SessionConfig
7171

7272
// eventCh serializes user event handler dispatch. dispatchEvent enqueues;
@@ -89,8 +89,7 @@ func (s *Session) WorkspacePath() string {
8989
func (s *Session) getAgentConfig(agentName string) *CustomAgentConfig {
9090
for i := range s.customAgents {
9191
if s.customAgents[i].Name == agentName {
92-
config := s.customAgents[i]
93-
return &config
92+
return &s.customAgents[i]
9493
}
9594
}
9695
return nil

0 commit comments

Comments
 (0)