mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 04:14:56 +00:00
Merge pull request #1533 from yatul/fix/teammate-run-streaming
fix(teams): stream teammate runs so long generations keep the connection alive
This commit is contained in:
2 files changed
+94
-1
No files matched your search
@@ -276,7 +276,17 @@ func handleTeammateMessage(
|
||||
SenderID: teammateSenderID, // real user who triggered the teammate dispatch (#915)
|
||||
Role: teammateRole, // RBAC role for admin bypass during teammate turn (#915)
|
||||
RunID: fmt.Sprintf("teammate-%s-%s", msg.Metadata[tools.MetaFromAgent], msg.Metadata[tools.MetaToAgent]),
|
||||
Stream: false,
|
||||
// Streamed for connection liveness, not for delivery. A teammate run is
|
||||
// never registered with the channel manager, so HandleAgentEvent drops its
|
||||
// chunks on the first line and nothing is delivered incrementally; the task
|
||||
// result still comes from the final RunResult. What streaming buys is the
|
||||
// response headers arriving immediately: a non-streamed request to a slow
|
||||
// reasoning model holds a silent connection for the whole generation, and
|
||||
// ResponseHeaderTimeout kills it — observed as
|
||||
// `http2: timeout awaiting response headers` on a member asked to produce a
|
||||
// large file, while the same model over the same provider was fine on a
|
||||
// streamed channel run.
|
||||
Stream: true,
|
||||
TeamTaskID: msg.Metadata[tools.MetaTeamTaskID],
|
||||
TeamWorkspace: msg.Metadata[tools.MetaTeamWorkspace],
|
||||
LeaderAgentID: msg.Metadata[tools.MetaLeaderAgentID],
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/nextlevelbuilder/goclaw/internal/agent"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/bus"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/channels"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/config"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/scheduler"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/tools"
|
||||
)
|
||||
|
||||
// A teammate run must ask the provider to stream. Without it a slow reasoning
|
||||
// model holds a silent connection for the whole generation and
|
||||
// ResponseHeaderTimeout kills the request before a single token is produced.
|
||||
//
|
||||
// The same test pins the other half of the contract: streaming here is for
|
||||
// connection liveness only. The run is deliberately never registered with the
|
||||
// channel manager, so HandleAgentEvent drops its chunks and nothing reaches a
|
||||
// user incrementally — the task result keeps coming from the final RunResult.
|
||||
func TestHandleTeammateMessageSchedulesStreamedRun(t *testing.T) {
|
||||
var gotReq agent.RunRequest
|
||||
ran := make(chan struct{})
|
||||
|
||||
sched := scheduler.NewScheduler(
|
||||
scheduler.DefaultLanes(),
|
||||
scheduler.QueueConfig{
|
||||
Mode: scheduler.QueueModeQueue,
|
||||
Cap: 1,
|
||||
Drop: scheduler.DropOld,
|
||||
MaxConcurrent: 1,
|
||||
},
|
||||
func(_ context.Context, req agent.RunRequest) (*agent.RunResult, error) {
|
||||
gotReq = req
|
||||
close(ran)
|
||||
return &agent.RunResult{Content: "member deliverable"}, nil
|
||||
},
|
||||
)
|
||||
defer sched.Stop()
|
||||
|
||||
channelMgr := channels.NewManager(nil)
|
||||
deps := &ConsumerDeps{
|
||||
Cfg: &config.Config{},
|
||||
Sched: sched,
|
||||
ChannelMgr: channelMgr,
|
||||
}
|
||||
|
||||
msg := bus.InboundMessage{
|
||||
Channel: tools.ChannelSystem,
|
||||
SenderID: "teammate:dashboard",
|
||||
AgentID: "coder",
|
||||
Content: "[Assigned task #1 (id: 00000000-0000-0000-0000-000000000001)]: build something",
|
||||
Metadata: map[string]string{
|
||||
tools.MetaOriginChannel: "telegram",
|
||||
tools.MetaOriginChatID: "12345",
|
||||
tools.MetaFromAgent: "brain",
|
||||
tools.MetaToAgent: "coder",
|
||||
},
|
||||
}
|
||||
|
||||
if !handleTeammateMessage(context.Background(), msg, deps) {
|
||||
t.Fatal("handleTeammateMessage() = false, want true for a teammate: message on the system channel")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ran:
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("teammate run was never scheduled")
|
||||
}
|
||||
|
||||
if !gotReq.Stream {
|
||||
t.Error("teammate run requested a non-streamed provider call: a slow model then holds a silent " +
|
||||
"connection for the whole generation until ResponseHeaderTimeout kills it")
|
||||
}
|
||||
|
||||
if delivered, last := channelMgr.InterimDeliverySnapshot(gotReq.RunID); delivered != 0 || last != "" {
|
||||
t.Errorf("teammate run is registered for channel delivery (delivered=%d, last=%q); "+
|
||||
"streamed chunks would reach a user incrementally", delivered, last)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user