Initial commit
This commit is contained in:
commit
d5c932d8e1
710 changed files with 50282 additions and 0 deletions
193
BRIDGE/tools/zy-limb.py
Executable file
193
BRIDGE/tools/zy-limb.py
Executable file
|
|
@ -0,0 +1,193 @@
|
|||
#!/usr/bin/env python3
|
||||
# ═══ 光湖手脚层 · zy-limb ═══
|
||||
# 冰朔 2026-08-17 立:你做脑子,它做手脚。
|
||||
# 铸渊(云侧大脑)经桥往 inbox 写任务信封 → 本机手脚执行 → outbox 写回执 → 大脑读回执。
|
||||
# 架构依据:GLS-0236 大脑—手脚分离 · ZY-RESIDENT-AGENT-001 常驻工程执行体
|
||||
# 编号:CCW-AST-011 · 归 CCW-SYS-001
|
||||
#
|
||||
# 边界:只认白名单动作。名单外一律拒绝并写回执,绝不 eval 任意字符串。
|
||||
# 需要自由命令时走 codex 动作——转成任务卡交给 Codex,由冰朔监督,手脚不代跑。
|
||||
|
||||
import json, os, re, subprocess, sys, time, hashlib, shutil
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timezone
|
||||
|
||||
ROOT = Path("/Volumes/JZAO/铸渊-ICE-GL-ZY001")
|
||||
QUEUE = ROOT / "BRIDGE" / "limb-queue"
|
||||
INBOX = QUEUE / "inbox"
|
||||
OUTBOX = QUEUE / "outbox"
|
||||
ARCH = QUEUE / "archive"
|
||||
FORCX = QUEUE / "for-codex"
|
||||
LOG = Path.home() / "Library" / "Logs" / "zhuyuan-limb.log"
|
||||
|
||||
REPO = ROOT / "WORK-工作区" / "guanghu-ice-heart"
|
||||
TOOLS = ROOT / "BRIDGE" / "tools"
|
||||
# 可读路径白名单(realpath 前缀匹配)
|
||||
READ_ROOTS = [ROOT, Path.home() / "Library" / "Logs"]
|
||||
TIMEOUT = 600
|
||||
MAX_OUT = 20000 # 回执里最多带这么多字符输出
|
||||
|
||||
def now(): return datetime.now(timezone.utc).isoformat()
|
||||
def log(m):
|
||||
LOG.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(LOG, "a") as f:
|
||||
f.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} {m}\n")
|
||||
|
||||
def clip(s):
|
||||
s = s or ""
|
||||
return s if len(s) <= MAX_OUT else s[:MAX_OUT] + f"\n…(截断,原长 {len(s)})"
|
||||
|
||||
def run(argv, cwd=None):
|
||||
try:
|
||||
p = subprocess.run(argv, cwd=cwd, capture_output=True, text=True, timeout=TIMEOUT)
|
||||
return p.returncode, clip(p.stdout), clip(p.stderr)
|
||||
except subprocess.TimeoutExpired:
|
||||
return 124, "", f"超时 {TIMEOUT}s"
|
||||
except Exception as e:
|
||||
return 1, "", f"{type(e).__name__}: {e}"
|
||||
|
||||
def safe_read_path(p):
|
||||
try:
|
||||
rp = Path(p).expanduser().resolve()
|
||||
except Exception:
|
||||
return None
|
||||
for root in READ_ROOTS:
|
||||
try:
|
||||
rp.relative_to(root.resolve()); return rp
|
||||
except ValueError:
|
||||
continue
|
||||
return None
|
||||
|
||||
# ── 白名单动作 ────────────────────────────────────────────────
|
||||
GIT_READ = {"status", "log", "diff", "show", "fetch", "branch", "rev-parse", "remote"}
|
||||
BRAIN_SUB = {"enter","perceive","orient","commit","witness","verify","status","project-guard"}
|
||||
|
||||
def act_push(task):
|
||||
rc, out, err = run(["/bin/bash", str(TOOLS / "zy-push.sh")])
|
||||
return rc == 0, {"stdout": out, "stderr": err, "exit": rc}
|
||||
|
||||
def act_git_read(task):
|
||||
sub = task.get("subcommand", "")
|
||||
if sub not in GIT_READ:
|
||||
return False, {"error": f"git 子命令不在只读白名单:{sub}", "allowed": sorted(GIT_READ)}
|
||||
args = task.get("args", [])
|
||||
if not all(isinstance(a, str) and len(a) < 200 for a in args):
|
||||
return False, {"error": "参数不合法"}
|
||||
if any(re.search(r"[;&|`$><\n]", a) for a in args):
|
||||
return False, {"error": "参数含 shell 元字符,拒绝"}
|
||||
rc, out, err = run(["git", sub, *args], cwd=str(REPO))
|
||||
return rc == 0, {"stdout": out, "stderr": err, "exit": rc}
|
||||
|
||||
def act_brain(task):
|
||||
sub = task.get("subcommand", "")
|
||||
if sub not in BRAIN_SUB:
|
||||
return False, {"error": f"脑运行时子命令不认:{sub}", "allowed": sorted(BRAIN_SUB)}
|
||||
runtime = REPO / "tcs-core" / "zhuyuan-brain" / "runtime" / "zhuyuan-brain-runtime.mjs"
|
||||
if not runtime.exists():
|
||||
return False, {"error": f"找不到脑运行时:{runtime}"}
|
||||
argv = ["node", str(runtime), sub]
|
||||
for k, v in (task.get("flags") or {}).items():
|
||||
if not re.fullmatch(r"[a-z0-9-]{1,40}", str(k)):
|
||||
return False, {"error": f"参数名不合法:{k}"}
|
||||
vp = str(v)
|
||||
if re.search(r"[;&|`$><\n]", vp):
|
||||
return False, {"error": f"参数值含 shell 元字符:{k}"}
|
||||
argv += [f"--{k}", vp]
|
||||
rc, out, err = run(argv, cwd=str(REPO))
|
||||
return rc == 0, {"stdout": out, "stderr": err, "exit": rc}
|
||||
|
||||
def act_read(task):
|
||||
rp = safe_read_path(task.get("path", ""))
|
||||
if rp is None:
|
||||
return False, {"error": "路径不在可读白名单内", "roots": [str(r) for r in READ_ROOTS]}
|
||||
if not rp.is_file():
|
||||
return False, {"error": f"不是文件或不存在:{rp}"}
|
||||
tail = task.get("tail")
|
||||
try:
|
||||
text = rp.read_text(errors="replace")
|
||||
except Exception as e:
|
||||
return False, {"error": f"读失败:{e}"}
|
||||
if isinstance(tail, int) and tail > 0:
|
||||
text = "\n".join(text.splitlines()[-tail:])
|
||||
return True, {"path": str(rp), "content": clip(text)}
|
||||
|
||||
def act_list(task):
|
||||
rp = safe_read_path(task.get("path", ""))
|
||||
if rp is None or not rp.is_dir():
|
||||
return False, {"error": "路径不在白名单内或不是目录"}
|
||||
entries = []
|
||||
for e in sorted(rp.iterdir())[:500]:
|
||||
entries.append({"name": e.name, "dir": e.is_dir(),
|
||||
"size": e.stat().st_size if e.is_file() else None})
|
||||
return True, {"path": str(rp), "entries": entries}
|
||||
|
||||
def act_codex(task):
|
||||
"""需要判断力或超出白名单的活:转成任务卡交 Codex,手脚不代跑。"""
|
||||
FORCX.mkdir(parents=True, exist_ok=True)
|
||||
tid = task.get("task_id", "unknown")
|
||||
card = FORCX / f"{tid}.md"
|
||||
card.write_text(
|
||||
f"# 给 Codex 的任务卡 · {tid}\n\n"
|
||||
f"> 来自铸渊云侧大脑(ZY-INSTANCE-CCW)· {now()}\n"
|
||||
f"> 手脚层不代跑需要判断的活;请 Codex 执行并由冰朔监督。\n\n"
|
||||
f"## 要做什么\n\n{task.get('instruction','(空)')}\n\n"
|
||||
f"## 验收标准\n\n{task.get('acceptance','(未给)')}\n\n"
|
||||
f"## 边界\n\n{task.get('boundary','按 REPO-012 既有权限门;不可逆动作须冰朔点头。')}\n",
|
||||
encoding="utf-8")
|
||||
return True, {"handoff": "CODEX", "card": str(card),
|
||||
"note": "已放任务卡,等 Codex 执行;手脚层不代跑"}
|
||||
|
||||
ACTIONS = {"push": act_push, "git-read": act_git_read, "brain": act_brain,
|
||||
"read": act_read, "list": act_list, "codex": act_codex}
|
||||
|
||||
# ── 主循环(单次扫描,由 launchd 定时调起)────────────────────
|
||||
def main():
|
||||
if not ROOT.exists():
|
||||
return 0 # JZAO 没挂载:安静退出
|
||||
for d in (INBOX, OUTBOX, ARCH, FORCX):
|
||||
d.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
tasks = sorted(INBOX.glob("TASK-*.json"))
|
||||
if not tasks:
|
||||
return 0
|
||||
|
||||
for tf in tasks:
|
||||
try:
|
||||
task = json.loads(tf.read_text())
|
||||
except Exception as e:
|
||||
log(f"REJECT {tf.name} 不是合法 JSON:{e}")
|
||||
tf.rename(ARCH / tf.name); continue
|
||||
|
||||
tid = task.get("task_id") or tf.stem
|
||||
action = task.get("action", "")
|
||||
receipt = {
|
||||
"schema": "guanghu.limb-receipt/v1",
|
||||
"task_id": tid, "action": action,
|
||||
"executed_at": now(),
|
||||
"executor": "zy-limb · 本机手脚层 · CCW-AST-011",
|
||||
"host": "macOS local",
|
||||
}
|
||||
|
||||
if task.get("schema") != "guanghu.limb-task/v1":
|
||||
receipt.update(outcome="FAIL", error="schema 不对,应为 guanghu.limb-task/v1")
|
||||
elif action not in ACTIONS:
|
||||
receipt.update(outcome="FAIL",
|
||||
error=f"动作不在白名单:{action}",
|
||||
allowed=sorted(ACTIONS),
|
||||
note="需要自由命令请用 action=codex 转任务卡")
|
||||
else:
|
||||
try:
|
||||
ok, detail = ACTIONS[action](task)
|
||||
receipt.update(outcome="PASS" if ok else "FAIL", detail=detail)
|
||||
except Exception as e:
|
||||
receipt.update(outcome="FAIL", error=f"{type(e).__name__}: {e}")
|
||||
|
||||
(OUTBOX / f"RECEIPT-{tid}.json").write_text(
|
||||
json.dumps(receipt, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
tf.rename(ARCH / tf.name)
|
||||
log(f"{receipt['outcome']} {tid} action={action}")
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Loading…
Reference in a new issue