feat: 持久对话 MCP 插件 v1.6.37 · Hub + Panel + MCP 三形态部署
- src/: server.js (MCP 4 工具) + hub.js (HTTP Hub) + cursor-title.js - web/: panel.html (主面板) + panel.remote.html (远端 1637) - scripts/: ensure-hub.sh (stdio→http 桥) + mongo_sync.py - mcp-config/: trae.mcp.json + cursor.mcp.json - docs/: INSTALL / DEPLOY / MCP_SETUP / ARCHITECTURE / QUICKSTART - ZIP 原始安装包备查 关键机制: - 4 MCP 工具:init / select / wait / merge - 垂直绑定:workspace::cursorTitle@hostIp 三件指纹 - 真挂起:关闭 timedReply 后 wait 真正阻塞,agent turn 不结束 - 双 Hub 部署:远端 NAS 24×7 + 本地 launchd 守护备份 - 三形态组合:远端 + 本地 + Trae 插件,可同时跑
This commit is contained in:
58
scripts/ensure-hub.sh
Executable file
58
scripts/ensure-hub.sh
Executable file
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env bash
|
||||
# 启动 MCP 前确保 Hub 在 13458 运行(全局唯一)
|
||||
set -eo pipefail
|
||||
INSTALL="$(cd "$(dirname "$0")" && pwd)"
|
||||
PORT="${PCHAT_HTTP_PORT:-13458}"
|
||||
LOG="$INSTALL/logs/hub.log"
|
||||
NODE="${PCHAT_NODE:-$(command -v node 2>/dev/null || true)}"
|
||||
[[ -z "$NODE" ]] && NODE="/usr/local/opt/node@22/bin/node"
|
||||
|
||||
mkdir -p "$INSTALL/logs"
|
||||
|
||||
if [[ ! -x "$NODE" ]]; then
|
||||
echo "[ensure-hub] FATAL: node not found: $NODE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
hub_health() {
|
||||
curl -sf -m 2 "http://127.0.0.1:${PORT}/api/health" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
start_hub() {
|
||||
if hub_health; then return 0; fi
|
||||
echo "[ensure-hub] starting hub on :${PORT}" >> "$LOG"
|
||||
nohup "$NODE" "$INSTALL/hub.js" >> "$INSTALL/logs/hub.stdout.log" 2>&1 &
|
||||
local i=0
|
||||
while [[ $i -lt 80 ]]; do
|
||||
hub_health && return 0
|
||||
sleep 0.15
|
||||
i=$((i + 1))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
if ! start_hub; then
|
||||
echo "[ensure-hub] FATAL: Hub not ready on 127.0.0.1:${PORT} (waited ~12s). Run: bash 卡若AI/.../Cursor持久对话/脚本/sync-runtime.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Cursor 无工作区时 ${workspaceFolder} 可能为空/none/未替换字面量 → 兜底 HOME
|
||||
ARGS=()
|
||||
for a in "$@"; do
|
||||
case "$a" in
|
||||
--workspace=|--workspace=none|--workspace=\$\{workspaceFolder\})
|
||||
ARGS+=("--workspace=$HOME")
|
||||
;;
|
||||
--workspace=*)
|
||||
ARGS+=("$a")
|
||||
;;
|
||||
*)
|
||||
ARGS+=("$a")
|
||||
;;
|
||||
esac
|
||||
done
|
||||
if [[ ${#ARGS[@]} -eq 0 ]] || ! printf '%s\n' "${ARGS[@]}" | grep -q '^--workspace='; then
|
||||
ARGS+=("--workspace=${PWD:-$HOME}")
|
||||
fi
|
||||
|
||||
exec "$NODE" "$INSTALL/server.js" "${ARGS[@]}"
|
||||
52
scripts/mongo_sync.py
Executable file
52
scripts/mongo_sync.py
Executable file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python3
|
||||
"""持久对话会话 → MongoDB karuo_site.持久对话会话(本地 ~/.persistent-chat-local/sessions.json)"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
|
||||
HOME = os.path.expanduser("~")
|
||||
SESSIONS_FILE = os.path.join(HOME, ".persistent-chat-local", "sessions.json")
|
||||
MONGO_URI = os.environ.get("MONGO_URI", "mongodb://127.0.0.1:27017")
|
||||
DB_NAME = os.environ.get("MONGO_DB", "karuo_site")
|
||||
COLL_NAME = "持久对话会话"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if not os.path.isfile(SESSIONS_FILE):
|
||||
print("SKIP: no sessions.json")
|
||||
return 0
|
||||
try:
|
||||
from pymongo import MongoClient
|
||||
except ImportError:
|
||||
print("SKIP: pymongo not installed (pip3 install pymongo)")
|
||||
return 0
|
||||
|
||||
with open(SESSIONS_FILE, encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
client = MongoClient(MONGO_URI, serverSelectionTimeoutMS=4000)
|
||||
client.admin.command("ping")
|
||||
col = client[DB_NAME][COLL_NAME]
|
||||
col.create_index("token", unique=True)
|
||||
col.create_index("workspace")
|
||||
col.create_index("lastActiveAt")
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
n = 0
|
||||
for token, sess in data.items():
|
||||
doc = dict(sess)
|
||||
doc["token"] = token
|
||||
doc["mongo_sync_at"] = now
|
||||
col.update_one({"token": token}, {"$set": doc}, upsert=True)
|
||||
n += 1
|
||||
print(f"OK: synced {n} sessions → {DB_NAME}.{COLL_NAME}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
sys.exit(main())
|
||||
except Exception as e:
|
||||
print(f"FAIL: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user