feat(core): answer cognitive channel questions #79
@@ -387,6 +387,11 @@ func main() {
|
||||
}
|
||||
needsCognition = false
|
||||
}
|
||||
if isCognitiveChannelQuestion(utterance.Content) {
|
||||
health, healthErr := capabilityRepository.LoadHealth(r.Context(), capability.CapabilityID("cognitive-proposal"))
|
||||
planningReply = humanCognitiveChannelReply(utterance.Content, health, healthErr)
|
||||
needsCognition = false
|
||||
}
|
||||
if helpPath.Reply != "" {
|
||||
planningReply = helpPath.Reply
|
||||
needsCognition = false
|
||||
@@ -1094,6 +1099,42 @@ func isCapabilityQuestion(message string) bool {
|
||||
return strings.Contains(lower, "что ты умеешь") || strings.Contains(lower, "что умеешь") || strings.Contains(lower, "какие возможности")
|
||||
}
|
||||
|
||||
func isCognitiveChannelQuestion(message string) bool {
|
||||
lower := strings.ToLower(message)
|
||||
markers := []string{"локальн", "модель", "когнитивн", "канал мышления", "проверь канал", "настройк"}
|
||||
for _, marker := range markers {
|
||||
if strings.Contains(lower, marker) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func humanCognitiveChannelReply(message string, health capability.Health, healthErr error) string {
|
||||
changeRequested := strings.Contains(strings.ToLower(message), "переключ") || strings.Contains(strings.ToLower(message), "настрой") || strings.Contains(strings.ToLower(message), "измени") || strings.Contains(strings.ToLower(message), "замени")
|
||||
if healthErr != nil {
|
||||
if changeRequested {
|
||||
return "Я не вижу подтверждённого состояния локального канала. Изменение модели не выполнено. Откройте консоль, проверьте настройку и запустите проверку ещё раз."
|
||||
}
|
||||
return "Я не вижу подтверждённого состояния локального канала. Проверьте его в консоли или повторите вопрос позже."
|
||||
}
|
||||
var observation string
|
||||
switch health.State {
|
||||
case capability.HealthAvailable:
|
||||
observation = "Основной локальный канал сейчас доступен."
|
||||
case capability.HealthDegraded:
|
||||
observation = "Основной локальный канал сейчас работает с ограничениями."
|
||||
case capability.HealthUnavailable:
|
||||
observation = "Основной локальный канал сейчас недоступен."
|
||||
default:
|
||||
observation = "Состояние основного локального канала пока не подтверждено."
|
||||
}
|
||||
if changeRequested {
|
||||
return observation + " Я не меняю модель прямо из разговора: изменение выполняется в консоли с подтверждением. После изменения я увижу новую проверку канала автоматически."
|
||||
}
|
||||
return observation + " Это наблюдение о готовности capability, а не прямой доступ к модели."
|
||||
}
|
||||
|
||||
func humanCapabilityList(kinds []capability.CapabilityKind) string {
|
||||
labels := make([]string, 0, len(kinds))
|
||||
for _, kind := range kinds {
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/capability"
|
||||
)
|
||||
|
||||
func TestBearerTokenRequiresBearerScheme(t *testing.T) {
|
||||
@@ -23,6 +25,23 @@ func TestBearerTokenRequiresBearerScheme(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCognitiveChannelQuestionUsesHumanSafeHealthReply(t *testing.T) {
|
||||
if !isCognitiveChannelQuestion("Проверь локальную модель") {
|
||||
t.Fatal("expected local model question to be recognized")
|
||||
}
|
||||
reply := humanCognitiveChannelReply("Проверь локальную модель", capability.Health{State: capability.HealthAvailable, CapabilityID: "provider-secret-model"}, nil)
|
||||
if !strings.Contains(reply, "доступен") || strings.Contains(reply, "provider-secret-model") {
|
||||
t.Fatalf("unexpected safe reply: %q", reply)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCognitiveConfigurationRequestDoesNotPretendToMutate(t *testing.T) {
|
||||
reply := humanCognitiveChannelReply("Переключи локальную модель", capability.Health{State: capability.HealthUnavailable}, nil)
|
||||
if !strings.Contains(reply, "не меняю модель") || !strings.Contains(reply, "консоли") {
|
||||
t.Fatalf("unexpected configuration reply: %q", reply)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreatorSessionHandlerAcceptsConfiguredCredentials(t *testing.T) {
|
||||
handler := creatorSessionHandler("recovery", "m", "password", []byte("01234567890123456789012345678901"), func() time.Time { return time.Unix(1000, 0) })
|
||||
request := httptest.NewRequest(http.MethodPost, "/v1/creator/sessions", strings.NewReader(`{"username":"m","password":"password"}`))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"task_file": "dev/tasks/DEV-000077-creator-chat-cognitive-bridge.json",
|
||||
"task_id": "DEV-000077",
|
||||
"task_file": "dev/tasks/DEV-000078-core-conversational-cognitive-health.json",
|
||||
"task_id": "DEV-000078",
|
||||
"task_revision": 1
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"task_id": "DEV-000078",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "DEV-000073-baseline",
|
||||
"base_repo_revision": "0d10e6fd82a7c374bebbc8d3cee9c1ed0b420745",
|
||||
"risk_tier": "E",
|
||||
"intent": "Let Domovoy answer explicit questions about the local cognitive channel from the Capability-owned health observation and route configuration changes to the operator console without mutating runtime configuration.",
|
||||
"requirement_ids": [
|
||||
"Explicit model/channel questions use observed capability health",
|
||||
"Model configuration requests never call a provider directly from Core",
|
||||
"Configuration changes are explained as a separate operator-console action",
|
||||
"Provider and model identifiers remain hidden from human chat"
|
||||
],
|
||||
"allowed_change_scope": {
|
||||
"modules": ["cmd/core-runtime"],
|
||||
"paths": [
|
||||
"cmd/core-runtime/main.go",
|
||||
"cmd/core-runtime/main_test.go",
|
||||
"dev/ACTIVE_TASK.json",
|
||||
"dev/tasks/DEV-000078-core-conversational-cognitive-health.json"
|
||||
]
|
||||
},
|
||||
"forbidden_change_scope": {
|
||||
"paths": ["architecture/**", "security/**", "database/**", "modules/**", "contracts/**", "adapters/**", "deploy/**"]
|
||||
},
|
||||
"allowed_architecture_delta": {
|
||||
"new_cross_module_dependencies": 0,
|
||||
"new_canonical_owners": 0,
|
||||
"new_privileges": 0,
|
||||
"new_third_party_dependencies": 0,
|
||||
"new_provider_adapters": 0,
|
||||
"new_database_owner_roles": 0
|
||||
},
|
||||
"contracts": {"consume": ["Capability repository LoadHealth", "existing cognitive-proposal health semantics", "existing /admin operator surface"], "may_change": []},
|
||||
"semantic_owner": "capability-observation-consumer",
|
||||
"canonical_state": [],
|
||||
"state_transitions": ["Human asks about local cognitive channel", "Core reads Capability-owned health observation", "Core returns observation or safe unavailable explanation"],
|
||||
"side_effects_and_privileges": ["No provider call for explicit status/configuration questions", "No runtime configuration mutation", "No new privilege"],
|
||||
"persistence_and_migrations": ["Existing utterance and semantic persistence only", "No migration"],
|
||||
"failure_retry_restart": ["Missing health observation produces an honest unavailable reply", "Future status polling remains available in chat UI"],
|
||||
"assumptions": ["The operator console is the authorized configuration surface", "Capability health is a sufficient provider-neutral observation for the first conversational response"],
|
||||
"acceptance_scenarios": ["Проверь локальную модель returns a human-readable health observation", "Переключи модель does not mutate configuration and directs to console", "Replies contain no provider/model/binding identifiers", "Unit tests cover available, unavailable and repository failure states"],
|
||||
"required_tests": ["go test ./cmd/core-runtime", "go test ./...", "go vet ./...", "devctl preflight", "devctl check-diff", "devctl check-architecture", "git diff --check"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
Reference in New Issue
Block a user