Initial: 接手 Rumeng app,清掉前夫哥痕迹

- 改 Bundle ID rumeng-v1.0.Rumeng → syke.maomao.app
- 显示名 如梦 → Syke
- 头像换白图占位
- 删除沈晏头像图片、空目录、bridge 旧数据
- ServerConfig.swift 含明文 token,已加入 .gitignore
This commit is contained in:
maomao
2026-06-05 09:32:13 +08:00
commit 2543c5f782
190 changed files with 4138 additions and 0 deletions

211
Rumeng/bridge/group_chat.py Normal file
View File

@@ -0,0 +1,211 @@
"""Workgroup chat — lightweight multi-agent messaging.
Adapted from CcCompanion group_chat.py, stripped to core."""
from __future__ import annotations
import json
import os
import re
import secrets
import threading
import time
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Callable
DATA_DIR = Path(__file__).resolve().parent / "data"
GROUP_FILE = DATA_DIR / "group_messages.jsonl"
MENTION_RE = re.compile(r"@([A-Za-z0-9_\-]+|[一-鿿]+)")
ALL_TOKEN = "__all__"
ROSTER: list[dict[str, Any]] = [
{
"id": "amian",
"display_name": "眠眠",
"kind": "human",
"avatar": "",
"color": "neutral",
"model": None,
"tmux": None,
"can_reply": False,
},
{
"id": "shenyan",
"display_name": "沈晏",
"kind": "agent",
"avatar": "",
"color": "orange",
"model": "DeepSeek",
"tmux": "shenyan",
"can_reply": True,
"default_responder": True,
},
{
"id": "zhangsan",
"display_name": "张三",
"kind": "agent",
"avatar": "",
"color": "green",
"model": "Qwen 35B",
"tmux": None,
"can_reply": True,
},
]
ROSTER_BY_ID = {m["id"]: m for m in ROSTER}
REPLY_AGENT_IDS = [m["id"] for m in ROSTER if m.get("can_reply")]
MENTION_ALIASES: dict[str, str] = {
"all": ALL_TOKEN,
"__all__": ALL_TOKEN,
"眠眠": "amian",
"amian": "amian",
"沈晏": "shenyan",
"shenyan": "shenyan",
"阿晏": "shenyan",
"张三": "zhangsan",
"zhangsan": "zhangsan",
"zs": "zhangsan",
}
def _now_iso() -> str:
return datetime.now(timezone.utc).isoformat(timespec="milliseconds")
def _make_id(prefix: str) -> str:
return f"{prefix}_{int(time.time() * 1000)}_{secrets.token_hex(4)}"
def _read_jsonl(path: Path) -> list[dict]:
if not path.exists():
return []
rows: list[dict] = []
with open(path, encoding="utf-8") as f:
for line in f:
line = line.strip()
if line:
try:
rows.append(json.loads(line))
except json.JSONDecodeError:
continue
return rows
def _append_jsonl(path: Path, record: dict) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, "a", encoding="utf-8") as f:
f.write(json.dumps(record, ensure_ascii=False) + "\n")
def normalize_mentions(mentions_raw: Any = None, text: str = "") -> list[str]:
"""Extract and resolve @mentions from explicit list + text body."""
raw: list[str] = []
if isinstance(mentions_raw, str):
raw.extend(m.strip() for m in mentions_raw.split(",") if m.strip())
elif isinstance(mentions_raw, list):
raw.extend(str(m).strip() for m in mentions_raw if str(m).strip())
if text:
raw.extend(m.group(1).strip() for m in MENTION_RE.finditer(text))
seen: set[str] = set()
resolved: list[str] = []
for item in raw:
key = item.strip().lstrip("@").lower()
agent_id = MENTION_ALIASES.get(key)
if agent_id and agent_id not in seen:
resolved.append(agent_id)
seen.add(agent_id)
return resolved
def targets_for(sender_id: str, mentions: list[str]) -> list[str]:
"""Decide which agents should receive a message."""
if sender_id == "amian":
# human: no @mention → broadcast to all agents
if not mentions or ALL_TOKEN in mentions:
return list(REPLY_AGENT_IDS)
return [m for m in mentions if m in REPLY_AGENT_IDS]
else:
# agent → agent: only explicit @mentions
if not mentions or ALL_TOKEN in mentions:
return []
return [m for m in mentions if m in REPLY_AGENT_IDS and m != sender_id]
def append_group_message(
sender_id: str,
text: str,
*,
mentions: list[str] | None = None,
reply_to: str | None = None,
message_type: str = "chat",
) -> dict:
"""Write a message to the group JSONL and return the record."""
member = ROSTER_BY_ID.get(sender_id)
if not member:
raise ValueError(f"unknown sender_id: {sender_id}")
text = str(text or "").strip()
if not text:
raise ValueError("text required")
mentions = mentions or normalize_mentions(text=text)
targets = targets_for(sender_id, mentions)
record = {
"id": _make_id("grp"),
"ts": _now_iso(),
"sender_id": sender_id,
"text": text,
"mentions": mentions,
"targets": targets,
"reply_to": reply_to,
"message_type": message_type,
}
_append_jsonl(GROUP_FILE, record)
return record
def read_since(since_ts: str | None = None, limit: int = 100) -> list[dict]:
"""Return messages newer than since_ts, up to limit."""
rows = _read_jsonl(GROUP_FILE)
if since_ts:
rows = [r for r in rows if r.get("ts", "") > since_ts]
return rows[:limit]
return rows[-limit:]
def roster() -> list[dict]:
return [dict(m) for m in ROSTER]
def agent_status(session_exists: Callable[[str], bool] | None = None) -> dict:
"""Return online/offline status for each agent."""
agents: dict[str, Any] = {}
for m in ROSTER:
if m.get("kind") != "agent":
continue
aid = m["id"]
tmux = m.get("tmux")
online = bool(tmux and session_exists and session_exists(tmux))
agents[aid] = {"state": "online" if online else "offline", "tmux": tmux}
return {"agents": agents}
def context_lines(limit: int = 20) -> list[str]:
"""Last N messages formatted for fan-out context injection."""
records = read_since(limit=limit)
lines: list[str] = []
for rec in records:
sender_id = rec.get("sender_id", "")
member = ROSTER_BY_ID.get(sender_id) or {}
name = member.get("display_name") or sender_id
ts = str(rec.get("ts", ""))[11:16]
text = str(rec.get("text", "")).replace("\n", " ")
if len(text) > 180:
text = text[:177] + "..."
lines.append(f"[{ts}] {name}: {text}")
if len(lines) >= limit:
break
return lines