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
661 lines
24 KiB
Go
661 lines
24 KiB
Go
package agenthost
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
storesqlite "github.com/tutti-os/tutti/packages/agent/store-sqlite"
|
|
"github.com/tutti-os/tutti/packages/agent/store-sqlite/canonical"
|
|
)
|
|
|
|
const canonicalRuntimeSessionOrigin = "WORKSPACE_AGENT_SESSION_ORIGIN_RUNTIME"
|
|
|
|
// SQLiteWorkspaceStore is the official workspace-routed store composition for
|
|
// Agent Host. It owns canonical runtime-session initialization and post-commit
|
|
// notifications so product services cannot drift on lifecycle projection.
|
|
//
|
|
// Product-owned sidecar projections should wrap this type and observe the
|
|
// persisted result; they must not reimplement canonical initialization.
|
|
type SQLiteWorkspaceStore struct {
|
|
StoreForWorkspace func(string) *storesqlite.Store
|
|
CurrentUserID func() string
|
|
Clock Clock
|
|
Observer CommitObserver
|
|
InitializationPolicy RuntimeSessionInitializationPolicy
|
|
InitializationObserver RuntimeSessionInitializationObserver
|
|
}
|
|
|
|
// RuntimeSessionInitializationPolicy normalizes product-owned identity fields
|
|
// before the shared store commits the canonical session shell. It cannot write
|
|
// canonical state itself.
|
|
type RuntimeSessionInitializationPolicy interface {
|
|
NormalizeRuntimeSessionInitialization(context.Context, ProviderRuntimeSession) (ProviderRuntimeSession, error)
|
|
}
|
|
|
|
// RuntimeSessionInitializationObserver projects product-owned, repairable
|
|
// sidecars after canonical initialization succeeds. It cannot reject or alter
|
|
// the canonical commit; implementations own their own diagnostics and repair.
|
|
type RuntimeSessionInitializationObserver interface {
|
|
ObserveRuntimeSessionInitialized(context.Context, ProviderRuntimeSession, storesqlite.Session)
|
|
}
|
|
|
|
var _ CanonicalStore = (*SQLiteWorkspaceStore)(nil)
|
|
var _ CanonicalRuntimeContextCASStore = (*SQLiteWorkspaceStore)(nil)
|
|
var _ TurnSubmissionStore = (*SQLiteWorkspaceStore)(nil)
|
|
var _ EffectiveHistoryStore = (*SQLiteWorkspaceStore)(nil)
|
|
var _ SessionManagementStore = (*SQLiteWorkspaceStore)(nil)
|
|
var _ SessionBatchManagementStore = (*SQLiteWorkspaceStore)(nil)
|
|
var _ SessionForkStore = (*SQLiteWorkspaceStore)(nil)
|
|
var _ SessionForkTurnIdentityStore = (*SQLiteWorkspaceStore)(nil)
|
|
|
|
func (s *SQLiteWorkspaceStore) GetSession(ctx context.Context, workspaceID, sessionID string) (storesqlite.Session, bool, error) {
|
|
store, err := s.store(workspaceID)
|
|
if err != nil {
|
|
return storesqlite.Session{}, false, err
|
|
}
|
|
return store.GetSession(ctx, workspaceID, sessionID)
|
|
}
|
|
|
|
// GetSessionAndTurn preserves the SQLite snapshot boundary through the
|
|
// workspace-routing Host adapter. Shared Agent live projections must not fall
|
|
// back to independent Session and Turn reads at a terminal transition.
|
|
func (s *SQLiteWorkspaceStore) GetSessionAndTurn(
|
|
ctx context.Context,
|
|
workspaceID string,
|
|
sessionID string,
|
|
turnID string,
|
|
) (storesqlite.Session, storesqlite.Turn, bool, error) {
|
|
store, err := s.store(workspaceID)
|
|
if err != nil {
|
|
return storesqlite.Session{}, storesqlite.Turn{}, false, err
|
|
}
|
|
return store.GetSessionAndTurn(ctx, workspaceID, sessionID, turnID)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) SessionDeleted(ctx context.Context, workspaceID, sessionID string) (bool, error) {
|
|
store, err := s.store(workspaceID)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return store.SessionDeleted(ctx, workspaceID, sessionID)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) RollbackRuntimeSessionInitialization(ctx context.Context, workspaceID, sessionID string) (bool, error) {
|
|
store, err := s.store(workspaceID)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
changed, err := store.RollbackRuntimeSessionInitialization(ctx, workspaceID, sessionID)
|
|
if err == nil && changed {
|
|
NotifyCommitted(ctx, s.Observer, CommittedDelta{
|
|
ProjectionDirty: []CanonicalProjectionDirty{{
|
|
WorkspaceID: workspaceID, AgentSessionID: sessionID,
|
|
EntityKind: storesqlite.MutationEntitySession, EntityID: sessionID, Operation: "delete",
|
|
}},
|
|
ViewsInvalidated: []CanonicalViewInvalidated{{WorkspaceID: workspaceID, AgentSessionID: sessionID}},
|
|
})
|
|
}
|
|
return changed, err
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) InitializeRuntimeSession(ctx context.Context, input RuntimeSessionInitialization) (storesqlite.Session, error) {
|
|
session := input.Session
|
|
if s != nil && s.InitializationPolicy != nil {
|
|
var err error
|
|
session, err = s.InitializationPolicy.NormalizeRuntimeSessionInitialization(ctx, session)
|
|
if err != nil {
|
|
return storesqlite.Session{}, err
|
|
}
|
|
}
|
|
workspaceID := strings.TrimSpace(session.WorkspaceID)
|
|
sessionID := strings.TrimSpace(session.ID)
|
|
if workspaceID == "" || sessionID == "" {
|
|
return storesqlite.Session{}, ErrInvalidArgument
|
|
}
|
|
store, err := s.store(workspaceID)
|
|
if err != nil {
|
|
return storesqlite.Session{}, err
|
|
}
|
|
occurredAt := session.UpdatedAtUnixMS
|
|
if occurredAt <= 0 {
|
|
occurredAt = session.CreatedAtUnixMS
|
|
}
|
|
if occurredAt <= 0 {
|
|
occurredAt = s.now().UnixMilli()
|
|
}
|
|
runtimeContext := cloneStringAnyMap(session.RuntimeContext)
|
|
if runtimeContext == nil {
|
|
runtimeContext = map[string]any{}
|
|
}
|
|
runtimeContext["visible"] = session.Visible && !session.Provisional
|
|
userID := firstNonBlank(session.UserID, s.currentUserID())
|
|
var railPlacement *storesqlite.RailSection
|
|
if input.RailPlacement != nil {
|
|
railPlacement = &storesqlite.RailSection{
|
|
Kind: string(input.RailPlacement.Kind),
|
|
ProjectPath: input.RailPlacement.ProjectPath,
|
|
Key: input.RailPlacement.SectionKey,
|
|
}
|
|
}
|
|
result, err := store.ReportActivityState(ctx, storesqlite.ActivityStateReport{
|
|
Session: storesqlite.SessionStateReport{
|
|
WorkspaceID: workspaceID,
|
|
AgentSessionID: sessionID,
|
|
Kind: storesqlite.SessionKindRoot,
|
|
Origin: canonicalRuntimeSessionOrigin,
|
|
UserID: userID,
|
|
AgentTargetID: strings.TrimSpace(session.AgentTargetID),
|
|
Provider: strings.TrimSpace(session.Provider),
|
|
ProviderSessionID: strings.TrimSpace(session.ProviderSessionID),
|
|
Model: composerSettingsModel(session.Settings),
|
|
Settings: composerSettingsPayload(session.Settings),
|
|
Capabilities: canonical.CloneCapabilitySnapshot(session.Capabilities),
|
|
RuntimeContext: runtimeContext,
|
|
Cwd: strings.TrimSpace(session.Cwd),
|
|
RailPlacement: railPlacement,
|
|
RailPlacementAuthoritative: input.RailPlacementAuthoritative,
|
|
Title: strings.TrimSpace(session.Title),
|
|
Status: runtimeLifecycleStatus(session.Status),
|
|
CurrentPhase: runtimeLifecyclePhase(session.Status),
|
|
LastError: strings.TrimSpace(session.LastError),
|
|
OccurredAtUnixMS: occurredAt,
|
|
StartedAtUnixMS: session.CreatedAtUnixMS,
|
|
CreatedAtUnixMS: session.CreatedAtUnixMS,
|
|
},
|
|
})
|
|
if err != nil {
|
|
if errors.Is(err, storesqlite.ErrRailSectionConflict) {
|
|
return storesqlite.Session{}, fmt.Errorf("%w: %v", ErrRailPlacementConflict, err)
|
|
}
|
|
return storesqlite.Session{}, err
|
|
}
|
|
persisted := result.State.Session
|
|
if strings.TrimSpace(persisted.ID) == "" {
|
|
return storesqlite.Session{}, errors.New("initialized agent session was not persisted")
|
|
}
|
|
if s.InitializationObserver != nil {
|
|
s.InitializationObserver.ObserveRuntimeSessionInitialized(ctx, session, persisted)
|
|
}
|
|
NotifyCommitted(ctx, s.Observer, CanonicalDelta(result.CommitDelta))
|
|
persisted, found, err := store.GetSession(ctx, workspaceID, sessionID)
|
|
if err != nil {
|
|
return storesqlite.Session{}, err
|
|
}
|
|
if !found || strings.TrimSpace(persisted.ID) == "" {
|
|
return storesqlite.Session{}, errors.New("initialized agent session was not persisted")
|
|
}
|
|
return persisted, nil
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) ResolveRuntimeSessionRailPlacement(
|
|
ctx context.Context,
|
|
input ResolveRuntimeSessionRailPlacementInput,
|
|
) (*RailPlacement, error) {
|
|
workspaceID := strings.TrimSpace(input.WorkspaceID)
|
|
agentSessionID := strings.TrimSpace(input.AgentSessionID)
|
|
if workspaceID == "" || agentSessionID == "" {
|
|
return nil, ErrInvalidArgument
|
|
}
|
|
requested, err := normalizeRailPlacement(input.RailPlacement)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
input.RailPlacement = requested
|
|
store, err := s.store(workspaceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var explicit *storesqlite.RailSection
|
|
if input.RailPlacement != nil {
|
|
explicit = &storesqlite.RailSection{
|
|
Kind: string(input.RailPlacement.Kind),
|
|
ProjectPath: input.RailPlacement.ProjectPath,
|
|
Key: input.RailPlacement.SectionKey,
|
|
}
|
|
}
|
|
section, err := store.ResolveAgentSessionRailSection(ctx, storesqlite.ResolveAgentSessionRailSectionInput{
|
|
WorkspaceID: workspaceID,
|
|
AgentSessionID: agentSessionID,
|
|
Cwd: input.Cwd,
|
|
RuntimeContext: cloneStringAnyMap(input.RuntimeContext),
|
|
ExplicitPlacement: explicit,
|
|
ExplicitPlacementAuthoritative: input.RailPlacementAuthoritative,
|
|
})
|
|
if err != nil {
|
|
if errors.Is(err, storesqlite.ErrRailSectionConflict) {
|
|
return nil, fmt.Errorf("%w: %v", ErrRailPlacementConflict, err)
|
|
}
|
|
return nil, err
|
|
}
|
|
resolved, err := normalizeRailPlacement(&RailPlacement{
|
|
Version: RailPlacementVersion,
|
|
Kind: RailPlacementKind(section.Kind),
|
|
ProjectPath: section.ProjectPath,
|
|
SectionKey: section.Key,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if input.RailPlacement != nil && !railPlacementsEqual(input.RailPlacement, resolved) {
|
|
return nil, ErrRailPlacementConflict
|
|
}
|
|
return resolved, nil
|
|
}
|
|
|
|
func railPlacementsEqual(left, right *RailPlacement) bool {
|
|
if left == nil || right == nil {
|
|
return left == nil && right == nil
|
|
}
|
|
return left.Version == right.Version && left.Kind == right.Kind &&
|
|
storesqlite.AreProjectPathsEqual(left.ProjectPath, right.ProjectPath) &&
|
|
storesqlite.NormalizeRailSectionKey(left.SectionKey) ==
|
|
storesqlite.NormalizeRailSectionKey(right.SectionKey)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) UpdateSessionTitle(ctx context.Context, workspaceID, sessionID, title string) (storesqlite.Session, bool, error) {
|
|
store, err := s.store(workspaceID)
|
|
if err != nil {
|
|
return storesqlite.Session{}, false, err
|
|
}
|
|
session, changed, err := store.UpdateSessionTitle(ctx, workspaceID, sessionID, title)
|
|
if err == nil && changed {
|
|
NotifyCommitted(ctx, s.Observer, CanonicalDelta(session.CommitDelta))
|
|
}
|
|
return session, changed, err
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) UpdateSessionSettings(ctx context.Context, workspaceID, sessionID string, settings ComposerSettings) (storesqlite.Session, bool, error) {
|
|
store, err := s.store(workspaceID)
|
|
if err != nil {
|
|
return storesqlite.Session{}, false, err
|
|
}
|
|
session, changed, err := store.UpdateSessionSettings(ctx, workspaceID, sessionID, settings.Model, composerSettingsPayload(&settings))
|
|
if err == nil && changed {
|
|
NotifyCommitted(ctx, s.Observer, CanonicalDelta(session.CommitDelta))
|
|
}
|
|
return session, changed, err
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) UpdateSessionPinned(ctx context.Context, workspaceID, sessionID string, pinned bool) (storesqlite.Session, bool, error) {
|
|
store, err := s.store(workspaceID)
|
|
if err != nil {
|
|
return storesqlite.Session{}, false, err
|
|
}
|
|
session, changed, err := store.UpdateSessionPinned(ctx, workspaceID, sessionID, pinned)
|
|
if err == nil && changed {
|
|
NotifyCommitted(ctx, s.Observer, CanonicalDelta(session.CommitDelta))
|
|
}
|
|
return session, changed, err
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) PlanDeleteSessions(ctx context.Context, input storesqlite.DeleteSessionsBatchInput) (storesqlite.DeleteSessionsPlan, error) {
|
|
store, err := s.store(input.WorkspaceID)
|
|
if err != nil {
|
|
return storesqlite.DeleteSessionsPlan{}, err
|
|
}
|
|
return store.PlanDeleteSessions(ctx, input)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) PlanClearSessions(ctx context.Context, workspaceID string) (storesqlite.DeleteSessionsPlan, error) {
|
|
store, err := s.store(strings.TrimSpace(workspaceID))
|
|
if err != nil {
|
|
return storesqlite.DeleteSessionsPlan{}, err
|
|
}
|
|
return store.PlanClearSessions(ctx, workspaceID)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) DeleteSessionsBatch(ctx context.Context, input storesqlite.DeleteSessionsBatchInput) (storesqlite.DeleteSessionsBatchResult, error) {
|
|
store, err := s.store(input.WorkspaceID)
|
|
if err != nil {
|
|
return storesqlite.DeleteSessionsBatchResult{}, err
|
|
}
|
|
result, err := store.DeleteSessionsBatch(ctx, input)
|
|
if err == nil && result.RemovedSessions > 0 {
|
|
NotifyCommitted(ctx, s.Observer, CanonicalDelta(result.CommitDelta))
|
|
}
|
|
return result, err
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) ListChildSessions(ctx context.Context, workspaceID, sessionID string) ([]storesqlite.Session, error) {
|
|
store, err := s.store(workspaceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return store.ListChildSessions(ctx, workspaceID, sessionID)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) GetTurn(ctx context.Context, workspaceID, sessionID, turnID string) (storesqlite.Turn, bool, error) {
|
|
store, err := s.store(workspaceID)
|
|
if err != nil {
|
|
return storesqlite.Turn{}, false, err
|
|
}
|
|
return store.GetTurn(ctx, workspaceID, sessionID, turnID)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) FindTurnByClientSubmitID(ctx context.Context, workspaceID, sessionID, clientSubmitID string) (string, bool, error) {
|
|
store, err := s.store(workspaceID)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
return store.FindTurnByClientSubmitID(ctx, workspaceID, sessionID, clientSubmitID)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) FindSubmitClaimByCanonicalTurn(
|
|
ctx context.Context,
|
|
workspaceID, sessionID, turnID string,
|
|
) (storesqlite.SubmitClaim, bool, error) {
|
|
store, err := s.store(workspaceID)
|
|
if err != nil {
|
|
return storesqlite.SubmitClaim{}, false, err
|
|
}
|
|
return store.FindSubmitClaimByCanonicalTurn(
|
|
ctx,
|
|
workspaceID,
|
|
sessionID,
|
|
turnID,
|
|
)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) RecoverProviderTurnBinding(
|
|
ctx context.Context,
|
|
input storesqlite.ProviderTurnBindingRecovery,
|
|
) (storesqlite.ProviderTurnBindingRecoveryResult, error) {
|
|
store, err := s.store(input.WorkspaceID)
|
|
if err != nil {
|
|
return storesqlite.ProviderTurnBindingRecoveryResult{}, err
|
|
}
|
|
result, err := store.RecoverProviderTurnBinding(ctx, input)
|
|
if err == nil && result.Changed {
|
|
NotifyCommitted(ctx, s.Observer, CanonicalDelta(result.CommitDelta))
|
|
}
|
|
return result, err
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) ListLatestTurnInteractions(ctx context.Context, workspaceID string, sessionIDs []string) (map[string][]storesqlite.Interaction, error) {
|
|
store, err := s.store(workspaceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return store.ListLatestTurnInteractions(ctx, workspaceID, sessionIDs)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) GetSessionInteractionTreeSnapshot(
|
|
ctx context.Context,
|
|
query storesqlite.SessionInteractionTreeQuery,
|
|
) (storesqlite.SessionInteractionTreeSnapshot, bool, error) {
|
|
store, err := s.store(query.WorkspaceID)
|
|
if err != nil {
|
|
return storesqlite.SessionInteractionTreeSnapshot{}, false, err
|
|
}
|
|
return store.GetSessionInteractionTreeSnapshot(ctx, query)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) ListSessionInteractions(ctx context.Context, input storesqlite.ListSessionInteractionsInput) ([]storesqlite.Interaction, error) {
|
|
store, err := s.store(input.WorkspaceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return store.ListSessionInteractions(ctx, input)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) ListSessionMessages(ctx context.Context, input storesqlite.ListSessionMessagesInput) (storesqlite.MessagePage, bool, error) {
|
|
store, err := s.store(input.WorkspaceID)
|
|
if err != nil {
|
|
return storesqlite.MessagePage{}, false, err
|
|
}
|
|
return store.ListSessionMessages(ctx, input)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) RecordTurnSubmission(ctx context.Context, input storesqlite.TurnSubmission) (storesqlite.TurnSubmission, bool, error) {
|
|
store, err := s.store(input.WorkspaceID)
|
|
if err != nil {
|
|
return storesqlite.TurnSubmission{}, false, err
|
|
}
|
|
return store.RecordTurnSubmission(ctx, input)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) GetTurnSubmission(ctx context.Context, workspaceID, sessionID, turnID string) (storesqlite.TurnSubmission, bool, error) {
|
|
store, err := s.store(workspaceID)
|
|
if err != nil {
|
|
return storesqlite.TurnSubmission{}, false, err
|
|
}
|
|
return store.GetTurnSubmission(ctx, workspaceID, sessionID, turnID)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) GetSessionHistory(ctx context.Context, workspaceID, sessionID string) (storesqlite.SessionHistory, bool, error) {
|
|
store, err := s.store(workspaceID)
|
|
if err != nil {
|
|
return storesqlite.SessionHistory{}, false, err
|
|
}
|
|
return store.GetSessionHistory(ctx, workspaceID, sessionID)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) GetTurnHistory(ctx context.Context, workspaceID, sessionID, turnID string) (storesqlite.TurnHistory, bool, error) {
|
|
store, err := s.store(workspaceID)
|
|
if err != nil {
|
|
return storesqlite.TurnHistory{}, false, err
|
|
}
|
|
return store.GetTurnHistory(ctx, workspaceID, sessionID, turnID)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) ListEffectiveSessionTurns(ctx context.Context, workspaceID, sessionID string) ([]storesqlite.Turn, error) {
|
|
store, err := s.store(workspaceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return store.ListEffectiveSessionTurns(ctx, workspaceID, sessionID)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) MarkEditRetryRollbackDispatched(ctx context.Context, input storesqlite.MarkEditRetryRollbackDispatchedInput) (storesqlite.RuntimeOperation, bool, error) {
|
|
store, err := s.store(input.WorkspaceID)
|
|
if err != nil {
|
|
return storesqlite.RuntimeOperation{}, false, err
|
|
}
|
|
return store.MarkEditRetryRollbackDispatched(ctx, input)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) ConfirmEditRetryRollback(ctx context.Context, input storesqlite.ConfirmEditRetryRollbackInput) (storesqlite.RuntimeOperation, bool, error) {
|
|
store, err := s.store(input.WorkspaceID)
|
|
if err != nil {
|
|
return storesqlite.RuntimeOperation{}, false, err
|
|
}
|
|
return store.ConfirmEditRetryRollback(ctx, input)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) AbortEditRetryRollback(ctx context.Context, input storesqlite.AbortEditRetryRollbackInput) (storesqlite.RuntimeOperation, bool, error) {
|
|
store, err := s.store(input.WorkspaceID)
|
|
if err != nil {
|
|
return storesqlite.RuntimeOperation{}, false, err
|
|
}
|
|
return store.AbortEditRetryRollback(ctx, input)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) PrepareEditRetryReplacementRedispatch(
|
|
ctx context.Context,
|
|
input storesqlite.PrepareEditRetryReplacementRedispatchInput,
|
|
) (storesqlite.RuntimeOperation, bool, error) {
|
|
store, err := s.store(input.WorkspaceID)
|
|
if err != nil {
|
|
return storesqlite.RuntimeOperation{}, false, err
|
|
}
|
|
return store.PrepareEditRetryReplacementRedispatch(ctx, input)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) CompleteEditRetryRuntimeOperation(ctx context.Context, input storesqlite.CompleteEditRetryRuntimeOperationInput) (storesqlite.RuntimeOperationCompletion, bool, error) {
|
|
store, err := s.store(input.WorkspaceID)
|
|
if err != nil {
|
|
return storesqlite.RuntimeOperationCompletion{}, false, err
|
|
}
|
|
return store.CompleteEditRetryRuntimeOperation(ctx, input)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) FailEditRetryRecovery(ctx context.Context, input storesqlite.FailEditRetryRecoveryInput) (storesqlite.RuntimeOperation, bool, error) {
|
|
store, err := s.store(input.WorkspaceID)
|
|
if err != nil {
|
|
return storesqlite.RuntimeOperation{}, false, err
|
|
}
|
|
return store.FailEditRetryRecovery(ctx, input)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) QuarantineEditRetryOperation(ctx context.Context, input storesqlite.QuarantineEditRetryOperationInput) (storesqlite.RuntimeOperation, bool, error) {
|
|
store, err := s.store(input.WorkspaceID)
|
|
if err != nil {
|
|
return storesqlite.RuntimeOperation{}, false, err
|
|
}
|
|
return store.QuarantineEditRetryOperation(ctx, input)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) ClearAbandonedEditRetryFence(ctx context.Context, input storesqlite.ClearAbandonedEditRetryFenceInput) (bool, error) {
|
|
store, err := s.store(input.WorkspaceID)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return store.ClearAbandonedEditRetryFence(ctx, input)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) ListSessionTurnSummaries(ctx context.Context, input storesqlite.ListSessionTurnSummariesInput) (storesqlite.SessionTurnSummaryPage, error) {
|
|
store, err := s.store(input.WorkspaceID)
|
|
if err != nil {
|
|
return storesqlite.SessionTurnSummaryPage{}, err
|
|
}
|
|
return store.ListSessionTurnSummaries(ctx, input)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) GetProviderSessionResumeEvidence(
|
|
ctx context.Context,
|
|
workspaceID string,
|
|
sessionID string,
|
|
) (storesqlite.ProviderSessionResumeEvidence, error) {
|
|
store, err := s.store(workspaceID)
|
|
if err != nil {
|
|
return storesqlite.ProviderSessionResumeEvidence{}, err
|
|
}
|
|
return store.GetProviderSessionResumeEvidence(ctx, workspaceID, sessionID)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) PrepareSubmitClaim(ctx context.Context, input storesqlite.SubmitClaimPrepare) (storesqlite.SubmitClaim, bool, error) {
|
|
store, err := s.store(input.WorkspaceID)
|
|
if err != nil {
|
|
return storesqlite.SubmitClaim{}, false, err
|
|
}
|
|
return store.PrepareSubmitClaim(ctx, input)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) AcceptSubmitClaim(ctx context.Context, workspaceID, sessionID, clientSubmitID, turnID string, now int64) (storesqlite.SubmitClaim, bool, error) {
|
|
store, err := s.store(workspaceID)
|
|
if err != nil {
|
|
return storesqlite.SubmitClaim{}, false, err
|
|
}
|
|
return store.AcceptSubmitClaim(ctx, workspaceID, sessionID, clientSubmitID, turnID, now)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) RejectSubmitClaim(ctx context.Context, workspaceID, sessionID, clientSubmitID, turnID string, now int64) (storesqlite.SubmitClaim, bool, error) {
|
|
store, err := s.store(workspaceID)
|
|
if err != nil {
|
|
return storesqlite.SubmitClaim{}, false, err
|
|
}
|
|
return store.RejectSubmitClaim(ctx, workspaceID, sessionID, clientSubmitID, turnID, now)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) DeleteSubmitClaim(ctx context.Context, workspaceID, sessionID, clientSubmitID string) (bool, error) {
|
|
store, err := s.store(workspaceID)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return store.DeleteSubmitClaim(ctx, workspaceID, sessionID, clientSubmitID)
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) store(workspaceID string) (*storesqlite.Store, error) {
|
|
workspaceID = strings.TrimSpace(workspaceID)
|
|
if s == nil || s.StoreForWorkspace == nil || workspaceID == "" {
|
|
return nil, errors.New("workspace-scoped canonical agent store is unavailable")
|
|
}
|
|
store := s.StoreForWorkspace(workspaceID)
|
|
if store == nil {
|
|
return nil, errors.New("workspace-scoped canonical agent store is unavailable")
|
|
}
|
|
return store, nil
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) currentUserID() string {
|
|
if s != nil && s.CurrentUserID != nil {
|
|
return strings.TrimSpace(s.CurrentUserID())
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (s *SQLiteWorkspaceStore) now() time.Time {
|
|
if s != nil && s.Clock != nil {
|
|
return s.Clock.Now()
|
|
}
|
|
return time.Now().UTC()
|
|
}
|
|
|
|
func composerSettingsPayload(settings *ComposerSettings) map[string]any {
|
|
if settings == nil {
|
|
return nil
|
|
}
|
|
return map[string]any{
|
|
"codexSaverMode": settings.CodexSaverMode,
|
|
"rtkSaverMode": settings.RTKSaverMode,
|
|
"model": settings.Model, "permissionModeId": settings.PermissionModeID, "planMode": settings.PlanMode,
|
|
"browserUse": settings.BrowserUse, "computerUse": settings.ComputerUse,
|
|
"reasoningEffort": settings.ReasoningEffort, "speed": settings.Speed,
|
|
"conversationDetailMode": settings.ConversationDetailMode,
|
|
}
|
|
}
|
|
|
|
func composerSettingsModel(settings *ComposerSettings) string {
|
|
if settings == nil {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(settings.Model)
|
|
}
|
|
|
|
func runtimeLifecycleStatus(status string) string {
|
|
switch strings.ToLower(strings.TrimSpace(status)) {
|
|
case "failed":
|
|
return "failed"
|
|
case "completed", "canceled":
|
|
return "ended"
|
|
default:
|
|
return "active"
|
|
}
|
|
}
|
|
|
|
func runtimeLifecyclePhase(status string) string {
|
|
switch strings.ToLower(strings.TrimSpace(status)) {
|
|
case "working":
|
|
return "working"
|
|
case "waiting":
|
|
return "waiting"
|
|
case "failed":
|
|
return "failed"
|
|
default:
|
|
return "idle"
|
|
}
|
|
}
|
|
|
|
func cloneStringAnyMap(input map[string]any) map[string]any {
|
|
if input == nil {
|
|
return nil
|
|
}
|
|
result := make(map[string]any, len(input))
|
|
for key, value := range input {
|
|
result[key] = value
|
|
}
|
|
return result
|
|
}
|
|
|
|
func firstNonBlank(values ...string) string {
|
|
for _, value := range values {
|
|
if value = strings.TrimSpace(value); value != "" {
|
|
return value
|
|
}
|
|
}
|
|
return ""
|
|
}
|