package main import ( "context" "encoding/json" "errors" "fmt" "git.cin.su/m/Assistant/adapters/developmentexecutor" "git.cin.su/m/Assistant/adapters/postgres" "git.cin.su/m/Assistant/contracts/authority" "git.cin.su/m/Assistant/contracts/capability" "git.cin.su/m/Assistant/contracts/cognitive" "git.cin.su/m/Assistant/contracts/continuity" "git.cin.su/m/Assistant/contracts/epistemics" "git.cin.su/m/Assistant/contracts/identity" "git.cin.su/m/Assistant/contracts/interaction" secretcontracts "git.cin.su/m/Assistant/contracts/secrets" "git.cin.su/m/Assistant/contracts/situation" "git.cin.su/m/Assistant/contracts/work" capabilitydomain "git.cin.su/m/Assistant/modules/capability/domain" identitydomain "git.cin.su/m/Assistant/modules/identity/domain" interactiondomain "git.cin.su/m/Assistant/modules/interaction/domain" situationdomain "git.cin.su/m/Assistant/modules/situation/domain" workdomain "git.cin.su/m/Assistant/modules/work/domain" "git.cin.su/m/Assistant/platform/ids" platformsecrets "git.cin.su/m/Assistant/platform/secrets" "git.cin.su/m/Assistant/platform/telemetry" "git.cin.su/m/Assistant/runtime/health" "github.com/jackc/pgx/v5" "io" "log" "net/http" "os" "regexp" "sort" "strconv" "strings" "time" ) const ( creatorPasswordPath = "/run/secrets/creator_password" creatorBootstrapTokenPath = "/run/secrets/creator_bootstrap_token" creatorSessionKeyPath = "/run/secrets/creator_session_signing_key" epistemicsDBPasswordPath = "/run/secrets/epistemics_db_password" interactionDBPasswordPath = "/run/secrets/interaction_db_password" ) const maxCreatorCredentialsBytes = 4 << 10 func main() { addr := os.Getenv("LISTEN_ADDR") if addr == "" { addr = ":8080" } bootstrapToken, err := readRequiredSecretFile("CREATOR_BOOTSTRAP_TOKEN", creatorBootstrapTokenPath) if err != nil { log.Fatal(err) } sessionKeyValue, err := readRequiredSecretFile("CREATOR_SESSION_SIGNING_KEY", creatorSessionKeyPath) if err != nil { log.Fatal(err) } sessionKey := []byte(sessionKeyValue) creatorUsername := os.Getenv("CREATOR_USERNAME") if creatorUsername == "" { log.Fatal("CREATOR_USERNAME is required") } telemetry.RecordCompatibility("non-bootstrap-env-consumers", "core-runtime", "read", "runtime-startup-configuration", true, true, true, "OBSERVED", "startup", "core-runtime", time.Now()) telemetry.RecordCompatibility("migration-compatibility-switches", "core-runtime", "observe", "runtime-startup-switch-scan", true, false, false, "NOT_OBSERVED", "startup", "core-runtime", time.Now()) repository, err := postgres.OpenSituation(context.Background(), postgres.ContinuityConfig{Host: os.Getenv("POSTGRES_HOST"), Port: os.Getenv("POSTGRES_PORT"), Database: os.Getenv("POSTGRES_DATABASE"), User: os.Getenv("POSTGRES_USER"), Password: os.Getenv("POSTGRES_PASSWORD")}) if err != nil { log.Fatal(err) } defer repository.Close() workRepository, err := postgres.OpenWork(context.Background(), postgres.ContinuityConfig{Host: os.Getenv("POSTGRES_HOST"), Port: os.Getenv("POSTGRES_PORT"), Database: os.Getenv("POSTGRES_DATABASE"), User: os.Getenv("WORK_POSTGRES_USER"), Password: os.Getenv("WORK_POSTGRES_PASSWORD")}) if err != nil { log.Fatal(err) } defer workRepository.Close() capabilityRepository, err := postgres.OpenCapability(context.Background(), postgres.ContinuityConfig{Host: os.Getenv("POSTGRES_HOST"), Port: os.Getenv("POSTGRES_PORT"), Database: os.Getenv("POSTGRES_DATABASE"), User: os.Getenv("CAPABILITY_POSTGRES_USER"), Password: os.Getenv("CAPABILITY_POSTGRES_PASSWORD")}) if err != nil { log.Fatal(err) } defer capabilityRepository.Close() epistemicsPassword, err := readCredentialRefSecret("EPISTEMICS_POSTGRES_PASSWORD", "EPISTEMICS_POSTGRES_CREDENTIAL_REF", epistemicsDBPasswordPath) if err != nil { log.Fatal(err) } epistemicsUser := os.Getenv("EPISTEMICS_POSTGRES_USER") if epistemicsUser == "" { epistemicsUser = "epistemics_role" } epistemicsRepository, err := postgres.OpenEpistemics(context.Background(), postgres.ContinuityConfig{Host: os.Getenv("POSTGRES_HOST"), Port: os.Getenv("POSTGRES_PORT"), Database: os.Getenv("POSTGRES_DATABASE"), User: epistemicsUser, Password: epistemicsPassword}) if err != nil { log.Fatal(err) } defer epistemicsRepository.Close() identityRepository, err := postgres.OpenIdentity(context.Background(), postgres.ContinuityConfig{Host: os.Getenv("POSTGRES_HOST"), Port: os.Getenv("POSTGRES_PORT"), Database: os.Getenv("POSTGRES_DATABASE"), User: os.Getenv("IDENTITY_POSTGRES_USER"), Password: os.Getenv("IDENTITY_POSTGRES_PASSWORD")}) if err != nil { log.Fatal(err) } defer identityRepository.Close() if err := bootstrapCreatorAccount(context.Background(), identityRepository, creatorUsername, creatorPasswordPath); err != nil { log.Fatal(err) } interactionPassword, err := readCredentialRefSecret("INTERACTION_POSTGRES_PASSWORD", "INTERACTION_POSTGRES_CREDENTIAL_REF", interactionDBPasswordPath) if err != nil { log.Fatal(err) } interactionUser := os.Getenv("INTERACTION_POSTGRES_USER") if interactionUser == "" { interactionUser = "interaction_role" } interactionRepository, err := postgres.OpenInteraction(context.Background(), postgres.ContinuityConfig{Host: os.Getenv("POSTGRES_HOST"), Port: os.Getenv("POSTGRES_PORT"), Database: os.Getenv("POSTGRES_DATABASE"), User: interactionUser, Password: interactionPassword}) if err != nil { log.Fatal(err) } defer interactionRepository.Close() reminderClient, err := newContinuityReminderClient(envOr("CONTINUITY_RUNTIME_URL", "http://continuity-runtime:8081"), &http.Client{Timeout: 5 * time.Second}) if err != nil { log.Fatal(err) } responseClient, err := newContinuityResponseClient(envOr("CONTINUITY_RUNTIME_URL", "http://continuity-runtime:8081"), &http.Client{Timeout: 5 * time.Second}) if err != nil { log.Fatal(err) } attentionClient, err := newContinuityAttentionClient(envOr("CONTINUITY_RUNTIME_URL", "http://continuity-runtime:8081"), &http.Client{Timeout: 5 * time.Second}) if err != nil { log.Fatal(err) } cognitiveClient, err := newCognitiveInterpretationClient(envOr("COGNITIVE_RUNTIME_URL", "http://cognitive-worker:8083"), &http.Client{Timeout: 90 * time.Second}) if err != nil { log.Fatal(err) } researchClient, err := newResearchRuntimeClient(envOr("RESEARCH_RUNTIME_URL", "http://research-runtime:8087"), &http.Client{Timeout: 20 * time.Second}) if err != nil { log.Fatal(err) } learning := learningRecorder{epistemics: epistemicsRepository, capability: capabilityRepository} mux := http.NewServeMux() authenticateCreator := func(w http.ResponseWriter, r *http.Request) (identity.CreatorSession, bool) { session, err := identitydomain.ValidateCreatorSession(sessionKey, bearerToken(r), time.Now()) if err != nil { http.Error(w, "creator session denied", http.StatusUnauthorized) return identity.CreatorSession{}, false } return session, true } resolveFocus := func(r *http.Request) (situation.FocusBinding, error) { runtime := situation.RuntimeBinding{SessionID: situation.SessionID(r.URL.Query().Get("session_id")), Workspace: situation.WorkspaceID(r.URL.Query().Get("workspace_id"))} bindings, err := repository.ByRuntime(r.Context(), runtime) if err != nil { return situation.FocusBinding{}, err } return situationdomain.NewResolver(bindings).Resolve(runtime) } mux.Handle("/healthz", health.Handler{Runtime: "core-runtime", SemanticGeneration: 1, ArchitectureGeneration: 1}) readinessClient := &http.Client{Timeout: 2 * time.Second} cognitiveReadinessClient := &http.Client{Timeout: 135 * time.Second} cognitiveReadinessProbe := health.HTTPProbe(cognitiveReadinessClient, strings.TrimRight(envOr("COGNITIVE_RUNTIME_URL", "http://cognitive-worker:8083"), "/")+"/readyz") cognitiveReadinessProbe.Timeout = 135 * time.Second mux.Handle("/readyz", health.ReadinessHandler{Runtime: "core-runtime", Timeout: 140 * time.Second, Probes: []health.Probe{ health.TCPProbe("postgres", envOr("POSTGRES_HOST", "postgres"), envOr("POSTGRES_PORT", "5432")), health.HTTPProbe(readinessClient, strings.TrimRight(envOr("CONTINUITY_RUNTIME_URL", "http://continuity-runtime:8081"), "/")+"/healthz"), // A running worker is not necessarily ready to serve cognitive traffic. // Keep deterministic paths alive, but do not advertise overall readiness // while the cognitive dependency is still warming. cognitiveReadinessProbe, }}) admissionPolicy := capability.AdmissionPolicy{ID: envOr("COGNITIVE_ADMISSION_POLICY_ID", "creator-advisory-local-v1"), MaxConcurrent: envInt("COGNITIVE_ADMISSION_MAX_CONCURRENT", 1), MaxAcceptedPerHour: envInt("COGNITIVE_ADMISSION_MAX_ACCEPTED_PER_HOUR", 60), MaxInputBytes: envInt("COGNITIVE_ADMISSION_MAX_INPUT_BYTES", 80<<10), MaxOutputBytes: envInt("COGNITIVE_ADMISSION_MAX_OUTPUT_BYTES", 16<<10)} mux.HandleFunc("POST /v1/internal/cognitive/admission", func(w http.ResponseWriter, r *http.Request) { var request struct { LeaseID string `json:"lease_id"` Request capability.AdmissionRequest `json:"request"` } if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 16<<10)).Decode(&request); err != nil || request.LeaseID == "" { http.Error(w, "invalid admission request", http.StatusBadRequest) return } decision, lease, err := capabilityRepository.AdmitCognitive(r.Context(), request.LeaseID, admissionPolicy, request.Request, time.Now().UnixMilli()) if err != nil { http.Error(w, "admission unavailable", http.StatusServiceUnavailable) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(struct { Decision capability.AdmissionDecision `json:"decision"` Lease capability.AdmissionLease `json:"lease"` }{decision, lease}) }) mux.HandleFunc("POST /v1/internal/cognitive/admission/{lease}/release", func(w http.ResponseWriter, r *http.Request) { if err := capabilityRepository.ReleaseCognitiveAdmission(r.Context(), capability.AdmissionLease{ID: r.PathValue("lease")}, time.Now().UnixMilli()); err != nil { http.Error(w, "admission release unavailable", http.StatusServiceUnavailable) return } w.WriteHeader(http.StatusNoContent) }) mux.HandleFunc("POST /v1/creator/sessions", creatorDatabaseSessionHandler(bootstrapToken, creatorUsername, identityRepository, sessionKey, time.Now)) mux.HandleFunc("POST /v1/creator/account/password", func(w http.ResponseWriter, r *http.Request) { session, ok := authenticateCreator(w, r) if !ok { return } var request struct { CurrentPassword string `json:"current_password"` NewPassword string `json:"new_password"` } decoder := json.NewDecoder(io.LimitReader(r.Body, maxCreatorCredentialsBytes+1)) decoder.DisallowUnknownFields() if err := decoder.Decode(&request); err != nil || request.CurrentPassword == "" || request.NewPassword == "" { http.Error(w, "invalid password change request", http.StatusBadRequest) return } account, err := identityRepository.LoadCreatorAccountByBinding(r.Context(), session.ActorID, session.PrincipalID) if err != nil || !identitydomain.VerifyCreatorPassword(account.PasswordHash, request.CurrentPassword) { http.Error(w, "current password denied", http.StatusUnauthorized) return } newHash, err := identitydomain.HashCreatorPassword(request.NewPassword) if err != nil { http.Error(w, "invalid new password", http.StatusBadRequest) return } if err := identityRepository.UpdateCreatorPassword(r.Context(), session.ActorID, session.PrincipalID, account.Revision, newHash); err != nil { http.Error(w, "password change conflict", http.StatusConflict) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]string{"status": "changed"}) }) mux.HandleFunc("GET /v1/creator/sessions/current", func(w http.ResponseWriter, r *http.Request) { session, ok := authenticateCreator(w, r) if !ok { return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(session) }) mux.HandleFunc("POST /v1/creator/research/web", func(w http.ResponseWriter, r *http.Request) { if _, ok := authenticateCreator(w, r); !ok { return } var request epistemics.WebResearchRequest decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 128<<10)) decoder.DisallowUnknownFields() if err := decoder.Decode(&request); err != nil { http.Error(w, "invalid web research request", http.StatusBadRequest) return } result, err := researchClient.EvaluateWeb(r.Context(), request) if err != nil { http.Error(w, "web research unavailable", http.StatusBadGateway) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(result) }) mux.HandleFunc("POST /v1/creator/development/proposals", func(w http.ResponseWriter, r *http.Request) { session, ok := authenticateCreator(w, r) if !ok { return } var request struct { Evidence []work.ExtensionEvidence `json:"evidence"` Proposal work.ExtensionProposalRequest `json:"proposal"` } decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 64<<10)) decoder.DisallowUnknownFields() if err := decoder.Decode(&request); err != nil { http.Error(w, "invalid development proposal", http.StatusBadRequest) return } proposal, err := workdomain.BuildExtensionProposal(request.Evidence, request.Proposal) if err != nil { http.Error(w, "development proposal rejected", http.StatusUnprocessableEntity) return } proposal.RequestedByPrincipalID = string(session.PrincipalID) if err := workRepository.SaveExtensionProposal(r.Context(), proposal); err != nil { http.Error(w, "development proposal conflict", http.StatusConflict) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) _ = json.NewEncoder(w).Encode(proposal) }) mux.HandleFunc("POST /v1/creator/development/scaffolds", func(w http.ResponseWriter, r *http.Request) { session, ok := authenticateCreator(w, r) if !ok { return } var request work.DevelopmentScaffoldRequest decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 64<<10)) decoder.DisallowUnknownFields() if err := decoder.Decode(&request); err != nil { http.Error(w, "invalid development scaffold", http.StatusBadRequest) return } scaffold, err := workdomain.BuildDevelopmentScaffold(request) if err != nil { http.Error(w, "development scaffold rejected", http.StatusUnprocessableEntity) return } scaffold.RequestedByPrincipalID = string(session.PrincipalID) if err := workRepository.SaveDevelopmentScaffold(r.Context(), scaffold); err != nil { http.Error(w, "development scaffold conflict", http.StatusConflict) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) _ = json.NewEncoder(w).Encode(scaffold) }) mux.HandleFunc("POST /v1/creator/development/gates", func(w http.ResponseWriter, r *http.Request) { if _, ok := authenticateCreator(w, r); !ok { return } var gate work.DevelopmentGate decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 64<<10)) decoder.DisallowUnknownFields() if err := decoder.Decode(&gate); err != nil { http.Error(w, "invalid development gate", http.StatusBadRequest) return } gate, err := prepareDevelopmentGate(gate, time.Now()) if err != nil { http.Error(w, "development gate rejected", http.StatusUnprocessableEntity) return } if err := workRepository.SaveDevelopmentGate(r.Context(), gate, 0); err != nil { http.Error(w, "development gate conflict", http.StatusConflict) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) _ = json.NewEncoder(w).Encode(gate) }) mux.HandleFunc("POST /v1/creator/development/gates/{id}/advance", func(w http.ResponseWriter, r *http.Request) { if _, ok := authenticateCreator(w, r); !ok { return } var request struct { ExpectedRevision int64 `json:"expected_revision"` NextStage work.DevelopmentStage `json:"next_stage"` } decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 16<<10)) decoder.DisallowUnknownFields() if err := decoder.Decode(&request); err != nil || request.ExpectedRevision < 1 || request.NextStage == "" { http.Error(w, "invalid development gate transition", http.StatusBadRequest) return } gate, err := workRepository.LoadDevelopmentGate(r.Context(), r.PathValue("id")) if err != nil || gate.Revision != request.ExpectedRevision { http.Error(w, "development gate revision conflict", http.StatusConflict) return } gate, err = advanceDevelopmentGate(gate, request.NextStage, time.Now()) if err != nil { http.Error(w, "development gate transition rejected", http.StatusUnprocessableEntity) return } if err := workRepository.SaveDevelopmentGate(r.Context(), gate, request.ExpectedRevision); err != nil { http.Error(w, "development gate conflict", http.StatusConflict) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(gate) }) mux.HandleFunc("POST /v1/creator/development/gates/{id}/verify", func(w http.ResponseWriter, r *http.Request) { if _, ok := authenticateCreator(w, r); !ok { return } var request struct { ExpectedRevision int64 `json:"expected_revision"` VerifierRunID work.AgentRunID `json:"verifier_run_id"` Plan developmentexecutor.Plan `json:"plan"` } decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 64<<10)) decoder.DisallowUnknownFields() if err := decoder.Decode(&request); err != nil || request.ExpectedRevision < 1 || request.VerifierRunID == "" { http.Error(w, "invalid development verification request", http.StatusBadRequest) return } gate, result, err := verifyDevelopmentGate(r.Context(), workRepository, developmentGateVerificationRequest{GateID: r.PathValue("id"), ExpectedRevision: request.ExpectedRevision, VerifierRunID: request.VerifierRunID, Plan: request.Plan}, time.Now(), developmentexecutor.OSCommandRunner{}) if err != nil { http.Error(w, err.Error(), http.StatusUnprocessableEntity) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]any{"gate": gate, "verification": result}) }) registerDevelopmentGateEvidenceRoute := func(path string, next work.DevelopmentStage, fieldName string) { mux.HandleFunc("POST /v1/creator/development/gates/{id}/"+path, func(w http.ResponseWriter, r *http.Request) { if _, ok := authenticateCreator(w, r); !ok { return } var request struct { ExpectedRevision int64 `json:"expected_revision"` EvidenceRef string `json:"evidence_ref"` } decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 16<<10)) decoder.DisallowUnknownFields() if err := decoder.Decode(&request); err != nil || request.ExpectedRevision < 1 || request.EvidenceRef == "" { http.Error(w, "invalid development transition evidence", http.StatusBadRequest) return } gate, err := recordDevelopmentGateEvidence(r.Context(), workRepository, developmentGateEvidenceRequest{GateID: r.PathValue("id"), ExpectedRevision: request.ExpectedRevision, NextStage: next, EvidenceRef: request.EvidenceRef}, time.Now()) if err != nil { http.Error(w, "development "+fieldName+" transition rejected", http.StatusUnprocessableEntity) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(gate) }) } registerDevelopmentGateEvidenceRoute("canary", work.DevelopmentCanary, "canary") registerDevelopmentGateEvidenceRoute("promote", work.DevelopmentPromoted, "promotion") registerDevelopmentGateEvidenceRoute("rollback", work.DevelopmentRolledBack, "rollback") mux.HandleFunc("GET /v1/creator/focus", func(w http.ResponseWriter, r *http.Request) { session, ok := authenticateCreator(w, r) if !ok { return } focus, err := resolveFocus(r) if err != nil { http.Error(w, "creator focus unavailable", http.StatusConflict) return } if focus.ActorID != session.ActorID { http.Error(w, "creator focus denied", http.StatusForbidden) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(focus) }) mux.HandleFunc("GET /v1/creator/tasks/{id}", func(w http.ResponseWriter, r *http.Request) { session, ok := authenticateCreator(w, r) if !ok { return } focus, err := resolveFocus(r) if err != nil || focus.ActorID != session.ActorID { http.Error(w, "creator focus denied", http.StatusForbidden) return } target, err := workRepository.Load(r.Context(), work.TaskID(r.PathValue("id"))) if err != nil { http.Error(w, "task not found", http.StatusNotFound) return } if err := situationdomain.ValidateTaskTarget(focus, target); err != nil { http.Error(w, "task outside creator focus", http.StatusForbidden) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(target) }) mux.HandleFunc("GET /v1/creator/capabilities/{id}/health", func(w http.ResponseWriter, r *http.Request) { if _, ok := authenticateCreator(w, r); !ok { return } status, err := capabilityRepository.LoadHealth(r.Context(), capability.CapabilityID(r.PathValue("id"))) if err != nil { http.Error(w, "capability health not found", http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(status) }) mux.HandleFunc("GET /v1/creator/learning/recent", func(w http.ResponseWriter, r *http.Request) { if _, ok := authenticateCreator(w, r); !ok { return } limit := 10 if raw := r.URL.Query().Get("limit"); raw != "" { parsed, err := strconv.Atoi(raw) if err != nil || parsed < 1 || parsed > 50 { http.Error(w, "invalid learning limit", http.StatusBadRequest) return } limit = parsed } experiences, err := epistemicsRepository.RecentExperienceRecords(r.Context(), limit) if err != nil { log.Printf("learning recent experiences unavailable: %v", err) http.Error(w, "learning experience unavailable", http.StatusServiceUnavailable) return } reflections, err := epistemicsRepository.RecentReflectionCandidates(r.Context(), limit) if err != nil { log.Printf("learning recent reflections unavailable: %v", err) http.Error(w, "learning reflection unavailable", http.StatusServiceUnavailable) return } skills, err := capabilityRepository.RecentSkillProfiles(r.Context(), limit) if err != nil { log.Printf("learning recent skills unavailable: %v", err) http.Error(w, "learning skills unavailable", http.StatusServiceUnavailable) return } w.Header().Set("Content-Type", "application/json") w.Header().Set("Cache-Control", "no-store") _ = json.NewEncoder(w).Encode(struct { Experiences []epistemics.ExperienceRecord Reflections []epistemics.ReflectionCandidate Skills []capability.SkillProfile }{Experiences: experiences, Reflections: reflections, Skills: skills}) }) mux.HandleFunc("POST /v1/creator/chat/messages", func(w http.ResponseWriter, r *http.Request) { session, ok := authenticateCreator(w, r) if !ok { return } var request interaction.ChatMessageRequest decoder := json.NewDecoder(io.LimitReader(r.Body, interactiondomain.MaxHumanUtteranceBytes+1024)) decoder.DisallowUnknownFields() if err := decoder.Decode(&request); err != nil || decoder.Decode(&struct{}{}) != io.EOF { http.Error(w, "invalid chat message", http.StatusBadRequest) return } now := time.Now() var conversation interaction.Conversation if request.ConversationID == "" { conversation, err = interactiondomain.NewConversation(session.ActorID, session.PrincipalID, now) if err != nil { http.Error(w, "conversation unavailable", http.StatusServiceUnavailable) return } } else { conversation, err = interactionRepository.Conversation(r.Context(), request.ConversationID) if errors.Is(err, pgx.ErrNoRows) { http.Error(w, "conversation not found", http.StatusNotFound) return } if err != nil { http.Error(w, "conversation unavailable", http.StatusServiceUnavailable) return } if conversation.ActorID != session.ActorID || conversation.PrincipalID != session.PrincipalID { http.Error(w, "conversation denied", http.StatusForbidden) return } conversation.UpdatedAtUnixMillis = now.UnixMilli() } utterance, intent, relations, err := interactiondomain.NewHumanUtterance(conversation, request, now) if err != nil { http.Error(w, "invalid chat message", http.StatusBadRequest) return } projects, err := repository.ListProjects(r.Context()) if err != nil { http.Error(w, "project registry unavailable", http.StatusServiceUnavailable) return } var previousFocus *situation.ActiveProjectFocus storedFocus, err := repository.ActiveProjectFocus(r.Context(), session.ActorID, session.PrincipalID) if err == nil { previousFocus = &storedFocus telemetry.RecordCompatibility("active-project-focus-read", "core-runtime", "read", "chat-focus-load", true, false, true, "SUCCESS", utterance.CorrelationID, string(session.PrincipalID), now) } else if !errors.Is(err, pgx.ErrNoRows) { telemetry.RecordCompatibility("active-project-focus-read", "core-runtime", "read", "chat-focus-load", true, false, true, "FAILURE", utterance.CorrelationID, string(session.PrincipalID), now) http.Error(w, "project focus unavailable", http.StatusServiceUnavailable) return } else { telemetry.RecordCompatibility("active-project-focus-read", "core-runtime", "read", "chat-focus-load", true, false, true, "EMPTY", utterance.CorrelationID, string(session.PrincipalID), now) } var effectiveTaskMessage = interactiondomain.StripAssistantVocative(utterance.Content) creativeUserRequest := interactiondomain.IsCreativeRequest(utterance.Content) locationReply := interactiondomain.LocationContextReply(utterance.Content) continuationMessage := "" travelContinuation := false var previousTask *interaction.HumanUtterance var conversationHistory []interaction.HumanUtterance if request.ConversationID != "" { var historyErr error conversationHistory, historyErr = interactionRepository.Utterances(r.Context(), conversation.ID) if historyErr != nil { log.Printf("interaction conversation history unavailable: %v", historyErr) } } // Keep durable user context available even when the web client carries a // conversation ID; a new or rotated conversation must not erase learned // preferences. recentUserHistory, historyErr := interactionRepository.RecentUtterancesForPrincipal(r.Context(), session.PrincipalID, 64) if historyErr != nil { log.Printf("interaction principal history unavailable: %v", historyErr) } else { log.Printf("interaction principal history loaded: %d utterances", len(recentUserHistory)) } conversationHistory = append(conversationHistory, recentUserHistory...) gen2Context := interactiondomain.BuildScopedGen2Context(conversationHistory, string(session.PrincipalID)) if gen2Context.UsedPersonalMemoryFallback { telemetry.RecordCompatibility("personal-memory-fallback", "core-runtime", "read", "gen2-context-empty", true, true, false, "USED", utterance.CorrelationID, string(session.PrincipalID), now) } retrievedMemoryReply := interactiondomain.RecallPersonalPreference(conversationHistory, effectiveTaskMessage) stableConversationalReply, stableConversational := interactiondomain.StableConversationalReply( effectiveTaskMessage, interactiondomain.ClassifySpeechAct(effectiveTaskMessage, utterance.CorrectsUtteranceID), ) if !creativeUserRequest && locationReply == "" && !stableConversational && retrievedMemoryReply == "" { if continuation := interactiondomain.ContinueLocalSearch(effectiveTaskMessage, conversationHistory); continuation != "" { continuationMessage = continuation effectiveTaskMessage = continuation } else if continuation := interactiondomain.ContinueTravelOrigin(effectiveTaskMessage, conversationHistory); continuation != "" { continuationMessage = continuation travelContinuation = true effectiveTaskMessage = continuation } else if continuation := interactiondomain.ContinueTravelDate(effectiveTaskMessage, conversationHistory); continuation != "" { continuationMessage = continuation travelContinuation = true effectiveTaskMessage = continuation } } if request.ConversationID != "" && interactiondomain.IsRetryRequest(utterance.Content) { if len(conversationHistory) > 0 { for index := len(conversationHistory) - 1; index >= 0; index-- { if interactiondomain.IsRetryRequest(conversationHistory[index].Content) { continue } candidate := conversationHistory[index] previousTask = &candidate effectiveTaskMessage = interactiondomain.StripAssistantVocative(candidate.Content) break } } } semantic, err := interactiondomain.InterpretUtterance(utterance, projects, previousFocus, now) if err != nil { http.Error(w, "interpret chat message", http.StatusServiceUnavailable) return } if continuationMessage != "" { continuedUtterance := utterance continuedUtterance.Content = continuationMessage continuedSemantic, continuedErr := interactiondomain.InterpretUtterance(continuedUtterance, projects, previousFocus, now) if continuedErr == nil { continuedSemantic.Interpretation.Source = "CONVERSATION_CONTINUATION" if travelContinuation { continuedSemantic.Interpretation.SelectionReason = "travel follow-up completed the previous travel task" } else { continuedSemantic.Interpretation.SelectionReason = "location follow-up completed the previous local-search task" } semantic = continuedSemantic } } if !creativeUserRequest && travelContinuation && interactiondomain.TravelSearchClarification(effectiveTaskMessage, conversationHistory) == "" { semantic.CapabilityHints = append(semantic.CapabilityHints, string(capability.CapabilityKindWebResearch)) semantic.NeedsCognition = false semantic.Interpretation.NeedsCognition = false } creativeRequest := creativeUserRequest || interactiondomain.IsCreativeRequest(effectiveTaskMessage) creativeReply := interactiondomain.CreativeDeterministicReply(utterance.Content) if creativeReply == "" { creativeReply = interactiondomain.CreativeDeterministicReply(effectiveTaskMessage) } creativeFallbackReply := interactiondomain.CreativeFallbackReply(utterance.Content) if creativeFallbackReply == "" { creativeFallbackReply = interactiondomain.CreativeFallbackReply(effectiveTaskMessage) } if creativeRequest { semantic.CapabilityHints = interactiondomain.RemoveCapabilityHint(semantic.CapabilityHints, string(capability.CapabilityKindWebResearch)) if creativeReply != "" { semantic.Reply = creativeReply semantic.NeedsCognition = false semantic.Interpretation.NeedsCognition = false } else { semantic.NeedsCognition = true semantic.Interpretation.NeedsCognition = true semantic.CapabilityHints = append(semantic.CapabilityHints, string(capability.CapabilityKindCognitiveReasoning)) if creativeFallbackReply != "" { semantic.Reply = creativeFallbackReply } } } if locationReply != "" { continuationMessage = "" travelContinuation = false effectiveTaskMessage = interactiondomain.StripAssistantVocative(utterance.Content) previousTask = nil semantic.Reply = locationReply semantic.NeedsCognition = false semantic.Interpretation.NeedsCognition = false } if previousTask != nil { previousSemantic, previousErr := interactiondomain.InterpretUtterance(*previousTask, projects, previousFocus, now) if previousErr == nil { semantic = interactiondomain.CarryForwardPreviousTask(semantic, previousSemantic) } } assistantName := interactiondomain.PreferredAssistantName(utterance.Content) if assistantName == "" { assistantName = latestAssistantName(conversationHistory) } identityReply := "" memoryInstructionReply := "" log.Printf("interaction identity context: name=%q history=%d", assistantName, len(conversationHistory)) if interactiondomain.IsIdentityInquiry(utterance.Content) && assistantName != "" { identityReply = "Меня зовут " + assistantName + ". Я Домовой, ваш помощник." semantic.Reply = identityReply semantic.NeedsCognition = false semantic.CapabilityHints = nil semantic.Interpretation.Applicability = interaction.SemanticApplicable semantic.Interpretation.Clarification = interaction.ClarificationNotRequired } if identityReply == "" && interactiondomain.IsExplicitMemoryInstruction(utterance.Content) { memoryInstructionReply = "Запомнил." semantic.Reply = memoryInstructionReply semantic.NeedsCognition = false semantic.CapabilityHints = nil semantic.Interpretation.Applicability = interaction.SemanticApplicable semantic.Interpretation.Clarification = interaction.ClarificationNotRequired } deterministicSpeechAct := semantic.Interpretation.SpeechAct deterministicReply := semantic.Reply routePlan := buildChatRoutePlan(semantic) cognitiveAttempted := false cognitiveAvailable := true semanticInterpretationRequested := shouldRequestSemanticInterpretation(semantic) semanticReflexAudit := shouldAuditDeterministicReflex(semantic, string(utterance.ID), now) // A selected specialized route already has an executable semantic outcome. // Do not block it on the general cognitive channel: research and image // generation must reach their own adapters even when cognition is degraded. if specializedRouteBypassesGeneralInterpretation(routePlan) { semanticInterpretationRequested = false semanticReflexAudit = false } if stableConversational { semanticInterpretationRequested = false semanticReflexAudit = false } if retrievedMemoryReply != "" { semanticInterpretationRequested = false semanticReflexAudit = false } if identityReply != "" || memoryInstructionReply != "" { semanticReflexAudit = false } if semanticInterpretationRequested || semanticReflexAudit { cognitiveAttempted = true projectNames := make([]string, 0, len(semantic.Projects)) for _, ref := range semantic.Projects { if ref.ProjectDisplayName != "" { projectNames = append(projectNames, ref.ProjectDisplayName) } } cognitiveUtterance := utterance cognitiveUtterance.Content = effectiveTaskMessage if response, interpretationErr := cognitiveClient.Interpret(r.Context(), buildGatewayInterpretationRequest(session, cognitiveUtterance, semantic, projectNames)); interpretationErr == nil && response.FinishReason == cognitive.FinishCompleted { if candidate, candidateErr := interactionCandidateFromGateway(utterance.ID, response.Candidate, now); candidateErr == nil { semantic = interactiondomain.ApplyCognitiveCandidate(semantic, effectiveTaskMessage, candidate, previousFocus) } else { cognitiveAvailable = false } } else { cognitiveAvailable = false } } if !creativeRequest && !stableConversational && retrievedMemoryReply == "" { if travelReply := interactiondomain.TravelSearchClarification(effectiveTaskMessage, conversationHistory); travelReply != "" { semantic.Interpretation.Clarification = interaction.ClarificationRequired semantic.Interpretation.Applicability = interaction.SemanticCandidateClarify semantic.Reply = travelReply deterministicReply = travelReply semantic.NeedsCognition = false semantic.Interpretation.NeedsCognition = false } } if err := interactionRepository.AppendUtterance(r.Context(), conversation, utterance, intent, relations); err != nil { http.Error(w, "save chat message", http.StatusInternalServerError) return } if err := interactionRepository.SaveSemanticResult(r.Context(), semantic.Interpretation, semantic.Candidates, semantic.Projects, semantic.Relations); err != nil { http.Error(w, "save chat semantics", http.StatusInternalServerError) return } var responseResult continuity.HumanResponseResult var responseErr error if shouldInvokeHumanResponse(stableConversational, identityReply, memoryInstructionReply) { responseResult, responseErr = responseClient.Respond(r.Context(), r.Header.Get("Authorization"), continuity.HumanResponseRequest{ SourceUtteranceID: string(utterance.ID), SourceIntentID: string(intent.ID), Message: effectiveTaskMessage, RequestedAtUnixMillis: now.UnixMilli(), }) } if semantic.Interpretation.SpeechAct == interaction.SpeechActCorrection { learning.recordCorrection(r.Context(), correctionLearningInput{Utterance: utterance, Intent: intent, At: now}) } activeFocusName := "" activeFocusProvenance := situation.ProjectResolutionProvenance("") if semantic.ActiveFocus != nil { if err := repository.SaveActiveProjectFocus(r.Context(), *semantic.ActiveFocus); err != nil { telemetry.RecordCompatibility("active-project-focus-write", "core-runtime", "write", "semantic-focus-update", true, false, true, "FAILURE", utterance.CorrelationID, string(session.PrincipalID), now) http.Error(w, "save project focus", http.StatusInternalServerError) return } telemetry.RecordCompatibility("active-project-focus-write", "core-runtime", "write", "semantic-focus-update", true, false, true, "SUCCESS", utterance.CorrelationID, string(session.PrincipalID), now) activeFocusName = semantic.ActiveFocus.ProjectDisplayName activeFocusProvenance = semantic.ActiveFocus.Provenance } else if len(semantic.Projects) > 0 && semantic.Projects[0].Provenance == situation.ProjectResolutionInheritedFocus && previousFocus != nil { activeFocusName = previousFocus.ProjectDisplayName activeFocusProvenance = previousFocus.Provenance } planningReply := deterministicReply workPlanned := false needsCognition := semantic.NeedsCognition projectID := "" if semantic.ActiveFocus != nil { projectID = string(semantic.ActiveFocus.ProjectID) } else if len(semantic.Projects) > 0 && semantic.Projects[0].Provenance == situation.ProjectResolutionInheritedFocus { projectID = string(previousFocus.ProjectID) } if responseErr == nil && responseResult.Handled && !stableConversational { planningReply = responseResult.Reply needsCognition = false learning.recordReminderResponse(r.Context(), reminderResponseLearningInput{Utterance: utterance, Intent: intent, Result: responseResult, At: now}) if resumed, err := continueDelegationFromHumanResponse(r.Context(), workRepository, responseResult.Response, now); err == nil && resumed { planningReply = "Принял. Продолжаю ту же задачу coding-agent без потери контекста." } } else if responseErr == nil && looksLikeUnsupportedContinuation(utterance.Content, responseResult) { learning.recordFastPathDecline(r.Context(), fastPathDeclineLearningInput{Utterance: utterance, Intent: intent, Result: responseResult, Reason: declineReasonForResponse(responseResult), At: now}) } planningInput := work.PlanningInput{ ConversationID: work.ConversationRef(conversation.ID), UtteranceID: work.SourceUtteranceRef(utterance.ID), IntentID: work.SourceIntentRef(intent.ID), ActorID: session.ActorID, PrincipalID: session.PrincipalID, ScopeID: authority.ScopeID("domovoy-global-scope"), ProjectID: work.ProjectRef(projectID), ProjectDisplayName: activeFocusName, ProjectProvenance: work.ProjectProvenanceRef(activeFocusProvenance), Message: effectiveTaskMessage, SpeechAct: work.SpeechActRef(semantic.Interpretation.SpeechAct), SecondarySpeechAct: work.SpeechActRef(semantic.Interpretation.SecondarySpeechAct), AssertionVerification: epistemics.VerificationState(semantic.Interpretation.AssertionVerification), CapabilityHints: toCapabilityKinds(semantic.CapabilityHints), NeedsCognition: semantic.NeedsCognition, RequiresClarification: semantic.Interpretation.Clarification == interaction.ClarificationRequired, ReceivedAtUnixMillis: utterance.ReceivedAtUnixMillis, } helpPath := workdomain.DetermineHelpPath(planningInput) // Introspection requests are read-only household UX. A cognitive // candidate must not reinterpret them as an executable delegation. switch semantic.InformationRequestKind { case interaction.InformationRequestCapabilityInventory, interaction.InformationRequestLocalCapabilityInventory, interaction.InformationRequestCognitiveModelInventory, interaction.InformationRequestCognitiveCurrentModel, interaction.InformationRequestCognitiveChannelStatus, interaction.InformationRequestCognitiveRecovery, interaction.InformationRequestMemoryInventory: helpPath = work.HelpPath{} } if responseErr == nil && responseResult.Handled { helpPath = work.HelpPath{} } switch semantic.InformationRequestKind { case interaction.InformationRequestMemoryInventory: planningReply = humanMemoryInventory(conversationHistory, assistantName) needsCognition = false case interaction.InformationRequestResearchFailureExplanation: planningReply = "Я не выдаю предположение как факт, если не получил согласованных проверяемых данных. Могу повторить поиск, уточнить запрос или проверить другой источник." needsCognition = false case interaction.InformationRequestCapabilityInventory: kinds, err := capabilityRepository.AvailableCapabilityKinds(r.Context(), session.PrincipalID) if err == nil && len(kinds) > 0 { planningReply = "Я умею общаться, искать проверяемую информацию, работать с подключёнными моделями и " + humanCapabilityList(kinds) + "." } else { planningReply = "Я умею общаться, сохранять контекст, искать проверяемую информацию и работать с подключёнными моделями. Дополнительные действия подключаются отдельно." } needsCognition = false case interaction.InformationRequestLocalCapabilityInventory: planningReply = "Без внешних моделей я могу поддерживать разговор, хранить контекст, вести цели и напоминания, а также честно сообщать состояние подключённых возможностей." needsCognition = false case interaction.InformationRequestCognitiveModelInventory: candidates, inventoryErr := capabilityRepository.LoadCognitiveCandidates(r.Context(), capability.CapabilityID("creator-advisory"), session.PrincipalID) planningReply = humanCognitiveModelInventory(candidates, inventoryErr) needsCognition = false case interaction.InformationRequestCognitiveCurrentModel: health, healthErr := capabilityRepository.LoadHealth(r.Context(), capability.CapabilityID("cognitive-proposal")) planningReply = humanCurrentCognitiveModelReply(health, healthErr) needsCognition = false case interaction.InformationRequestCognitiveChannelStatus, interaction.InformationRequestCognitiveRecovery: health, healthErr := capabilityRepository.LoadHealth(r.Context(), capability.CapabilityID("cognitive-proposal")) planningReply = humanCognitiveChannelReply(semantic.InformationRequestKind, semantic.Interpretation.SpeechAct, health, healthErr) needsCognition = false } researchBacked := false researchAttempted := false stableKnowledgeReply := false if retrievedMemoryReply != "" { planningReply = retrievedMemoryReply needsCognition = false } if hasChatRoute(routePlan, chatRouteResearch) && semantic.Interpretation.Clarification != interaction.ClarificationRequired && shouldRunGeneralResearch(semantic.InformationRequestKind) { researchAttempted = true if planID, idErr := ids.New(); idErr == nil { parts := interactiondomain.IndependentRequestParts(effectiveTaskMessage) replies := make([]string, 0, len(parts)) for index, part := range parts { if clarification := interactiondomain.LocalSearchClarification(part); clarification != "" { replies = append(replies, "По запросу «"+part+"»: "+clarification) continue } learnedLexicon := interactiondomain.TaskLexiconFromHistory(conversationHistory) // Preserve the original utterance for semantic route compilation // before removing a search imperative. Distance and route skills // need the user's relation words (for example "до") to bind a // remembered current location to the requested destination. searchQuery := interactiondomain.TravelResearchQuery(part, conversationHistory, now) searchQuery = interactiondomain.SearchQueryWithLexicon(searchQuery, learnedLexicon) searchQuery = interactiondomain.TimeDifferenceResearchQuery(searchQuery) // The research capability may try its configured source and a // source-recovery fallback. Its caller must leave enough budget for // both attempts and evidence evaluation. researchCtx, cancelResearch := context.WithTimeout(r.Context(), 18*time.Second) result, researchErr := researchClient.SearchWeb(researchCtx, epistemics.WebSearchRequest{ Plan: epistemics.ResearchPlan{ID: epistemics.ResearchPlanID(fmt.Sprintf("plan:%s:%d", planID, index)), Question: searchQuery, SourceClasses: []string{"web-search"}, FreshnessPolicy: "current"}, PropositionKey: fmt.Sprintf("question:%s:%d", utterance.ID, index), Query: searchQuery, }) reply, backed := renderResearchReply(searchQuery, result, researchErr) if !backed && cognitiveAvailable { answerCtx, cancelAnswer := context.WithTimeout(r.Context(), 12*time.Second) answer, answerErr := cognitiveClient.AnswerStableKnowledge(answerCtx, session, part, gen2Context.Text, cognitive.TaskMeaning{ SpeechAct: string(semantic.Interpretation.SpeechAct), CapabilityHints: toCapabilityKinds(semantic.CapabilityHints), RequestedOutcome: semantic.Interpretation.RequestedOutcome, InformationRequestKind: semantic.InformationRequestKind, ConfidenceMillis: semantic.Interpretation.ConfidenceMillis, }) cancelAnswer() if answerErr == nil && stableKnowledgeAnswer(answer) { reply, backed = answer, true stableKnowledgeReply = true } } cancelResearch() if backed { researchBacked = true } if len(parts) > 1 { reply = "По запросу «" + part + "»: " + reply } replies = append(replies, reply) } planningReply = strings.Join(replies, "\n\n") if researchBacked { semantic.Reply = planningReply } // A failed source must not turn the complete conversation into a // runtime failure or invite an unsupported local guess for a fact. needsCognition = false } } if helpPath.Reply != "" { planningReply = helpPath.Reply needsCognition = false } if !creativeRequest && !stableConversational && retrievedMemoryReply == "" { if travelReply := interactiondomain.TravelSearchClarification(effectiveTaskMessage, conversationHistory); travelReply != "" { planningReply = travelReply needsCognition = false } } canGenerateTextReply := shouldGenerateNonMutatingTextDirective(semantic, routePlan, helpPath, creativeRequest) && planningReply == deterministicReply if (planningReply == "" && needsCognition && semantic.Interpretation.SpeechAct == interaction.SpeechActQuestion || canGenerateTextReply) && !hasChatRoute(routePlan, chatRouteResearch) && !creativeRequest { answerCtx, cancelAnswer := context.WithTimeout(r.Context(), 12*time.Second) answer, answerErr := cognitiveClient.AnswerStableKnowledge(answerCtx, session, effectiveTaskMessage, gen2Context.Text, cognitive.TaskMeaning{ SpeechAct: string(semantic.Interpretation.SpeechAct), CapabilityHints: toCapabilityKinds(semantic.CapabilityHints), RequestedOutcome: semantic.Interpretation.RequestedOutcome, InformationRequestKind: semantic.InformationRequestKind, ConfidenceMillis: semantic.Interpretation.ConfidenceMillis, }) cancelAnswer() if answerErr == nil && stableKnowledgeAnswer(answer) { planningReply = answer stableKnowledgeReply = true needsCognition = false } } if planningReply == "" && needsCognition && cognitiveAttempted && !cognitiveAvailable { planningReply = humanCognitiveDegradedReply(utterance.Content) needsCognition = false } if creativeFallbackReply != "" && cognitiveAttempted && !cognitiveAvailable { planningReply = creativeFallbackReply needsCognition = false } var capabilityStatus capability.CapabilityResolutionStatus var capabilityKind capability.CapabilityKind var capabilityGapID capability.CapabilityGapID var delegationID work.DelegationID var delegationState work.DelegationState var reminder *continuity.ProspectiveItem var generatedImage capability.MultimodalResponse mediaHandled := false mediaAttempted := false if hasChatRoute(routePlan, chatRouteMedia) && semantic.Interpretation.Clarification != interaction.ClarificationRequired { mediaAttempted = true imageCtx, cancelImage := context.WithTimeout(r.Context(), 15*time.Minute) generatedImage, err = cognitiveClient.GenerateImage(imageCtx, effectiveTaskMessage, "") cancelImage() if err == nil && strings.TrimSpace(generatedImage.DataBase64) != "" { planningReply = "Готово." semantic.Reply = planningReply needsCognition = false mediaHandled = true } else { planningReply = "Сейчас не удалось создать изображение." semantic.Reply = planningReply needsCognition = false } } if helpPath.Kind == work.HelpPathDirectCapability && len(helpPath.SuggestedCapabilityKinds) > 0 { capabilityKind = helpPath.SuggestedCapabilityKinds[0] started := time.Now() resolution, resolveErr := resolveChatCapability(r.Context(), capabilityRepository, capabilityKind, session, intent, utterance, now) capabilityStatus = resolution.Status if resolveErr == nil && len(resolution.Eligible) > 0 { reminderRequest, reminderErr := buildReminderRequest(utterance.Content, session, intent, utterance, now) if reminderErr == nil { result, scheduleErr := reminderClient.Schedule(r.Context(), r.Header.Get("Authorization"), reminderRequest) finished := time.Now() if scheduleErr == nil { reminder = &result.Item planningReply = fmt.Sprintf("Готово. Я создал напоминание: %s.", result.Item.Summary) usageID, _ := observeCapabilityUsage(r.Context(), capabilityRepository, resolution.Eligible[0], resolution.Requirement, capability.UsageSucceeded, string(result.Item.ID), started, finished) learning.recordCapabilityUse(r.Context(), capabilityLearningInput{Utterance: utterance, Intent: intent, ImplementationID: resolution.Eligible[0].Implementation.ID, UsageObservation: usageID, Outcome: capability.UsageSucceeded, OutcomeRef: "continuity:prospective:" + string(result.Item.ID), At: finished}) } else { planningReply = "Scheduling capability сейчас недоступна: напоминание не создано. Я не буду притворяться, что действие выполнено." usageID, _ := observeCapabilityUsage(r.Context(), capabilityRepository, resolution.Eligible[0], resolution.Requirement, capability.UsageFailed, "continuity-reminder-unavailable", started, finished) learning.recordCapabilityUse(r.Context(), capabilityLearningInput{Utterance: utterance, Intent: intent, ImplementationID: resolution.Eligible[0].Implementation.ID, UsageObservation: usageID, Outcome: capability.UsageFailed, OutcomeRef: "continuity-reminder-unavailable", At: finished}) } } else { planningReply = "Мне нужно уточнить время напоминания. Например: «Напомни завтра в 10 позвонить врачу»." capabilityStatus = capability.ResolutionPartiallyAvailable } } else if resolution.Gap != nil { capabilityGapID = resolution.Gap.ID planningReply = "Сейчас scheduling capability недоступна, поэтому напоминание не создано. Я зафиксировал capability gap." learning.recordCapabilityUse(r.Context(), capabilityLearningInput{Utterance: utterance, Intent: intent, GapID: resolution.Gap.ID, Outcome: capability.UsageSkipped, OutcomeRef: "capability:gap:" + string(resolution.Gap.ID), At: now}) } needsCognition = false } if helpPath.Kind == work.HelpPathCapabilityGap && len(helpPath.SuggestedCapabilityKinds) > 0 { capabilityKind = helpPath.SuggestedCapabilityKinds[0] resolution, resolveErr := resolveChatCapability(r.Context(), capabilityRepository, capabilityKind, session, intent, utterance, now) capabilityStatus = resolution.Status if resolveErr == nil { planningReply = "Capability найдена, но в этом шаге она не исполняется без отдельного разрешённого action path." } else if resolution.Gap != nil { capabilityGapID = resolution.Gap.ID planningReply = "Сейчас у меня ещё не подключена возможность отправлять почту. Я зафиксировал capability gap, без ложного выполнения." learning.recordCapabilityUse(r.Context(), capabilityLearningInput{Utterance: utterance, Intent: intent, GapID: resolution.Gap.ID, Outcome: capability.UsageSkipped, OutcomeRef: "capability:gap:" + string(resolution.Gap.ID), At: now}) } needsCognition = false } if helpPath.NeedsDurableWork { planning, err := workdomain.BuildPlanningResult(planningInput, now) if err != nil { http.Error(w, "plan chat work", http.StatusServiceUnavailable) return } if err := workRepository.SavePlanningResult(r.Context(), planning); err != nil { http.Error(w, "save work plan", http.StatusInternalServerError) return } planningReply = planning.Reply workPlanned = true needsCognition = false if created, resolution, createErr := maybeCreateAgentDelegation(r.Context(), workRepository, capabilityRepository, session, conversation, intent, utterance, planning, now); createErr == nil && created != nil { delegationID = created.Delegation.ID delegationState = created.Delegation.State capabilityKind = created.Delegation.CapabilityKind capabilityStatus = resolution.Status planningReply = "Понял задачу. Я собрал work item, выбрал coding-agent в CANARY-режиме и создал delegation. Буду вести её как отдельный run с checkpoint/resume, без потери исходного human intent." } else if resolution.Gap != nil { capabilityKind = resolution.Requirement.CapabilityKind capabilityStatus = resolution.Status capabilityGapID = resolution.Gap.ID planningReply = planningReply + " Для coding delegation подходящий agent implementation сейчас недоступен, поэтому я сохранил план без ложного запуска." } } if identityReply != "" { planningReply = identityReply needsCognition = false } if memoryInstructionReply != "" { planningReply = memoryInstructionReply needsCognition = false } // Stable-knowledge answers have already passed the local cognitive // route with compact, source-aware context. Re-reviewing the same answer // serially can exhaust the household request budget on a loaded local // provider and turn a valid answer into a degraded response. if shouldReviewResponse(planningReply, stableKnowledgeReply, stableConversational, identityReply, memoryInstructionReply, retrievedMemoryReply != "") { reviewContext, cancelReview := context.WithTimeout(r.Context(), 12*time.Second) reviewedReply, reviewErr := cognitiveClient.ReviewResponse(reviewContext, session, utterance.Content, planningReply, gen2Context.Text, cognitive.TaskMeaning{ SpeechAct: string(semantic.Interpretation.SpeechAct), CapabilityHints: toCapabilityKinds(semantic.CapabilityHints), RequestedOutcome: semantic.Interpretation.RequestedOutcome, InformationRequestKind: semantic.InformationRequestKind, ConfidenceMillis: semantic.Interpretation.ConfidenceMillis, }) cancelReview() // A source-backed research result is owned by the research capability, // but its wording is not canonical state. Let the cognitive editor turn // it into a useful human answer while reviewPreservesResearchEvidence // still requires the source evidence to remain present. Information // requests without research (identity, channel state and so on) remain // stable semantic replies and cannot be rewritten here. stableSemanticReply := mediaAttempted || identityReply != "" || memoryInstructionReply != "" || creativeReply != "" || (creativeFallbackReply != "" && !cognitiveAvailable) || (semantic.InformationRequestKind != "" && !(researchAttempted && researchBacked)) || locationReply != "" || deterministicSpeechAct == interaction.SpeechActConversational || deterministicSpeechAct == interaction.SpeechActConfirmation || deterministicSpeechAct == interaction.SpeechActRejection if reviewErr != nil { log.Printf("cognitive response review unavailable: %v", reviewErr) } else if reviewMayReplacePlanningReply(stableSemanticReply, stableKnowledgeReply, researchAttempted, researchBacked, reviewedReply, planningReply) { planningReply = reviewedReply } else if reviewedReply != planningReply { log.Printf("cognitive response review proposed stable semantic rewrite; preserving semantic reply") } } if !mediaAttempted && !mediaHandled && identityReply == "" && creativeReply == "" && creativeFallbackReply == "" { if stableConversational { planningReply = stableConversationalReply needsCognition = false } } if identityReply != "" { semantic.Reply = identityReply needsCognition = false planningReply = identityReply } if memoryInstructionReply != "" { semantic.Reply = memoryInstructionReply needsCognition = false planningReply = memoryInstructionReply } if creativeReply != "" { semantic.Reply = creativeReply needsCognition = false planningReply = creativeReply } if creativeFallbackReply != "" && !cognitiveAvailable { semantic.Reply = creativeFallbackReply needsCognition = false planningReply = creativeFallbackReply } if strings.TrimSpace(planningReply) != "" { semantic.Reply = planningReply } log.Printf("chat response final message=%q act=%s deterministic=%s handled=%t fallback=%q planning=%q", utterance.Content, semantic.Interpretation.SpeechAct, deterministicSpeechAct, responseResult.Handled, semantic.Reply, planningReply) response := interaction.ChatMessageResponse{ ConversationID: conversation.ID, UtteranceID: utterance.ID, OriginalIntentID: intent.ID, SpeechAct: semantic.Interpretation.SpeechAct, InterpretationState: utterance.InterpretationState, NeedsCognition: needsCognition, FallbackText: semantic.Reply, AssistantName: assistantName, ActiveProjectName: activeFocusName, ProjectProvenance: activeFocusProvenance, Clarification: semantic.Interpretation.Clarification, WorkPlanned: workPlanned, PlanningReply: planningReply, ResearchBacked: researchBacked, CapabilityStatus: capabilityStatus, CapabilityKind: capabilityKind, CapabilityHints: toCapabilityKinds(semantic.CapabilityHints), RequestedOutcome: semantic.Interpretation.RequestedOutcome, InformationRequestKind: semantic.InformationRequestKind, InterpretationConfidenceMillis: semantic.Interpretation.ConfidenceMillis, CapabilityGapID: capabilityGapID, DelegationID: string(delegationID), DelegationState: string(delegationState), Reminder: reminder, GeneratedImageContentType: generatedImage.ContentType, GeneratedImageBase64: generatedImage.DataBase64, } w.Header().Set("Content-Type", "application/json") w.Header().Set("Cache-Control", "no-store") w.WriteHeader(http.StatusCreated) _ = json.NewEncoder(w).Encode(response) }) mux.HandleFunc("GET /v1/creator/delegations/{id}", func(w http.ResponseWriter, r *http.Request) { if _, ok := authenticateCreator(w, r); !ok { return } value, err := workRepository.LoadDelegation(r.Context(), work.DelegationID(r.PathValue("id"))) if err != nil { http.Error(w, "delegation not found", http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(value) }) mux.HandleFunc("GET /v1/creator/delegations/{id}/latest-checkpoint", func(w http.ResponseWriter, r *http.Request) { if _, ok := authenticateCreator(w, r); !ok { return } value, err := workRepository.LatestAgentCheckpoint(r.Context(), work.DelegationID(r.PathValue("id"))) if err != nil { http.Error(w, "checkpoint not found", http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(value) }) mux.HandleFunc("POST /v1/creator/delegations/{id}/runs/{run}/checkpoints", func(w http.ResponseWriter, r *http.Request) { session, ok := authenticateCreator(w, r) if !ok { return } var request struct { BlockerClass work.AgentBlockerClass CompletedSummary string RemainingSummary string TestSummary string BranchName string CommitHash string NextStep string ArtifactRefs []work.EvidenceRef RecordedAtUnixMillis int64 } decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 32<<10)) decoder.DisallowUnknownFields() if err := decoder.Decode(&request); err != nil { http.Error(w, "invalid checkpoint", http.StatusBadRequest) return } run, err := workRepository.LoadAgentRun(r.Context(), work.AgentRunID(r.PathValue("run"))) if err != nil || run.DelegationID != work.DelegationID(r.PathValue("id")) { http.Error(w, "run not found", http.StatusNotFound) return } updatedRun, checkpoint, err := workdomain.CheckpointRun(run, request.BlockerClass, request.CompletedSummary, request.RemainingSummary, request.TestSummary, request.BranchName, request.CommitHash, request.NextStep, request.ArtifactRefs, time.UnixMilli(request.RecordedAtUnixMillis)) if err != nil { http.Error(w, "invalid checkpoint", http.StatusBadRequest) return } if err := workRepository.SaveAgentCheckpoint(r.Context(), updatedRun, checkpoint); err != nil { http.Error(w, "save checkpoint", http.StatusInternalServerError) return } if request.BlockerClass == work.AgentBlockerHumanDecisionRequired { delegation, err := workRepository.LoadDelegation(r.Context(), run.DelegationID) if err == nil { _, _ = attentionClient.Create(r.Context(), r.Header.Get("Authorization"), continuity.CreateAttentionRequest{ OwnerPrincipalID: session.PrincipalID, ScopeID: authority.ScopeID("domovoy-global-scope"), SourceKind: continuity.AttentionSourceAgentDelegation, SourceExternalRef: delegationAttentionRef(delegation.ID), Level: continuity.AttentionActionRequired, Summary: "Нужен твой выбор по задаче coding-agent: " + firstNonEmpty(request.RemainingSummary, delegation.DesiredOutcome), DueAtUnixMillis: checkpoint.RecordedAtUnixMillis, CreatedAtUnixMillis: checkpoint.RecordedAtUnixMillis, }) } } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) _ = json.NewEncoder(w).Encode(checkpoint) }) mux.HandleFunc("POST /v1/creator/delegations/{id}/runs/{run}/results", func(w http.ResponseWriter, r *http.Request) { if _, ok := authenticateCreator(w, r); !ok { return } var request struct { Summary string OutcomeRef string VerificationSummary string ArtifactRefs []work.EvidenceRef RecordedAtUnixMillis int64 } decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 32<<10)) decoder.DisallowUnknownFields() if err := decoder.Decode(&request); err != nil { http.Error(w, "invalid result", http.StatusBadRequest) return } run, err := workRepository.LoadAgentRun(r.Context(), work.AgentRunID(r.PathValue("run"))) if err != nil || run.DelegationID != work.DelegationID(r.PathValue("id")) { http.Error(w, "run not found", http.StatusNotFound) return } updatedRun, result, err := workdomain.CompleteRun(run, request.Summary, request.OutcomeRef, request.VerificationSummary, request.ArtifactRefs, time.UnixMilli(request.RecordedAtUnixMillis)) if err != nil { http.Error(w, "invalid result", http.StatusBadRequest) return } if err := workRepository.SaveAgentResult(r.Context(), updatedRun, result); err != nil { http.Error(w, "save result", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) _ = json.NewEncoder(w).Encode(result) }) mux.HandleFunc("POST /v1/creator/delegations/{id}/resume", func(w http.ResponseWriter, r *http.Request) { if _, ok := authenticateCreator(w, r); !ok { return } delegation, err := workRepository.LoadDelegation(r.Context(), work.DelegationID(r.PathValue("id"))) if err != nil { http.Error(w, "delegation not found", http.StatusNotFound) return } run, err := workRepository.LoadAgentRun(r.Context(), delegation.LatestRunID) if err != nil { http.Error(w, "run not found", http.StatusNotFound) return } checkpoint, err := workRepository.LatestAgentCheckpoint(r.Context(), delegation.ID) if err != nil { http.Error(w, "checkpoint not found", http.StatusNotFound) return } updatedDelegation, resumedRun, err := workdomain.ResumeDelegationRun(delegation, run, checkpoint, time.Now()) if err != nil { http.Error(w, "resume not allowed", http.StatusConflict) return } if err := workRepository.SaveResumedRun(r.Context(), updatedDelegation, resumedRun); err != nil { http.Error(w, "save resumed run", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) _ = json.NewEncoder(w).Encode(resumedRun) }) mux.HandleFunc("POST /v1/creator/delegations/{id}/verify", func(w http.ResponseWriter, r *http.Request) { if _, ok := authenticateCreator(w, r); !ok { return } var request struct { Verified bool Summary string EvidenceRefs []work.EvidenceRef RecordedAtUnixMillis int64 } decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 32<<10)) decoder.DisallowUnknownFields() if err := decoder.Decode(&request); err != nil || request.RecordedAtUnixMillis < 1 { http.Error(w, "invalid verification", http.StatusBadRequest) return } delegation, err := workRepository.LoadDelegation(r.Context(), work.DelegationID(r.PathValue("id"))) if err != nil { http.Error(w, "delegation not found", http.StatusNotFound) return } run, err := workRepository.LoadAgentRun(r.Context(), delegation.LatestRunID) if err != nil { http.Error(w, "run not found", http.StatusNotFound) return } result, err := workRepository.LatestAgentResult(r.Context(), delegation.ID) if err != nil { http.Error(w, "result not found", http.StatusNotFound) return } updatedDelegation, verification, err := workdomain.VerifyDelegationResult(delegation, run, result, request.Verified, request.Summary, request.EvidenceRefs, time.UnixMilli(request.RecordedAtUnixMillis)) if err != nil { http.Error(w, "invalid verification", http.StatusBadRequest) return } if err := workRepository.SaveDelegationVerification(r.Context(), updatedDelegation, verification); err != nil { http.Error(w, "save verification", http.StatusInternalServerError) return } runCount, _ := workRepository.CountAgentRuns(r.Context(), delegation.ID) learning.recordDelegationOutcome(r.Context(), delegationLearningInput{ Intent: interaction.OriginalHumanIntent{ID: interaction.OriginalHumanIntentID(delegation.SourceIntentID), Text: delegation.DesiredOutcome}, Utterance: interaction.HumanUtterance{ID: interaction.HumanUtteranceID(delegation.SourceUtteranceID), Content: delegation.DesiredOutcome}, DelegationID: string(delegation.ID), RunID: string(run.ID), ImplementationID: run.ImplementationID, OutcomeRef: result.OutcomeRef, VerificationRefs: evidenceRefsToEpistemics(request.EvidenceRefs), Verified: request.Verified, ExecutionWindows: runCount, HumanBlocked: false, At: time.UnixMilli(request.RecordedAtUnixMillis), }) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) _ = json.NewEncoder(w).Encode(verification) }) mux.HandleFunc("GET /v1/principal-bindings/{actor}/{principal}", func(w http.ResponseWriter, r *http.Request) { binding, err := identityRepository.LoadBinding(r.Context(), identity.ActorID(r.PathValue("actor")), identity.PrincipalID(r.PathValue("principal"))) if err != nil { http.Error(w, "principal binding not found", http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(binding) }) mux.HandleFunc("GET /v1/tasks/{id}", func(w http.ResponseWriter, r *http.Request) { target, err := workRepository.Load(r.Context(), work.TaskID(r.PathValue("id"))) if err != nil { http.Error(w, "task not found", http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(target) }) mux.HandleFunc("POST /v1/capabilities/health", func(w http.ResponseWriter, r *http.Request) { var status capability.Health if err := json.NewDecoder(r.Body).Decode(&status); err != nil { http.Error(w, "invalid capability health", http.StatusBadRequest) return } if err := capabilitydomain.ValidateHealth(status); err != nil { http.Error(w, "invalid capability health", http.StatusBadRequest) return } if err := capabilityRepository.SaveHealth(r.Context(), status); err != nil { http.Error(w, "save capability health", http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) }) mux.HandleFunc("GET /v1/capabilities/{id}/health", func(w http.ResponseWriter, r *http.Request) { status, err := capabilityRepository.LoadHealth(r.Context(), capability.CapabilityID(r.PathValue("id"))) if err != nil { http.Error(w, "capability health not found", http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(status) }) mux.HandleFunc("GET /v1/capability-bindings/{id}/revisions/{revision}", func(w http.ResponseWriter, r *http.Request) { revision, err := strconv.ParseInt(r.PathValue("revision"), 10, 64) if err != nil || revision < 1 { http.Error(w, "invalid binding revision", http.StatusBadRequest) return } binding, err := capabilityRepository.LoadBinding(r.Context(), capability.CapabilityBindingID(r.PathValue("id")), revision) if err != nil { http.Error(w, "capability binding not found", http.StatusNotFound) return } if err = capabilitydomain.ValidateBinding(binding); err != nil { http.Error(w, "capability binding invalid", http.StatusConflict) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(binding) }) mux.HandleFunc("GET /v1/capability-bindings/active", func(w http.ResponseWriter, r *http.Request) { capabilityID := capability.CapabilityID(r.URL.Query().Get("capability_id")) ownerID := identity.PrincipalID(r.URL.Query().Get("owner_principal_id")) if capabilityID == "" || ownerID == "" { http.Error(w, "invalid binding query", http.StatusBadRequest) return } binding, err := capabilityRepository.LoadActiveBinding(r.Context(), capabilityID, ownerID) if err != nil { http.Error(w, "active capability binding not found", http.StatusNotFound) return } if err = capabilitydomain.UsableBinding(binding); err != nil { http.Error(w, "active capability binding unavailable", http.StatusConflict) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(binding) }) mux.HandleFunc("GET /v1/cognitive-bindings/{id}/revisions/{revision}/snapshot", func(w http.ResponseWriter, r *http.Request) { revision, err := strconv.ParseInt(r.PathValue("revision"), 10, 64) if err != nil || revision < 1 { http.Error(w, "invalid cognitive binding revision", http.StatusBadRequest) return } snapshot, err := capabilityRepository.LoadCognitiveCandidate(r.Context(), capability.CapabilityBindingID(r.PathValue("id")), revision) if err != nil { http.Error(w, "cognitive binding snapshot not found", http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(snapshot) }) mux.HandleFunc("POST /v1/cognitive-bindings/resolve", func(w http.ResponseWriter, r *http.Request) { var requirement capability.CognitiveRequirement decoder := json.NewDecoder(io.LimitReader(r.Body, 16*1024+1)) decoder.DisallowUnknownFields() if err := decoder.Decode(&requirement); err != nil || requirement.CapabilityID == "" || requirement.OwnerPrincipalID == "" || requirement.InputBytes < 0 || requirement.MaxOutputBytes < 1 { http.Error(w, "invalid cognitive requirement", http.StatusBadRequest) return } var candidates []capability.ResolvedCognitiveBinding var err error if requirement.TaskProfileID != "" { candidates, err = capabilityRepository.LoadCognitiveCandidatesForProfile(r.Context(), requirement.CapabilityID, requirement.OwnerPrincipalID, requirement.TaskProfileID) } else { candidates, err = capabilityRepository.LoadCognitiveCandidates(r.Context(), requirement.CapabilityID, requirement.OwnerPrincipalID) } if err != nil { http.Error(w, "cognitive registry unavailable", http.StatusServiceUnavailable) return } selected, err := capabilitydomain.SelectCognitiveBinding(requirement, candidates) if err != nil { http.Error(w, "no invocable cognitive binding", http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(selected) }) mux.HandleFunc("POST /v1/cognitive-registry/discoveries", func(w http.ResponseWriter, r *http.Request) { var snapshot capability.CognitiveDiscoverySnapshot decoder := json.NewDecoder(io.LimitReader(r.Body, 1<<20)) decoder.DisallowUnknownFields() if err := decoder.Decode(&snapshot); err != nil { http.Error(w, "invalid cognitive discovery", http.StatusBadRequest) return } registration, err := capabilitydomain.BuildDiscoveryRegistration(snapshot) if err != nil { http.Error(w, "invalid cognitive discovery", http.StatusBadRequest) return } if err := capabilityRepository.SaveCognitiveDiscovery(r.Context(), registration); err != nil { log.Printf("cognitive discovery persistence failed: %v", err) http.Error(w, "save cognitive discovery", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) _ = json.NewEncoder(w).Encode(registration) }) mux.HandleFunc("POST /v1/cognitive-registry/evaluated-capabilities", func(w http.ResponseWriter, r *http.Request) { var value capability.EvaluatedCapability decoder := json.NewDecoder(io.LimitReader(r.Body, 64<<10)) decoder.DisallowUnknownFields() if err := decoder.Decode(&value); err != nil || value.ID == "" || value.BindingID == "" || value.BindingRevision < 1 || value.CapabilityID == "" || value.BenchmarkVersion == "" || value.ArtifactID == "" || value.ObservedAtUnixMillis < 1 { http.Error(w, "invalid evaluated capability", http.StatusBadRequest) return } if err := capabilityRepository.SaveEvaluatedCapability(r.Context(), value); err != nil { http.Error(w, "save evaluated capability", http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) }) mux.HandleFunc("POST /v1/cognitive-registry/task-evaluations", func(w http.ResponseWriter, r *http.Request) { var value capability.TaskEvaluation decoder := json.NewDecoder(io.LimitReader(r.Body, 64<<10)) decoder.DisallowUnknownFields() if err := decoder.Decode(&value); err != nil || capabilitydomain.ValidateTaskEvaluation(value) != nil { http.Error(w, "invalid task evaluation", http.StatusBadRequest) return } if err := capabilityRepository.SaveTaskEvaluation(r.Context(), value); err != nil { http.Error(w, "save task evaluation", http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) }) mux.HandleFunc("POST /v1/cognitive-registry/model-qualification-observations", func(w http.ResponseWriter, r *http.Request) { var value capability.ModelQualificationObservation decoder := json.NewDecoder(io.LimitReader(r.Body, 16<<10)) decoder.DisallowUnknownFields() if err := decoder.Decode(&value); err != nil || value.Validate() != nil { http.Error(w, "invalid model qualification observation", http.StatusBadRequest) return } if err := capabilityRepository.SaveModelQualificationObservation(r.Context(), value); err != nil { http.Error(w, "save model qualification observation", http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) }) mux.HandleFunc("POST /v1/creator/cognitive-canary", func(w http.ResponseWriter, r *http.Request) { session, ok := authenticateCreator(w, r) if !ok { return } var request capability.CognitiveCanaryPromotionRequest decoder := json.NewDecoder(io.LimitReader(r.Body, 16<<10)) decoder.DisallowUnknownFields() if err := decoder.Decode(&request); err != nil { http.Error(w, "invalid canary promotion", http.StatusBadRequest) return } request.RequestedByPrincipalID = session.PrincipalID request.CapabilityID = "creator-advisory" candidates, err := capabilityRepository.LoadCognitiveCandidatesForEvaluation(r.Context(), request.CapabilityID, session.PrincipalID, request.EvaluationID) if err != nil { http.Error(w, "cognitive registry unavailable", http.StatusServiceUnavailable) return } var current capability.ResolvedCognitiveBinding for _, candidate := range candidates { if candidate.Binding.ID == request.BindingID && candidate.Binding.Revision == request.ExpectedBindingRevision { current = candidate break } } promotion, err := capabilitydomain.BuildCanaryPromotion(request, current) if err != nil { http.Error(w, "canary promotion precondition failed", http.StatusConflict) return } if err = capabilityRepository.SaveCognitiveCanaryPromotion(r.Context(), promotion); err != nil { http.Error(w, "canary promotion conflict", http.StatusConflict) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) _ = json.NewEncoder(w).Encode(promotion) }) mux.HandleFunc("POST /v1/creator/cognitive-activate", func(w http.ResponseWriter, r *http.Request) { session, ok := authenticateCreator(w, r) if !ok { return } var request capability.CognitiveActivationPromotionRequest decoder := json.NewDecoder(io.LimitReader(r.Body, 16<<10)) decoder.DisallowUnknownFields() if err := decoder.Decode(&request); err != nil { http.Error(w, "invalid cognitive activation", http.StatusBadRequest) return } request.RequestedByPrincipalID = session.PrincipalID request.CapabilityID = "creator-advisory" current, err := capabilityRepository.LoadCognitiveCandidate(r.Context(), request.BindingID, request.ExpectedBindingRevision) if err != nil { http.Error(w, "cognitive registry unavailable", http.StatusServiceUnavailable) return } promotion, err := capabilitydomain.BuildActivationPromotion(request, current) if err != nil { http.Error(w, "cognitive activation precondition failed", http.StatusConflict) return } if err = capabilityRepository.SaveCognitiveActivationPromotion(r.Context(), promotion); err != nil { http.Error(w, "cognitive activation conflict", http.StatusConflict) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) _ = json.NewEncoder(w).Encode(promotion) }) mux.HandleFunc("POST /v1/cognitive-registry/performance-evidence", func(w http.ResponseWriter, r *http.Request) { var value capability.PerformanceEvidence decoder := json.NewDecoder(io.LimitReader(r.Body, 64<<10)) decoder.DisallowUnknownFields() if err := decoder.Decode(&value); err != nil || value.ID == "" || value.BindingID == "" || value.BindingRevision < 1 || value.ModelDescriptorID == "" || value.ModelRevision < 1 || value.HardwareProfile == "" || value.Runtime == "" || value.BenchmarkVersion == "" || value.ArtifactID == "" || value.ObservedAtUnixMillis < 1 { http.Error(w, "invalid performance evidence", http.StatusBadRequest) return } if err := capabilityRepository.SavePerformanceEvidence(r.Context(), value); err != nil { http.Error(w, "save performance evidence", http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) }) mux.HandleFunc("POST /v1/cognitive-usage-observations", func(w http.ResponseWriter, r *http.Request) { var observation capability.CognitiveUsageObservation decoder := json.NewDecoder(io.LimitReader(r.Body, 16*1024+1)) decoder.DisallowUnknownFields() if err := decoder.Decode(&observation); err != nil || observation.ObservationID == "" || observation.RequestID == "" || observation.BindingID == "" || observation.BindingRevision < 1 || observation.ProviderInstanceID == "" || observation.ModelDescriptorID == "" || observation.ObservedAtUnixMillis < 1 || observation.LatencyMillis < 0 || observation.InputUnits < 0 || observation.OutputUnits < 0 || (observation.EstimatedCostMicros != nil && *observation.EstimatedCostMicros < 0) || (observation.ActualCostMicros != nil && *observation.ActualCostMicros < 0) || observation.ResultClass == "" { http.Error(w, "invalid cognitive usage observation", http.StatusBadRequest) return } if err := capabilityRepository.SaveCognitiveUsageObservation(r.Context(), observation); err != nil { http.Error(w, "save cognitive usage observation", http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) }) mux.HandleFunc("POST /v1/capabilities/health-projection/rebuild", func(w http.ResponseWriter, r *http.Request) { rebuilt, err := capabilityRepository.RebuildHealthProjection(r.Context()) if err != nil { http.Error(w, "rebuild capability health projection", http.StatusInternalServerError) return } _ = json.NewEncoder(w).Encode(struct{ Rebuilt int64 }{rebuilt}) }) mux.HandleFunc("POST /v1/focus-bindings", func(w http.ResponseWriter, r *http.Request) { var binding situation.FocusBinding if err := json.NewDecoder(r.Body).Decode(&binding); err != nil { http.Error(w, "invalid focus binding", http.StatusBadRequest) return } if err := repository.Save(r.Context(), binding); err != nil { http.Error(w, "save focus binding", http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) }) mux.HandleFunc("GET /v1/focus", func(w http.ResponseWriter, r *http.Request) { focus, err := resolveFocus(r) if err != nil { http.Error(w, err.Error(), http.StatusConflict) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(focus) }) mux.HandleFunc("POST /v1/tasks", func(w http.ResponseWriter, r *http.Request) { var target work.TaskTarget if err := json.NewDecoder(r.Body).Decode(&target); err != nil { http.Error(w, "invalid task", 400) return } if err := workRepository.Create(r.Context(), target); err != nil { http.Error(w, "create task", 500) return } w.WriteHeader(http.StatusCreated) }) mux.HandleFunc("POST /v1/tasks/{id}/commit", func(w http.ResponseWriter, r *http.Request) { var target work.TaskTarget if err := json.NewDecoder(r.Body).Decode(&target); err != nil { http.Error(w, "invalid task commit", 400) return } target.TaskID = work.TaskID(r.PathValue("id")) revision, err := workRepository.Commit(r.Context(), target) if err != nil { http.Error(w, "task revision conflict", http.StatusConflict) return } _ = json.NewEncoder(w).Encode(struct{ Revision work.TaskRevision }{revision}) }) mux.HandleFunc("POST /v1/focus-task-commits", func(w http.ResponseWriter, r *http.Request) { var request struct { SessionID situation.SessionID Workspace situation.WorkspaceID Target work.TaskTarget } if err := json.NewDecoder(r.Body).Decode(&request); err != nil { http.Error(w, "invalid guarded commit", http.StatusBadRequest) return } bindings, err := repository.ByRuntime(r.Context(), situation.RuntimeBinding{SessionID: request.SessionID, Workspace: request.Workspace}) if err != nil { http.Error(w, "load focus", http.StatusInternalServerError) return } focus, err := situationdomain.NewResolver(bindings).Resolve(situation.RuntimeBinding{SessionID: request.SessionID, Workspace: request.Workspace}) if err != nil { http.Error(w, "focus unavailable", http.StatusConflict) return } if err := situationdomain.ValidateTaskTarget(focus, request.Target); err != nil { http.Error(w, "target outside resolved focus", http.StatusConflict) return } revision, err := workRepository.Commit(r.Context(), request.Target) if err != nil { http.Error(w, "task revision conflict", http.StatusConflict) return } _ = json.NewEncoder(w).Encode(struct{ Revision work.TaskRevision }{revision}) }) log.Printf("core-runtime listening on %s", addr) if err := http.ListenAndServe(addr, mux); err != nil { log.Fatal(err) } } func shouldInvokeHumanResponse(stableConversational bool, identityReply, memoryInstructionReply string) bool { return !stableConversational && identityReply == "" && memoryInstructionReply == "" } func shouldReviewResponse(planningReply string, stableKnowledgeReply, stableConversational bool, identityReply, memoryInstructionReply string, deterministicMemoryReply bool) bool { return strings.TrimSpace(planningReply) != "" && !stableKnowledgeReply && !stableConversational && identityReply == "" && memoryInstructionReply == "" && !deterministicMemoryReply } func shouldRunGeneralResearch(kind interaction.InformationRequestKind) bool { return kind == "" || kind == interaction.InformationRequestTimeDifference } func creatorSessionHandler(bootstrapToken, creatorUsername, creatorPassword string, sessionKey []byte, now func() time.Time) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { authenticated := identitydomain.BootstrapMatches(bootstrapToken, bearerToken(r)) if !authenticated { var credentials identity.CreatorCredentials decoder := json.NewDecoder(io.LimitReader(r.Body, maxCreatorCredentialsBytes+1)) decoder.DisallowUnknownFields() if err := decoder.Decode(&credentials); err != nil || decoder.Decode(&struct{}{}) != io.EOF { http.Error(w, "creator authentication denied", http.StatusUnauthorized) return } authenticated = identitydomain.CreatorCredentialsMatch(creatorUsername, creatorPassword, credentials) } if !authenticated { http.Error(w, "creator authentication denied", http.StatusUnauthorized) return } token, session, err := identitydomain.IssueCreatorSession(sessionKey, now(), 8*time.Hour) if err != nil { http.Error(w, "creator session unavailable", http.StatusServiceUnavailable) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(struct { Token string Session identity.CreatorSession }{token, session}) } } func creatorDatabaseSessionHandler(bootstrapToken, creatorUsername string, repository *postgres.IdentityRepository, sessionKey []byte, now func() time.Time) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if identitydomain.BootstrapMatches(bootstrapToken, bearerToken(r)) { token, session, err := identitydomain.IssueCreatorSession(sessionKey, now(), 8*time.Hour) if err != nil { http.Error(w, "creator session unavailable", http.StatusServiceUnavailable) return } writeCreatorSession(w, token, session) return } var credentials identity.CreatorCredentials decoder := json.NewDecoder(io.LimitReader(r.Body, maxCreatorCredentialsBytes+1)) decoder.DisallowUnknownFields() if err := decoder.Decode(&credentials); err != nil || decoder.Decode(&struct{}{}) != io.EOF { http.Error(w, "creator authentication denied", http.StatusUnauthorized) return } account, err := repository.LoadCreatorAccount(r.Context(), credentials.Username) if err != nil || !identitydomain.VerifyCreatorPassword(account.PasswordHash, credentials.Password) { http.Error(w, "creator authentication denied", http.StatusUnauthorized) return } binding, err := repository.LoadBinding(r.Context(), account.ActorID, account.PrincipalID) if err != nil || !binding.Active { http.Error(w, "creator authentication denied", http.StatusUnauthorized) return } token, session, err := identitydomain.IssueCreatorSessionForBinding(sessionKey, account.ActorID, account.PrincipalID, now(), 8*time.Hour) if err != nil { http.Error(w, "creator session unavailable", http.StatusServiceUnavailable) return } writeCreatorSession(w, token, session) } } func writeCreatorSession(w http.ResponseWriter, token string, session identity.CreatorSession) { w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(struct { Token string Session identity.CreatorSession }{token, session}) } func bootstrapCreatorAccount(ctx context.Context, repository *postgres.IdentityRepository, username, passwordPath string) error { if account, err := repository.LoadCreatorAccount(ctx, username); err == nil { if account.ActorID == "" || account.PrincipalID == "" { return errors.New("creator account binding is incomplete") } return nil } else if !errors.Is(unwrapIdentityRepositoryError(err), pgx.ErrNoRows) { return err } password, err := readRequiredSecretFile("CREATOR_PASSWORD", passwordPath) if err != nil { return fmt.Errorf("creator account is missing and bootstrap password is unavailable: %w", err) } hash, err := identitydomain.HashCreatorPassword(password) if err != nil { return fmt.Errorf("hash creator bootstrap password: %w", err) } if err := repository.CreateCreatorAccount(ctx, identity.CreatorAccount{Username: username, ActorID: "domovoy-creator-actor", PrincipalID: "domovoy-creator-principal", PasswordHash: hash}); err != nil { return fmt.Errorf("create creator account: %w", err) } return nil } func unwrapIdentityRepositoryError(err error) error { for err != nil { if errors.Is(err, pgx.ErrNoRows) { return pgx.ErrNoRows } unwrapped := errors.Unwrap(err) if unwrapped == nil { break } err = unwrapped } return err } func bearerToken(r *http.Request) string { value := r.Header.Get("Authorization") if !strings.HasPrefix(value, "Bearer ") { return "" } return strings.TrimPrefix(value, "Bearer ") } func readRequiredSecret(name string) (string, error) { direct, directSet := os.LookupEnv(name) fileName, fileSet := os.LookupEnv(name + "_FILE") if directSet && fileSet { return "", fmt.Errorf("%s and %s_FILE cannot both be configured", name, name) } if fileSet { if fileName == "" { return "", fmt.Errorf("%s_FILE is empty", name) } contents, err := os.ReadFile(fileName) if err != nil { return "", fmt.Errorf("read %s_FILE: %w", name, err) } return normalizeSecretFile(name, contents) } if direct == "" { return "", fmt.Errorf("%s is required", name) } return direct, nil } func readRequiredSecretFile(name, path string) (string, error) { contents, err := os.ReadFile(path) if err != nil { return "", fmt.Errorf("read %s secret file: %w", name, err) } return normalizeSecretFile(name, contents) } func normalizeSecretFile(name string, contents []byte) (string, error) { value := strings.TrimRight(string(contents), "\r\n") if strings.ContainsAny(value, "\r\n") { return "", fmt.Errorf("%s secret file must contain one line", name) } if value == "" { return "", fmt.Errorf("%s is required", name) } return value, nil } func readCredentialRefSecret(name, refName, fileName string) (string, error) { if _, directSet := os.LookupEnv(name); directSet { return "", fmt.Errorf("%s direct environment secret is forbidden; use the standard secret file", name) } ref := os.Getenv(refName) if ref == "" { return "", fmt.Errorf("%s is required", refName) } root := os.Getenv("SECRET_BACKEND_ROOT") if root == "" { root = "/run/secrets" } resolver, err := platformsecrets.NewFileResolver(root) if err != nil { return "", err } return resolver.ResolveFile(secretcontracts.SecretFileRef{ CredentialRef: secretcontracts.CredentialRef(ref), Purpose: secretcontracts.SecretPurposeDatabasePassword, Consumer: secretcontracts.RuntimeConsumerCore, Path: fileName, }) } func envOr(name, fallback string) string { if value := os.Getenv(name); value != "" { return value } return fallback } func envInt(name string, fallback int) int { value, err := strconv.Atoi(os.Getenv(name)) if err != nil || value < 1 { return fallback } return value } func hasCapabilityHint(hints []string, wanted string) bool { for _, hint := range hints { if hint == wanted { return true } } return false } func renderResearchReply(query string, result epistemics.ResearchResult, researchErr error) (string, bool) { if researchErr != nil { return "Сейчас не удалось получить данные из подключённого источника. Попробую снова через доступный маршрут.", false } lines := make([]string, 0, 2) for _, finding := range result.Findings { if finding.State != epistemics.FindingSupported || strings.TrimSpace(finding.Excerpt.Text) == "" || strings.TrimSpace(finding.Source.Locator) == "" { continue } if !researchFindingMatchesIntent(query, finding) { continue } excerpt := humanResearchExcerpt(query, finding.Excerpt.Text) limit := 180 if len(lines) > 0 { limit = 100 } if len([]rune(excerpt)) > limit { excerpt = string([]rune(excerpt)[:limit]) + "…" } lines = append(lines, excerpt+"\nИсточник: "+researchSourceLabel(finding)) // A nearest-flight request needs one route-relevant schedule result. // Appending weaker matches makes unrelated category pages look like // alternatives even after the relevance check has accepted the route. if nearestFlightQuery(query) { break } if len(lines) == 2 { break } } if len(lines) == 0 { if identifiers := requestedResearchIdentifiers(query); len(identifiers) > 0 { return "Не нашёл проверяемых данных о «" + strings.Join(identifiers, "», «") + "». Возможно, название написано иначе; пришлите ссылку или уточните написание, и я проверю.", false } return "Проверяемых данных пока не найдено. Могу повторить поиск или уточнить, что именно проверить.", false } if isInternetStatusQuery(query) { return "Интернет работает: Домовой получил ответ от внешнего источника.", true } if nearestFlightQuery(query) && !containsFlightTimeEvidence(strings.Join(lines, " ")) { return "Нашёл расписание по маршруту, но точное время ближайшего вылета в доступных фрагментах не указано. Откройте источник и выберите ближайший рейс:\n\n" + strings.Join(lines, "\n\n"), true } if trainScheduleQuery(query) && !containsScheduleTimeEvidence(strings.Join(lines, " ")) { return "Нашёл страницу расписания по маршруту, но в доступных фрагментах нет точного времени отправления:\n\n" + strings.Join(lines, "\n\n"), true } return "Нашёл проверяемые данные:\n\n" + strings.Join(lines, "\n\n"), true } func isInternetStatusQuery(query string) bool { lower := strings.ToLower(query) return (strings.Contains(lower, "интернет") || strings.Contains(lower, "сет")) && (strings.Contains(lower, "работает") || strings.Contains(lower, "доступ") || strings.Contains(lower, "проверь") || strings.Contains(lower, "связ")) } func nearestFlightQuery(query string) bool { lower := strings.ToLower(query) return strings.Contains(lower, "ближ") && (strings.Contains(lower, "рейс") || strings.Contains(lower, "самолет") || strings.Contains(lower, "самолёт") || strings.Contains(lower, "вылет")) } // researchFindingMatchesIntent is a final relevance check over evidence // already obtained by the read-only research route. It prevents unrelated // search-engine snippets from becoming a human-facing answer when a search // term has a common non-travel meaning. func researchFindingMatchesIntent(query string, finding epistemics.ResearchFinding) bool { if !findingContainsRequestedIdentifier(query, finding) { return false } if ticketSearchQuery(query) && !ticketFindingMatchesDestination(query, finding) { return false } if distanceResearchQuery(query) && !containsDistanceEvidence(finding.Excerpt.Text) { return false } if localPlaceSearchQuery(query) && !containsLocalPlaceEvidence(finding.Excerpt.Text) { return false } if !nearestFlightQuery(query) { return true } displayedExcerpt := strings.ToLower(humanResearchExcerpt(query, finding.Excerpt.Text)) travelEvidence := strings.Contains(displayedExcerpt, "рейс") || strings.Contains(displayedExcerpt, "вылет") || strings.Contains(displayedExcerpt, "авиабил") || strings.Contains(displayedExcerpt, "аэропорт") || strings.Contains(displayedExcerpt, "самолет") || strings.Contains(displayedExcerpt, "самолёт") || strings.Contains(displayedExcerpt, "расписан") if !travelEvidence { return false } routeTerms := nearestFlightRouteTerms(query) if len(routeTerms) < 2 { return true } // A route-looking URL or page title alone is not enough for a human-facing // answer: the displayed excerpt itself must support both endpoints. excerptEvidence := strings.ToLower(finding.Excerpt.Text) for _, term := range routeTerms { if !strings.Contains(excerptEvidence, term) { return false } } return true } func localPlaceSearchQuery(query string) bool { lower := strings.ToLower(query) return (strings.Contains(lower, "ресторан") || strings.Contains(lower, "кафе")) && (strings.Contains(lower, "адрес") || strings.Contains(lower, "отзыв") || strings.Contains(lower, "меню")) } func containsLocalPlaceEvidence(value string) bool { lower := strings.ToLower(value) for _, term := range []string{"ресторан", "кафе", "адрес", "меню", "отзыв", "кухн"} { if strings.Contains(lower, term) { return true } } return false } func distanceResearchQuery(query string) bool { lower := strings.ToLower(query) return strings.Contains(lower, "расстоян") || strings.Contains(lower, "километр") } func containsDistanceEvidence(value string) bool { return regexp.MustCompile(`(?i)\b\d[\d\s]*\s*(?:км|километр)`).MatchString(value) } func ticketSearchQuery(query string) bool { return interactiondomain.TicketSearchDestination(query) != "" } func ticketFindingMatchesDestination(query string, finding epistemics.ResearchFinding) bool { destination := strings.ToLower(interactiondomain.TicketSearchDestination(query)) if destination == "" { return true } // The source title or URL may describe a category; human-facing proof must // mention the requested destination in the actual displayed excerpt. return strings.Contains(strings.ToLower(finding.Excerpt.Text), destination) } // findingContainsRequestedIdentifier prevents a generic landing page from // being presented as a result for a concrete model, article or brand request. // Identifiers are detected by their syntax, not by a catalogue of providers or // entities, so the rule stays valid as user vocabulary changes. func findingContainsRequestedIdentifier(query string, finding epistemics.ResearchFinding) bool { identifiers := requestedResearchIdentifiers(query) if len(identifiers) == 0 { return true } evidence := strings.ToLower(strings.Join([]string{finding.Source.Title, finding.Source.Locator, finding.Excerpt.Text}, " ")) for _, identifier := range identifiers { if !strings.Contains(evidence, strings.ToLower(identifier)) { return false } } return true } func requestedResearchIdentifiers(query string) []string { rawIdentifiers := regexp.MustCompile(`(?i)[a-z0-9][a-z0-9._-]{3,}`).FindAllString(query, -1) identifiers := make([]string, 0, len(rawIdentifiers)) exact := make([]string, 0, len(rawIdentifiers)) for _, identifier := range rawIdentifiers { if researchDateIdentifier(identifier) { continue } identifiers = append(identifiers, identifier) if strings.ContainsAny(identifier, "._-") || strings.IndexFunc(identifier, func(r rune) bool { return r >= '0' && r <= '9' }) >= 0 { exact = append(exact, identifier) } } if len(exact) > 0 { return exact } return identifiers } func researchDateIdentifier(value string) bool { return regexp.MustCompile(`^\d{1,2}[.-]\d{1,2}[.-]\d{2,4}$`).MatchString(value) || regexp.MustCompile(`^\d{4}[.-]\d{1,2}[.-]\d{1,2}$`).MatchString(value) } // nearestFlightRouteTerms retains only the two endpoint-shaped terms from a // structured flight search. It has no city or provider vocabulary: endpoint // relevance is determined from the request itself. func nearestFlightRouteTerms(query string) []string { fields := regexp.MustCompile(`[\p{L}]{4,}`).FindAllString(strings.ToLower(query), -1) ignored := []string{"ближай", "авиарейс", "сегодня", "самолет", "самолёт", "вылет", "рейс", "пункт", "отправлен"} terms := make([]string, 0, 2) for _, field := range fields { skip := false for _, prefix := range ignored { if strings.HasPrefix(field, prefix) { skip = true break } } if skip { continue } prefixLength := 4 if len([]rune(field)) < prefixLength { prefixLength = len([]rune(field)) } term := string([]rune(field)[:prefixLength]) already := false for _, current := range terms { if current == term { already = true break } } if !already { terms = append(terms, term) } } return terms } func containsFlightTimeEvidence(value string) bool { return regexp.MustCompile(`(?m)\b(?:[01]?\d|2[0-3]):[0-5]\d\b`).MatchString(value) } func humanResearchExcerpt(query, raw string) string { text := strings.Join(strings.Fields(strings.TrimSpace(raw)), " ") if text == "" { return text } parts := strings.Split(text, " — ") if len(parts) < 2 { return text } lowerQuery := strings.ToLower(query) for index := len(parts) - 1; index >= 0; index-- { candidate := strings.TrimSpace(parts[index]) lower := strings.ToLower(candidate) if len([]rune(candidate)) >= 24 && (strings.Contains(lower, "сейчас") || strings.Contains(lower, "температур") || strings.Contains(lower, "цена") || strings.Contains(lower, "стоим") || strings.Contains(lower, "рейс") || strings.Contains(lower, "вылет") || strings.Contains(lowerQuery, "найди")) { return candidate } } return text } func trainScheduleQuery(query string) bool { lower := strings.ToLower(query) return strings.Contains(lower, "поезд") && strings.Contains(lower, "время отправления") } func containsScheduleTimeEvidence(value string) bool { return regexp.MustCompile(`\b(?:[01]?\d|2[0-3]):[0-5]\d\b`).MatchString(value) } func stableKnowledgeAnswer(answer string) bool { return strings.TrimSpace(answer) != "" && strings.TrimSpace(answer) != "NEEDS_VERIFIED_RESEARCH" } // shouldGenerateNonMutatingTextDirective identifies a request that can be // completed entirely in the reply. It excludes specialized routes and durable // work, so cognition can produce content but cannot stand in for an executor. func shouldGenerateNonMutatingTextDirective(semantic interactiondomain.SemanticResult, routes []chatRoute, helpPath work.HelpPath, creativeRequest bool) bool { if semantic.Interpretation.SpeechAct != interaction.SpeechActDirective || creativeRequest || helpPath.NeedsDurableWork { return false } // ANSWER and COGNITION are reply-only help paths. They do not execute an // external action, unlike direct capabilities, gaps or durable work. if helpPath.Kind != work.HelpPathAnswer && helpPath.Kind != work.HelpPathCognition && helpPath.Kind != "" { return false } return !hasChatRoute(routes, chatRouteResearch) && !hasChatRoute(routes, chatRouteMedia) && !hasChatRoute(routes, chatRouteAgent) } func reviewPreservesResearchEvidence(reviewed, draft string) bool { if strings.Contains(draft, "http://") || strings.Contains(draft, "https://") { return strings.Contains(reviewed, "http://") || strings.Contains(reviewed, "https://") } if strings.Contains(draft, "Источник:") { return strings.Contains(reviewed, "Источник:") } return true } // reviewMayReplacePlanningReply keeps ownership of a concrete capability // result with that capability. A review may improve source-backed research, // but must not turn an honest no-result into an invented clarification. func reviewMayReplacePlanningReply(stableSemanticReply, stableKnowledgeReply, researchAttempted, researchBacked bool, reviewed, draft string) bool { // A stable-knowledge answer is a model-produced draft rather than an owned // capability result. It therefore goes through a second cognitive pass for // conversational quality, while identity, memory, media and action outcomes // remain immutable at this boundary. if stableKnowledgeReply { return true } if stableSemanticReply { return false } if !researchAttempted { return true } return researchBacked && reviewPreservesResearchEvidence(reviewed, draft) } func humanMemoryInventory(history []interaction.HumanUtterance, assistantName string) string { rows := make([]string, 0, 4) if assistantName != "" { rows = append(rows, "| Имя помощника | "+assistantName+" |") } latestPreferenceAt := int64(-1) latestPreference := "" for _, utterance := range history { message := strings.TrimSpace(utterance.Content) lower := strings.ToLower(message) if (strings.Contains(lower, "нравится") || strings.Contains(lower, "люблю")) && utterance.ReceivedAtUnixMillis >= latestPreferenceAt { latestPreferenceAt = utterance.ReceivedAtUnixMillis latestPreference = strings.TrimSpace(message) for _, prefix := range []string{"запомни:", "запомни", "помни:"} { if strings.HasPrefix(strings.ToLower(latestPreference), prefix) { latestPreference = strings.TrimSpace(latestPreference[len(prefix):]) break } } } } if latestPreference != "" { rows = append(rows, "| Предпочтение пользователя | "+latestPreference+" |") } if len(rows) == 0 { return "| Ячейка памяти | Значение |\n|---|---|\n| — | Сохранённых пользовательских предпочтений пока не найдено. |" } return "| Ячейка памяти | Значение |\n|---|---|\n" + strings.Join(rows, "\n") } func latestAssistantName(history []interaction.HumanUtterance) string { latestAt := int64(-1) latestName := "" for _, utterance := range history { if name := interactiondomain.PreferredAssistantName(utterance.Content); name != "" && utterance.ReceivedAtUnixMillis >= latestAt { latestAt = utterance.ReceivedAtUnixMillis latestName = name } } return latestName } func researchSourceLabel(finding epistemics.ResearchFinding) string { title := strings.TrimSpace(finding.Source.Title) locator := strings.TrimSpace(finding.Source.Locator) publisher := strings.TrimSpace(finding.Source.Publisher) if strings.Contains(locator, "duckduckgo.com/html") || strings.Contains(locator, "bing.com/search") { locator = "" } if title != "" && publisher != "" { if locator != "" { return publisher + " (" + locator + ")" } return publisher } if title != "" { return title } if publisher != "" { return publisher } if locator != "" { return locator } return "подключённый источник" } func humanCognitiveModelInventory(candidates []capability.ResolvedCognitiveBinding, inventoryErr error) string { if inventoryErr != nil { return "Я не смогла прочитать реестр подключённых моделей. Встроенный канал Домового остаётся доступен; настройки можно проверить повторно после восстановления реестра. Ключи и пароли не выводятся в чат." } if len(candidates) == 0 { return "В реестре пока нет подтверждённых подключённых моделей. Встроенный локальный канал Домового доступен всегда; внешние модели добавляются через настройки подключения. Ключи и пароли в чат не выводятся." } type inventoryEntry struct { name string status string rank int seenAt int } entries := make([]inventoryEntry, 0, len(candidates)) byName := make(map[string]int, len(candidates)) for _, candidate := range candidates { modelName := string(candidate.Model.ProviderModelID) if modelName == "" { continue } status := conciseCognitiveInventoryStatus(candidate) rank := cognitiveInventoryRank(candidate) if index, exists := byName[modelName]; exists { if rank < entries[index].rank { entries[index].status = status entries[index].rank = rank } continue } byName[modelName] = len(entries) entries = append(entries, inventoryEntry{name: modelName, status: status, rank: rank, seenAt: len(entries)}) } sort.SliceStable(entries, func(left, right int) bool { if entries[left].rank != entries[right].rank { return entries[left].rank < entries[right].rank } return entries[left].seenAt < entries[right].seenAt }) lines := make([]string, 0, len(entries)+2) lines = append(lines, "Доступные модели:") unconfirmed := 0 for _, entry := range entries { if entry.rank >= 3 { unconfirmed++ continue } lines = append(lines, "- "+entry.name+" — "+entry.status) } if len(lines) == 1 { return "Подтверждённых доступных моделей сейчас нет." } if unconfirmed > 0 { lines = append(lines, fmt.Sprintf("Ещё %d моделей требуют проверки.", unconfirmed)) } return strings.Join(lines, "\n") } func conciseCognitiveInventoryStatus(candidate capability.ResolvedCognitiveBinding) string { if candidate.Provider.Health == capability.HealthAvailable && candidate.Model.Health == capability.HealthAvailable { if candidate.Binding.Activation == capability.ActivationActive { return "активна" } return "доступна" } if candidate.Provider.Health == capability.HealthDegraded || candidate.Model.Health == capability.HealthDegraded { return "работает с ограничениями" } return "не подтверждена" } func cognitiveInventoryRank(candidate capability.ResolvedCognitiveBinding) int { if candidate.Provider.Health == capability.HealthAvailable && candidate.Model.Health == capability.HealthAvailable { if candidate.Binding.Activation == capability.ActivationActive { return 0 } return 1 } if candidate.Provider.Health == capability.HealthDegraded || candidate.Model.Health == capability.HealthDegraded { return 2 } return 3 } func cognitiveInventoryStatus(candidate capability.ResolvedCognitiveBinding) string { if candidate.Provider.Health == capability.HealthAvailable && candidate.Model.Health == capability.HealthAvailable { if candidate.Binding.Activation == capability.ActivationActive { return "активна" } return "доступна, не активирована для автоматического выбора" } if candidate.Provider.Health == capability.HealthDegraded || candidate.Model.Health == capability.HealthDegraded { return "работает с ограничениями" } return "недоступна или не подтверждена" } func cognitiveInventoryPricing(provider capability.ProviderInstance) string { switch provider.BillingMode { case capability.BillingFree: return "бесплатная" case capability.BillingPerInput: return "платная: стоимость входа указана в реестре" case capability.BillingPerOutput: return "платная: стоимость выхода указана в реестре" case capability.BillingPerRequest: return "платная: стоимость запроса указана в реестре" default: return "стоимость не подтверждена" } } func humanAuthMode(mode capability.AuthMode) string { switch mode { case capability.AuthKey: return "API-ключ" case capability.AuthBearer: return "Bearer-токен" case capability.AuthUsernamePassword: return "логин и пароль" case capability.AuthOAuth: return "OAuth" case capability.AuthSecretFile: return "защищённый файл секрета" default: return "защищённый способ" } } func humanCognitiveChannelReply(kind interaction.InformationRequestKind, act interaction.SpeechAct, health capability.Health, healthErr error) string { if healthErr != nil { return "Не удалось проверить канал. Домовой остаётся на связи." } switch health.State { case capability.HealthAvailable: return "Канал мышления доступен." case capability.HealthDegraded: return "Канал мышления работает с ограничениями." case capability.HealthUnavailable: return "Канал мышления недоступен. Домовой остаётся на связи." default: return "Состояние канала пока не подтверждено. Домовой остаётся на связи." } } // humanCurrentCognitiveModelReply answers about the route that produced this // reply. It intentionally does not infer a provider/model name from health: // that would claim a model was used when this direct status path did not call it. func humanCurrentCognitiveModelReply(health capability.Health, healthErr error) string { base := "Этот ответ сформирован встроенным режимом Домового; внешняя модель для него не использовалась." if healthErr != nil { return base + " Состояние канала мышления сейчас не удалось проверить." } switch health.State { case capability.HealthAvailable: return base + " Канал мышления доступен для следующих задач." case capability.HealthDegraded: return base + " Канал мышления работает с ограничениями." case capability.HealthUnavailable: return base + " Канал мышления сейчас недоступен." default: return base + " Состояние канала мышления пока не подтверждено." } } func humanCognitiveDegradedReply(message string) string { if strings.TrimSpace(message) == "" { return "Сейчас не удалось обработать запрос." } return "Не удалось получить проверяемые данные. Ответ не выдумываю." } func humanCapabilityList(kinds []capability.CapabilityKind) string { labels := make([]string, 0, len(kinds)) for _, kind := range kinds { switch kind { case capability.CapabilityKindScheduleReminder: labels = append(labels, "создавать напоминания") case capability.CapabilityKindCognitiveReasoning: labels = append(labels, "отвечать через когнитивный gateway") default: labels = append(labels, strings.ToLower(strings.ReplaceAll(string(kind), "_", " "))) } } return strings.Join(labels, ", ") } func toCapabilityKinds(values []string) []capability.CapabilityKind { result := make([]capability.CapabilityKind, 0, len(values)) for _, value := range values { kind := capability.CapabilityKind(strings.TrimSpace(value)) if kind == "" { continue } result = append(result, kind) } return result } func delegationAttentionRef(id work.DelegationID) string { return "work:delegation:" + string(id) } func continueDelegationFromHumanResponse(ctx context.Context, repository *postgres.WorkRepository, response continuity.HumanResponse, now time.Time) (bool, error) { if response.Operation != continuity.ResponseContinue || !strings.HasPrefix(response.TargetExternalRef, "work:delegation:") { return false, nil } delegationID := work.DelegationID(strings.TrimPrefix(response.TargetExternalRef, "work:delegation:")) delegation, err := repository.LoadDelegation(ctx, delegationID) if err != nil { return false, err } run, err := repository.LoadAgentRun(ctx, delegation.LatestRunID) if err != nil { return false, err } checkpoint, err := repository.LatestAgentCheckpoint(ctx, delegationID) if err != nil { return false, err } updatedDelegation, resumedRun, err := workdomain.ResumeDelegationRun(delegation, run, checkpoint, now) if err != nil { return false, err } if err := repository.SaveResumedRun(ctx, updatedDelegation, resumedRun); err != nil { return false, err } return true, nil } func firstNonEmpty(values ...string) string { for _, value := range values { if strings.TrimSpace(value) != "" { return strings.TrimSpace(value) } } return "" } func evidenceRefsToEpistemics(values []work.EvidenceRef) []epistemics.EvidenceRef { result := make([]epistemics.EvidenceRef, 0, len(values)) for _, value := range values { if strings.TrimSpace(string(value)) == "" { continue } result = append(result, epistemics.EvidenceRef(value)) } return result }