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
909 lines
42 KiB
Go
909 lines
42 KiB
Go
//revive:disable:file-length-limit // Composition root is intentionally kept as one auditable dependency graph.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
agentdaemon "github.com/tutti-os/tutti/packages/agent/daemon"
|
|
agenthostadapter "github.com/tutti-os/tutti/packages/agent/daemon/hostadapter"
|
|
agenthost "github.com/tutti-os/tutti/packages/agent/host"
|
|
runtimeprep "github.com/tutti-os/tutti/packages/agent/runtimeprep"
|
|
agentstoresqlite "github.com/tutti-os/tutti/packages/agent/store-sqlite"
|
|
workspaceissues "github.com/tutti-os/tutti/packages/workspace/issues"
|
|
tuttiapi "github.com/tutti-os/tutti/services/tuttid/api"
|
|
preferencesbiz "github.com/tutti-os/tutti/services/tuttid/biz/preferences"
|
|
agentextensiondata "github.com/tutti-os/tutti/services/tuttid/data/agentextension"
|
|
workspacedata "github.com/tutti-os/tutti/services/tuttid/data/workspace"
|
|
accountservice "github.com/tutti-os/tutti/services/tuttid/service/account"
|
|
agentservice "github.com/tutti-os/tutti/services/tuttid/service/agent"
|
|
agentextensionservice "github.com/tutti-os/tutti/services/tuttid/service/agentextension"
|
|
agentmaintenanceservice "github.com/tutti-os/tutti/services/tuttid/service/agentmaintenance"
|
|
agentquickpromptservice "github.com/tutti-os/tutti/services/tuttid/service/agentquickprompt"
|
|
agentsessionreplayservice "github.com/tutti-os/tutti/services/tuttid/service/agentsessionreplay"
|
|
agentstatusservice "github.com/tutti-os/tutti/services/tuttid/service/agentstatus"
|
|
agenttargetservice "github.com/tutti-os/tutti/services/tuttid/service/agenttarget"
|
|
automationruleservice "github.com/tutti-os/tutti/services/tuttid/service/automationrule"
|
|
browsersvc "github.com/tutti-os/tutti/services/tuttid/service/browser"
|
|
appclicli "github.com/tutti-os/tutti/services/tuttid/service/cli/appcli"
|
|
collabrunservice "github.com/tutti-os/tutti/services/tuttid/service/collabrun"
|
|
computersvc "github.com/tutti-os/tutti/services/tuttid/service/computer"
|
|
eventstreamservice "github.com/tutti-os/tutti/services/tuttid/service/eventstream"
|
|
globalagentactivityservice "github.com/tutti-os/tutti/services/tuttid/service/globalagentactivity"
|
|
managedcredentialsservice "github.com/tutti-os/tutti/services/tuttid/service/managedcredentials"
|
|
managedruntime "github.com/tutti-os/tutti/services/tuttid/service/managedruntime"
|
|
modelbindingservice "github.com/tutti-os/tutti/services/tuttid/service/modelbinding"
|
|
modelgatewayservice "github.com/tutti-os/tutti/services/tuttid/service/modelgateway"
|
|
modelplanservice "github.com/tutti-os/tutti/services/tuttid/service/modelplan"
|
|
modelpolicyservice "github.com/tutti-os/tutti/services/tuttid/service/modelpolicy"
|
|
preferencesservice "github.com/tutti-os/tutti/services/tuttid/service/preferences"
|
|
reporterservice "github.com/tutti-os/tutti/services/tuttid/service/reporter"
|
|
tuttiagentservice "github.com/tutti-os/tutti/services/tuttid/service/tuttiagent"
|
|
tuttimodeactivationservice "github.com/tutti-os/tutti/services/tuttid/service/tuttimodeactivation"
|
|
tuttimodeexecutionservice "github.com/tutti-os/tutti/services/tuttid/service/tuttimodeexecution"
|
|
tuttimodeplanservice "github.com/tutti-os/tutti/services/tuttid/service/tuttimodeplan"
|
|
userprojectservice "github.com/tutti-os/tutti/services/tuttid/service/userproject"
|
|
workspaceservice "github.com/tutti-os/tutti/services/tuttid/service/workspace"
|
|
workspaceagentservice "github.com/tutti-os/tutti/services/tuttid/service/workspaceagent"
|
|
tuttitypes "github.com/tutti-os/tutti/services/tuttid/types"
|
|
)
|
|
|
|
func buildDaemonAPI(
|
|
ctx context.Context,
|
|
store workspacedata.CatalogStore,
|
|
analyticsReporter reporterservice.Reporter,
|
|
browserService *browsersvc.Service,
|
|
computerService *computersvc.Service,
|
|
modelGateway *modelgatewayservice.Gateway,
|
|
connectorRuntime agentservice.ConnectorRuntime,
|
|
installTuttiModeWatchdog func(tuttimodeexecutionservice.Worker),
|
|
) (tuttiapi.DaemonAPI, *workspaceservice.AppCenterService, *agentdaemon.Runtime, *agentservice.ProviderAuthWatcher, error) {
|
|
workspaceStore, _ := store.(workspacedata.WorkbenchStore)
|
|
issueStore, _ := store.(workspaceissues.Store)
|
|
tuttiModeExecutionStore, _ := store.(tuttimodeexecutionservice.Store)
|
|
tuttiModeArchiveStore, _ := store.(tuttimodeexecutionservice.ArchiveStore)
|
|
tuttiModeDeletionAdmissionStore, _ := store.(tuttimodeexecutionservice.SourceDeletionAdmissionStore)
|
|
tuttiModeWakeStore, _ := store.(tuttimodeexecutionservice.WakeStore)
|
|
tuttiModeReviewerActivity, ok := store.(tuttimodeexecutionservice.ReviewerActivityReader)
|
|
if !ok {
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, fmt.Errorf(
|
|
"tutti mode reviewer activity reader is unavailable",
|
|
)
|
|
}
|
|
preferencesStore, _ := store.(workspacedata.PreferencesStore)
|
|
agentTargetStore, _ := store.(workspacedata.AgentTargetStore)
|
|
managedCredentialsStore, _ := store.(workspacedata.ManagedCredentialsStore)
|
|
modelPlansStore, _ := store.(workspacedata.ModelPlansStore)
|
|
agentActivityRepo, _ := store.(workspacedata.AgentActivityStore)
|
|
agentQuickPromptStore, _ := store.(workspacedata.AgentQuickPromptStore)
|
|
agentProviderRuntimeSelectionStore, _ := store.(workspacedata.AgentProviderRuntimeSelectionStore)
|
|
userProjectStore, _ := store.(workspacedata.UserProjectStore)
|
|
appStore, _ := store.(workspacedata.AppStore)
|
|
appFactoryStore, _ := store.(workspacedata.AppFactoryStore)
|
|
workflowStore, _ := store.(tuttimodeplanservice.Store)
|
|
tuttiModeActivationStore, _ := store.(tuttimodeactivationservice.Store)
|
|
fileAdapter := workspacedata.LocalFilesAdapter{}
|
|
issueAttachmentFiles, err := reconcileIssueAttachmentFiles(ctx, store)
|
|
if err != nil {
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, err
|
|
}
|
|
|
|
events := eventstreamservice.NewService(eventstreamservice.DefaultCatalog(), nil)
|
|
preferencesPublisher := eventstreamservice.DesktopPreferencesPublisher{Service: events}
|
|
tuttiModeActivations := &tuttimodeactivationservice.Service{
|
|
Store: tuttiModeActivationStore,
|
|
Publisher: eventstreamservice.TuttiModeActivationPublisher{Service: events},
|
|
}
|
|
preferences := &preferencesservice.Service{
|
|
Store: preferencesStore,
|
|
Publisher: preferencesPublisher,
|
|
AgentComposerDefaultsPublisher: preferencesPublisher,
|
|
}
|
|
agentTargets := agenttargetservice.Service{Store: agentTargetStore}
|
|
agentRuntimeDir, err := tuttitypes.DefaultAgentRuntimeDir()
|
|
if err != nil {
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, fmt.Errorf("resolve agent runtime directory: %w", err)
|
|
}
|
|
agentExtensionBinDir, err := tuttitypes.DefaultAgentExecutableDir()
|
|
if err != nil {
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, fmt.Errorf("resolve agent extension executable directory: %w", err)
|
|
}
|
|
agentExtensionStateDir := tuttitypes.DefaultStateDir()
|
|
agentSetupDiscovery := agentextensiondata.NewFileSetupDiscoveryDirectory(agentExtensionStateDir)
|
|
agentExtensionManager := &agentextensionservice.Manager{
|
|
Sources: tuttitypes.ResolveAgentExtensionSources(),
|
|
RuntimeInstallDir: agentRuntimeDir,
|
|
RuntimeBinDir: agentExtensionBinDir,
|
|
AccountUsageNodeSnapshotDir: filepath.Join(agentExtensionStateDir, "agent", "account-usage-node-snapshots"),
|
|
Store: agentTargetStore,
|
|
Installations: agentextensiondata.NewFileInstallationStore(agentExtensionStateDir),
|
|
Discovery: agentSetupDiscovery,
|
|
Preferences: preferencesStore,
|
|
UserPathAdapter: agentstatusservice.NewUserPathAdapter(),
|
|
}
|
|
agentTargetInstallPlans := agentextensionservice.InstallPlanService{
|
|
Manager: agentExtensionManager, Workspaces: store, Targets: agentTargetStore,
|
|
}
|
|
agentTargetAccountUsage := agentextensionservice.AccountUsageService{
|
|
Manager: agentExtensionManager, Targets: agentTargetStore,
|
|
}
|
|
agentTargets.AvailabilityResolver = agentExtensionManager
|
|
refreshAgentExtensionsInBackground := restoreAgentExtensionsForStartup(ctx, agentExtensionManager)
|
|
managedCredentials := &managedcredentialsservice.Service{
|
|
Store: managedCredentialsStore,
|
|
}
|
|
modelBindingsStore, _ := store.(workspacedata.AgentModelBindingsStore)
|
|
modelPolicyStore, _ := store.(modelpolicyservice.Store)
|
|
// Narrow cross-domain reads over biz types keep referential integrity
|
|
// bidirectional without any modelbinding <-> modelpolicy service cycle:
|
|
// bindings validate their policy link, and policy deletion checks bindings.
|
|
bindingPolicyLookup, _ := store.(modelbindingservice.PolicyLookup)
|
|
policyBindingReferences, _ := store.(modelpolicyservice.BindingReferenceReader)
|
|
modelBindings := &modelbindingservice.Service{
|
|
Store: modelBindingsStore,
|
|
Plans: modelPlansStore,
|
|
Targets: agentTargetStore,
|
|
Policies: bindingPolicyLookup,
|
|
}
|
|
modelPolicies := &modelpolicyservice.Service{
|
|
Store: modelPolicyStore,
|
|
BindingReferences: policyBindingReferences,
|
|
}
|
|
modelConfigurationPublisher := eventstreamservice.AgentModelConfigurationPublisher{Service: events}
|
|
workspaceAgentsStore, _ := store.(workspacedata.WorkspaceAgentsStore)
|
|
workspaceAgents := &workspaceagentservice.Service{
|
|
Store: workspaceAgentsStore,
|
|
Targets: agentTargetStore,
|
|
Plans: modelPlansStore,
|
|
Workspaces: store,
|
|
Publisher: modelConfigurationPublisher,
|
|
}
|
|
automationRulesStore, _ := store.(workspacedata.AutomationRulesStore)
|
|
automationRules := &automationruleservice.Service{
|
|
Store: automationRulesStore,
|
|
Agents: workspaceAgents,
|
|
Targets: agentTargetStore,
|
|
Usage: automationRulesStore,
|
|
Publisher: eventstreamservice.AgentAutomationRulesPublisher{Service: events},
|
|
}
|
|
modelPlans := &modelplanservice.Service{
|
|
Store: modelPlansStore,
|
|
// Plan deletion stays blocked while any consumer domain still points at
|
|
// the plan: agent model bindings, model usage policies, and workspace agents.
|
|
References: modelplanservice.CompositeReferenceResolver{modelBindings, modelPolicies, workspaceAgents},
|
|
}
|
|
collabRunsStore, _ := store.(workspacedata.CollaborationRunsStore)
|
|
collabRuns := &collabrunservice.Service{
|
|
Store: collabRunsStore,
|
|
Plans: modelPlansStore,
|
|
Completer: modelPlans,
|
|
Publisher: eventstreamservice.AgentCollaborationPublisher{Service: events},
|
|
}
|
|
modelPolicies.ConfigureReviewAutomation(modelBindingsStore, nil, collabRuns, collabRuns)
|
|
events.RegisterIntentHandler(
|
|
eventstreamservice.TopicPreferencesDesktopUpdateRequested,
|
|
eventstreamservice.NewPreferencesDesktopUpdateRequestedHandler(preferences),
|
|
)
|
|
events.RegisterIntentHandler(
|
|
eventstreamservice.TopicPreferencesAgentComposerDefaultsPatchRequested,
|
|
eventstreamservice.NewPreferencesAgentComposerDefaultsPatchRequestedHandler(preferences),
|
|
)
|
|
events.RegisterIntentHandler(
|
|
eventstreamservice.TopicPreferencesAgentSessionLaunchModePatchRequested,
|
|
eventstreamservice.NewPreferencesAgentSessionLaunchModePatchRequestedHandler(preferences),
|
|
)
|
|
agentActivityProjection := agentservice.NewActivityProjection(agentActivityRepo)
|
|
modelPolicies.Sessions = modelPolicySessionTargetResolver{projection: agentActivityProjection}
|
|
collabRuns.Timeline = agentservice.CollaborationTimelineReporter{Projection: agentActivityProjection}
|
|
agentActivityProjection.SetAnalyticsReporter(analyticsReporter)
|
|
agentActivityProjection.SetPublisher(eventstreamservice.AgentActivityPublisher{Service: events})
|
|
if agentTargetResolver, ok := store.(agentservice.AgentTargetResolver); ok {
|
|
agentActivityProjection.SetAgentTargetResolver(agentTargetResolver)
|
|
}
|
|
managedRuntimeResolver := managedruntime.DefaultResolver{}
|
|
agentStatusService := agentstatusservice.NewService(agentstatusservice.ServiceDependencies{
|
|
AnalyticsReporter: analyticsReporter,
|
|
ManagedRuntime: managedRuntimeResolver,
|
|
ClaudeCodeRuntimeDir: filepath.Join(agentRuntimeDir, "claude-code"),
|
|
UserCommandBinDir: agentExtensionBinDir,
|
|
CodexRuntimeSelectionStore: agentProviderRuntimeSelectionStore,
|
|
UserPathAdapter: agentstatusservice.NewUserPathAdapter(),
|
|
})
|
|
tuttiAgentAuth := tuttiagentservice.NewAuthBootstrapper(&agentStatusService)
|
|
agentTargetAccountUsage.ProbeLocal = func(ctx context.Context, provider string) agentextensionservice.AccountUsageResult {
|
|
probed := agentStatusService.ProbeProviderAccountUsage(ctx, provider)
|
|
result := agentextensionservice.AccountUsageResult{
|
|
Outcome: probed.Outcome, CapturedAtUnixMS: probed.CapturedAtUnixMS,
|
|
BillingMode: probed.BillingMode, QuotaState: probed.QuotaState,
|
|
ErrorCode: probed.ErrorCode,
|
|
}
|
|
for _, quota := range probed.Quotas {
|
|
result.Quotas = append(result.Quotas, agentextensionservice.AccountUsageQuota{
|
|
QuotaType: quota.QuotaType, PercentRemaining: quota.PercentRemaining,
|
|
ResetsAtUnixMS: quota.ResetsAtUnixMS, ModelName: quota.ModelName,
|
|
})
|
|
}
|
|
return result
|
|
}
|
|
// Shared so a runtime auth failure (reporter side) surfaces in the status
|
|
// probe (List side) — see agentRunOutcomeReporter.
|
|
runOutcomes := agentStatusService.RunOutcomes
|
|
accountService := accountservice.NewService("")
|
|
globalAgentActivityService := globalagentactivityservice.NewService(accountService)
|
|
mobileRemoteService, err := buildMobileRemoteService(
|
|
agentExtensionStateDir,
|
|
accountService,
|
|
events,
|
|
)
|
|
if err != nil {
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, err
|
|
}
|
|
agentProcessComposition, err := buildAgentProcessComposition(
|
|
resolveAgentSessionRecordingEnabled(ctx, preferences),
|
|
)
|
|
if err != nil {
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, err
|
|
}
|
|
replayComposition := agentCassetteReplayActive()
|
|
agentHostMetadata := agentdaemon.HostMetadata{
|
|
ClientInfo: agentdaemon.ClientInfo{Name: "tutti-desktop", Title: "Tutti", Version: "0.1.0"},
|
|
WorkspaceEnvName: "TUTTI_WORKSPACE_ID", OpenClawSessionKeyPrefix: "agent:main:tsh-",
|
|
}
|
|
agentTargetSetup := agentextensionservice.NewSetupService(context.Background())
|
|
agentTargetSetup.Plans = agentTargetInstallPlans
|
|
agentTargetSetup.Transport = agentProcessComposition.transport
|
|
agentTargetSetup.Host = agentHostMetadata
|
|
agentTargetSetup.Actions = agentextensiondata.NewFileSetupActionStore(agentExtensionStateDir)
|
|
agentTargetSetup.AccountUsageFailures = agentextensiondata.NewFileAccountUsageCompanionFailureStore(agentExtensionStateDir)
|
|
agentTargetSetup.Discovery = agentSetupDiscovery
|
|
agentTargetSetup.AuthInvalidation = runOutcomes
|
|
preferences.RegisterChangeObserver(func(ctx context.Context, previous, current preferencesbiz.DesktopPreferences) {
|
|
for _, reconcileErr := range agentExtensionManager.ReconcileDesktopPreferencesChange(ctx, previous, current) {
|
|
payload, _ := json.Marshal(map[string]string{"error": reconcileErr.Error()})
|
|
slog.Warn("agent_extension.reconcile_failed", "payload", string(payload))
|
|
}
|
|
agentTargetSetup.WakeManagedRuntimeReconciler()
|
|
agentTargetSetup.WakeAccountUsageCompanionReconciler()
|
|
})
|
|
agentRuntimeConfig := agentdaemon.Config{
|
|
Reporter: agentRunOutcomeReporter{
|
|
DurableActivityReporter: agentActivityProjection,
|
|
store: runOutcomes,
|
|
},
|
|
ProcessTransport: agentProcessComposition.transport,
|
|
HostMetadata: agentHostMetadata,
|
|
AdapterResolver: agentextensionservice.RuntimeResolver{
|
|
Manager: agentExtensionManager, Transport: agentProcessComposition.transport, Host: agentHostMetadata,
|
|
},
|
|
ProviderCommandResolver: agentProviderCommandResolver(&agentStatusService),
|
|
CommandNetworkAccessPolicy: tuttiDesktopCommandNetworkAccessPolicy,
|
|
}
|
|
agentRuntimeConfig = applyAgentReplayRuntimeComposition(agentRuntimeConfig, replayComposition)
|
|
agentRuntime, err := agentdaemon.NewRuntime(agentRuntimeConfig)
|
|
if err != nil {
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, fmt.Errorf("create agent runtime: %w", err)
|
|
}
|
|
agentRuntimePreparer := runtimeprep.NewDefaultPreparer(tuttitypes.DefaultStateDir())
|
|
rtkExecutableResolver := func(ctx context.Context) (string, error) {
|
|
return resolveTuttiRTKExecutable(ctx, managedRuntimeResolver)
|
|
}
|
|
agentRuntimePreparer.RTKExecutableResolver = rtkExecutableResolver
|
|
userHome, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, fmt.Errorf("resolve user home for personal Codex Skills: %w", err)
|
|
}
|
|
if err := registerDaemonCodexPreparer(agentRuntimePreparer, tuttitypes.DefaultStateDir(), userHome); err != nil {
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, err
|
|
}
|
|
agentRuntimePreparer.RegisterProvider(tuttiagentservice.NewPreparer(
|
|
tuttitypes.DefaultStateDir(),
|
|
tuttiAgentAuth.Bootstrap,
|
|
))
|
|
configureAgentRuntimeAvailability(agentRuntimePreparer, browserService, computerService)
|
|
userProjectService := userprojectservice.Service{
|
|
Store: userProjectStore,
|
|
Publisher: eventstreamservice.UserProjectPublisher{Service: events},
|
|
}
|
|
agentQuickPromptService := agentquickpromptservice.Service{
|
|
Store: agentQuickPromptStore,
|
|
Publisher: eventstreamservice.AgentQuickPromptPublisher{Service: events},
|
|
}
|
|
agentRuntimeController := newAgentRuntimeAdapter(agentRuntime.Controller())
|
|
configureAgentRuntimeEventObservers(agentRuntime.Controller(), events)
|
|
agentModelCapabilities := agentservice.NewModelCapabilitiesService()
|
|
agentModelCatalog := agentservice.NewAgentModelCatalog()
|
|
agentModelCatalog.PersistentPath = filepath.Join(
|
|
tuttitypes.DefaultStateDir(), "agent-model-catalog", "model-catalog.json",
|
|
)
|
|
agentModelCatalog.ModelCapabilities = agentModelCapabilities
|
|
agentModelCatalog.ProviderCommands = &agentStatusService
|
|
agentModelCatalog.TuttiAgentAuthBootstrap = tuttiAgentAuth.BootstrapUserAuth
|
|
agentSessionPurgeStore, ok := agentActivityRepo.(agenthost.SessionPurgeStore)
|
|
if !ok {
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, fmt.Errorf("agent session purge store is unavailable")
|
|
}
|
|
var sessionDeletionGuard agenthost.SessionDeletionGuard
|
|
if tuttiModeDeletionAdmissionStore != nil {
|
|
sourceDeletionGuard := &tuttimodeexecutionservice.SourceDeletionGuard{
|
|
Store: tuttiModeDeletionAdmissionStore,
|
|
Context: ctx,
|
|
}
|
|
if err := sourceDeletionGuard.Recover(ctx); err != nil {
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, fmt.Errorf(
|
|
"recover source session deletion admissions: %w", err,
|
|
)
|
|
}
|
|
sessionDeletionGuard = sourceDeletionGuard
|
|
}
|
|
goalReconcileInbox, ok := agentActivityRepo.(interface {
|
|
agentservice.GoalReconcileInboxStore
|
|
agentservice.GoalReconcileInboxWriter
|
|
})
|
|
if !ok {
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, fmt.Errorf("agent goal reconcile inbox store is unavailable")
|
|
}
|
|
agentActivityProjection.SetGoalReconcileInboxWriter(goalReconcileInbox)
|
|
goalProvenanceLedger, ok := agentActivityRepo.(agentservice.GoalProvenanceLedgerStore)
|
|
if !ok {
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, fmt.Errorf("agent goal provenance ledger store is unavailable")
|
|
}
|
|
agentActivityProjection.SetGoalProvenanceLedger(goalProvenanceLedger)
|
|
workspaceIDs := agentWorkspaceIDs(store)
|
|
promptAttachments := agentservice.PromptAttachmentStore{
|
|
RootDir: tuttitypes.DefaultStateDir(),
|
|
SourceRootDir: filepath.Join(tuttitypes.DefaultStateDir(), "agent-prompt-assets"),
|
|
}
|
|
var agentRuntimePreparation runtimeprep.Preparer
|
|
var browserUseAvailable func() bool
|
|
var computerUseAvailable func() bool
|
|
var availabilityChecker agentservice.ProviderAvailabilityChecker
|
|
if !replayComposition {
|
|
agentRuntimePreparation = agentRuntimePreparer
|
|
browserUseAvailable = agentRuntimePreparer.BrowserUseAvailable
|
|
computerUseAvailable = agentRuntimePreparer.ComputerUseAvailable
|
|
availabilityChecker = agentservice.AgentStatusProviderAvailabilityChecker{
|
|
Service: &agentStatusService,
|
|
}
|
|
} else {
|
|
availabilityChecker = replayProviderAvailabilityChecker{}
|
|
}
|
|
canonicalStoreProvider, ok := store.(interface {
|
|
AgentCanonicalStore() *agentstoresqlite.Store
|
|
})
|
|
if !ok || canonicalStoreProvider.AgentCanonicalStore() == nil {
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, fmt.Errorf("canonical agent store is unavailable")
|
|
}
|
|
historicalStateStore := canonicalStoreProvider.AgentCanonicalStore()
|
|
canonicalHostStore := &agenthost.SQLiteWorkspaceStore{
|
|
StoreForWorkspace: func(string) *agentstoresqlite.Store {
|
|
return canonicalStoreProvider.AgentCanonicalStore()
|
|
},
|
|
Observer: agentActivityProjection,
|
|
InitializationPolicy: agentActivityProjection,
|
|
}
|
|
var agentSessionResourceReleaser agentservice.AgentSessionResourceReleaser
|
|
if browserService != nil {
|
|
agentSessionResourceReleaser = browserService
|
|
}
|
|
configureWorkspaceAgentProjection(agentActivityProjection, workspaceAgentsStore)
|
|
sourceActivityObservers := &agentservice.TuttiModeSourceActivityObservers{}
|
|
turnCancelObservers := &agentservice.TurnCancelObservers{}
|
|
commitObserver, commitObservers := buildAgentCommitObserver(
|
|
agentActivityProjection,
|
|
agentProcessComposition.recorder != nil,
|
|
)
|
|
agentSessionConfig := agentservice.ServiceConfig{
|
|
Runtime: agentservice.ServiceRuntimeConfig{
|
|
Preparer: agentRuntimePreparation,
|
|
Connector: connectorRuntime,
|
|
ConnectorCapabilities: agentRuntimeController,
|
|
ModelGateway: modelGateway,
|
|
BrowserUseAvailable: browserUseAvailable,
|
|
ComputerUseAvailable: computerUseAvailable,
|
|
RuntimeOperationStore: agentActivityRepo,
|
|
RuntimeOperationOwner: uuid.NewString(),
|
|
StaleTurnSettler: agentActivityProjection,
|
|
GoalStateStore: agentActivityRepo,
|
|
GoalGenerationFenceStore: agentActivityRepo,
|
|
GoalReconcileInboxStore: goalReconcileInbox,
|
|
GoalOperationOwner: uuid.NewString(),
|
|
ModelBindings: modelBindingsStore,
|
|
ModelPlans: modelPlansStore,
|
|
},
|
|
Sessions: agentservice.ServiceSessionConfig{
|
|
Initializer: agentActivityProjection,
|
|
Reader: agentActivityProjection,
|
|
DeletedSessions: agentActivityProjection,
|
|
PurgeStore: agentSessionPurgeStore,
|
|
DeletionGuard: sessionDeletionGuard,
|
|
UserProjectReader: userProjectService,
|
|
MessageReader: agentActivityProjection,
|
|
TurnStore: agentActivityRepo,
|
|
TurnSummaryReader: agentActivityRepo,
|
|
SubmitClaimStore: agentActivityRepo,
|
|
},
|
|
Composer: agentservice.ServiceComposerConfig{
|
|
AvailabilityChecker: availabilityChecker,
|
|
ModelCatalog: replayAgentModelCatalog(replayComposition, agentProcessComposition, agentModelCatalog),
|
|
ReplayMode: replayComposition,
|
|
ModelCapabilities: agentModelCapabilities,
|
|
AgentTargetStore: agentTargetStore,
|
|
WorkspaceAgentResolver: workspaceAgents,
|
|
AgentComposerDefaultsReader: preferences,
|
|
DesktopPreferencesReader: preferences,
|
|
ExtensionComposerProfiles: agentExtensionComposerProfileResolver{
|
|
manager: agentExtensionManager,
|
|
},
|
|
},
|
|
ExternalImport: agentservice.ServiceExternalImportConfig{
|
|
Store: agentActivityRepo,
|
|
},
|
|
Resources: agentservice.ServiceResourceConfig{
|
|
AgentSessionResourceReleaser: agentSessionResourceReleaser,
|
|
SessionDirectoryAllocator: agentservice.LocalSessionDirectoryAllocator{
|
|
StateDir: tuttitypes.DefaultStateDir(),
|
|
},
|
|
WorktreeStateDir: tuttitypes.DefaultStateDir(),
|
|
WorkspaceIDs: workspaceIDs,
|
|
PromptAttachmentStore: promptAttachments,
|
|
},
|
|
Observers: agentservice.ServiceObserverConfig{
|
|
AnalyticsReporter: analyticsReporter,
|
|
CommitObserver: commitObserver,
|
|
RuntimeOperationEventPublisher: agentActivityProjection,
|
|
TuttiModeActivations: tuttiModeActivations,
|
|
TuttiModeSourceActivity: sourceActivityObservers,
|
|
TurnCancelObserver: turnCancelObservers,
|
|
},
|
|
}
|
|
agentHostRuntime := &agenthostadapter.RuntimeController{
|
|
Backend: agentRuntime.Controller(),
|
|
}
|
|
agentServiceComponents := agentservice.NewServiceComponents(
|
|
agentRuntimeController,
|
|
agentSessionConfig,
|
|
canonicalHostStore,
|
|
)
|
|
agentHost := agentservice.NewApplicationHostWithPorts(
|
|
agentServiceComponents.HostSupportPorts(),
|
|
canonicalHostStore,
|
|
canonicalStoreProvider.AgentCanonicalStore(),
|
|
historicalStateStore,
|
|
agentHostRuntime,
|
|
)
|
|
if agentHost == nil {
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, fmt.Errorf("compose agent host")
|
|
}
|
|
configureAgentProviderGoalAdoption(agentRuntime.Controller(), agentHost)
|
|
agentActivityProjection.SetTurnForkabilityResolver(agentHost)
|
|
agentSessionConfig.Host = agentservice.ServiceHostConfig{
|
|
ApplicationHost: agentHost,
|
|
Components: agentServiceComponents,
|
|
}
|
|
agentSessionService := agentservice.NewService(agentRuntimeController, agentSessionConfig)
|
|
configureUserProjectSessionDeletion(&userProjectService, agentSessionService)
|
|
agentStatusService.OnProviderStatusInvalidated = agentSessionService.InvalidateProviderAvailabilityCache
|
|
preferences.AgentComposerDefaultsValidator = agentSessionService
|
|
modelPlans.NativeSubscriptionProbe = modelPlanNativeSubscriptionProbe{Agents: agentSessionService}
|
|
automationExecutor := &automationruleservice.DaemonExecutor{Agents: agentSessionService, Ledger: automationRulesStore}
|
|
automationRules.Executor = automationExecutor
|
|
automationRules.Sources = automationExecutor
|
|
agentSessionRecordingService, err := buildAgentSessionRecordingService(
|
|
store,
|
|
agentProcessComposition.recorder,
|
|
agentSessionService,
|
|
)
|
|
if err != nil {
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, err
|
|
}
|
|
if commitObservers != nil && agentSessionRecordingService != nil {
|
|
commitObservers.Add(agentSessionRecordingService)
|
|
}
|
|
configureAgentSessionRecordingObservers(
|
|
agentActivityProjection,
|
|
agentRuntimeController,
|
|
agentSessionRecordingService,
|
|
)
|
|
var replaySemanticRuntime *agentsessionreplayservice.SemanticRuntime
|
|
if replayComposition {
|
|
sqliteWorkspaceStore, ok := store.(*workspacedata.SQLiteStore)
|
|
if !ok {
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, fmt.Errorf(
|
|
"agent session replay semantic store is unavailable",
|
|
)
|
|
}
|
|
replaySemanticRuntime, err = prepareReplaySemanticRuntime(
|
|
ctx,
|
|
sqliteWorkspaceStore,
|
|
agentHost,
|
|
agentProcessComposition.replayRegistrations,
|
|
)
|
|
if err != nil {
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, err
|
|
}
|
|
}
|
|
var providerObservers agentProviderObservationObservers
|
|
if agentSessionRecordingService != nil {
|
|
providerObservers = append(providerObservers, agentSessionRecordingService)
|
|
}
|
|
if replaySemanticRuntime != nil {
|
|
providerObservers = append(providerObservers, replaySemanticRuntime)
|
|
}
|
|
if providerObserver := composeAgentProviderObservationObserver(
|
|
providerObservers,
|
|
); providerObserver != nil {
|
|
agentRuntime.Controller().SetProviderObservationObserver(providerObserver)
|
|
}
|
|
// Host fixes startup order: durable runtime operations first, then goal
|
|
// operations and reconcile inbox work, and only then stale turns.
|
|
if err := agentHost.Recover(ctx); err != nil {
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, fmt.Errorf("recover agent host: %w", err)
|
|
}
|
|
go agentActivityProjection.RunTurnTerminalAnalytics(ctx)
|
|
go func() {
|
|
if err := agentHost.Run(ctx); err != nil && !errors.Is(err, context.Canceled) {
|
|
slog.ErrorContext(ctx, "agent Host worker lifecycle stopped", "error", err)
|
|
}
|
|
}()
|
|
var agentMaintenance *agentmaintenanceservice.Service
|
|
if maintenanceState, ok := store.(agentmaintenanceservice.StateStore); ok {
|
|
agentMaintenance = &agentmaintenanceservice.Service{
|
|
Host: agentHost, Preferences: preferences, State: maintenanceState,
|
|
Resources: agentSessionService,
|
|
IsIdle: agentSessionService.IdleForDataMaintenance,
|
|
}
|
|
if compactor, ok := store.(agentmaintenanceservice.DatabaseCompactor); ok {
|
|
agentMaintenance.Compactor = compactor
|
|
}
|
|
go agentMaintenance.Run(ctx)
|
|
}
|
|
|
|
workspaceService := workspaceservice.CatalogService{
|
|
Store: store,
|
|
PreferencesStore: preferencesStore,
|
|
}
|
|
issueRunLaunchGate := workspaceservice.NewIssueRunLaunchGate()
|
|
issueRunCanceller := issueRunSessionCanceller{Host: agentHost, Sessions: agentSessionService}
|
|
tuttiModeMainWakeOwner := "tuttid-main-wake:" + uuid.NewString()
|
|
tuttiModeExecutions := &tuttimodeexecutionservice.Service{
|
|
Store: tuttiModeExecutionStore,
|
|
Archives: tuttiModeArchiveStore,
|
|
Wakes: tuttiModeWakeStore,
|
|
ArchiveAutomationTurns: issueRunCanceller,
|
|
MainWakeTargets: tuttiModeMainWakeAgentAdapter{
|
|
Host: agentHost,
|
|
Sessions: agentSessionService,
|
|
},
|
|
ReviewerActivity: tuttiModeReviewerActivity,
|
|
}
|
|
tuttiModeExecutions.ReviewerTargets = tuttiModeReviewerAgentAdapter{
|
|
Host: agentHost,
|
|
Sessions: agentSessionService,
|
|
}
|
|
tuttiModeSourceActivity := tuttiModeSourceActivityAdapter{
|
|
Executions: tuttiModeExecutions,
|
|
}
|
|
sourceActivityObservers.Add(tuttiModeSourceActivity)
|
|
tuttiModeMainWakeRecovery := &tuttiModeMainWakeReadyRecovery{Delegate: tuttiModeExecutions}
|
|
issueService := workspaceservice.IssueManagerService{
|
|
RunLauncher: issueRunAgentLauncher{Sessions: agentSessionService, Host: agentHost},
|
|
RunLaunchGate: issueRunLaunchGate,
|
|
RunCancellationRequester: issueRunCanceller,
|
|
SourceSessionContextResolver: issueSourceSessionContextResolver{Sessions: agentActivityProjection},
|
|
Publisher: eventstreamservice.WorkspaceIssuePublisher{Service: events},
|
|
Store: issueStore,
|
|
AttachmentFiles: issueAttachmentFiles,
|
|
AttachmentLaunchPins: workspaceservice.NewIssueAttachmentLaunchPins(),
|
|
AgentTargetReader: agentTargetStore,
|
|
PlanningTimeline: agentservice.IssuePlanningTimelineReporter{Projection: agentActivityProjection},
|
|
TuttiModeExecutions: tuttiModeExecutions,
|
|
MutationLocks: workspaceservice.NewIssueMutationLocks(),
|
|
}
|
|
tuttiModePlans := &tuttimodeplanservice.Service{
|
|
Store: workflowStore,
|
|
TurnSnapshots: tuttiModeActivations,
|
|
Revisions: workspacedata.WorkflowRevisionFiles{StateDir: tuttitypes.DefaultStateDir()},
|
|
Publisher: eventstreamservice.WorkspaceWorkflowPublisher{Service: events},
|
|
IssueMaterializer: tuttimodeplanservice.WorkspaceIssueMaterializer{Issues: &issueService},
|
|
FeedbackDispatcher: &tuttiModePlanFeedbackDispatcher{
|
|
Agents: agentSessionService,
|
|
TurnLinks: workflowStore,
|
|
},
|
|
}
|
|
// Recover accepted Tutti Mode plans before buildDaemonAPI returns the
|
|
// public service graph. This is a one-shot durable recovery pass, not a
|
|
// background worker; deterministic Issue materialization makes retries
|
|
// converge after a response or process loss.
|
|
if workflowStore != nil {
|
|
// The single-review flow retired the two-phase configuration
|
|
// checkpoint. Cancel any legacy pending configuration reviews first so
|
|
// stale review panels cannot reappear alongside the new flow. A
|
|
// failure to retire one legacy row must not block daemon startup; the
|
|
// next boot retries the remaining pending rows idempotently.
|
|
if err := tuttiModePlans.RetireConfigurationReviewWorkflows(ctx); err != nil {
|
|
slog.Warn("retire legacy Tutti Mode configuration reviews failed",
|
|
"event", "tutti_mode_plan.configuration_review_retirement_failed",
|
|
"error", err)
|
|
}
|
|
if err := tuttiModePlans.RecoverCreateIssueOperations(ctx); err != nil {
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, fmt.Errorf("recover Tutti Mode plan operations: %w", err)
|
|
}
|
|
}
|
|
issueExecutionCoordinator := &workspaceservice.IssueExecutionCoordinator{
|
|
Issues: &issueService,
|
|
RunSessionCanceller: issueRunCanceller,
|
|
SettlementReader: issueRunSettlementReader{Host: agentHost},
|
|
}
|
|
issueService.RunReconciler = issueExecutionCoordinator
|
|
tuttiModeExecutions.ArchiveRuns = issueExecutionCoordinator
|
|
// A user's stop on a planning conversation cascades to every running task
|
|
// run its accepted plan dispatched.
|
|
turnCancelObservers.Add(issueExecutionCoordinator)
|
|
issueService.ExecutionRecoveryQueue = workspaceservice.NewWorkspaceExecutionRecoveryQueue(workspaceservice.WorkspaceExecutionRecoveryQueueOptions{
|
|
Context: ctx,
|
|
Delay: 3 * time.Second,
|
|
Interval: 15 * time.Second,
|
|
Reconcile: func(ctx context.Context, workspaceID string) (workspaceservice.WorkspaceExecutionRecoveryResult, error) {
|
|
runResult, err := reconcileTuttiModeRunsAndMainWakes(
|
|
ctx,
|
|
workspaceID,
|
|
tuttiModeMainWakeOwner,
|
|
issueExecutionCoordinator.ReconcileIssueExecutions,
|
|
tuttiModeMainWakeRecovery,
|
|
)
|
|
if err != nil {
|
|
return workspaceservice.WorkspaceExecutionRecoveryResult{}, err
|
|
}
|
|
pendingArchives, err := tuttiModeExecutions.RecoverArchivesAndCount(ctx, workspaceID)
|
|
return workspaceservice.WorkspaceExecutionRecoveryResult{
|
|
Pending: runResult.RunningCount > runResult.CompletedCount ||
|
|
pendingArchives > 0,
|
|
}, err
|
|
},
|
|
})
|
|
tuttiModeExecutions.ArchiveRecoveryQueue = issueService.ExecutionRecoveryQueue
|
|
if tuttiModeArchiveStore != nil {
|
|
workspaces, err := store.List(ctx)
|
|
if err != nil {
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, fmt.Errorf("list workspaces for Tutti archive recovery: %w", err)
|
|
}
|
|
for _, workspace := range workspaces {
|
|
pendingArchives, err := tuttiModeExecutions.RecoverArchivesAndCount(ctx, workspace.ID)
|
|
if err != nil {
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, fmt.Errorf(
|
|
"recover Tutti archives for workspace %s: %w", workspace.ID, err,
|
|
)
|
|
}
|
|
if pendingArchives > 0 {
|
|
issueService.ExecutionRecoveryQueue.Enqueue(workspace.ID)
|
|
}
|
|
}
|
|
}
|
|
appShellAdapter := workspaceservice.NewPlatformAppShellAdapter()
|
|
appCenterService := &workspaceservice.AppCenterService{
|
|
Store: appStore,
|
|
AppFactoryStore: appFactoryStore,
|
|
WorkspaceStore: store,
|
|
PreferencesStore: preferencesStore,
|
|
Runner: &workspaceservice.AppRunner{RuntimeResolver: managedRuntimeResolver, ShellAdapter: appShellAdapter},
|
|
ShellAdapter: appShellAdapter,
|
|
StateDir: tuttitypes.DefaultStateDir(),
|
|
HostTuttiVersion: tuttitypes.ResolveAppVersion(),
|
|
HostTuttiCapabilities: tuttitypes.ResolveAppCapabilities(),
|
|
Publisher: eventstreamservice.WorkspaceAppPublisher{Service: events},
|
|
}
|
|
startManagedRuntimeProfilePreload(managedRuntimeResolver)
|
|
go func() {
|
|
// The packaged sidecar bundle no longer carries the native claude
|
|
// binary; provision it up front so the first Claude session does not
|
|
// pay the download. Sessions started before this completes fall back
|
|
// to a PATH-installed claude (see runtimeprep.ClaudeCodePreparer).
|
|
// The deadline bounds a stalled CDN/npm connection (the shared HTTP
|
|
// client deliberately has no timeout) while leaving room for a large
|
|
// fallback download through a slow proxy.
|
|
preloadCtx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
|
|
defer cancel()
|
|
startedAt := time.Now()
|
|
slog.Info("claude code binary preload started", "event", "tutti.claude_code_binary.preload_started")
|
|
status, err := agentStatusService.EnsureClaudeCodeBinary(preloadCtx)
|
|
if err != nil {
|
|
slog.Warn("claude code binary preload failed", "event", "tutti.claude_code_binary.preload_failed", "durationMs", time.Since(startedAt).Milliseconds(), "error", err)
|
|
return
|
|
}
|
|
slog.Info("claude code binary preload completed", "event", "tutti.claude_code_binary.preload_completed", "source", status.Source, "version", status.Version, "path", status.Path, "durationMs", time.Since(startedAt).Milliseconds())
|
|
}()
|
|
appCLIRegistry := appclicli.NewRegistry(workspaceService, appCenterService)
|
|
appCenterService.AppCLIRegistry = appCLIRegistry
|
|
if err := appCenterService.InitBuiltinPackages(ctx); err != nil {
|
|
agentRuntime.Close()
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, fmt.Errorf("initialize builtin workspace apps: %w", err)
|
|
}
|
|
appFactoryService := &workspaceservice.AppFactoryService{
|
|
Store: appFactoryStore,
|
|
AppStore: appStore,
|
|
WorkspaceStore: store,
|
|
WorkspaceRootResolver: workspaceservice.FileService{Adapter: fileAdapter},
|
|
AppCenter: appCenterService,
|
|
AgentSessionService: agentSessionService,
|
|
AgentTargetStore: agentTargetStore,
|
|
AgentMessageReader: agentActivityProjection,
|
|
AgentSessionReader: agentActivityProjection,
|
|
AgentSessionState: agentActivityProjection,
|
|
Runner: &workspaceservice.AppRunner{RuntimeResolver: managedRuntimeResolver, ShellAdapter: appShellAdapter},
|
|
ShellAdapter: appShellAdapter,
|
|
StateDir: tuttitypes.DefaultStateDir(),
|
|
Publisher: eventstreamservice.WorkspaceAppFactoryPublisher{Service: events},
|
|
}
|
|
agentActivityProjection.SetRootTurnObserver(rootTurnObserverFanout{
|
|
agentRuntimeController,
|
|
tuttiModeSourceTurnActivityObserver{
|
|
Activities: tuttiModeSourceActivity,
|
|
},
|
|
tuttiModeMainWakeTurnObserver{
|
|
Settlements: tuttiModeExecutions,
|
|
Queue: issueService.ExecutionRecoveryQueue,
|
|
},
|
|
tuttiModeReviewerTurnObserver{
|
|
Settlements: tuttiModeExecutions,
|
|
},
|
|
})
|
|
agentActivityProjection.SetSessionMessageObserver(appFactoryService)
|
|
sessionStateObservers := []agentservice.SessionStateObserverRegistration{
|
|
{Observer: appFactoryService, RootTurnSettlements: agentservice.RootTurnSettlementsObserve},
|
|
{Observer: modelPolicies, RootTurnSettlements: agentservice.RootTurnSettlementsObserve},
|
|
{Observer: automationRules, RootTurnSettlements: agentservice.RootTurnSettlementsObserve},
|
|
{Observer: issueExecutionCoordinator, RootTurnSettlements: agentservice.RootTurnSettlementsObserve},
|
|
}
|
|
if err := agentActivityProjection.ConfigureSessionStateObservers(sessionStateObservers...); err != nil {
|
|
agentRuntime.Close()
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, fmt.Errorf("configure agent session state observers: %w", err)
|
|
}
|
|
if _, err := appFactoryService.ReconcileInterruptedJobs(ctx); err != nil {
|
|
agentRuntime.Close()
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, fmt.Errorf("reconcile interrupted app factory jobs: %w", err)
|
|
}
|
|
workspaces, err := recoverIssueExecutionsAtStartup(
|
|
ctx,
|
|
workspaceService,
|
|
issueExecutionCoordinator,
|
|
tuttiModeExecutions,
|
|
)
|
|
if err != nil {
|
|
agentRuntime.Close()
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, err
|
|
}
|
|
tuttiModeWatchdogWorker := newTuttiModeWatchdogWorker(
|
|
ctx, tuttiModeExecutions, tuttiModeMainWakeOwner,
|
|
func(ctx context.Context) ([]string, error) {
|
|
summaries, err := workspaceService.List(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ids := make([]string, 0, len(summaries))
|
|
for _, summary := range summaries {
|
|
ids = append(ids, summary.ID)
|
|
}
|
|
return ids, nil
|
|
},
|
|
)
|
|
if installTuttiModeWatchdog != nil {
|
|
installTuttiModeWatchdog(tuttiModeWatchdogWorker)
|
|
}
|
|
cliRegistry, err := buildDaemonCLIRegistry(daemonCLIRegistryInput{
|
|
Workspaces: workspaceService, Issues: &issueService,
|
|
Apps: appCenterService, Events: events,
|
|
ManagedCredentials: managedCredentials,
|
|
AgentSessions: agentSessionService, AgentTargets: agentTargets,
|
|
AgentTargetSetup: agentTargetSetup,
|
|
Preferences: preferences, TuttiModePlans: tuttiModePlans,
|
|
TuttiModeExecutions: tuttiModeExecutions,
|
|
TuttiModeActivations: tuttiModeActivations,
|
|
Browser: browserService, Computer: computerService,
|
|
AppCommands: appCLIRegistry,
|
|
})
|
|
if err != nil {
|
|
agentRuntime.Close()
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, fmt.Errorf("create cli registry: %w", err)
|
|
}
|
|
agentRuntimePreparer.CommandCatalog = runtimePrepCommandCatalog{Catalog: cliRegistry}
|
|
|
|
terminalService := workspaceservice.NewTerminalService(workspaceservice.NewPlatformTerminalProcessFactory())
|
|
terminalService.RTKExecutableResolver = rtkExecutableResolver
|
|
tuttiAgentReadiness := configureReplayAwareTuttiAgentReadiness(
|
|
replayComposition, accountService, &agentStatusService, agentTargets,
|
|
tuttiAgentAuth.BootstrapUserAuth,
|
|
)
|
|
|
|
providerAuthWatcher := startAgentModelInvalidationAuthWatcher(
|
|
replayComposition, agentModelCatalog, agentSessionService, events,
|
|
)
|
|
if err := startAgentExtensionReconcilers(agentTargetSetup); err != nil {
|
|
providerAuthWatcher.Close()
|
|
agentRuntime.Close()
|
|
return tuttiapi.DaemonAPI{}, nil, nil, nil, err
|
|
}
|
|
if refreshAgentExtensionsInBackground {
|
|
startAgentExtensionBackgroundRefresh(agentExtensionManager, agentTargetSetup)
|
|
}
|
|
agentSessionReplayVerifier := composeAgentReplayVerifier(agentProcessComposition.replay, replaySemanticRuntime)
|
|
|
|
return tuttiapi.DaemonAPI{
|
|
AccountService: accountService,
|
|
GlobalAgentActivityService: globalAgentActivityService,
|
|
MobileRemoteService: mobileRemoteService,
|
|
UserProjectService: userProjectService,
|
|
AgentQuickPromptService: agentQuickPromptService,
|
|
AgentTargetService: agentTargets,
|
|
AgentTargetSetupService: agentTargetSetup,
|
|
AgentTargetAccountUsage: agentTargetAccountUsage,
|
|
PreferencesService: preferences,
|
|
AgentMaintenanceService: agentMaintenance,
|
|
ManagedCredentialsService: managedCredentials,
|
|
ModelPlanService: modelPlans,
|
|
WorkspaceAgentService: workspaceAgents,
|
|
AgentModelBindingService: modelBindings,
|
|
ModelPolicyService: modelPolicies,
|
|
CollaborationRunService: collabRuns,
|
|
AutomationRuleService: automationRules,
|
|
EventStreamService: events,
|
|
WorkspaceService: workspaceService,
|
|
WorkbenchService: workspaceservice.WorkbenchService{
|
|
Store: workspaceStore,
|
|
SnapshotReconciler: workspaceservice.TerminalWorkbenchSnapshotReconciler{
|
|
TerminalService: terminalService,
|
|
},
|
|
},
|
|
AppCenterService: appCenterService,
|
|
AppFactoryService: appFactoryService,
|
|
FileService: workspaceservice.FileService{
|
|
Adapter: fileAdapter,
|
|
},
|
|
AgentSessionService: agentSessionService,
|
|
SideConversationService: agentSessionService,
|
|
AgentSessionRecordingService: agentSessionRecordingService,
|
|
AgentSessionReplayVerifier: agentSessionReplayVerifier,
|
|
AgentStatusService: replayAgentProviderStatusAPI(replayComposition, &agentStatusService),
|
|
TuttiAgentReadiness: tuttiAgentReadiness,
|
|
TerminalService: terminalService,
|
|
IssueService: issueService,
|
|
IssueExecutionService: issueExecutionCoordinator,
|
|
TuttiModePlanService: tuttiModePlans,
|
|
TuttiModeExecutionService: tuttiModeExecutions,
|
|
TuttiModeActivationService: tuttiModeActivations,
|
|
|
|
TuttiModeGoalReviewService: tuttiModeExecutions,
|
|
|
|
CLIRegistry: cliRegistry,
|
|
AnalyticsReporter: analyticsReporter,
|
|
OnListenerReady: func() {
|
|
tuttiModeMainWakeRecovery.MarkReady()
|
|
for _, workspace := range workspaces {
|
|
issueService.ExecutionRecoveryQueue.Enqueue(workspace.ID)
|
|
}
|
|
},
|
|
}, appCenterService, agentRuntime, providerAuthWatcher, nil
|
|
}
|
|
|
|
func registerDaemonCodexPreparer(
|
|
preparer *runtimeprep.DefaultPreparer,
|
|
stateDir string,
|
|
userHome string,
|
|
) error {
|
|
personalSkillRoot := filepath.Join(userHome, ".codex", "skills")
|
|
if err := os.MkdirAll(personalSkillRoot, 0o700); err != nil {
|
|
return fmt.Errorf("create personal Codex Skills root: %w", err)
|
|
}
|
|
preparer.RegisterProvider(runtimeprep.CodexPreparer{
|
|
AuthProjector: runtimeprep.MutagenAuthFileProjector{StateDir: stateDir},
|
|
PersonalSkillRoot: personalSkillRoot,
|
|
})
|
|
return nil
|
|
}
|