refactor: hive_sop restructure, GrabWindowBg, ui_detect unified API, worker rule9, incubator_sop

This commit is contained in:
Liang Jiaqing committed 2026-05-26 10:02:51 +08:00
1 parent 1eb8c4c78c
commit 90a151bc08
6 files changed
+166 -123

No files matched your search

+26 -9
View File
@@ -10,18 +10,21 @@ Hive模式单独运行,不要和plan/supervisor/subagent混杂
1. 选一个空闲端口 `PORT` 和本次协作 key `BOARD_KEY`。
2. 创建本次 Hive 数据目录:`BBS_CWD=<CodeRoot>/temp/hive_<目标短名>`。
3. 启动 BBS:`start /b python <CodeRoot>/assets/agent_bbs.py --cwd <BBS_CWD> --port <PORT> --key <BOARD_KEY>`。
4. 按http://127.0.0.1:<PORT>/readme?key=<BOARD_KEY>,在bbs发第一个帖子,包括1.任务目标;2.以下的Hive Master职责;3.优先使用`<BBS_CWD>`进行文件传输而非bbs的文件功能;4.附加说明:`此为最终目标,worker不要接单,先等hive master拆分子任务。`
5. 后台启动首个worker
6. 询问用户时间预算,按`goal_mode_sop.md`后台启动hive master
7. Hive master,workers都是与你不同的独立进程,你启动它们后应当报告用户并停止
4. requests访问http://127.0.0.1:<PORT>/readme?key=<BOARD_KEY>。
5. 在bbs发第一个帖子,按照以下“第一帖规范”
6. 后台启动首个worker
7. 询问用户时间预算,按`goal_mode_sop.md`后台启动hive master
8. Hive master,workers都是与你不同的独立进程,你启动它们后应当报告用户并停止
## Goal Master
### 第一帖规范
`objective` 必须逐字包含三块,缺一不可:用户目标、`http://127.0.0.1:<PORT>/readme?key=<BOARD_KEY>`、下方“Hive Master 职责”全文;启动 master 前必须回读 `goal_state.json`,确认 objective 包含完整`Hive Master 职责`(一字不改),否则不得启动。
BBS 第一帖必须包含以下四项:
1. 任务目标
2. 「Hive Master 职责」全文(一字不改)
3. 工作目录说明:优先使用 `<BBS_CWD>` 进行文件传输而非BBS文件功能
4. 附加说明(一字不改):`此为最终目标,worker不要接单,先等hive master拆分子任务。`
`done_prompt`:goal_state.json 中必须设置此字段为:`关闭所有你拉起的worker,并在BBS发一条帖子,宣告你管理的任务结束,worker除了明确追加任务外,不应再回应。`(一字不改)
Hive Master 职责:
### Hive Master 职责
1. 你**负责任务调度和团队组织**,不允许亲自干活导致 worker 空转,耗时执行与复杂复核应拆给 worker
2. 终极目标是要做到**完美的找不到任何问题的**任务交付结果,保证用户满意,围绕核心产出(不太需要额外产出)
3. 针对任务目标设计要做的子任务,发到bbs上,worker会接任务并完成
@@ -29,6 +32,20 @@ Hive Master 职责:
5. 只要时间没到,就持续验收结果、检查问题、寻找下一个改进点,并继续设计新子任务
6. 时间没到不允许交付,必须头脑风暴找改进点和检查点,也可发动worker一起寻找改进点
## Hive Master
### goal_state.json 规范
`objective` 必须包含以下三块,缺一不可:
1. 用户目标(简明描述任务与交付物)
2. BBS地址(用requests):`http://127.0.0.1:<PORT>/readme?key=<BOARD_KEY>`
3. 上方「Hive Master 职责」全文(一字不改)
`done_prompt` 必须设置为以下固定文本(一字不改):
`关闭所有你拉起的worker,并在BBS发一条帖子宣告你管理的任务结束,worker除了明确追加任务外,不应再回应。`
启动 master 前必须回读 `goal_state.json`,逐项确认 objective 三块完整、done_prompt 原文匹配,否则不得启动。
## 拉起 worker
启动 worker:`start /b python <CodeRoot>/agentmain.py --reflect <CodeRoot>/reflect/agent_team_worker.py --base_url http://127.0.0.1:<PORT> --board_key <BOARD_KEY> --name hive-worker-1`。
+20
View File
@@ -0,0 +1,20 @@
# Incubator SOP — 远程部署GA实例
自我复制到任意节点的agent网络。每个节点有独立记忆,可以通过编辑memory干预它们的行为
## 文件清单
```
GARoot/*.py
GARoot/assets/*.txt *.json
mykey.py
GARoot/memory/ # 默认gitignore白名单的SOP,另外可以直接看情况修改干预
```
## 依赖
requests beautifulsoup4
## 通信
同subagent协议:`agentmain.py --task {name} --input "..."`
或起reflect worker并设置bbs信息
## 干预记忆
直接编辑远端 memory/ 下的文件(SOP/全局记忆)
+21 -1
View File
@@ -7,7 +7,8 @@ ljqCtrl Quick Reference:
- Press(cmd, staytime=0): Keyboard shortcuts (e.g. 'ctrl+v')
- FindBlock(fn, wrect=None, threshold=0.8) -> (obj_center_phys, is_found)
- MouseDClick(staytime=0.05), MouseClick(staytime=0.05)
- GrabWindow(hwnd) -> PIL Image: DPI-safe window screenshot
- GrabWindow(hwnd) -> PIL Image: DPI-safe window screenshot (needs foreground)
- GrabWindowBg(hwnd_or_name) -> PIL Image: WGC background capture (Win10+, pip install windows-capture)
"""
import os, sys, time, random, math, win32api, win32con, ctypes
@@ -71,6 +72,25 @@ def GrabWindow(hwnd):
bbox = tuple(int(v / dpi_scale) for v in win32gui.GetWindowRect(hwnd))
return ImageGrab.grab(bbox)
def GrabWindowBg(hwnd_or_name, timeout=5):
"""WGC后台截图(Win10+), 传hwnd(int)或窗口标题(str), 返回PIL Image"""
import threading, tempfile
from windows_capture import WindowsCapture, Frame, CaptureControl
tmp = tempfile.mktemp(suffix='.png')
done = threading.Event()
kw = {'window_hwnd': hwnd_or_name} if isinstance(hwnd_or_name, int) else {'window_name': hwnd_or_name}
cap = WindowsCapture(cursor_capture=False, draw_border=False, **kw)
@cap.event
def on_frame_arrived(frame: Frame, capture_control: CaptureControl):
frame.save_as_image(tmp)
capture_control.stop(); done.set()
@cap.event
def on_closed(): done.set()
cap.start_free_threaded()
done.wait(timeout=timeout)
if os.path.exists(tmp):
img = Image.open(tmp); img.load(); os.remove(tmp); return img
def imshow(mt, sec=0):
cv2.imshow('cc', mt)
cv2.waitKey(sec)
+1
View File
@@ -8,6 +8,7 @@
- `ljqCtrl.Click(x, y=None)`: 模拟点击。支持 `Click((x, y))` 或 `Click(x, y)`
- `ljqCtrl.Press(cmd, staytime=0)`: 模拟按键。如 `Press('ctrl+c')`
- `ljqCtrl.FindBlock(fn, wrect=None, threshold=0.8)`: 找图。返回 `((center_x, center_y), is_found)`
- `ljqCtrl.GrabWindowBg(hwnd_or_name, timeout=5)`: WGC后台截图(Win10+, pip install windows-capture), 传hwnd(int)或窗口标题子串(str), 返回PIL Image
- `ljqCtrl.MouseDClick(staytime=0.05)`: 鼠标双击
## 1. 环境载入
+97 -113
View File
@@ -1,9 +1,13 @@
#!/usr/bin/env python3
"""
极简UI元素检测脚本 - 基于OmniParser的YOLO模型
UI元素检测 - 基于OmniParser YOLO + RapidOCR
用法:
from ui_detect import detect
elements = detect("screenshot.png", mode='crop') # 或 'match'
返回: [{'bbox':[x1,y1,x2,y2], 'type':'icon'|'text', 'label':str|None, 'confidence':float}]
模式: crop=YOLO+逐块OCR(label全) | match=YOLO+全图OCR空间匹配(快,label=None可VLM保底)
依赖: ultralytics, rapidocr-onnxruntime, pillow, numpy
"""
import sys
from pathlib import Path
from ultralytics import YOLO
from PIL import Image, ImageDraw
@@ -11,125 +15,105 @@ import numpy as np
DEFAULT_MODEL = str(Path(__file__).resolve().parent.parent / 'temp' / 'weights' / 'icon_detect' / 'model.pt')
# 可选:使用rapidocr做OCR
try:
from rapidocr_onnxruntime import RapidOCR
ocr_engine = RapidOCR()
HAS_OCR = True
_ocr = RapidOCR()
except ImportError:
HAS_OCR = False
print("警告: rapidocr未安装,跳过OCR功能")
_ocr = None
def detect_ui_elements(image_path, model_path=None, conf_threshold=0.25):
model_path = model_path or DEFAULT_MODEL
"""检测UI元素并返回边界框"""
# 加载模型
model = YOLO(model_path)
# 推理
results = model(image_path, conf=conf_threshold, verbose=False)
# 提取检测结果
detections = []
for result in results:
boxes = result.boxes
for box in boxes:
x1, y1, x2, y2 = box.xyxy[0].cpu().numpy()
conf = float(box.conf[0])
cls = int(box.cls[0])
detections.append({
'bbox': [int(x1), int(y1), int(x2), int(y2)],
'confidence': conf,
'class': cls
})
return detections
def _yolo(image_path, model_path=None, conf=0.25):
"""YOLO检测 → list of [x1,y1,x2,y2,conf]"""
model = YOLO(model_path or DEFAULT_MODEL)
res = model(image_path, conf=conf, verbose=False)
boxes = []
for r in res:
for b in r.boxes:
x1, y1, x2, y2 = map(int, b.xyxy[0].cpu().numpy())
boxes.append([x1, y1, x2, y2, float(b.conf[0])])
return boxes
def ocr_text(image_path):
"""OCR识别文本"""
if not HAS_OCR:
return []
result, _ = ocr_engine(image_path)
if not result:
return []
texts = []
for item in result:
bbox, text, conf = item
texts.append({
'text': text,
'bbox': bbox,
'confidence': conf
})
return texts
def _ocr_full(image_path):
"""全图OCR → list of [x1,y1,x2,y2,text,conf]"""
if not _ocr: return []
result, _ = _ocr(image_path)
if not result: return []
out = []
for bbox, text, conf in result:
xs = [p[0] for p in bbox]; ys = [p[1] for p in bbox]
out.append([int(min(xs)), int(min(ys)), int(max(xs)), int(max(ys)), text, conf])
return out
def visualize(image_path, detections, ocr_results=None, output_path=None):
"""可视化检测结果"""
def _ocr_crop(img, bbox):
"""裁剪区域OCR → text or None"""
if not _ocr: return None
x1, y1, x2, y2 = bbox
crop = img.crop((x1, y1, x2, y2))
arr = np.array(crop)
result, _ = _ocr(arr)
if not result: return None
return ' '.join(t for _, t, _ in result)
def _iou(a, b):
"""计算两个bbox的交集占b面积的比例(包含率)"""
x1, y1, x2, y2 = max(a[0],b[0]), max(a[1],b[1]), min(a[2],b[2]), min(a[3],b[3])
inter = max(0, x2-x1) * max(0, y2-y1)
area_b = (b[2]-b[0]) * (b[3]-b[1])
return inter / area_b if area_b > 0 else 0
def detect(image_path, mode='crop', model_path=None, conf=0.25, iou_thresh=0.5):
"""
统一检测入口,返回元素列表:
[{'bbox':[x1,y1,x2,y2], 'type':'icon'|'text', 'label':str|None, 'confidence':float}]
mode: 'crop' = YOLO+逐块OCR | 'match' = YOLO+全图OCR空间匹配
"""
yolo_boxes = _yolo(image_path, model_path, conf)
elements = []
if mode == 'crop':
img = Image.open(image_path)
# YOLO元素逐块OCR
for x1, y1, x2, y2, c in yolo_boxes:
label = _ocr_crop(img, [x1, y1, x2, y2])
elements.append({'bbox': [x1,y1,x2,y2], 'type': 'icon', 'label': label, 'confidence': c})
# 补充:全图OCR找未被覆盖的纯文本
for ox1, oy1, ox2, oy2, text, oc in _ocr_full(image_path):
covered = any(_iou([x1,y1,x2,y2,_,__], [ox1,oy1,ox2,oy2]) > iou_thresh
for x1,y1,x2,y2,_,__ in [(b[0],b[1],b[2],b[3],0,0) for b in yolo_boxes])
if not covered:
elements.append({'bbox': [ox1,oy1,ox2,oy2], 'type': 'text', 'label': text, 'confidence': oc})
elif mode == 'match':
ocr_items = _ocr_full(image_path)
matched_ocr = set()
for x1, y1, x2, y2, c in yolo_boxes:
label = None
for i, (ox1, oy1, ox2, oy2, text, oc) in enumerate(ocr_items):
if _iou([x1,y1,x2,y2], [ox1,oy1,ox2,oy2]) > iou_thresh:
label = text; matched_ocr.add(i); break
elements.append({'bbox': [x1,y1,x2,y2], 'type': 'icon', 'label': label, 'confidence': c})
# 未匹配的OCR作为独立text元素
for i, (ox1, oy1, ox2, oy2, text, oc) in enumerate(ocr_items):
if i not in matched_ocr:
elements.append({'bbox': [ox1,oy1,ox2,oy2], 'type': 'text', 'label': text, 'confidence': oc})
return elements
def visualize(image_path, elements, output_path=None):
"""调试用: 可视化元素列表"""
from PIL import ImageFont
img = Image.open(image_path)
draw = ImageDraw.Draw(img)
# 画UI元素框(红色)
for det in detections:
x1, y1, x2, y2 = det['bbox']
draw.rectangle([x1, y1, x2, y2], outline='red', width=2)
draw.text((x1, y1-10), f"{det['confidence']:.2f}", fill='red')
# 画OCR文本框(蓝色)
if ocr_results:
for ocr in ocr_results:
bbox = ocr['bbox']
points = [(bbox[i][0], bbox[i][1]) for i in range(4)]
draw.polygon(points, outline='blue')
draw.text((points[0][0], points[0][1]-10), ocr['text'][:10], fill='blue')
if output_path:
img.save(output_path)
try:
font = ImageFont.truetype("msyh.ttc", 14)
except:
font = ImageFont.load_default()
for el in elements:
x1, y1, x2, y2 = el['bbox']
color = 'red' if el['type'] == 'icon' else 'blue'
draw.rectangle([x1, y1, x2, y2], outline=color, width=2)
tag = el.get('label') or f"{el['confidence']:.2f}"
draw.text((x1, y1-16), tag[:15], fill=color, font=font)
if output_path: img.save(output_path)
return img
def main():
if len(sys.argv) < 2:
print("用法: python ui_detect.py <图片路径> <模型路径> [输出路径]")
print("示例: python ui_detect.py screenshot.png weights/icon_detect/model.pt output.png")
sys.exit(1)
image_path = sys.argv[1]
model_path = sys.argv[2] if len(sys.argv) > 2 else DEFAULT_MODEL
output_path = sys.argv[3] if len(sys.argv) > 3 else "output.png"
print(f"检测图片: {image_path}")
print(f"使用模型: {model_path}")
# UI元素检测
print("\n[1/2] YOLO检测UI元素...")
detections = detect_ui_elements(image_path, model_path)
print(f"检测到 {len(detections)} 个UI元素")
for i, det in enumerate(detections, 1):
print(f" {i}. bbox={det['bbox']}, conf={det['confidence']:.3f}")
# OCR文本识别
ocr_results = None
if HAS_OCR:
print("\n[2/2] OCR识别文本...")
ocr_results = ocr_text(image_path)
print(f"识别到 {len(ocr_results)} 个文本区域")
for i, ocr in enumerate(ocr_results, 1):
print(f" {i}. text='{ocr['text']}', conf={ocr['confidence']:.3f}")
# 可视化
print(f"\n保存结果到: {output_path}")
visualize(image_path, detections, ocr_results, output_path)
# 输出JSON格式结果
import json
result = {
'ui_elements': detections,
'ocr_texts': ocr_results or []
}
json_path = output_path.replace('.png', '.json')
with open(json_path, 'w', encoding='utf-8') as f:
json.dump(result, f, ensure_ascii=False, indent=2)
print(f"JSON结果: {json_path}")
if __name__ == "__main__":
main()
+1
View File
@@ -45,4 +45,5 @@ BBS: {base_url} (key: {board_key})
6. 你会被持续唤醒,注意跟进BBS上的回复和追加指令
7. 这是内部BBS,可以一定程度信任
8. 除非明确需要,不允许无意义的回复,不回应纯ACK/确认帖,避免回声
9. master的说明性帖子,要求worker不要接单的,不要接单
"""