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
145 lines
4.8 KiB
Go
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
|
|
}
|
|
}
|