Files
nmfs-agents/scripts/dashboard.py
T
qiuruiandClaude Sonnet 4.6 df59fc0c83 feat: NMFS Agents 全量入库(org v2 + dashboard + 多队列 + 组织架构)
包含从项目创建至今的全部代码首次入库:

核心框架:
- TaskQueue(SQLite,6 种状态,原子 dequeue)
- Executor(asyncio 并发,semaphore 限流,agent_role 路由)
- Scheduler(APScheduler,三队列独立调度)
- Watchdog(超时检测 + 优先级防饥饿)
- Scanner(项目扫描,README/TODO/CLAUDE.md 提取)

Agent 体系(20+ 角色):
- 项目交付组:architect/developer/tester/productizer
- 基础技术组:base-architect/validator/hw/os/kernel/lowlevel/system-tester
- 算法组:algo-antishake/position/nav
- 洞察组:planner/vision-analyst/media-producer
- 市场组:market-pm/sport/elder/safety
- 组织层:boss/group-leader/senior-dev/ops
- 平台扩展:nrf-dev/esp32-dev
- 三专项调研:rtp-researcher/net-researcher/kernel-analyzer

工具层:
- ProjectMemory(goals/facts/history SQLite)
- VersionPipeline(并行版本流水线 + 反幻觉门控)
- EventBus(Redis Streams,5 consumer groups)
- DailyReport(append-only 日报 + Boss 决策视图)
- AgentScorer(4 维评分:完成度/质量/自主性/协作)
- DeviceAgent(SSH rsync + 板端执行)
- EnvCollector(本机+SSH 环境采集)
- ArchVersion(快照/优化/promote/rollback)

Dashboard(React + Vite + Tailwind):
- 看板/项目/配置/洞察/日报/Visual 六标签页
- WebSocket 实时更新
- Markdown 渲染(react-markdown + remark-gfm)

测试:314 passed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 07:24:19 +08:00

94 lines
2.9 KiB
Python

#!/usr/bin/env python3
"""NMFS Agents Dashboard 独立服务"""
from __future__ import annotations
import subprocess, sys as _sys
try:
import visual_tester # noqa: F401
except ImportError:
subprocess.run([_sys.executable, "-m", "pip", "install", "-e",
"/app/visual-tester", "-q"], check=False)
# pip install 是子进程,不会更新当前进程的 sys.path;手动添加 src/ 路径后重试
_vt_src = "/app/visual-tester/src"
if _vt_src not in _sys.path:
_sys.path.insert(0, _vt_src)
try:
import visual_tester # noqa: F401
except ImportError:
pass
import asyncio
import hashlib
import json
import logging
import os
import threading
import time
from pathlib import Path
import uvicorn
logging.basicConfig(level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s: %(message)s")
logger = logging.getLogger("dashboard")
DB_PATH = Path(os.environ.get("DB_PATH", "data/tasks.db"))
CONFIG_DIR = os.environ.get("CONFIG_DIR", None)
GLOBAL_CLAUDE_DIR = os.environ.get("CLAUDE_GLOBAL_DIR", None)
PORT = int(os.environ.get("DASHBOARD_PORT", "8080"))
def _task_hash(tasks: list[dict]) -> str:
s = json.dumps(tasks, sort_keys=True, ensure_ascii=False)
return hashlib.md5(s.encode()).hexdigest()
def _watcher(app_state, loop: asyncio.AbstractEventLoop) -> None:
"""后台线程:每秒检测 SQLite 变更,有变化则 broadcast 给所有 WS 连接"""
last_hash = ""
while True:
try:
tasks = app_state.all_tasks_fn()
h = _task_hash(tasks)
if h != last_hash:
last_hash = h
payload = json.dumps(
{"type": "snapshot", "tasks": tasks},
ensure_ascii=False
)
conns = list(app_state.ws_connections)
for ws in conns:
try:
asyncio.run_coroutine_threadsafe(
ws.send_text(payload), loop
)
except Exception:
pass
except Exception as e:
logger.debug("watcher 异常(非关键): %s", e)
time.sleep(1)
def main() -> None:
from nmfs_agents.dashboard.api import make_app
app = make_app(db_path=DB_PATH, config_dir=CONFIG_DIR,
global_claude_dir=GLOBAL_CLAUDE_DIR)
@app.on_event("startup")
async def startup() -> None:
event_loop = asyncio.get_running_loop()
t = threading.Thread(
target=_watcher, args=(app.state, event_loop),
daemon=True, name="db-watcher"
)
t.start()
logger.info("Dashboard Watcher 已启动")
logger.info("Dashboard 启动: http://localhost:%d", PORT)
uvicorn.run(app, host="0.0.0.0", port=PORT, log_level="warning")
if __name__ == "__main__":
main()