Hardcode Python default empty response

This commit is contained in:
Brandon Zhang
2026-03-27 17:54:38 +08:00
parent 8a0dffbcae
commit 372e30ff6f
5 changed files with 22 additions and 25 deletions

View File

@@ -146,15 +146,14 @@ All routes are local-only and intended for `localhost` usage.
**Purpose** **Purpose**
- Return the next pending instruction, if one exists. - Return the next pending instruction, if one exists.
- If none exists, wait for a configurable duration, then return either an empty response or a configured default response. - If none exists, wait for a configurable duration, then return the server-controlled default response.
- Record agent activity so the UI can infer whether an agent is currently connected/recently active. - Record agent activity so the UI can infer whether an agent is currently connected/recently active.
**Suggested input schema** **Suggested input schema**
```json ```json
{ {
"agent_id": "optional-string", "agent_id": "optional-string"
"default_response_override": "optional-string"
} }
``` ```
@@ -194,7 +193,7 @@ All routes are local-only and intended for `localhost` usage.
"status": "ok", "status": "ok",
"result_type": "default_response", "result_type": "default_response",
"instruction": null, "instruction": null,
"response": "No new instructions available.", "response": "call this tool `get_user_request` again to fetch latest user input...",
"remaining_pending": 0, "remaining_pending": 0,
"waited_seconds": 10 "waited_seconds": 10
} }
@@ -250,7 +249,7 @@ Returns current server and agent summary.
}, },
"settings": { "settings": {
"default_wait_seconds": 10, "default_wait_seconds": 10,
"default_empty_response": "No new instructions available.", "default_empty_response": "call this tool `get_user_request` again to fetch latest user input...",
"agent_stale_after_seconds": 30 "agent_stale_after_seconds": 30
} }
} }
@@ -347,7 +346,7 @@ Returns editable runtime settings.
```json ```json
{ {
"default_wait_seconds": 10, "default_wait_seconds": 10,
"default_empty_response": "No new instructions available.", "default_empty_response": "call this tool `get_user_request` again to fetch latest user input...",
"agent_stale_after_seconds": 30 "agent_stale_after_seconds": 30
} }
``` ```
@@ -562,7 +561,7 @@ The server starts on `http://localhost:8000` by default.
| `DB_PATH` | `data/local_mcp.sqlite3` | SQLite database path | | `DB_PATH` | `data/local_mcp.sqlite3` | SQLite database path |
| `LOG_LEVEL` | `INFO` | Logging level | | `LOG_LEVEL` | `INFO` | Logging level |
| `DEFAULT_WAIT_SECONDS` | `10` | Default tool wait timeout | | `DEFAULT_WAIT_SECONDS` | `10` | Default tool wait timeout |
| `DEFAULT_EMPTY_RESPONSE` | _(empty)_ | Default response when queue is empty | | `DEFAULT_EMPTY_RESPONSE` | `call this tool \`get_user_request\` again to fetch latest user input...` | Default response when queue is empty |
| `AGENT_STALE_AFTER_SECONDS` | `30` | Seconds of inactivity before agent shown as idle | | `AGENT_STALE_AFTER_SECONDS` | `30` | Seconds of inactivity before agent shown as idle |
| `MCP_STATELESS` | `true` | `true` for stateless sessions (survives restarts, recommended); `false` for stateful | | `MCP_STATELESS` | `true` | `true` for stateless sessions (survives restarts, recommended); `false` for stateful |
| `API_TOKEN` | _(empty)_ | When set, all `/api/*` and `/mcp` requests require `Authorization: Bearer <token>`; web UI prompts for the token on first load | | `API_TOKEN` | _(empty)_ | When set, all `/api/*` and `/mcp` requests require `Authorization: Bearer <token>`; web UI prompts for the token on first load |

View File

@@ -9,6 +9,9 @@ import os
from dataclasses import dataclass, field from dataclasses import dataclass, field
DEFAULT_EMPTY_RESPONSE = "call this tool `get_user_request` again to fetch latest user input..."
@dataclass @dataclass
class Settings: class Settings:
# Server # Server
@@ -23,7 +26,7 @@ class Settings:
# MCP / queue behaviour (runtime-editable values are stored in DB; these are defaults for first run) # MCP / queue behaviour (runtime-editable values are stored in DB; these are defaults for first run)
default_wait_seconds: int = 10 default_wait_seconds: int = 10
default_empty_response: str = "" default_empty_response: str = DEFAULT_EMPTY_RESPONSE
agent_stale_after_seconds: int = 30 agent_stale_after_seconds: int = 30
# MCP server name # MCP server name
@@ -53,7 +56,7 @@ def load_settings() -> Settings:
db_path=os.getenv("DB_PATH", "data/local_mcp.sqlite3"), db_path=os.getenv("DB_PATH", "data/local_mcp.sqlite3"),
log_level=os.getenv("LOG_LEVEL", "INFO"), log_level=os.getenv("LOG_LEVEL", "INFO"),
default_wait_seconds=int(os.getenv("DEFAULT_WAIT_SECONDS", "10")), default_wait_seconds=int(os.getenv("DEFAULT_WAIT_SECONDS", "10")),
default_empty_response=os.getenv("DEFAULT_EMPTY_RESPONSE", ""), default_empty_response=os.getenv("DEFAULT_EMPTY_RESPONSE", DEFAULT_EMPTY_RESPONSE),
agent_stale_after_seconds=int(os.getenv("AGENT_STALE_AFTER_SECONDS", "30")), agent_stale_after_seconds=int(os.getenv("AGENT_STALE_AFTER_SECONDS", "30")),
mcp_server_name=os.getenv("MCP_SERVER_NAME", "local-mcp"), mcp_server_name=os.getenv("MCP_SERVER_NAME", "local-mcp"),
mcp_stateless=_parse_bool(os.getenv("MCP_STATELESS", "true"), default=True), mcp_stateless=_parse_bool(os.getenv("MCP_STATELESS", "true"), default=True),

