diff --git a/app/api/routes_config.py b/app/api/routes_config.py
index 5a95684..fa7f2ed 100644
--- a/app/api/routes_config.py
+++ b/app/api/routes_config.py
@@ -27,14 +27,12 @@ def update_config(body: UpdateConfigRequest):
cfg = config_service.update_config(
default_wait_seconds=body.default_wait_seconds,
default_empty_response=body.default_empty_response,
- agent_stale_after_seconds=body.agent_stale_after_seconds,
)
event_service.broadcast(
"config.updated",
{
"default_wait_seconds": cfg.default_wait_seconds,
"default_empty_response": cfg.default_empty_response,
- "agent_stale_after_seconds": cfg.agent_stale_after_seconds,
},
)
return cfg
diff --git a/app/config.py b/app/config.py
index f30eb93..c3db7ba 100644
--- a/app/config.py
+++ b/app/config.py
@@ -11,6 +11,7 @@ from dataclasses import dataclass, field
APP_VERSION = "1.0.1"
DEFAULT_EMPTY_RESPONSE = "call this tool `get_user_request` again to fetch latest user input..."
+AGENT_STALE_AFTER_SECONDS = 30
@dataclass
@@ -28,7 +29,6 @@ class Settings:
# MCP / queue behaviour (runtime-editable values are stored in DB; these are defaults for first run)
default_wait_seconds: int = 10
default_empty_response: str = DEFAULT_EMPTY_RESPONSE
- agent_stale_after_seconds: int = 30
# MCP server name
mcp_server_name: str = "local-mcp"
@@ -58,7 +58,6 @@ def load_settings() -> Settings:
log_level=os.getenv("LOG_LEVEL", "INFO"),
default_wait_seconds=int(os.getenv("DEFAULT_WAIT_SECONDS", "10")),
default_empty_response=os.getenv("DEFAULT_EMPTY_RESPONSE", DEFAULT_EMPTY_RESPONSE),
- agent_stale_after_seconds=int(os.getenv("AGENT_STALE_AFTER_SECONDS", "30")),
mcp_server_name=os.getenv("MCP_SERVER_NAME", "local-mcp"),
mcp_stateless=_parse_bool(os.getenv("MCP_STATELESS", "true"), default=True),
api_token=os.getenv("API_TOKEN", ""),
diff --git a/app/database.py b/app/database.py
index 17b1a7a..872d1a7 100644
--- a/app/database.py
+++ b/app/database.py
@@ -105,7 +105,6 @@ CREATE TABLE IF NOT EXISTS agent_activity (
_DEFAULT_SETTINGS = {
"default_wait_seconds": "10",
"default_empty_response": DEFAULT_EMPTY_RESPONSE,
- "agent_stale_after_seconds": "30",
}
@@ -125,6 +124,7 @@ def _seed_defaults(conn: sqlite3.Connection) -> None:
"UPDATE settings SET value = ? WHERE key = 'default_empty_response' AND value = ''",
(_DEFAULT_SETTINGS["default_empty_response"],),
)
+ conn.execute("DELETE FROM settings WHERE key = 'agent_stale_after_seconds'")
conn.commit()
logger.debug("Default settings seeded")
diff --git a/app/models.py b/app/models.py
index 23f42cb..cf1b34c 100644
--- a/app/models.py
+++ b/app/models.py
@@ -109,13 +109,11 @@ class StatusResponse(BaseModel):
class ConfigResponse(BaseModel):
default_wait_seconds: int
default_empty_response: str
- agent_stale_after_seconds: int
class UpdateConfigRequest(BaseModel):
default_wait_seconds: Optional[int] = None
default_empty_response: Optional[str] = None
- agent_stale_after_seconds: Optional[int] = None
# ---------------------------------------------------------------------------
diff --git a/app/services/config_service.py b/app/services/config_service.py
index 9ff2379..6d684cc 100644
--- a/app/services/config_service.py
+++ b/app/services/config_service.py
@@ -13,7 +13,6 @@ from app.models import ConfigResponse
logger = logging.getLogger(__name__)
-_SETTING_KEYS = {"default_wait_seconds", "default_empty_response", "agent_stale_after_seconds"}
def get_config() -> ConfigResponse:
with get_conn() as conn:
rows = conn.execute("SELECT key, value FROM settings").fetchall()
@@ -21,22 +20,18 @@ def get_config() -> ConfigResponse:
return ConfigResponse(
default_wait_seconds=int(data.get("default_wait_seconds", 10)),
default_empty_response=data.get("default_empty_response") or DEFAULT_EMPTY_RESPONSE,
- agent_stale_after_seconds=int(data.get("agent_stale_after_seconds", 30)),
)
def update_config(
default_wait_seconds: int | None = None,
default_empty_response: str | None = None,
- agent_stale_after_seconds: int | None = None,
) -> ConfigResponse:
updates: dict[str, str] = {}
if default_wait_seconds is not None:
updates["default_wait_seconds"] = str(default_wait_seconds)
if default_empty_response is not None:
updates["default_empty_response"] = default_empty_response
- if agent_stale_after_seconds is not None:
- updates["agent_stale_after_seconds"] = str(agent_stale_after_seconds)
if updates:
with get_write_conn() as conn:
diff --git a/app/services/status_service.py b/app/services/status_service.py
index 507a59d..b98a8ef 100644
--- a/app/services/status_service.py
+++ b/app/services/status_service.py
@@ -10,6 +10,7 @@ import sqlite3
from datetime import datetime, timezone
from typing import Optional
+from app.config import AGENT_STALE_AFTER_SECONDS
from app.database import get_conn, get_write_conn
logger = logging.getLogger(__name__)
@@ -55,25 +56,15 @@ def get_latest_agent_activity() -> Optional[sqlite3.Row]:
).fetchone()
-def get_agent_stale_seconds() -> int:
- """Read agent_stale_after_seconds from settings table."""
- with get_conn() as conn:
- row = conn.execute(
- "SELECT value FROM settings WHERE key = 'agent_stale_after_seconds'"
- ).fetchone()
- return int(row["value"]) if row else 30
-
-
def is_agent_connected() -> bool:
"""True if the most recent agent activity is within the stale threshold."""
row = get_latest_agent_activity()
if row is None:
return False
- stale_seconds = get_agent_stale_seconds()
last_seen = datetime.fromisoformat(row["last_seen_at"])
now = datetime.now(timezone.utc)
if last_seen.tzinfo is None:
last_seen = last_seen.replace(tzinfo=timezone.utc)
delta = (now - last_seen).total_seconds()
- return delta <= stale_seconds
+ return delta <= AGENT_STALE_AFTER_SECONDS
diff --git a/go-server/internal/api/config.go b/go-server/internal/api/config.go
index d15a918..2e49c29 100644
--- a/go-server/internal/api/config.go
+++ b/go-server/internal/api/config.go
@@ -22,8 +22,7 @@ func handleUpdateConfig(stores Stores, broker *events.Broker) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Decode partial patch
var patch struct {
- DefaultWaitSeconds *int `json:"default_wait_seconds"`
- AgentStaleAfterSeconds *int `json:"agent_stale_after_seconds"`
+ DefaultWaitSeconds *int `json:"default_wait_seconds"`
}
if err := json.NewDecoder(r.Body).Decode(&patch); err != nil {
writeError(w, http.StatusBadRequest, "Invalid JSON")
@@ -41,9 +40,6 @@ func handleUpdateConfig(stores Stores, broker *events.Broker) http.HandlerFunc {
if patch.DefaultWaitSeconds != nil {
current.DefaultWaitSeconds = *patch.DefaultWaitSeconds
}
- if patch.AgentStaleAfterSeconds != nil {
- current.AgentStaleAfterSeconds = *patch.AgentStaleAfterSeconds
- }
if err := stores.Settings.Update(current); err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
diff --git a/go-server/internal/api/status.go b/go-server/internal/api/status.go
index 9561bf8..fb902cf 100644
--- a/go-server/internal/api/status.go
+++ b/go-server/internal/api/status.go
@@ -45,7 +45,7 @@ func handleStatus(stores Stores) http.HandlerFunc {
}
if latest != nil {
- connected := time.Since(latest.LastSeenAt).Seconds() <= float64(cfg.AgentStaleAfterSeconds)
+ connected := time.Since(latest.LastSeenAt).Seconds() <= float64(config.AgentStaleAfterSeconds)
agent = map[string]any{
"connected": connected,
"last_seen_at": latest.LastSeenAt.Format(time.RFC3339Nano),
@@ -69,7 +69,6 @@ func handleStatus(stores Stores) http.HandlerFunc {
"settings": models.Settings{
DefaultWaitSeconds: cfg.DefaultWaitSeconds,
DefaultEmptyResponse: cfg.DefaultEmptyResponse,
- AgentStaleAfterSeconds: cfg.AgentStaleAfterSeconds,
},
}
diff --git a/go-server/internal/config/config.go b/go-server/internal/config/config.go
index 3e7ce16..2de1961 100644
--- a/go-server/internal/config/config.go
+++ b/go-server/internal/config/config.go
@@ -8,6 +8,7 @@ import (
const (
AppVersion = "1.0.1"
+ AgentStaleAfterSeconds = 30
DefaultEmptyResponse = "call this tool `get_user_request` again to fetch latest user input..."
)
@@ -18,7 +19,6 @@ type Config struct {
DBPath string
LogLevel string
DefaultWaitSeconds int
- AgentStaleAfterSeconds int
MCPStateless bool
APIToken string
}
@@ -31,7 +31,6 @@ func Load() Config {
DBPath: getEnv("DB_PATH", "data/local_mcp.sqlite3"),
LogLevel: getEnv("LOG_LEVEL", "INFO"),
DefaultWaitSeconds: getEnvInt("DEFAULT_WAIT_SECONDS", 10),
- AgentStaleAfterSeconds: getEnvInt("AGENT_STALE_AFTER_SECONDS", 30),
MCPStateless: getEnvBool("MCP_STATELESS", true),
APIToken: getEnv("API_TOKEN", ""),
}
diff --git a/go-server/internal/db/db.go b/go-server/internal/db/db.go
index 2e99e85..f905b7d 100644
--- a/go-server/internal/db/db.go
+++ b/go-server/internal/db/db.go
@@ -42,8 +42,8 @@ CREATE TABLE IF NOT EXISTS agent_activity (
// defaultSettings seeds initial values; OR IGNORE means existing rows are unchanged.
const defaultSettings = "" +
"INSERT OR IGNORE INTO settings (key, value) VALUES ('default_wait_seconds', '10');\n" +
- "INSERT OR IGNORE INTO settings (key, value) VALUES ('agent_stale_after_seconds', '30');\n" +
- "DELETE FROM settings WHERE key = 'default_empty_response';\n"
+ "DELETE FROM settings WHERE key = 'default_empty_response';\n" +
+ "DELETE FROM settings WHERE key = 'agent_stale_after_seconds';\n"
// Open opens (creating if necessary) a SQLite database at dbPath, applies the
// schema, and seeds default settings.
diff --git a/go-server/internal/models/models.go b/go-server/internal/models/models.go
index c63693e..dc07379 100644
--- a/go-server/internal/models/models.go
+++ b/go-server/internal/models/models.go
@@ -27,7 +27,6 @@ type Instruction struct {
type Settings struct {
DefaultWaitSeconds int `json:"default_wait_seconds"`
DefaultEmptyResponse string `json:"default_empty_response"`
- AgentStaleAfterSeconds int `json:"agent_stale_after_seconds"`
}
// AgentActivity tracks the last time an agent called get_user_request.
diff --git a/go-server/internal/store/settings.go b/go-server/internal/store/settings.go
index 9a6bcc0..3aa458b 100644
--- a/go-server/internal/store/settings.go
+++ b/go-server/internal/store/settings.go
@@ -30,7 +30,6 @@ func (s *SettingsStore) Get() (models.Settings, error) {
cfg := models.Settings{
DefaultWaitSeconds: 10,
DefaultEmptyResponse: config.DefaultEmptyResponse,
- AgentStaleAfterSeconds: 30,
}
for rows.Next() {
@@ -43,10 +42,6 @@ func (s *SettingsStore) Get() (models.Settings, error) {
if n, err := strconv.Atoi(value); err == nil {
cfg.DefaultWaitSeconds = n
}
- case "agent_stale_after_seconds":
- if n, err := strconv.Atoi(value); err == nil {
- cfg.AgentStaleAfterSeconds = n
- }
}
}
return cfg, rows.Err()
@@ -57,10 +52,8 @@ func (s *SettingsStore) Get() (models.Settings, error) {
func (s *SettingsStore) Update(patch models.Settings) error {
_, err := s.db.Exec(`
INSERT OR REPLACE INTO settings (key, value) VALUES
- ('default_wait_seconds', ?),
- ('agent_stale_after_seconds', ?)`,
+ ('default_wait_seconds', ?)` ,
strconv.Itoa(patch.DefaultWaitSeconds),
- strconv.Itoa(patch.AgentStaleAfterSeconds),
)
return err
}
diff --git a/static/css/layout.css b/static/css/layout.css
index 3e9e843..bd9c2eb 100644
--- a/static/css/layout.css
+++ b/static/css/layout.css
@@ -37,11 +37,6 @@
color: var(--cyan);
}
-.header-version-badge {
- letter-spacing: 0.08em;
- text-transform: uppercase;
-}
-
.header-indicators {
display: flex;
align-items: center;
diff --git a/static/index.html b/static/index.html
index a016570..9508640 100644
--- a/static/index.html
+++ b/static/index.html
@@ -30,8 +30,6 @@
+ Agent connectivity is now inferred with a fixed 30-second timeout managed by the server. +