Files
tutti/packages/agent/daemon/runtime/claude_sdk_stop_task_test.go
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

161 lines
5.2 KiB
Go

package agentruntime
import (
"context"
"encoding/json"
"errors"
"sync"
"testing"
)
// stopTaskAckConnection acks every request; stop_task acks carry the stopped
// flag in the payload like the real sidecar response.
type stopTaskAckConnection struct {
mu sync.Mutex
sent []claudeSDKSidecarRequest
frames []ProcessFrame
stopped bool
}
func (c *stopTaskAckConnection) Send(data []byte) error {
var request claudeSDKSidecarRequest
if err := json.Unmarshal(data, &request); err != nil {
return err
}
event := claudeSDKSidecarEvent{Version: claudeSDKSidecarProtocolVersion, ID: request.ID, Type: "ok"}
if request.Type == "stop_task" {
event.Payload = map[string]any{"stopped": c.stopped}
}
response, err := json.Marshal(event)
if err != nil {
return err
}
c.mu.Lock()
c.sent = append(c.sent, request)
c.frames = append(c.frames, ProcessFrame{Stdout: append(response, '\n')})
c.mu.Unlock()
return nil
}
func (c *stopTaskAckConnection) Recv() (ProcessFrame, error) {
c.mu.Lock()
defer c.mu.Unlock()
if len(c.frames) == 0 {
return ProcessFrame{}, errors.New("stop task ack connection has no frames")
}
frame := versionClaudeSDKTestFrame(c.frames[0])
c.frames = c.frames[1:]
return frame, nil
}
func (*stopTaskAckConnection) Close() error {
return nil
}
func (c *stopTaskAckConnection) sentRequests() []claudeSDKSidecarRequest {
c.mu.Lock()
defer c.mu.Unlock()
return append([]claudeSDKSidecarRequest(nil), c.sent...)
}
func TestClaudeCodeSDKAdapterCancelTargetsStopsChildTaskWithoutRootInterrupt(t *testing.T) {
adapter := NewClaudeCodeSDKAdapter(nil)
session := standardTestSession(ProviderClaudeCode)
conn := &stopTaskAckConnection{stopped: true}
adapterSession := &claudeSDKAdapterSession{
conn: conn,
reader: &claudeSDKLineReader{conn: conn},
pendingRequests: make(map[string]*pendingInteractiveRequest),
liveState: newClaudeSDKLiveState(),
}
adapter.storeSession(session.AgentSessionID, adapterSession)
child, _, _, ok := adapterSession.updateClaudeSDKChild(session, claudeSDKChildUpdate{
Key: "toolu-agent",
ParentToolUseID: "toolu-agent",
RootTurnID: "turn-1",
TaskID: "task-1",
Async: true,
Started: true,
})
if !ok || child.AgentSessionID == "" {
t.Fatalf("child session not registered: %#v", child)
}
result, err := adapter.CancelTargets(context.Background(), session, []CancelTarget{
{AgentSessionID: child.AgentSessionID, TurnID: child.TurnID},
}, "user_request")
if err != nil {
t.Fatalf("CancelTargets: %v", err)
}
if len(result.ConfirmedTargets) != 1 || result.ConfirmedTargets[0].AgentSessionID != child.AgentSessionID {
t.Fatalf("confirmed targets = %#v, want the child target", result.ConfirmedTargets)
}
sent := conn.sentRequests()
if len(sent) != 1 || sent[0].Type != "stop_task" {
t.Fatalf("sent requests = %#v, want a single stop_task", sent)
}
if sent[0].Payload["taskId"] != "task-1" || sent[0].Payload["agentSessionId"] != session.AgentSessionID {
t.Fatalf("stop_task payload = %#v, want task-1 on the root session", sent[0].Payload)
}
}
func TestClaudeCodeSDKAdapterCancelTargetsUnknownChildIsIdempotentNoop(t *testing.T) {
adapter := NewClaudeCodeSDKAdapter(nil)
session := standardTestSession(ProviderClaudeCode)
conn := &stopTaskAckConnection{stopped: true}
adapterSession := &claudeSDKAdapterSession{
conn: conn,
reader: &claudeSDKLineReader{conn: conn},
pendingRequests: make(map[string]*pendingInteractiveRequest),
liveState: newClaudeSDKLiveState(),
}
adapter.storeSession(session.AgentSessionID, adapterSession)
result, err := adapter.CancelTargets(context.Background(), session, []CancelTarget{
{AgentSessionID: "child-unknown", TurnID: "turn-unknown"},
}, "user_request")
if err != nil {
t.Fatalf("CancelTargets: %v", err)
}
if len(result.ConfirmedTargets) != 0 {
t.Fatalf("confirmed targets = %#v, want none", result.ConfirmedTargets)
}
if sent := conn.sentRequests(); len(sent) != 0 {
t.Fatalf("sent requests = %#v, want none", sent)
}
}
func TestClaudeCodeSDKAdapterCancelTargetsSettledStopIsUnconfirmed(t *testing.T) {
adapter := NewClaudeCodeSDKAdapter(nil)
session := standardTestSession(ProviderClaudeCode)
conn := &stopTaskAckConnection{stopped: false}
adapterSession := &claudeSDKAdapterSession{
conn: conn,
reader: &claudeSDKLineReader{conn: conn},
pendingRequests: make(map[string]*pendingInteractiveRequest),
liveState: newClaudeSDKLiveState(),
}
adapter.storeSession(session.AgentSessionID, adapterSession)
child, _, _, ok := adapterSession.updateClaudeSDKChild(session, claudeSDKChildUpdate{
Key: "toolu-agent",
ParentToolUseID: "toolu-agent",
RootTurnID: "turn-1",
TaskID: "task-1",
Async: true,
Started: true,
})
if !ok {
t.Fatalf("child session not registered: %#v", child)
}
result, err := adapter.CancelTargets(context.Background(), session, []CancelTarget{
{AgentSessionID: child.AgentSessionID, TurnID: child.TurnID},
}, "user_request")
if err != nil {
t.Fatalf("CancelTargets: %v", err)
}
if len(result.ConfirmedTargets) != 0 {
t.Fatalf("confirmed targets = %#v, want none for an already settled task", result.ConfirmedTargets)
}
}