View File

@@ -12,6 +12,8 @@ import threading
from contextlib import contextmanager from contextlib import contextmanager
from typing import Generator from typing import Generator
from app.config import DEFAULT_EMPTY_RESPONSE
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Module-level lock for write serialisation (consume atomicity, settings writes, etc.) # Module-level lock for write serialisation (consume atomicity, settings writes, etc.)
@@ -102,7 +104,7 @@ CREATE TABLE IF NOT EXISTS agent_activity (
_DEFAULT_SETTINGS = { _DEFAULT_SETTINGS = {
"default_wait_seconds": "10", "default_wait_seconds": "10",
"default_empty_response": "", "default_empty_response": DEFAULT_EMPTY_RESPONSE,
"agent_stale_after_seconds": "30", "agent_stale_after_seconds": "30",
} }
@@ -119,6 +121,10 @@ def _seed_defaults(conn: sqlite3.Connection) -> None:
"INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)", "INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)",
(key, value), (key, value),
) )
conn.execute(
"UPDATE settings SET value = ? WHERE key = 'default_empty_response' AND value = ''",
(_DEFAULT_SETTINGS["default_empty_response"],),
)
conn.commit() conn.commit()
logger.debug("Default settings seeded") logger.debug("Default settings seeded")

View File

@@ -13,7 +13,7 @@ from typing import Optional
from mcp.server.fastmcp import FastMCP, Context from mcp.server.fastmcp import FastMCP, Context
from app.config import settings from app.config import DEFAULT_EMPTY_RESPONSE, settings
from app.services import config_service, event_service, instruction_service, status_service from app.services import config_service, event_service, instruction_service, status_service
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -38,9 +38,6 @@ _MAX_WAIT_SECONDS = 86400 # 24 hours
# multiple keepalive progress updates. # multiple keepalive progress updates.
DEFAULT_WAIT_SECONDS = 50 DEFAULT_WAIT_SECONDS = 50
# Default response when queue is empty after waiting
DEFAULT_EMPTY_RESPONSE = "call this tool `get_user_request` again to fetch latest user input..."
# Per-agent generation counter — incremented on every new call. # Per-agent generation counter — incremented on every new call.
# The wait loop only consumes an instruction when it holds the latest generation, # The wait loop only consumes an instruction when it holds the latest generation,
# preventing abandoned (timed-out) coroutines from silently consuming queue items. # preventing abandoned (timed-out) coroutines from silently consuming queue items.
@@ -65,7 +62,6 @@ KEEPALIVE_INTERVAL_SECONDS: float = 5.0
@mcp.tool() @mcp.tool()
async def get_user_request( async def get_user_request(
agent_id: str = "unknown", agent_id: str = "unknown",
default_response_override: Optional[str] = None,
ctx: Optional[Context] = None, ctx: Optional[Context] = None,
) -> dict: ) -> dict:
""" """
@@ -76,8 +72,6 @@ async def get_user_request(
Args: Args:
agent_id: An identifier for this agent instance (used to track connectivity). agent_id: An identifier for this agent instance (used to track connectivity).
default_response_override: Override the server-default empty response text
for this single call.
Returns: Returns:
A dict with keys: status, result_type, instruction, response, A dict with keys: status, result_type, instruction, response,
@@ -222,11 +216,7 @@ async def get_user_request(
status_service.record_agent_activity(agent_id, "empty") status_service.record_agent_activity(agent_id, "empty")
event_service.broadcast("status.changed", {}) event_service.broadcast("status.changed", {})
empty_response = ( empty_response = DEFAULT_EMPTY_RESPONSE
default_response_override
if default_response_override is not None
else DEFAULT_EMPTY_RESPONSE
)
result_type = "default_response" if empty_response else "empty" result_type = "default_response" if empty_response else "empty"
if _i_am_active(): if _i_am_active():

View File

@@ -7,21 +7,20 @@ from __future__ import annotations
import logging import logging
from app.config import DEFAULT_EMPTY_RESPONSE
from app.database import get_conn, get_write_conn from app.database import get_conn, get_write_conn
from app.models import ConfigResponse from app.models import ConfigResponse
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
_SETTING_KEYS = {"default_wait_seconds", "default_empty_response", "agent_stale_after_seconds"} _SETTING_KEYS = {"default_wait_seconds", "default_empty_response", "agent_stale_after_seconds"}
def get_config() -> ConfigResponse: def get_config() -> ConfigResponse:
with get_conn() as conn: with get_conn() as conn:
rows = conn.execute("SELECT key, value FROM settings").fetchall() rows = conn.execute("SELECT key, value FROM settings").fetchall()
data = {r["key"]: r["value"] for r in rows} data = {r["key"]: r["value"] for r in rows}
return ConfigResponse( return ConfigResponse(
default_wait_seconds=int(data.get("default_wait_seconds", 10)), default_wait_seconds=int(data.get("default_wait_seconds", 10)),
default_empty_response=data.get("default_empty_response", ""), default_empty_response=data.get("default_empty_response") or DEFAULT_EMPTY_RESPONSE,
agent_stale_after_seconds=int(data.get("agent_stale_after_seconds", 30)), agent_stale_after_seconds=int(data.get("agent_stale_after_seconds", 30)),
) )