feat(chat): bridge cognitive status to console #78
@@ -25,7 +25,7 @@ func TestChatPageServedWithoutInternalIDs(t *testing.T) {
|
||||
t.Fatalf("expected /chat status 200, got %d", recorder.Code)
|
||||
}
|
||||
body := recorder.Body.String()
|
||||
for _, required := range []string{"cognitive-status", "aria-live=\"polite\""} {
|
||||
for _, required := range []string{"cognitive-status", "aria-live=\"polite\"", "cognitive-console-link", "href=\"/admin\""} {
|
||||
if !strings.Contains(body, required) {
|
||||
t.Fatalf("/chat is missing human status marker %q", required)
|
||||
}
|
||||
|
||||
@@ -35,11 +35,13 @@ textarea { resize: vertical; min-height: 84px; max-height: 240px; padding: 14px;
|
||||
.chat-header span { color: var(--muted); font: 10px/1 var(--mono); letter-spacing: .1em; text-transform: uppercase; }
|
||||
#active-focus { margin-left: auto; color: var(--good); }
|
||||
.cognitive-status { display: inline-flex; align-items: center; gap: 7px; white-space: nowrap; }
|
||||
.chat-header .cognitive-status { width: auto; height: auto; padding: 0; border: 0; background: transparent; color: var(--muted); font: 10px/1 var(--mono); letter-spacing: .1em; text-transform: uppercase; }
|
||||
.cognitive-status::before { content: ""; width: 7px; height: 7px; flex: 0 0 7px; border-radius: 50%; background: var(--signal); }
|
||||
.cognitive-status[data-state="available"] { color: var(--good); }
|
||||
.cognitive-status[data-state="available"]::before { background: var(--good); }
|
||||
.cognitive-status[data-state="unavailable"] { color: var(--bad); }
|
||||
.cognitive-status[data-state="unavailable"]::before { background: var(--bad); }
|
||||
.cognitive-console-link { color: var(--signal); font: 10px/1 var(--mono); letter-spacing: .08em; text-transform: uppercase; white-space: nowrap; }
|
||||
.chat-header button { width: 38px; height: 38px; border: 1px solid var(--line); background: transparent; }
|
||||
.human-nav { display: flex; align-items: center; gap: 8px; }
|
||||
.nav-tab, .operator-link { height: 34px; padding: 0 12px; display: inline-flex; align-items: center; border: 1px solid var(--line); color: var(--muted); background: #0b110e; text-decoration: none; font: 11px/1 var(--mono); text-transform: uppercase; letter-spacing: .08em; }
|
||||
|
||||
@@ -9,6 +9,7 @@ const sendButton = document.querySelector("#send");
|
||||
const logoutButton = document.querySelector("#logout");
|
||||
const activeFocus = document.querySelector("#active-focus");
|
||||
const cognitiveStatus = document.querySelector("#cognitive-status");
|
||||
const cognitiveConsoleLink = document.querySelector("#cognitive-console-link");
|
||||
const todayList = document.querySelector("#today-list");
|
||||
const refreshToday = document.querySelector("#refresh-today");
|
||||
const historyList = document.querySelector("#history-list");
|
||||
@@ -32,10 +33,14 @@ function renderCognitiveStatus(state) {
|
||||
};
|
||||
cognitiveStatus.dataset.state = displayState;
|
||||
cognitiveStatus.textContent = labels[displayState];
|
||||
cognitiveStatus.title = displayState === "available" ? "Канал доступен. Нажмите, чтобы проверить ещё раз" : "Проверить канал ещё раз";
|
||||
if (cognitiveConsoleLink) cognitiveConsoleLink.hidden = displayState === "available";
|
||||
}
|
||||
|
||||
async function loadCognitiveStatus() {
|
||||
if (!cognitiveStatus || !token) return;
|
||||
cognitiveStatus.dataset.state = "checking";
|
||||
cognitiveStatus.textContent = "проверяю канал мышления";
|
||||
try {
|
||||
const result = await api("/api/capabilities/cognitive-proposal/health", {method: "GET"});
|
||||
renderCognitiveStatus(result.State || result.state || "UNKNOWN");
|
||||
@@ -277,6 +282,7 @@ for (const tab of navTabs) {
|
||||
}
|
||||
|
||||
if (refreshToday) refreshToday.addEventListener("click", loadToday);
|
||||
if (cognitiveStatus) cognitiveStatus.addEventListener("click", loadCognitiveStatus);
|
||||
|
||||
logoutButton.addEventListener("click", () => {
|
||||
token = "";
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
<a href="/admin" class="operator-link">Консоль</a>
|
||||
</nav>
|
||||
<span id="active-focus">фокус: не выбран</span>
|
||||
<span id="cognitive-status" class="cognitive-status" data-state="checking" role="status" aria-live="polite">проверяю канал мышления</span>
|
||||
<button id="cognitive-status" class="cognitive-status" data-state="checking" type="button" role="status" aria-live="polite" title="Проверить канал ещё раз">проверяю канал мышления</button>
|
||||
<a id="cognitive-console-link" class="cognitive-console-link" href="/admin" hidden>настроить в консоли</a>
|
||||
<span id="session-state">сессия активна</span>
|
||||
<button id="logout" type="button" aria-label="Выйти">↗</button>
|
||||
</header>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"task_file": "dev/tasks/DEV-000076-creator-chat-cognitive-status.json",
|
||||
"task_id": "DEV-000076",
|
||||
"task_file": "dev/tasks/DEV-000077-creator-chat-cognitive-bridge.json",
|
||||
"task_id": "DEV-000077",
|
||||
"task_revision": 1
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"task_id": "DEV-000077",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "DEV-000073-baseline",
|
||||
"base_repo_revision": "75abfb204bb427a0500a8c37440c374576c2153c",
|
||||
"risk_tier": "E",
|
||||
"intent": "Make model configuration changes understandable from the human chat while keeping configuration mutation in the separate operator console.",
|
||||
"requirement_ids": [
|
||||
"User can manually re-check the cognitive channel from chat",
|
||||
"Chat clearly directs configuration changes to the operator console",
|
||||
"No configuration mutation is performed by the browser chat",
|
||||
"The conversation remains free of technical runtime identifiers"
|
||||
],
|
||||
"allowed_change_scope": {
|
||||
"modules": ["cmd/creator-web"],
|
||||
"paths": [
|
||||
"cmd/creator-web/chat_test.go",
|
||||
"cmd/creator-web/static/chat.html",
|
||||
"cmd/creator-web/static/assets/chat.css",
|
||||
"cmd/creator-web/static/assets/chat.js",
|
||||
"dev/ACTIVE_TASK.json",
|
||||
"dev/tasks/DEV-000077-creator-chat-cognitive-bridge.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": ["existing authenticated /api/capabilities/cognitive-proposal/health endpoint", "existing /admin operator surface"], "may_change": []},
|
||||
"semantic_owner": "none-interface-adapter",
|
||||
"canonical_state": [],
|
||||
"state_transitions": ["User requests a fresh read-only health observation", "Browser presents console handoff when the channel is not ready"],
|
||||
"side_effects_and_privileges": ["Read-only observation only", "No new privilege", "No provider/model routing change", "No chat mutation from status controls"],
|
||||
"persistence_and_migrations": ["No persistence or migration"],
|
||||
"failure_retry_restart": ["Manual check uses the same safe health route", "Polling remains the retry mechanism"],
|
||||
"assumptions": ["The operator console remains the authorized place for runtime configuration changes", "The existing health route is sufficient to observe readiness"],
|
||||
"acceptance_scenarios": ["Clicking status requests a fresh health observation", "Unavailable status exposes an operator-console handoff", "Available status hides the handoff", "Chat HTML remains free of internal identifiers"],
|
||||
"required_tests": ["go test ./cmd/creator-web", "go test ./...", "go vet ./...", "devctl preflight", "devctl check-diff", "devctl check-architecture", "git diff --check", "node --check cmd/creator-web/static/assets/chat.js"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
Reference in New Issue
Block a user