fix: explain chat session and runtime failures
CI / test (push) Successful in 26s

fix: explain chat session and runtime failures
This commit was merged in pull request #88.
This commit is contained in:
m
2026-08-21 22:59:57 +00:00
4 changed files with 72 additions and 13 deletions
+13
View File
@@ -37,6 +37,19 @@ func TestChatPageServedWithoutInternalIDs(t *testing.T) {
}
}
func TestChatScriptExplainsSessionAndRuntimeFailures(t *testing.T) {
body, err := staticFiles.ReadFile("static/assets/chat.js")
if err != nil {
t.Fatal(err)
}
script := string(body)
for _, required := range []string{"class ChatApiError", "Сессия устарела", "основной runtime сейчас недоступен", "Проверьте текст и повторите попытку"} {
if !strings.Contains(script, required) {
t.Fatalf("chat script is missing recovery message %q", required)
}
}
}
func TestPublicSurfacesSeparateChatAndConsole(t *testing.T) {
handler, err := newHandler("http://core", "http://continuity", "http://commit", http.DefaultClient, "http://cognitive")
if err != nil {
+34 -11
View File
@@ -24,6 +24,14 @@ let conversationID = sessionStorage.getItem("domovoy.chat.conversation") || "";
let localHistory = [];
let cognitiveStatusTimer = null;
class ChatApiError extends Error {
constructor(status, detail = "") {
super(detail || `chat api returned ${status}`);
this.name = "ChatApiError";
this.status = status;
}
}
function containsSecretShapedText(value) {
return /(-----BEGIN [A-Z ]+PRIVATE KEY-----|\b(?:password|passwd|пароль|token|токен|api[_ -]?key|секрет|ключ)\s*[:=]|\bsk-[a-zA-Z0-9_-]{12,})/i.test(value);
}
@@ -58,7 +66,11 @@ async function loadCognitiveStatus() {
try {
const result = await api("/api/capabilities/cognitive-proposal/health", {method: "GET"});
renderCognitiveStatus(result.State || result.state || "UNKNOWN");
} catch {
} catch (error) {
if (error instanceof ChatApiError && (error.status === 401 || error.status === 403)) {
expireSession("Сессия устарела. Войдите снова, чтобы продолжить разговор.");
return;
}
renderCognitiveStatus("UNAVAILABLE");
}
}
@@ -116,10 +128,21 @@ async function api(path, options = {}) {
const headers = {"Content-Type": "application/json", ...(options.headers || {})};
if (token) headers.Authorization = `Bearer ${token}`;
const response = await fetch(path, {...options, headers});
if (!response.ok) throw new Error(await response.text());
if (!response.ok) throw new ChatApiError(response.status, await response.text());
return response.json();
}
function expireSession(message = "") {
token = "";
conversationID = "";
sessionStorage.removeItem("domovoy.chat.token");
sessionStorage.removeItem("domovoy.chat.conversation");
messages.replaceChildren();
updateActiveFocus("");
if (message) loginError.textContent = message;
showLogin();
}
function humanMeta(result) {
if (result.reminder_scheduled) return "Напоминание сохранено. Я вернусь к нему сам.";
if (result.delegation_id && result.delegation_state) return `Передал в coding-agent: ${humanDelegationState(result.delegation_state)}.`;
@@ -249,8 +272,14 @@ async function sendChatMessage(text, options = {}) {
await loadToday();
activateTab("today-panel");
}
} catch {
appendMessage("assistant", "Чат сейчас недоступен. Сообщение не принято runtime.");
} catch (error) {
if (error instanceof ChatApiError && (error.status === 401 || error.status === 403)) {
expireSession("Сессия устарела. Войдите снова, чтобы продолжить разговор.");
} else if (error instanceof ChatApiError && error.status >= 500) {
appendMessage("assistant", "Домовой не смог завершить обработку: основной runtime сейчас недоступен. Встроенный канал проверяется; повторите сообщение через несколько секунд.");
} else {
appendMessage("assistant", "Домовой не принял сообщение. Проверьте текст и повторите попытку.");
}
} finally {
sendButton.disabled = false;
messageInput.focus();
@@ -323,13 +352,7 @@ if (aiDataForm) {
if (aiDataInput) aiDataInput.addEventListener("input", () => showAIDataError());
logoutButton.addEventListener("click", () => {
token = "";
conversationID = "";
sessionStorage.removeItem("domovoy.chat.token");
sessionStorage.removeItem("domovoy.chat.conversation");
messages.replaceChildren();
updateActiveFocus("");
showLogin();
expireSession();
});
if (token) {
+2 -2
View File
@@ -1,5 +1,5 @@
{
"task_file": "dev/tasks/DEV-000089-domovoy-cognitive-upgrade-integration.json",
"task_id": "DEV-000089",
"task_file": "dev/tasks/DEV-000090-chat-runtime-error-recovery.json",
"task_id": "DEV-000090",
"task_revision": 1
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000090",
"task_revision": 1,
"architecture_revision": "DEV-000073-baseline",
"base_repo_revision": "57fb572d5b7dadff9fd82e995448c1fea30e1b5f",
"risk_tier": "E",
"intent": "Make Domovoy chat failures understandable and recoverable by distinguishing expired sessions, runtime outages, and invalid requests in the human UI.",
"requirement_ids": ["Human sees the actual recovery state", "Expired sessions return to login", "Runtime failure does not pretend to be a model answer"],
"allowed_change_scope": {"modules": ["creator-web", "development-governance"], "paths": ["cmd/creator-web/chat_test.go", "cmd/creator-web/static/assets/chat.js", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000090-chat-runtime-error-recovery.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["existing creator-web HTTP status contract"], "may_change": ["human-facing chat error handling"]},
"semantic_owner": "creator-web",
"canonical_state": [],
"state_transitions": ["401/403 -> clear session and show login", "5xx -> explain runtime outage and allow retry", "other client error -> explain invalid request"],
"side_effects_and_privileges": ["No new privilege", "No internal identifiers or raw upstream error text shown"],
"persistence_and_migrations": ["No persistence or migration"],
"failure_retry_restart": ["Session recovery is local and deterministic", "Runtime outage remains retryable"],
"assumptions": ["Creator Web status codes remain the source of truth for session and runtime failure classes"],
"acceptance_scenarios": ["Expired browser token returns to login with an explanation", "Runtime outage shows a useful retry message", "Valid responses remain unchanged", "No generic 'message not accepted runtime' text remains"],
"required_tests": ["go test ./cmd/creator-web", "go test ./...", "go vet ./...", "devctl preflight", "devctl check-diff", "git diff --check"],
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
}