源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
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
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
This commit is contained in:
@@ -0,0 +1,684 @@
|
||||
package agentruntime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCodexAppServerForkCapabilitiesRequireExactSupportedRuntime(t *testing.T) {
|
||||
t.Run("supported codex", func(t *testing.T) {
|
||||
adapter, source, transport := startForkCapableCodexAdapter(t)
|
||||
capabilities, err := adapter.ForkCapabilities(context.Background(), source)
|
||||
if err != nil {
|
||||
t.Fatalf("ForkCapabilities: %v", err)
|
||||
}
|
||||
if capabilities.FullSession || !capabilities.ThroughTurn {
|
||||
t.Fatalf("capabilities = %#v, want through-turn only", capabilities)
|
||||
}
|
||||
if spawned, live := transport.snapshot(); spawned != 1 || len(live) != 1 {
|
||||
t.Fatalf(
|
||||
"capability probe processes = spawned %d/live %d, want existing 1/1",
|
||||
spawned,
|
||||
len(live),
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("older codex", func(t *testing.T) {
|
||||
transport := &multiProcAppServerTransport{}
|
||||
transport.setConfigure(func(server *fakeCodexAppServer) {
|
||||
server.userAgent = "codex/0.143.9"
|
||||
})
|
||||
adapter := NewCodexAppServerAdapter(transport)
|
||||
source := testAppServerSession()
|
||||
if _, err := adapter.Start(context.Background(), source); err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
source.ProviderSessionID = "codex-thread-1"
|
||||
capabilities, err := adapter.ForkCapabilities(context.Background(), source)
|
||||
if err != nil {
|
||||
t.Fatalf("ForkCapabilities: %v", err)
|
||||
}
|
||||
if capabilities.FullSession || capabilities.ThroughTurn {
|
||||
t.Fatalf("capabilities = %#v, want unsupported", capabilities)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("supported tutti agent", func(t *testing.T) {
|
||||
transport := &multiProcAppServerTransport{}
|
||||
transport.setConfigure(func(server *fakeCodexAppServer) {
|
||||
server.userAgent = tuttiAgentForkUserAgent()
|
||||
})
|
||||
adapter := NewTuttiAgentAppServerAdapterWithHostMetadata(
|
||||
transport,
|
||||
LegacyHostMetadata(),
|
||||
)
|
||||
source := testAppServerSession()
|
||||
source.Provider = ProviderTuttiAgent
|
||||
if _, err := adapter.Start(context.Background(), source); err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
source.ProviderSessionID = "codex-thread-1"
|
||||
capabilities, err := adapter.ForkCapabilities(context.Background(), source)
|
||||
if err != nil {
|
||||
t.Fatalf("ForkCapabilities: %v", err)
|
||||
}
|
||||
if capabilities.FullSession || !capabilities.ThroughTurn {
|
||||
t.Fatalf("capabilities = %#v, want through-turn only", capabilities)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("older tutti agent", func(t *testing.T) {
|
||||
transport := &multiProcAppServerTransport{}
|
||||
transport.setConfigure(func(server *fakeCodexAppServer) {
|
||||
server.userAgent = "tutti_agent/0.0.9"
|
||||
})
|
||||
adapter := NewTuttiAgentAppServerAdapterWithHostMetadata(
|
||||
transport,
|
||||
LegacyHostMetadata(),
|
||||
)
|
||||
source := testAppServerSession()
|
||||
source.Provider = ProviderTuttiAgent
|
||||
if _, err := adapter.Start(context.Background(), source); err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
source.ProviderSessionID = "tutti-thread-1"
|
||||
capabilities, err := adapter.ForkCapabilities(context.Background(), source)
|
||||
if err != nil {
|
||||
t.Fatalf("ForkCapabilities: %v", err)
|
||||
}
|
||||
if capabilities.FullSession || capabilities.ThroughTurn {
|
||||
t.Fatalf("capabilities = %#v, want unsupported", capabilities)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCodexAppServerForkCapabilitiesUsePersistedRuntimeAttestation(t *testing.T) {
|
||||
transport := &multiProcAppServerTransport{}
|
||||
adapter := NewCodexAppServerAdapter(transport)
|
||||
source := testAppServerSession()
|
||||
source.ProviderSessionID = "codex-thread-1"
|
||||
source.RuntimeContext = map[string]any{
|
||||
"agent": map[string]any{"userAgent": "codex/0.144.1"},
|
||||
}
|
||||
|
||||
capabilities, err := adapter.ForkCapabilities(context.Background(), source)
|
||||
if err != nil {
|
||||
t.Fatalf("ForkCapabilities: %v", err)
|
||||
}
|
||||
if capabilities.FullSession || !capabilities.ThroughTurn {
|
||||
t.Fatalf("capabilities = %#v, want through-turn only", capabilities)
|
||||
}
|
||||
if spawned, live := transport.snapshot(); spawned != 0 || len(live) != 0 {
|
||||
t.Fatalf(
|
||||
"capability projection processes = spawned %d/live %d, want 0/0",
|
||||
spawned,
|
||||
len(live),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTuttiAgentAppServerForkCapabilitiesUsePersistedRuntimeAttestation(
|
||||
t *testing.T,
|
||||
) {
|
||||
transport := &multiProcAppServerTransport{}
|
||||
adapter := NewTuttiAgentAppServerAdapterWithHostMetadata(
|
||||
transport,
|
||||
LegacyHostMetadata(),
|
||||
)
|
||||
source := testAppServerSession()
|
||||
source.Provider = ProviderTuttiAgent
|
||||
source.ProviderSessionID = "codex-thread-1"
|
||||
source.RuntimeContext = map[string]any{
|
||||
"agent": map[string]any{"userAgent": tuttiAgentForkUserAgent()},
|
||||
}
|
||||
|
||||
capabilities, err := adapter.ForkCapabilities(t.Context(), source)
|
||||
if err != nil {
|
||||
t.Fatalf("ForkCapabilities: %v", err)
|
||||
}
|
||||
if capabilities.FullSession || !capabilities.ThroughTurn {
|
||||
t.Fatalf("capabilities = %#v, want through-turn only", capabilities)
|
||||
}
|
||||
if spawned, live := transport.snapshot(); spawned != 0 || len(live) != 0 {
|
||||
t.Fatalf(
|
||||
"capability projection processes = spawned %d/live %d, want 0/0",
|
||||
spawned,
|
||||
len(live),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodexAppServerForkCapabilitiesRequirePersistedAttestation(t *testing.T) {
|
||||
t.Run("missing attestation", func(t *testing.T) {
|
||||
transport := &multiProcAppServerTransport{}
|
||||
adapter := NewCodexAppServerAdapter(transport)
|
||||
source := testAppServerSession()
|
||||
source.ProviderSessionID = "codex-thread-1"
|
||||
|
||||
capabilities, err := adapter.ForkCapabilities(t.Context(), source)
|
||||
if err != nil || capabilities.ThroughTurn {
|
||||
t.Fatalf("capabilities=%#v error=%v, want unsupported", capabilities, err)
|
||||
}
|
||||
if spawned, _ := transport.snapshot(); spawned != 0 {
|
||||
t.Fatalf("capability projection spawned=%d, want 0", spawned)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("older persisted runtime", func(t *testing.T) {
|
||||
transport := &multiProcAppServerTransport{}
|
||||
adapter := NewCodexAppServerAdapter(transport)
|
||||
source := testAppServerSession()
|
||||
source.ProviderSessionID = "codex-thread-1"
|
||||
source.RuntimeContext = map[string]any{
|
||||
"agent": map[string]any{"userAgent": "codex/0.143.9"},
|
||||
}
|
||||
|
||||
capabilities, err := adapter.ForkCapabilities(t.Context(), source)
|
||||
if err != nil || capabilities.ThroughTurn {
|
||||
t.Fatalf("capabilities=%#v error=%v, want unsupported", capabilities, err)
|
||||
}
|
||||
if spawned, _ := transport.snapshot(); spawned != 0 {
|
||||
t.Fatalf("capability projection spawned=%d, want 0", spawned)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCodexAppServerForkThroughProviderTurn(t *testing.T) {
|
||||
adapter, source, transport := startForkCapableCodexAdapter(t)
|
||||
|
||||
result, err := adapter.Fork(context.Background(), SessionForkInput{
|
||||
Source: source,
|
||||
ProviderTurnID: "provider-turn-2",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Fork: %v", err)
|
||||
}
|
||||
if result.ProviderSessionID != "codex-thread-fork" ||
|
||||
result.ForkedFromProviderSessionID != source.ProviderSessionID ||
|
||||
result.ThroughProviderTurnID != "provider-turn-2" {
|
||||
t.Fatalf("result = %#v", result)
|
||||
}
|
||||
request := appServerRequestParams(
|
||||
t,
|
||||
transport.conn(1),
|
||||
appServerMethodThreadFork,
|
||||
)
|
||||
if got := asString(request["threadId"]); got != source.ProviderSessionID {
|
||||
t.Fatalf("threadId = %q, want %q", got, source.ProviderSessionID)
|
||||
}
|
||||
if got := asString(request["lastTurnId"]); got != "provider-turn-2" {
|
||||
t.Fatalf("lastTurnId = %q, want provider-turn-2", got)
|
||||
}
|
||||
for _, forbidden := range []string{"path", "cwd", "model", "config"} {
|
||||
if _, ok := request[forbidden]; ok {
|
||||
t.Fatalf("fork request unexpectedly contains %q: %#v", forbidden, request)
|
||||
}
|
||||
}
|
||||
spawned, live := transport.snapshot()
|
||||
if spawned != 2 || len(live) != 1 {
|
||||
t.Fatalf("processes = spawned %d/live %d, want 2/1", spawned, len(live))
|
||||
}
|
||||
if !adapter.HasLiveSession(source) {
|
||||
t.Fatal("source live session was closed by fork")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTuttiAgentAppServerForkThroughProviderTurn(t *testing.T) {
|
||||
transport := &multiProcAppServerTransport{}
|
||||
transport.setConfigure(func(server *fakeCodexAppServer) {
|
||||
server.userAgent = tuttiAgentForkUserAgent()
|
||||
server.threadReadTurnIDs = []string{
|
||||
"provider-turn-1",
|
||||
"provider-turn-2",
|
||||
}
|
||||
})
|
||||
adapter := NewTuttiAgentAppServerAdapterWithHostMetadata(
|
||||
transport,
|
||||
LegacyHostMetadata(),
|
||||
)
|
||||
source := testAppServerSession()
|
||||
source.Provider = ProviderTuttiAgent
|
||||
if _, err := adapter.Start(t.Context(), source); err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
source.ProviderSessionID = "codex-thread-1"
|
||||
controller := NewController([]Adapter{adapter}, nil)
|
||||
|
||||
result, err := controller.Fork(t.Context(), SessionForkInput{
|
||||
Source: source,
|
||||
ProviderTurnID: "provider-turn-2",
|
||||
ProviderTurnBindingJSON: json.RawMessage(`{"schemaVersion":1}`),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Fork: %v", err)
|
||||
}
|
||||
if result.ProviderSessionID != "codex-thread-fork" ||
|
||||
result.ForkedFromProviderSessionID != source.ProviderSessionID ||
|
||||
result.ThroughProviderTurnID != "provider-turn-2" {
|
||||
t.Fatalf("result = %#v", result)
|
||||
}
|
||||
request := appServerRequestParams(
|
||||
t,
|
||||
transport.conn(1),
|
||||
appServerMethodThreadFork,
|
||||
)
|
||||
if got := asString(request["lastTurnId"]); got != "provider-turn-2" {
|
||||
t.Fatalf("lastTurnId = %q, want provider-turn-2", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodexAppServerForkedChildCanResumeAndStartTurn(t *testing.T) {
|
||||
adapter, source, transport := startForkCapableCodexAdapter(t)
|
||||
result, err := adapter.Fork(t.Context(), SessionForkInput{
|
||||
Source: source,
|
||||
ProviderTurnID: "provider-turn-2",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Fork: %v", err)
|
||||
}
|
||||
|
||||
transport.setConfigure(func(server *fakeCodexAppServer) {
|
||||
server.userAgent = "codex/0.144.1"
|
||||
server.holdTurn = true
|
||||
})
|
||||
target := source
|
||||
target.AgentSessionID = "agent-session-fork"
|
||||
target.ProviderSessionID = result.ProviderSessionID
|
||||
if err := adapter.Resume(t.Context(), target); err != nil {
|
||||
t.Fatalf("Resume forked child: %v", err)
|
||||
}
|
||||
if !adapter.HasLiveSession(target) {
|
||||
t.Fatal("forked child did not retain a live resumed session")
|
||||
}
|
||||
|
||||
if err := adapter.ExecAsync(
|
||||
t.Context(),
|
||||
target,
|
||||
[]PromptContentBlock{{Type: "text", Text: "continue from fork"}},
|
||||
"",
|
||||
"canonical-fork-turn-1",
|
||||
nil,
|
||||
nil,
|
||||
); err != nil {
|
||||
t.Fatalf("ExecAsync forked child: %v", err)
|
||||
}
|
||||
targetConn := transport.conn(2)
|
||||
waitForCondition(t, func() bool {
|
||||
return len(appServerRequestParamsList(
|
||||
t,
|
||||
targetConn,
|
||||
appServerMethodTurnStart,
|
||||
)) == 1
|
||||
})
|
||||
turnStart := appServerRequestParams(
|
||||
t,
|
||||
targetConn,
|
||||
appServerMethodTurnStart,
|
||||
)
|
||||
if got := asString(turnStart["threadId"]); got != result.ProviderSessionID {
|
||||
t.Fatalf(
|
||||
"forked child turn/start threadId = %q, want %q",
|
||||
got,
|
||||
result.ProviderSessionID,
|
||||
)
|
||||
}
|
||||
if !adapter.HasLiveSession(source) {
|
||||
t.Fatal("resuming forked child replaced the source live session")
|
||||
}
|
||||
if err := adapter.Close(t.Context(), target); err != nil {
|
||||
t.Fatalf("Close forked child: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodexAppServerForkRejectsUnavailableBoundaryBeforeProviderMutation(
|
||||
t *testing.T,
|
||||
) {
|
||||
adapter, source, transport := startForkCapableCodexAdapter(t)
|
||||
transport.setConfigure(func(server *fakeCodexAppServer) {
|
||||
server.userAgent = "codex/0.144.1"
|
||||
server.threadReadTurnIDs = []string{"provider-turn-1"}
|
||||
})
|
||||
|
||||
result, err := adapter.Fork(t.Context(), SessionForkInput{
|
||||
Source: source,
|
||||
ProviderTurnID: "provider-turn-2",
|
||||
})
|
||||
if err == nil ||
|
||||
result.DeliveryDisposition != SessionForkDeliveryNotStarted {
|
||||
t.Fatalf("Fork() result=%#v error=%v, want not-started", result, err)
|
||||
}
|
||||
if requests := appServerRequestParamsList(
|
||||
t,
|
||||
transport.conn(1),
|
||||
appServerMethodThreadFork,
|
||||
); len(requests) != 0 {
|
||||
t.Fatalf("thread/fork requests=%d, want zero", len(requests))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodexAppServerForkRejectsUnverifiedChild(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
configure func(*fakeCodexAppServer)
|
||||
}{
|
||||
{
|
||||
name: "lineage missing",
|
||||
configure: func(server *fakeCodexAppServer) {
|
||||
server.omitForkedFromThreadID = true
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "lineage empty",
|
||||
configure: func(server *fakeCodexAppServer) {
|
||||
server.emptyForkedFromThreadID = true
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "lineage mismatch",
|
||||
configure: func(server *fakeCodexAppServer) {
|
||||
server.forkedFromThreadID = "different-source"
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "boundary mismatch",
|
||||
configure: func(server *fakeCodexAppServer) {
|
||||
server.forkResponseLastTurnID = "different-turn"
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "source returned as child",
|
||||
configure: func(server *fakeCodexAppServer) {
|
||||
server.forkChildThreadID = "codex-thread-1"
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
adapter, source, transport := startForkCapableCodexAdapter(t)
|
||||
transport.setConfigure(func(server *fakeCodexAppServer) {
|
||||
server.userAgent = "codex/0.144.1"
|
||||
test.configure(server)
|
||||
})
|
||||
result, err := adapter.Fork(context.Background(), SessionForkInput{
|
||||
Source: source,
|
||||
ProviderTurnID: "provider-turn-2",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("Fork succeeded, want verification error")
|
||||
}
|
||||
if result.DeliveryDisposition != SessionForkDeliveryUnknown {
|
||||
t.Fatalf(
|
||||
"delivery disposition = %q, want %q",
|
||||
result.DeliveryDisposition,
|
||||
SessionForkDeliveryUnknown,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodexAppServerForkClassifiesExplicitRPCRejection(t *testing.T) {
|
||||
adapter, source, transport := startForkCapableCodexAdapter(t)
|
||||
transport.setConfigure(func(server *fakeCodexAppServer) {
|
||||
server.userAgent = "codex/0.144.1"
|
||||
server.forkRPCError = true
|
||||
})
|
||||
result, err := adapter.Fork(context.Background(), SessionForkInput{
|
||||
Source: source,
|
||||
ProviderTurnID: "provider-turn-2",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("Fork succeeded, want explicit provider rejection")
|
||||
}
|
||||
if result.DeliveryDisposition != SessionForkDeliveryRejected {
|
||||
t.Fatalf(
|
||||
"delivery disposition = %q, want %q",
|
||||
result.DeliveryDisposition,
|
||||
SessionForkDeliveryRejected,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodexAppServerForkVerifiesSelectedProviderTurnBinding(t *testing.T) {
|
||||
adapter, source, transport := startForkCapableCodexAdapter(t)
|
||||
transport.setConfigure(func(server *fakeCodexAppServer) {
|
||||
server.userAgent = "codex/0.144.1"
|
||||
server.forkResponseTurnIDs = []string{"provider-turn-1", "provider-turn-2"}
|
||||
})
|
||||
result, err := adapter.Fork(context.Background(), SessionForkInput{
|
||||
Source: source,
|
||||
ProviderTurnID: "provider-turn-2",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Fork: %v", err)
|
||||
}
|
||||
if result.DeliveryDisposition != SessionForkDeliveryAccepted {
|
||||
t.Fatalf("result = %#v", result)
|
||||
}
|
||||
|
||||
transport.setConfigure(func(server *fakeCodexAppServer) {
|
||||
server.userAgent = "codex/0.144.1"
|
||||
server.forkResponseTurnIDs = []string{"provider-turn-2"}
|
||||
})
|
||||
selectedOnly, err := adapter.Fork(context.Background(), SessionForkInput{
|
||||
Source: source,
|
||||
ProviderTurnID: "provider-turn-2",
|
||||
})
|
||||
if err != nil || selectedOnly.DeliveryDisposition != SessionForkDeliveryAccepted {
|
||||
t.Fatalf("selected-only Fork result=%#v error=%v", selectedOnly, err)
|
||||
}
|
||||
|
||||
transport.setConfigure(func(server *fakeCodexAppServer) {
|
||||
server.userAgent = "codex/0.144.1"
|
||||
server.forkResponseTurnIDs = []string{"provider-turn-1"}
|
||||
})
|
||||
rejected, err := adapter.Fork(context.Background(), SessionForkInput{
|
||||
Source: source,
|
||||
ProviderTurnID: "provider-turn-2",
|
||||
})
|
||||
if err == nil || rejected.DeliveryDisposition != SessionForkDeliveryUnknown {
|
||||
t.Fatalf("missing selected binding result=%#v error=%v", rejected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestControllerForkUsesOptionalSessionForkAdapter(t *testing.T) {
|
||||
transport := &multiProcAppServerTransport{}
|
||||
transport.setConfigure(func(server *fakeCodexAppServer) {
|
||||
server.userAgent = "codex/0.144.1"
|
||||
})
|
||||
adapter := NewCodexAppServerAdapter(transport)
|
||||
source := testAppServerSession()
|
||||
source.ProviderSessionID = "codex-thread-1"
|
||||
source.RuntimeContext = map[string]any{
|
||||
"agent": map[string]any{"userAgent": "codex/0.144.1"},
|
||||
}
|
||||
controller := NewController([]Adapter{adapter}, nil)
|
||||
|
||||
capabilities, err := controller.ForkCapabilities(context.Background(), source)
|
||||
if err != nil {
|
||||
t.Fatalf("ForkCapabilities: %v", err)
|
||||
}
|
||||
if !capabilities.ThroughTurn {
|
||||
t.Fatalf("capabilities = %#v", capabilities)
|
||||
}
|
||||
result, err := controller.Fork(context.Background(), SessionForkInput{
|
||||
Source: source,
|
||||
ProviderTurnID: "provider-turn-2",
|
||||
ProviderTurnBindingJSON: []byte(`{"schemaVersion":1}`),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Fork: %v", err)
|
||||
}
|
||||
if result.ProviderSessionID != "codex-thread-fork" {
|
||||
t.Fatalf("result = %#v", result)
|
||||
}
|
||||
spawned, live := transport.snapshot()
|
||||
if spawned != 1 || len(live) != 0 {
|
||||
t.Fatalf("processes = spawned %d/live %d, want 1/0", spawned, len(live))
|
||||
}
|
||||
if adapter.HasLiveSession(source) {
|
||||
t.Fatal("historical fork registered a live source session")
|
||||
}
|
||||
|
||||
controller.mu.Lock()
|
||||
controller.turns[sessionKey(source.RoomID, source.AgentSessionID)] = activeTurn{
|
||||
turnID: "canonical-turn-running",
|
||||
}
|
||||
controller.mu.Unlock()
|
||||
activeResult, err := controller.Fork(context.Background(), SessionForkInput{
|
||||
Source: source,
|
||||
ProviderTurnID: "provider-turn-2",
|
||||
ProviderTurnBindingJSON: []byte(`{"schemaVersion":1}`),
|
||||
})
|
||||
if err != nil || activeResult.DeliveryDisposition != SessionForkDeliveryAccepted {
|
||||
t.Fatalf("Fork active turn result=%#v error=%v", activeResult, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestControllerForkClassifiesSourcePreparationFailureAsNotStarted(t *testing.T) {
|
||||
source := testAppServerSession()
|
||||
source.ProviderSessionID = "codex-thread-1"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
controller *Controller
|
||||
mutate func(*Session)
|
||||
}{
|
||||
{
|
||||
name: "invalid source",
|
||||
controller: NewController(nil, nil),
|
||||
mutate: func(source *Session) {
|
||||
source.Provider = ""
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "adapter resolution failure",
|
||||
controller: NewControllerWithAdapterResolver(
|
||||
nil,
|
||||
nil,
|
||||
sessionForkErrorAdapterResolver{},
|
||||
),
|
||||
mutate: func(*Session) {},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
testSource := source
|
||||
test.mutate(&testSource)
|
||||
result, err := test.controller.Fork(
|
||||
context.Background(),
|
||||
SessionForkInput{
|
||||
Source: testSource,
|
||||
ProviderTurnID: "provider-turn-2",
|
||||
},
|
||||
)
|
||||
if err == nil {
|
||||
t.Fatal("Fork succeeded, want source preparation error")
|
||||
}
|
||||
if result.DeliveryDisposition != SessionForkDeliveryNotStarted {
|
||||
t.Fatalf(
|
||||
"delivery disposition = %q, want %q",
|
||||
result.DeliveryDisposition,
|
||||
SessionForkDeliveryNotStarted,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type sessionForkErrorAdapterResolver struct{}
|
||||
|
||||
func (sessionForkErrorAdapterResolver) ResolveAdapter(
|
||||
context.Context,
|
||||
AdapterResolveInput,
|
||||
) (Adapter, error) {
|
||||
return nil, errors.New("session fork adapter resolution failed")
|
||||
}
|
||||
|
||||
func TestCodexAppServerUserAgentVersionGate(t *testing.T) {
|
||||
strategy := appServerForkStrategyForTest(t, ProviderCodex)
|
||||
tests := []struct {
|
||||
userAgent string
|
||||
want bool
|
||||
}{
|
||||
{userAgent: "codex/0.144.0", want: true},
|
||||
{userAgent: "codex-cli 0.145.2", want: true},
|
||||
{userAgent: "codex/0.143.99", want: false},
|
||||
{userAgent: "codex/0.146.0-alpha.3.1", want: true},
|
||||
{userAgent: "tutti-agent/9.0.0", want: false},
|
||||
{userAgent: "", want: false},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.userAgent, func(t *testing.T) {
|
||||
version, ok := appServerForkVersion(
|
||||
strategy,
|
||||
map[string]any{"userAgent": test.userAgent},
|
||||
)
|
||||
got := ok && versionAtLeast(
|
||||
version,
|
||||
strategy.throughTurnMinimumVersion,
|
||||
)
|
||||
if got != test.want {
|
||||
t.Fatalf("gate(%q) = %v, want %v", test.userAgent, got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTuttiAgentAppServerUserAgentVersionGate(t *testing.T) {
|
||||
strategy := appServerForkStrategyForTest(t, ProviderTuttiAgent)
|
||||
tests := []struct {
|
||||
userAgent string
|
||||
want bool
|
||||
}{
|
||||
{userAgent: tuttiAgentForkUserAgent(), want: true},
|
||||
{
|
||||
userAgent: tuttiAgentForkUserAgent() + " (Mac OS 26.5.0; arm64) dumb",
|
||||
want: true,
|
||||
},
|
||||
{userAgent: "tutti-agent/0.0.9", want: false},
|
||||
{userAgent: "codex/0.144.0", want: false},
|
||||
{userAgent: "", want: false},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.userAgent, func(t *testing.T) {
|
||||
version, ok := appServerForkVersion(
|
||||
strategy,
|
||||
map[string]any{"userAgent": test.userAgent},
|
||||
)
|
||||
got := ok && versionAtLeast(
|
||||
version,
|
||||
strategy.throughTurnMinimumVersion,
|
||||
)
|
||||
if got != test.want {
|
||||
t.Fatalf(
|
||||
"gate(%q) = %v, want %v",
|
||||
test.userAgent,
|
||||
got,
|
||||
test.want,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func startForkCapableCodexAdapter(
|
||||
t *testing.T,
|
||||
) (*CodexAppServerAdapter, Session, *multiProcAppServerTransport) {
|
||||
t.Helper()
|
||||
transport := &multiProcAppServerTransport{}
|
||||
transport.setConfigure(func(server *fakeCodexAppServer) {
|
||||
server.userAgent = "codex/0.144.1"
|
||||
server.threadReadTurnIDs = []string{"provider-turn-1", "provider-turn-2"}
|
||||
})
|
||||
adapter := NewCodexAppServerAdapter(transport)
|
||||
source := testAppServerSession()
|
||||
if _, err := adapter.Start(context.Background(), source); err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
source.ProviderSessionID = "codex-thread-1"
|
||||
return adapter, source, transport
|
||||
}
|
||||
Reference in New Issue
Block a user