Files
ss-tools/run.sh
busya 12678c637b 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
2026-06-29 17:15:25 +03:00

297 lines
9.1 KiB
Bash
Executable File

#!/bin/bash
# #region run [C:2] [TYPE Module]
# @PURPOSE: Utility script for run
# @COMPLEXITY: 1
# Project Launch Script
# Automates setup and concurrent execution of backend and frontend servers.
set -e
# Default configuration
DEV_MODE="${DEV_MODE:-false}"
BACKEND_PORT=${BACKEND_PORT:-8000}
FRONTEND_PORT=${FRONTEND_PORT:-5173}
AGENT_PORT=${AGENT_PORT:-7860}
SKIP_INSTALL=false
# Help message
show_help() {
echo "Usage: ./run.sh [options]"
echo ""
echo "Options:"
echo " --help Show this help message"
echo " --skip-install Skip dependency checks and installation"
echo ""
echo "Environment Variables:"
echo " DEV_MODE Hot-reload mode (true/false). Default: false"
echo " Enables uvicorn --reload + watchfiles agent watcher"
echo " BACKEND_PORT Port for the backend server (default: 8000)"
echo " FRONTEND_PORT Port for the frontend server (default: 5173)"
echo " AGENT_PORT Port for the Gradio agent (default: 7860)"
echo ""
echo " LLM providers are fetched from FastAPI /api/agent/llm-config at startup."
echo " Configure them in Admin -> LLM Settings."
}
# Parse arguments
while [[ "$#" -gt 0 ]]; do
case $1 in
--help) show_help; exit 0 ;;
--skip-install) SKIP_INSTALL=true ;;
*) echo "Unknown parameter passed: $1"; show_help; exit 1 ;;
esac
shift
done
echo "Starting Project Launch Script..."
# Environment validation
validate_env() {
echo "Validating environment..."
if ! command -v python3 &> /dev/null; then
echo "Error: python3 is not installed."
exit 1
fi
if ! python3 -c 'import sys; exit(0) if sys.version_info >= (3, 9) else exit(1)'; then
PYTHON_VERSION=$(python3 -c 'import sys; print(".".join(map(str, sys.version_info[:2])))')
echo "Error: python3 version 3.9 or higher is required. Found $PYTHON_VERSION"
exit 1
fi
if ! command -v npm &> /dev/null; then
echo "Error: npm is not installed."
exit 1
fi
PYTHON_VERSION=$(python3 -c 'import sys; print(".".join(map(str, sys.version_info[:2])))')
echo "Environment validation passed (Python $PYTHON_VERSION, npm $(npm -v))"
}
validate_env
# Database connectivity preflight
check_database() {
# Keep resolution order aligned with backend/src/core/database.py defaults.
local DB_URL="${DATABASE_URL:-${POSTGRES_URL:-postgresql+psycopg2://postgres:postgres@localhost:5432/ss_tools}}"
# SQLite does not require external service.
if [[ "$DB_URL" == sqlite* ]]; then
echo "Database preflight: sqlite detected, skipping PostgreSQL connectivity check."
return
fi
local DB_HOST DB_PORT
read -r DB_HOST DB_PORT < <(
python3 - "$DB_URL" <<'PY'
import sys
from urllib.parse import urlparse
url = sys.argv[1]
if "://" not in url:
print("localhost 5432")
raise SystemExit(0)
# Support SQLAlchemy schemes like postgresql+psycopg2://...
scheme, rest = url.split("://", 1)
parsed = urlparse(f"{scheme.split('+', 1)[0]}://{rest}")
host = parsed.hostname or "localhost"
port = parsed.port or 5432
print(f"{host} {port}")
PY
)
local check_cmd
check_cmd='import socket,sys; socket.create_connection((sys.argv[1], int(sys.argv[2])), timeout=1).close()'
if python3 -c "$check_cmd" "$DB_HOST" "$DB_PORT" >/dev/null 2>&1; then
echo "Database preflight: reachable at ${DB_HOST}:${DB_PORT}."
return
fi
echo "Database preflight: cannot connect to ${DB_HOST}:${DB_PORT}."
# For local development defaults, attempt to auto-start bundled PostgreSQL.
if [ "$DB_HOST" = "localhost" ] && [ "$DB_PORT" = "5432" ] && command -v docker >/dev/null 2>&1; then
if [ -f "docker-compose.yml" ]; then
echo "Attempting to start local PostgreSQL via docker compose (service: db)..."
docker compose up -d db || true
fi
fi
for _ in {1..20}; do
if python3 -c "$check_cmd" "$DB_HOST" "$DB_PORT" >/dev/null 2>&1; then
echo "Database preflight: reachable at ${DB_HOST}:${DB_PORT}."
return
fi
sleep 1
done
echo "Error: PostgreSQL is unavailable at ${DB_HOST}:${DB_PORT}."
echo "Run: docker compose up -d db"
echo "Or set DATABASE_URL/POSTGRES_URL to a reachable database."
exit 1
}
check_database
# Backend dependency management
setup_backend() {
if [ "$SKIP_INSTALL" = true ]; then
echo "Skipping backend installation..."
return
fi
echo "Setting up backend..."
cd backend
if [ ! -d ".venv" ]; then
echo "Creating virtual environment..."
python3 -m venv .venv
fi
source .venv/bin/activate
if [ -f "requirements.txt" ]; then
echo "Installing backend dependencies..."
pip install -r requirements.txt
else
echo "Warning: backend/requirements.txt not found."
fi
cd ..
}
# Frontend dependency management
setup_frontend() {
if [ "$SKIP_INSTALL" = true ]; then
echo "Skipping frontend installation..."
return
fi
echo "Setting up frontend..."
cd frontend
if [ ! -d "node_modules" ]; then
echo "Installing frontend dependencies..."
npm install
else
echo "frontend/node_modules already exists. Skipping npm install."
fi
cd ..
}
setup_backend
setup_frontend
# Cleanup function for graceful shutdown
cleanup() {
echo ""
echo "Stopping services..."
if [ -n "$BACKEND_PID" ]; then
kill $BACKEND_PID 2>/dev/null || true
fi
if [ -n "$FRONTEND_PID" ]; then
kill $FRONTEND_PID 2>/dev/null || true
fi
if [ -n "$AGENT_PID" ]; then
kill $AGENT_PID 2>/dev/null || true
fi
echo "Services stopped."
exit 0
}
# Trap SIGINT (Ctrl+C)
trap cleanup SIGINT
# Start Backend
start_backend() {
echo -e "\033[0;34m[Backend]\033[0m Starting on port $BACKEND_PORT..."
cd backend
local -a uvicorn_env_args=()
if [ -f ".venv/bin/activate" ]; then
source .venv/bin/activate
else
echo -e "\033[0;31m[Backend]\033[0m Warning: .venv/bin/activate not found. Attempting to run without venv."
fi
if [ -f ".env" ]; then
uvicorn_env_args=(--env-file .env)
# Also export .env vars so Python's os.environ sees them before app.py imports.
# uvicorn --env-file only affects uvicorn's own config, not the application.
# Guard DEV_MODE from .env override — run.sh DEV_MODE must take precedence.
local _saved_dev_mode="$DEV_MODE"
set -a
# shellcheck disable=SC1091
. .env
set +a
DEV_MODE="${_saved_dev_mode:-$DEV_MODE}"
fi
# PYTHONUNBUFFERED=1 prevents stderr buffering when piped through awk.
# awk with fflush() handles child-process stderr correctly (unlike sed),
# making --reload safe in DEV_MODE.
local -a reload_args=()
if [ "$DEV_MODE" = "true" ]; then
reload_args=(--reload --reload-dir src)
echo -e "\033[0;34m[Backend]\033[0m Hot-reload enabled (watching src/)"
fi
local _color=$'\033[0;34m[Backend]\033[0m '
PYTHONUNBUFFERED=1 python3 -m uvicorn src.app:app --port "$BACKEND_PORT" \
"${uvicorn_env_args[@]}" "${reload_args[@]}" 2>&1 | \
awk -v prefix="$_color " '{print prefix $0; fflush()}' &
BACKEND_PID=$!
cd ..
}
# Start Frontend
start_frontend() {
echo -e "\033[0;32m[Frontend]\033[0m Starting on port $FRONTEND_PORT..."
cd frontend
local _color=$'\033[0;32m[Frontend]\033[0m '
BACKEND_URL="http://127.0.0.1:${BACKEND_PORT}" \
GRADIO_URL="http://127.0.0.1:${AGENT_PORT}" \
npm run dev -- --port "$FRONTEND_PORT" 2>&1 | \
awk -v prefix="$_color " '{print prefix $0; fflush()}' &
FRONTEND_PID=$!
cd ..
}
# Start Gradio Agent
start_agent() {
echo -e "\033[0;35m[Agent]\033[0m Starting Gradio agent on port $AGENT_PORT..."
echo -e "\033[0;35m[Agent]\033[0m LLM config will be fetched from FastAPI /api/agent/llm-config at startup."
echo -e "\033[0;35m[Agent]\033[0m Configure LLM providers in Admin → LLM Settings."
cd backend
if [ -f ".venv/bin/activate" ]; then
source .venv/bin/activate
fi
if [ -f ".env" ]; then
local _saved_dev_mode="$DEV_MODE"
set -a
# shellcheck disable=SC1091
. .env
set +a
DEV_MODE="${_saved_dev_mode:-$DEV_MODE}"
fi
local _color=$'\033[0;35m[Agent]\033[0m '
export GRADIO_SERVER_PORT="$AGENT_PORT"
export GRADIO_ALLOW_PORT_FALLBACK="${GRADIO_ALLOW_PORT_FALLBACK:-false}"
if [ "$DEV_MODE" = "true" ]; then
echo -e "\033[0;35m[Agent]\033[0m Hot-reload enabled via watchfiles (watching src/agent/)"
PYTHONUNBUFFERED=1 python3 -m watchfiles --filter python \
"python -m src.agent.run" src/agent 2>&1 | \
awk -v prefix="$_color " '{print prefix $0; fflush()}' &
else
PYTHONUNBUFFERED=1 python3 -m src.agent.run 2>&1 | \
awk -v prefix="$_color " '{print prefix $0; fflush()}' &
fi
AGENT_PID=$!
cd ..
}
start_backend
start_frontend
start_agent
echo "Services are running. Press Ctrl+C to stop."
wait
# [/DEF:run:Module]