包含从项目创建至今的全部代码首次入库: 核心框架: - 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>
94 lines
2.9 KiB
Python
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()
|