Files
tutti/packages/agent/host/session_fork_capabilities.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

145 lines
4.8 KiB
Go

package agenthost
import (
"context"
"strings"
storesqlite "github.com/tutti-os/tutti/packages/agent/store-sqlite"
)
func (h *Host) GetSessionForkCapabilities(
ctx context.Context,
input SessionForkCapabilityInput,
) (SessionForkCapabilities, error) {
normalizeSessionForkCapabilityInput(&input)
if h == nil || h.sessionForks == nil || h.sessionForkRuntime == nil ||
input.WorkspaceID == "" || input.SourceAgentSessionID == "" {
return SessionForkCapabilities{}, nil
}
sourceSession, found, err := h.sessionForks.GetSessionForkSource(
ctx, input.WorkspaceID, input.SourceAgentSessionID,
)
if err != nil || !found {
return SessionForkCapabilities{}, err
}
runtimeSource := h.sessionForkCapabilityRuntimeSource(sourceSession)
if strings.TrimSpace(runtimeSource.ProviderSessionID) !=
strings.TrimSpace(sourceSession.ProviderSessionID) {
return SessionForkCapabilities{}, nil
}
descriptor, err := h.sessionForkRuntime.ResolveSessionFork(
ctx,
cloneSessionForkRuntimeSource(runtimeSource),
)
if err != nil {
return SessionForkCapabilities{}, err
}
normalizeSessionForkDriverDescriptor(&descriptor)
capabilities := SessionForkCapabilities{
FullSession: descriptor.FullSession &&
descriptor.Kind != "" &&
descriptor.Version != "" &&
validSessionForkStateBindingMode(
descriptor.StateBindingMode,
h.sessionForkState,
runtimeSource.Provider,
),
ThroughTurn: descriptor.ThroughTurn &&
descriptor.Kind != "" &&
descriptor.Version != "" &&
validSessionForkStateBindingMode(
descriptor.StateBindingMode,
h.sessionForkState,
runtimeSource.Provider,
),
}
return capabilities, nil
}
func (h *Host) CanForkSessionTurn(
ctx context.Context,
input SessionTurnForkabilityInput,
) (bool, error) {
input.WorkspaceID = strings.TrimSpace(input.WorkspaceID)
input.SourceAgentSessionID = strings.TrimSpace(input.SourceAgentSessionID)
input.CanonicalTurnID = strings.TrimSpace(input.CanonicalTurnID)
input.ProviderTurnID = strings.TrimSpace(input.ProviderTurnID)
if h == nil || h.sessionForks == nil || h.sessionForkRuntime == nil ||
input.WorkspaceID == "" || input.SourceAgentSessionID == "" ||
input.CanonicalTurnID == "" || input.ProviderTurnID == "" ||
len(input.ProviderTurnBindingJSON) == 0 {
return false, nil
}
sourceSession, found, err := h.sessionForks.GetSessionForkSource(
ctx,
input.WorkspaceID,
input.SourceAgentSessionID,
)
if err != nil || !found {
return false, err
}
runtimeSource := h.sessionForkCapabilityRuntimeSource(sourceSession)
if strings.TrimSpace(runtimeSource.ProviderSessionID) !=
strings.TrimSpace(sourceSession.ProviderSessionID) {
return false, nil
}
return h.sessionForkRuntime.CanForkProviderTurn(
ctx,
RuntimeProviderTurnForkabilityInput{
Source: cloneSessionForkRuntimeSource(runtimeSource),
CanonicalTurnID: input.CanonicalTurnID,
ProviderTurnID: input.ProviderTurnID,
ProviderTurnBindingJSON: append([]byte(nil), input.ProviderTurnBindingJSON...),
},
)
}
// sessionForkCapabilityRuntimeSource is intentionally preparation-free.
// Capability projection runs on Session detail reads and may only consume a
// live runtime observation or persisted canonical attestation. Full runtime,
// target, credential, and worktree preparation belongs to ForkSession.
func (h *Host) sessionForkCapabilityRuntimeSource(
session storesqlite.Session,
) ProviderRuntimeSession {
if h != nil && h.runtime != nil {
if live, found := h.runtime.Session(session.WorkspaceID, session.ID); found {
return cloneSessionForkRuntimeSource(live)
}
}
settings := composerSettingsFromMap(session.Settings)
return ProviderRuntimeSession{
ID: session.ID, WorkspaceID: session.WorkspaceID, UserID: session.UserID,
AgentTargetID: session.AgentTargetID, Provider: session.Provider,
ProviderSessionID: session.ProviderSessionID, Resumable: true,
Cwd: session.Cwd, Settings: &settings,
RuntimeContext: cloneMap(session.InternalRuntimeContext),
Status: persistedRuntimeStatus(session.ActiveTurnID), Title: session.Title,
PinnedAtUnixMS: session.PinnedAtUnixMS, CreatedAtUnixMS: session.CreatedAtUnixMS,
UpdatedAtUnixMS: session.UpdatedAtUnixMS,
}
}
func normalizeSessionForkDriverDescriptor(input *SessionForkDriverDescriptor) {
input.Kind = strings.TrimSpace(input.Kind)
input.Version = strings.TrimSpace(input.Version)
if input.StateBindingMode == "" {
input.StateBindingMode = SessionForkStateBindingHostCopy
}
}
func validSessionForkStateBindingMode(
mode SessionForkStateBindingMode,
hostBinder SessionForkProviderStateBinder,
provider string,
) bool {
switch mode {
case SessionForkStateBindingHostCopy:
return hostBinder != nil &&
hostBinder.SupportsSessionForkProviderStateBinding(provider)
case SessionForkStateBindingProviderOwned:
return true
default:
return false
}
}