package app import ( "log/slog" "path/filepath" "slices" "strings" "time" "github.com/yeomyeonggeori/blueclaw/internal/agentruntime" "github.com/yeomyeonggeori/blueclaw/internal/approvalgate" "github.com/yeomyeonggeori/blueclaw/internal/backup" "github.com/yeomyeonggeori/blueclaw/internal/capability" "github.com/yeomyeonggeori/blueclaw/internal/config" "github.com/yeomyeonggeori/blueclaw/internal/connectors" apiconnector "github.com/yeomyeonggeori/blueclaw/internal/connectors/api" "github.com/yeomyeonggeori/blueclaw/internal/inboundengagement" "github.com/yeomyeonggeori/blueclaw/internal/launchfailure" "github.com/yeomyeonggeori/blueclaw/internal/reply" "github.com/yeomyeonggeori/blueclaw/internal/runtimecontrol" "github.com/yeomyeonggeori/blueclaw/internal/store/postgres" capabilitycatalog "github.com/yeomyeonggeori/blueclaw/protocol/generated" ) func newConnectorRuntime(runtimeConfiguration config.RuntimeConfiguration, foundation runtimeFoundation, directory identityDirectory, kernel agentKernel, services taskServices, taskLauncher *agentruntime.TaskLauncher, backupCoordinator *backup.Coordinator, taskIntakeController *runtimecontrol.TaskIntakeController) *connectors.ConnectorRuntime { logger := foundation.logger languageModelProvider := kernel.taskTierLanguageModels.High connectorRuntime := connectors.NewConnectorRuntime( directory.identityService, kernel.harness, services.taskRunService, services.taskEventService, logger, ) launchFailureCompleter := launchfailure.NewCompleter(services.taskRunService, languageModelProvider) connectorRuntime.UseUnknownAccountResolver(connectors.NewCapabilityUnknownAccountResolver(kernel.capabilityClient)) connectorRuntime.UseLaunchFailureCompleter(launchFailureCompleter) connectorRuntime.UseNoticeLanguageModel(languageModelProvider) replyGenerator := reply.NewGenerator(languageModelProvider, kernel.instructionBundleLoader) replyGenerator.UseAgentIdentityProvider(kernel.agentIdentityProvider) replyGenerator.UseCompanyProvider(directory.companyProvider) connectorRuntime.UseReplyGenerator(replyGenerator) connectorRuntime.UseCompanyProvider(directory.companyProvider) connectorRuntime.UseCompanyLocaleProvider(directory.companyLocaleProvider) connectorRuntime.UseGatewayDecider(inboundengagement.NewDecisionModelDecider(kernel.decisionModel, nil)) connectorRuntime.UseTasklessLLMCallRecorder(newTasklessLLMCallRecorder(services.repositories.llmCall, logger)) connectorRuntime.UseTaskLauncher(taskLauncher) connectorRuntime.UseApprovalGate(kernel.toolCatalog.approvalGate) connectorRuntime.UseApprovalReplyReader(kernel.toolCatalog.replyReader) connectorRuntime.UseAgentIdentityProvider(kernel.agentIdentityProvider) connectorRuntime.UseAllowedToolNamesByProfile(deriveAllowedToolNamesByProfile(runtimeConfiguration), agentruntime.AlwaysAllowedToolNames()) connectorRuntime.UseWorkspaceActorFactory(kernel.terminalService.WorkspaceActorFactory()) connectorRuntime.UseIngressGate(backupCoordinator) connectorRuntime.UseTaskIntakeGate(taskIntakeController) if foundation.database.SQL != nil { connectorRuntime.UseEventRepository(postgres.NewRawEventRepository(foundation.database)) } return connectorRuntime } func installThreadAsker(connectorRuntime *connectors.ConnectorRuntime, runtimeConfiguration config.RuntimeConfiguration, inbound InboundOptions, approvalGate *approvalgate.Gate, directory identityDirectory) { approvalGate.UsePermissionAsker(threadPermissionAskerFor(inbound, connectorRuntime)) if directory.platformAccountLister != nil { connectorRuntime.UseRequesterDirectMessages(newDirectMessageOpener(newChatdClient(runtimeConfiguration)), directory.platformAccountLister) } } func registerChatdAdapters(connectorRuntime *connectors.ConnectorRuntime, runtimeConfiguration config.RuntimeConfiguration, logger *slog.Logger) { chatdClient := newChatdClient(runtimeConfiguration) for _, platform := range capabilitycatalog.MessengerPlatformNames() { connectorRuntime.RegisterAdapter(connectors.NewChatdPlatformAdapter(platform, chatdClient)) } for _, platform := range platformsChatdServesBeyondTheProtocol(runtimeConfiguration.Connectors.Chatd) { logger.Warn("connector.platform.served_beyond_the_protocol", "platform", platform) connectorRuntime.RegisterAdapter(connectors.NewChatdPlatformAdapter(platform, chatdClient)) } } func newAgentReplyStore(runtimeConfiguration config.RuntimeConfiguration) *apiconnector.ReplyStore { return apiconnector.NewPersistentReplyStore(filepath.Join(runtimeConfiguration.Terminal.WorkspaceRootPath, ".blueclaw", "state", "agent-replies.json")) } func newChatdClient(runtimeConfiguration config.RuntimeConfiguration) capability.Client { return capability.NewClient(capability.Configuration{ Endpoint: firstNonEmptyString(runtimeConfiguration.Connectors.Chatd.Endpoint, connectors.DefaultChatdEndpoint), Timeout: time.Duration(runtimeConfiguration.Connectors.Chatd.TimeoutSecond) * time.Second, BearerTokenPath: runtimeConfiguration.ServiceTokenPath, }) } func platformsChatdServesBeyondTheProtocol(chatdConfiguration config.ChatdConnectorConfiguration) []string { platforms := []string{} for _, enabledPlatform := range chatdConfiguration.EnabledPlatforms { platform := strings.ToLower(strings.TrimSpace(enabledPlatform)) if platform == "" || capabilitycatalog.IsConnectorPlatform(platform) || slices.Contains(platforms, platform) { continue } platforms = append(platforms, platform) } return platforms } func threadPermissionAskerFor(inbound InboundOptions, connectorRuntime *connectors.ConnectorRuntime) approvalgate.PermissionAsker { if !inbound.admitsConnectorHTTPEvent() { return nil } return connectorRuntime.ThreadPermissionAsker() }