fix(agent-chat): streaming state leak, document parser magic bytes, HITL flow

### Bugfixes — Agent Chat 'Думаю' State Leak
- fix(agent-chat): loadHistory() now resets streamingState/idle + cancels stale
  submission — prevents 'Думаю' state leak across conversation switches
- fix(agent-chat): onDisconnected/onDisconnectedPermanent cascade to
  streamingState — prevents permanent hang on connection loss during stream
- fix(agent-chat):  guard on isLoadingHistory — prevents false commit
  of 'agent unavailable' fallback when switching conversations
- fix(agent-chat): remove race in _sendNow empty-response check vs Svelte
   microtask (duplicate logic removed,  handles correctly)
- fix(stream-processor): confirm_resolved now appends msg.text to partialText
  instead of dropping it

### Bugfixes — Backend PDF Upload
- fix(document-parser): _detect_format_by_magic() — reads file header magic
  bytes as fallback when Gradio loses filename
- fix(document-parser): improved name extraction — tries orig_name, path stem
- fix(document-parser): @RELATION AgentChatTypes -> AgentChat.Types

### HITL Flow & Agent Chat Improvements
- feat(agent): HITL resume confirm/deny with userId/userJwt/envId propagation
- feat(agent): confirm_required metadata fallback via aget_state() after
  'Event loop is closed' error during interrupt
- feat(agent): interrupt_before re-enabled via AGENT_CONFIRM_TOOLS env var
- feat(frontend): debug panel with connection/stream state monitoring
- feat(frontend): AgentChatModel constructor options + onBeforeSend callback
- feat(frontend): crypto.randomUUID() for local conversation ID on first send

### Backend Agent Refactoring
- refactor(agent): langgraph_setup — monkey-patch for PydanticSerializationError
- refactor(agent): tools.py — dual identity headers, expanded tool set
- refactor(agent): run.py — _find_free_port, Gradio server port fallback
- refactor(agent): app.py — file size validation, message truncation, HITL path

### Frontend
- feat(dashboard-hub): DashboardHubModel with filters, pagination, git actions
- feat(ui): DateRangeFilter component
- feat(i18n): new dashboard keys; cache tooltips fix
- fix(i18n): full run tooltips — cache is NOT ignored

### Semantic Protocol
- chore(agents): update all agents with canonical format
- chore(skills): sync semantics-core, semantics-contracts, molecular-cot-logging

### Housekeeping
- chore: remove stale semantic reports (10 files, Jan 2026)
- chore: update 033-gradio-agent-chat specs, contracts, UX, tasks, tests
- chore: add .agents/ directory (mirrors .opencode/ agent layouts)
- chore: update run.sh with DEV_MODE, port management
This commit is contained in:
2026-06-29 17:15:25 +03:00
parent 4fda63a8da
commit 12678c637b
115 changed files with 8948 additions and 1763 deletions

View File

@@ -61,8 +61,27 @@ def parse_xlsx(file_path: str) -> str:
raise ParseError(f"Failed to parse XLSX: {e}") from e
def _detect_format_by_magic(path: str) -> str | None:
"""Detect file format by reading magic bytes. Returns extension or None."""
try:
with open(path, "rb") as f:
header = f.read(8)
except OSError:
return None
if header[:4] == b"%PDF":
return ".pdf"
if header[:4] == b"PK\x03\x04":
# ZIP-based: XLSX, DOCX, etc. Try XLSX first.
return ".xlsx"
if header[:1] in (b"{", b"["):
return ".json"
# CSV often starts with text characters; safe fallback
return None
def parse_upload(file_data) -> str:
"""Parse an uploaded file based on its extension.
"""Parse an uploaded file based on its extension with magic-byte fallback.
Args:
file_data: str (file path) or dict with "name" and "path"/"file_path" keys.
@@ -71,9 +90,19 @@ def parse_upload(file_data) -> str:
path = file_data
name = Path(path).name
else:
name = file_data.get("name", "")
path = file_data.get("path", file_data.get("file_path", ""))
name = file_data.get("name") or file_data.get("orig_name", "")
path = file_data.get("path") or file_data.get("file_path", "")
# Gradio sometimes sends files without 'name' key — fall back to path stem
if not name and path:
name = Path(path).name
ext = Path(name).suffix.lower()
# Double fallback: if name has no extension, try the physical file path
if not ext and path:
ext = Path(path).suffix.lower()
# Triple fallback: detect by magic bytes (Gradio ChatInterface loses filename)
if not ext and path:
ext = _detect_format_by_magic(path)
if ext == ".pdf":
return parse_pdf(path)
@@ -82,6 +111,19 @@ def parse_upload(file_data) -> str:
elif ext in (".json", ".csv", ".txt"):
with open(path, encoding="utf-8", errors="replace") as f:
return f.read(100_000) # truncate at ~100k chars
elif ext is None:
# Magic bytes detection returned None — unknown binary, try as text
try:
with open(path, encoding="utf-8", errors="replace") as f:
return f.read(100_000)
except Exception as e:
raise ParseError(
f"Could not detect file format for '{name}'. "
f"Supported: PDF, XLSX, JSON, CSV, TXT"
) from e
else:
raise ParseError(f"Unsupported format: {ext}. Supported: PDF, XLSX, JSON, CSV, TXT")
raise ParseError(
f"Unsupported format: '{ext}' (file: {name}). "
f"Supported: PDF, XLSX, JSON, CSV, TXT"
)
# #endregion AgentChat.Document.Parser