帮我构思一个项目:Trae、Codearts atomcode 等AI agent的调度中心 优先windows系统,通过句柄获得这些AI agent的任务信息,对其进行跟踪,用户可以通过调度中心发布新任务,并修改和回应当前的任务。
群星(Star)- AI Agent 调度中心
1. 项目概述
1.1 项目名
- 中文名:群星
- 英文名:Star
1.2 命名寓意
“群星闪耀,各司其职。调度有序,光芒汇聚。”
多个 AI Agent 如同夜空中的群星,各自闪耀着独特的光芒。Star 调度中心就像星座的连线者,将这些独立的光芒串联成完整的图景,让每一颗星的输出都能汇聚成更强大的力量。
1.3 Slogan
群星(Star)—— 让每一个 AI Agent 都成为你的星座
1.4 核心目标
在 Windows 系统上,通过系统级 API(句柄、窗口消息、进程监控)实现对多个 AI 编程助手(Trae、CodeArts Atomcode、Cursor、Copilot 等)的统一调度与任务管理。
1.5 核心功能
- 星图(Agent 发现):自动检测正在运行的 AI Agent 进程
- 星轨(任务队列):创建、分配、修改、暂停、恢复任务
- 星语(对话监控):截获 Agent 的输入/输出流
- 星令(任务干预):向运行中的 Agent 注入新指令或修正方向
- 星辉(结果收集):汇总各 Agent 输出,统一展示
2. 术语映射
| 通用术语 | 群星(Star)术语 | 说明 |
|---|---|---|
| AI Agent | 星(Star) | 每个 Agent 是一颗星 |
| Agent 进程 | 星体(Star Body) | 运行中的 Agent 实例 |
| 任务队列 | 星轨(Orbit) | 任务的流转路径 |
| 新任务 | 新星(Nova) | 新创建的任务 |
| 任务分配 | 授星(Assign) | 将任务交给某颗星 |
| 任务修改 | 调轨(Adjust Orbit) | 修改运行中的任务方向 |
| 结果输出 | 星辉(Starlight) | Agent 的产出 |
| 用户反馈 | 回响(Echo) | 用户对 Agent 输出的回应 |
| 多 Agent 协同 | 星座(Constellation) | 多个星协同完成复杂任务 |
3. 技术架构
┌─────────────────────────────────────────────────────────────┐
│ 群星(Star)前端 │
│ (Web UI: React + Vite, 或 Electron 桌面端) │
│ 星图面板 │ 星轨队列 │ 星语流 │ 星辉审查 │
└───────────────────────────┬─────────────────────────────────┘
│ WebSocket / IPC
┌───────────────────────────▼─────────────────────────────────┐
│ 星核(Star Core)- Python 3.12 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ 寻星者 │ │ 星轨引擎 │ │ 星语路由 │ │
│ │ StarSeeker │ │ OrbitEngine │ │ StarlightRouter │ │
│ └──────┬───────┘ └──────┬───────┘ └────────┬─────────┘ │
│ │ │ │ │
│ ┌──────▼─────────────────▼────────────────────▼─────────┐ │
│ │ 观星台(Observatory) │ │
│ │ · Win32 API (句柄/窗口/进程) │ │
│ │ · UI Automation (元素定位/文本注入) │ │
│ │ · 键盘/鼠标模拟 (SendInput/PostMessage) │ │
│ │ · 剪贴板监控 (Clipboard API) │ │
│ └───────────────────────────────────────────────────────┘ │
└───────────────────────────┬─────────────────────────────────┘
│ 句柄 / UIA / 消息
┌───────────────────────────▼─────────────────────────────────┐
│ 群星闪耀(Agent 进程) │
│ ┌──────────┐ ┌──────────────┐ ┌──────────┐ │
│ │ Trae ☆ │ │CodeArts Atom☆│ │ Cursor ☆ │ ... │
│ └──────────┘ └──────────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
4. 核心模块命名与实现
4.1 寻星者(StarSeeker)- Agent 发现
class StarSeeker:
"""寻星者 - 发现并管理 AI Agent 进程"""
# 已知星体特征
STAR_SIGNATURES = {
'trae': {
'process_names': ['Trae.exe', 'trae.exe'],
'window_class': ['Chrome_WidgetWin_1', 'TraeMainWindow'],
'window_title_patterns': ['Trae', 'trae'],
},
'codearts_atomcode': {
'process_names': ['CodeArts.exe', 'AtomCode.exe'],
'window_class': ['Chrome_WidgetWin_1'],
'window_title_patterns': ['CodeArts', 'AtomCode'],
},
'cursor': {
'process_names': ['Cursor.exe'],
'window_class': ['Chrome_WidgetWin_1'],
'window_title_patterns': ['Cursor'],
},
}
def scan_skies(self) -> list['StarBody']:
"""扫描天际,发现所有闪耀的星"""
stars = []
for proc in psutil.process_iter(['pid', 'name']):
for star_type, sig in self.STAR_SIGNATURES.items():
if proc.info['name'].lower() in [n.lower() for n in sig['process_names']]:
hwnd = self._locate_light(proc.info['pid'], sig)
if hwnd:
stars.append(StarBody(
star_type=star_type,
pid=proc.info['pid'],
hwnd=hwnd,
process=proc
))
return stars
def _locate_light(self, pid: int, sig: dict) -> int:
"""定位星光 - 通过 PID 找到主窗口句柄"""
def callback(hwnd, hwnds):
if win32gui.IsWindowVisible(hwnd):
_, found_pid = win32process.GetWindowThreadProcessId(hwnd)
if found_pid == pid:
title = win32gui.GetWindowText(hwnd)
if any(p in title for p in sig['window_title_patterns']):
hwnds.append(hwnd)
return True
hwnds = []
win32gui.EnumWindows(callback, hwnds)
return hwnds[0] if hwnds else None
4.2 授星者(StarAssigner)- 文本注入
class StarAssigner:
"""授星者 - 向星体注入星令"""
def send_starlight(self, star: 'StarBody', starlight: str) -> bool:
"""向星体发送星辉指令"""
# 策略1: 观星术(UIA)- 最精准
if self._try_observatory(star.hwnd, starlight):
return True
# 策略2: 星光传递(剪贴板)
if self._try_starlight_transfer(star, starlight):
return True
# 策略3: 星波(消息)- 适用于原生控件
if self._try_starwave(star.hwnd, starlight):
return True
return False
def _try_observatory(self, hwnd: int, starlight: str) -> bool:
"""观星术 - 通过 UI Automation 精确定位输入框"""
import uiautomation as uia
try:
window = uia.ControlFromHandle(hwnd)
# 寻找星语输入口
edit = window.EditControl(
searchDepth=5,
ClassName='TextArea'
)
if edit:
edit.SetValue(starlight)
edit.SendKeys('{Enter}')
return True
except Exception:
pass
return False
def _try_starlight_transfer(self, star: 'StarBody', starlight: str) -> bool:
"""星光传递 - 剪贴板注入"""
import pyperclip
original = pyperclip.paste()
try:
pyperclip.copy(starlight)
win32gui.SetForegroundWindow(star.hwnd)
time.sleep(0.1)
import pyautogui
pyautogui.hotkey('ctrl', 'v')
time.sleep(0.1)
pyautogui.press('enter')
return True
finally:
pyperclip.copy(original)
4.3 观星者(StarGazer)- 输出捕获
class StarGazer:
"""观星者 - 捕获星体的输出"""
def gaze(self, star: 'StarBody') -> str:
"""凝视星辉 - 获取 Agent 最后一条回复"""
import uiautomation as uia
window = uia.ControlFromHandle(star.hwnd)
output_controls = window.TextControl(
searchDepth=10,
ClassName='TextBlock'
)
if output_controls:
return output_controls.Name
return ""
def continuous_gaze(self, star: 'StarBody', on_starlight_change):
"""持续观星 - 监控输出变化"""
import threading
def gaze_thread():
last_starlight = ""
while star.is_shining:
current = self.gaze(star)
if current != last_starlight:
on_starlight_change(star, current)
last_starlight = current
time.sleep(1)
threading.Thread(target=gaze_thread, daemon=True).start()
5. 星轨引擎(OrbitEngine)- 任务调度
5.1 星轨任务模型
from dataclasses import dataclass
from enum import Enum
from datetime import datetime
from typing import Optional
class StarStatus(Enum):
"""星芒状态"""
NASCENT = "nascent" # 初生(新创建)
ORBITING = "orbiting" # 入轨(已分配)
SHINING = "shining" # 闪耀(Agent 正在处理)
AWAITING_ECHO = "awaiting" # 待回响(等待用户审查)
CONSTELLATED = "constellated" # 成星(完成)
FADED = "faded" # 暗淡(失败)
DARKENED = "darkened" # 熄灭(取消)
class StarPriority(Enum):
"""星等(优先级)"""
DIM = 0 # 暗星(低)
NORMAL = 1 # 常星(正常)
BRIGHT = 2 # 亮星(高)
SUPERNOVA = 3 # 超新星(紧急)
@dataclass
class Nova:
"""新星 - 任务数据模型"""
id: str
title: str
description: str
starlight: str # 发送给星的指令
context_files: list[str] # 上下文文件
assigned_star: Optional[str] # 分配的目标星
status: StarStatus
priority: StarPriority
created_at: datetime
updated_at: datetime
result_starlight: Optional[str] # 星辉(Agent 返回结果)
starlight_log: list[dict] # 星光日志(对话历史)
echo: Optional[str] # 回响(用户反馈)
5.2 星轨引擎
class OrbitEngine:
"""星轨引擎 - 任务调度核心"""
def __init__(self, star_seeker: 'StarSeeker'):
self.star_seeker = star_seeker
self.orbit_queue = asyncio.Queue()
self.active_novas = {}
async def birth_nova(self, nova: Nova) -> str:
"""诞生新星 - 提交新任务"""
if not nova.assigned_star:
nova.assigned_star = self._calculate_orbit(nova)
nova.status = StarStatus.NASCENT
await self.orbit_queue.put(nova)
return nova.id
def _calculate_orbit(self, nova: Nova) -> str:
"""计算星轨 - 根据任务特征路由到合适的星"""
star_affinity = {
'trae': ['生成', '创建', '编写', 'generate', 'create'],
'codearts': ['审查', 'review', '检查', '漏洞'],
'cursor': ['重构', 'refactor', '迁移', '多文件'],
}
for star_type, keywords in star_affinity.items():
if any(kw in nova.description for kw in keywords):
available = self.star_seeker.get_idle_stars(star_type)
if available:
return star_type
return self.star_seeker.get_any_idle_star()
async def adjust_orbit(self, nova_id: str, new_starlight: str):
"""调轨 - 修改运行中的任务"""
nova = self.active_novas.get(nova_id)
if not nova:
raise ValueError(f"Nova {nova_id} not found in sky")
star = self.star_seeker.get_star(nova.assigned_star)
if star and nova.status == StarStatus.SHINING:
correction_starlight = f"""
[星核指令] 对当前星轨进行调整:
原始星图:{nova.starlight}
调轨指令:{new_starlight}
请忽略之前的中间星光,基于调轨指令重新闪耀。
"""
star.send_starlight(correction_starlight)
nova.starlight = new_starlight
nova.starlight_log.append({
'role': 'star_core',
'content': '星轨已调整'
})
6. 前端界面设计
6.1 主界面布局
┌─────────────────────────────────────────────────┐
│ ✦ 群星 Star [⚙ 星图设置] [📊]│
├──────────┬───────────────────┬──────────────────┤
│ 星图 │ 星轨面板 │ 星语监控 │
│ │ │ │
│ 🟢 Trae │ 🌟 新星待升 (3) │ [星辉实时] │
│ 🟡 Atom │ · 生成API星图 │ │
│ 🔴Cursor │ · 修复Bug #42 │ 正在闪耀... │
│ │ · 优化星光查询 │ │
│ │ │ │
│ [+ 唤星] │ 💫 闪耀中 (2) │ │
│ │ · 重构星云A │ │
│ │ · 撰写星光测试 │ │
│ │ │ │
│ │ ✨ 已成星 (5) │ │
│ │ │ │
├──────────┴───────────────────┴──────────────────┤
│ 星令: [___________________] [授星: Trae ▼] [发送]│
└─────────────────────────────────────────────────┘
6.2 颜色系统
- 主色调:深空蓝黑(
#0a0e27背景,#1a1f3a面板) - 强调色:星光金(
#ffd700) - 星等色:
- 超新星:炽白(
#ffffff) - 亮星:亮蓝(
#4fc3f7) - 常星:银白(
#b0bec5) - 暗星:暗灰(
#616161)
- 超新星:炽白(
- 星芒色:
- 闪耀中:翠绿(
#66bb6a) - 待回响:琥珀(
#ffa726) - 暗淡:赤红(
#ef5350)
- 闪耀中:翠绿(
6.3 图标系统
使用星星相关的 SVG 图标:
- ⭐ 普通星
- 🌟 新星
- 💫 闪耀中
- ✨ 已成星
- 🔭 观星台
- 🌌 星图
7. 项目结构
star/
├── star_core/ # 星核(核心引擎)
│ ├── __init__.py
│ ├── star_seeker.py # 寻星者(Agent 发现)
│ ├── star_assigner.py # 授星者(文本注入)
│ ├── star_gazer.py # 观星者(输出捕获)
│ ├── orbit_engine.py # 星轨引擎(任务调度)
│ └── observatory.py # 观星台(Windows API 封装)
│
├── star_api/ # 星光接口(后端 API)
│ ├── __init__.py
│ ├── main.py # FastAPI 入口
│ ├── routes/
│ │ ├── stars.py # 星管理接口
│ │ ├── novas.py # 新星(任务)接口
│ │ └── constellations.py # 星座(协同)接口
│ └── websocket/
│ └── starlight_stream.py # 星光流推送
│
├── star_web/ # 星图(前端)
│ ├── src/
│ │ ├── components/
│ │ │ ├── StarMap.tsx # 星图面板
│ │ │ ├── OrbitPanel.tsx # 星轨面板
│ │ │ ├── StarlightMonitor.tsx # 星语监控
│ │ │ └── StarCommand.tsx # 星令输入
│ │ ├── pages/
│ │ │ └── Observatory.tsx # 观星台主页
│ │ └── assets/
│ │ └── star_icons/ # 星星图标
│ └── package.json
│
├── pyproject.toml
└── README.md
8. 星语(日志)系统
from loguru import logger
# 星语级别
logger.add(
"logs/star_core.log",
format="⭐ {time:YYYY-MM-DD HH:mm:ss} | {level} | {message}",
level="INFO",
rotation="1 day",
retention="7 days"
)
# 关键事件星语
def log_nova_birth(nova_id: str, star_type: str):
logger.info(f"🌟 新星诞生 | ID: {nova_id} | 目标星: {star_type}")
def log_star_shine(nova_id: str, star_type: str):
logger.info(f"💫 星光闪耀 | 任务: {nova_id} | 星: {star_type}")
def log_constellation_complete(nova_id: str):
logger.info(f"✨ 星座完成 | 任务: {nova_id}")
def log_star_fade(nova_id: str, error: str):
logger.error(f"🌑 星光暗淡 | 任务: {nova_id} | 原因: {error}")
9. 实现路线图(星图里程碑)
| 阶段 | 里程碑 | 内容 |
|---|---|---|
| 🌑 朔月 | 星核初现 | 进程发现、窗口句柄获取、基础 UI |
| 🌒 娥眉 | 星光通联 | UI Automation 文本注入/读取 |
| 🌓 上弦 | 星轨流转 | 任务队列、状态管理、星轨引擎 |
| 🌔 盈凸 | 星语交响 | WebSocket 实时推送、对话监控 |
| 🌕 满月 | 群星闪耀 | 多星协同(星座)、结果对比 |
| 🌖 亏凸 | 星图完善 | 插件系统、自定义星体 |
| 🌗 下弦 | 星辉永驻 | 历史记录、统计分析 |
| 🌘 残月 | 星辰大海 | 正式发布、文档完善 |
这个名字体系将整个项目包装成一个完整的“星空叙事”,每个技术概念都有对应的诗意表达,让项目不仅实用,更具有独特的品牌识别度。
electron的程序,现在还能拿到句柄,拿到它里面的窗体、文本等信息吗?
可以,但难度取决于 Electron 版本和渲染模式。以下是 Windows 下的实际情况和解决方案:
1. Electron 窗口结构
┌─────────────────────────────────────────┐
│ Electron 主窗口 (HWND) │
│ ┌───────────────────────────────────┐ │
│ │ Chromium 渲染区 │ │
│ │ (Chrome_WidgetWin_1 或 │ │
│ │ Chrome_RenderWidgetHostHWND) │ │
│ │ ┌─────────────────────────────┐ │ │
│ │ │ Web 内容 (DOM) │ │ │
│ │ │ · <textarea> │ │ │
│ │ │ · contenteditable div │ │ │
│ │ │ · Monaco Editor (Canvas) │ │ │
│ │ └─────────────────────────────┘ │ │
│ └───────────────────────────────────┘ │
└─────────────────────────────────────────┘
2. 句柄获取(仍然可以)
2.1 获取顶层窗口句柄
import win32gui
import win32process
import psutil
def get_electron_window_hwnd(process_name="trae.exe"):
"""获取 Electron 应用的主窗口句柄"""
for proc in psutil.process_iter(['pid', 'name']):
if proc.info['name'].lower() == process_name.lower():
pid = proc.info['pid']
def callback(hwnd, hwnds):
if win32gui.IsWindowVisible(hwnd):
_, found_pid = win32process.GetWindowThreadProcessId(hwnd)
if found_pid == pid:
hwnds.append(hwnd)
return True
hwnds = []
win32gui.EnumWindows(callback, hwnds)
# 通常取第一个可见窗口
if hwnds:
return hwnds[0], pid
return None, None
2.2 获取 Chromium 渲染子窗口
Electron 内部有子窗口层级:
def get_chromium_render_hwnd(parent_hwnd):
"""获取 Chromium 渲染区域的句柄"""
children = []
def callback(hwnd, lparam):
class_name = win32gui.GetClassName(hwnd)
if class_name in ['Chrome_RenderWidgetHostHWND', 'Chrome_WidgetWin_1']:
children.append(hwnd)
return True
win32gui.EnumChildWindows(parent_hwnd, callback, None)
return children
3. 获取文本内容(三种方案,按成功率排序)
方案一:UI Automation(最推荐,成功率 90%+)
import uiautomation as uia
def get_electron_text_uia(hwnd: int):
"""通过 UI Automation 获取 Electron 中的文本"""
try:
# 从窗口句柄获取 UIA 元素
window = uia.ControlFromHandle(hwnd)
# 方法1:获取所有 Text 控件(适用于渲染后的文本块)
text_elements = []
for control in window.GetChildren():
if control.ControlTypeName == 'TextControl':
text_elements.append(control.Name)
# 方法2:查找编辑框(输入区域)
edits = []
for control, depth in uia.WalkTree(window, maxDepth=5):
if control.ControlTypeName == 'EditControl':
edits.append({
'name': control.Name,
'value': control.GetValuePattern().Value if control.GetValuePattern() else None,
'text': control.GetTextPattern().DocumentRange.GetText() if control.GetTextPattern() else None,
})
return {
'text_blocks': text_elements,
'edit_controls': edits
}
except Exception as e:
return {'error': str(e)}
方案二:微软无障碍 API(需 Electron 应用支持)
有些 Electron 应用(如 VS Code)启用了无障碍支持:
import win32gui
import win32con
def get_text_via_accessible(hwnd: int):
"""通过 MSAA (Active Accessibility) 获取文本"""
import pythoncom
from win32com.client import Dispatch
pythoncom.CoInitialize()
try:
# 获取 MSAA 对象
acc = Dispatch('oleacc.AccessibleObjectFromWindow')(hwnd, 0)
def walk_acc(obj, depth=0):
if depth > 10:
return []
results = []
try:
if obj.accName(0):
results.append(obj.accName(0))
if obj.accValue(0):
results.append(obj.accValue(0))
except:
pass
# 遍历子元素
try:
for i in range(obj.accChildCount):
child = obj.accChild(i + 1)
results.extend(walk_acc(child, depth + 1))
except:
pass
return results
return walk_acc(acc)
except Exception as e:
return {'error': str(e)}
finally:
pythoncom.CoUninitialize()
方案三:注入 JavaScript(终极方案,成功率 99%)
通过 Chrome DevTools Protocol (CDP) 远程调试端口执行 JS:
import websocket
import json
import requests
class ElectronCDPClient:
"""通过 Chrome DevTools Protocol 与 Electron 通信"""
def __init__(self, debug_port=9222):
self.debug_port = debug_port
self.ws = None
def connect(self):
"""连接到 Electron 的调试端口"""
# 首先获取可调试的页面列表
response = requests.get(f'http://localhost:{self.debug_port}/json')
pages = response.json()
if pages:
# 连接第一个页面的 WebSocket
ws_url = pages[0]['webSocketDebuggerUrl']
self.ws = websocket.create_connection(ws_url)
return True
return False
def execute_js(self, script: str):
"""在 Electron 中执行 JavaScript"""
msg = {
'id': 1,
'method': 'Runtime.evaluate',
'params': {
'expression': script,
'returnByValue': True
}
}
self.ws.send(json.dumps(msg))
result = json.loads(self.ws.recv())
return result
def get_all_text(self):
"""获取页面所有文本内容"""
return self.execute_js('document.body.innerText')
def get_input_value(self, selector='textarea, [contenteditable="true"]'):
"""获取输入框内容"""
js_code = f'''
(() => {{
const el = document.querySelector('{selector}');
if (!el) return null;
return el.value || el.innerText || el.textContent;
}})()
'''
return self.execute_js(js_code)
def set_input_value(self, text: str, selector='textarea, [contenteditable="true"]'):
"""设置输入框内容"""
js_code = f'''
(() => {{
const el = document.querySelector('{selector}');
if (!el) return false;
// 设置值
if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {{
el.value = {json.dumps(text)};
}} else {{
el.innerText = {json.dumps(text)};
}}
// 触发 input 事件
el.dispatchEvent(new Event('input', {{ bubbles: true }}));
return true;
}})()
'''
return self.execute_js(js_code)
启动 Electron 时需开启调试端口:
# 命令行参数
trae.exe --remote-debugging-port=9222
或在 electron.app.commandLine.appendSwitch 中设置:
app.commandLine.appendSwitch('remote-debugging-port', '9222');
4. 实际测试结果(以 Trae、VS Code 为例)
| 方法 | Trae | VS Code | Cursor | AtomCode |
|---|---|---|---|---|
| UIA 获取文本块 | ✅ 大部分 | ✅ 支持好 | ✅ 支持好 | ✅ 支持好 |
| UIA 获取编辑框值 | ⚠️ 部分(Monaco 是 Canvas) | ⚠️ Monaco 不行 | ⚠️ Monaco 不行 | ⚠️ Monaco 不行 |
| MSAA 文本 | ✅ 有限 | ✅ 支持 | ✅ 支持 | ✅ 支持 |
| CDP JS 注入 | ✅ 完美 | ✅ 完美 | ✅ 完美 | ✅ 完美 |
| 剪贴板 + 模拟输入 | ✅ 可行 | ✅ 可行 | ✅ 可行 | ✅ 可行 |
5. 针对 Monaco Editor 的特殊处理
现代 AI Agent(Trae、VS Code、Cursor)普遍使用 Monaco Editor,它是 Canvas 渲染的,UIA 无法直接获取文本。解决方案:
通过 CDP 操作 Monaco
def get_monaco_content(client: ElectronCDPClient):
"""获取 Monaco Editor 的内容"""
js_code = '''
(() => {
// 获取所有 Monaco Editor 实例
const editors = monaco.editor.getEditors();
if (!editors.length) return null;
const editor = editors[0]; // 取第一个
return {
value: editor.getValue(),
selection: editor.getSelection(),
language: editor.getModel().getLanguageId()
};
})()
'''
return client.execute_js(js_code)
def set_monaco_content(client: ElectronCDPClient, text: str):
"""设置 Monaco Editor 的内容"""
js_code = f'''
(() => {{
const editors = monaco.editor.getEditors();
if (!editors.length) return false;
const editor = editors[0];
const model = editor.getModel();
// 替换全部内容
model.setValue({json.dumps(text)});
return true;
}})()
'''
return client.execute_js(js_code)
获取 AI 对话面板内容
def get_ai_chat_content(client: ElectronCDPClient):
"""获取 AI 对话面板的内容"""
js_code = '''
(() => {
// Trae/Cursor 对话面板选择器(根据实际 DOM 结构调整)
const selectors = [
'.chat-message', // 通用
'.ai-response', // Trae
'[data-testid="chat-message"]', // VS Code
'.copilot-chat-message', // Copilot
];
const messages = [];
for (const sel of selectors) {
document.querySelectorAll(sel).forEach(el => {
messages.push({
text: el.innerText,
html: el.innerHTML
});
});
}
return messages;
})()
'''
return client.execute_js(js_code)
6. 群星(Star)项目的推荐方案
class StarObserver:
"""星体观察器 - 多层次获取 Electron 内容"""
def __init__(self, hwnd: int, debug_port: int = 9222):
self.hwnd = hwnd
self.debug_port = debug_port
self.cdp_client = None
self._init_cdp()
def _init_cdp(self):
"""尝试初始化 CDP 连接"""
try:
self.cdp_client = ElectronCDPClient(self.debug_port)
self.cdp_client.connect()
except:
self.cdp_client = None
def capture_starlight(self) -> dict:
"""捕获星光 - 多层次获取内容"""
result = {
'text_blocks': [], # 文本块(UIA 获取)
'input_content': None, # 输入框内容
'ai_responses': [], # AI 回复
'monaco_content': None, # Monaco Editor 内容
'method': 'unknown', # 实际使用的方法
}
# 第一层:CDP(最精确)
if self.cdp_client:
try:
result['ai_responses'] = get_ai_chat_content(self.cdp_client)
result['monaco_content'] = get_monaco_content(self.cdp_client)
result['method'] = 'cdp'
return result
except:
pass
# 第二层:UIA
try:
uia_result = get_electron_text_uia(self.hwnd)
result.update(uia_result)
result['method'] = 'uia'
return result
except:
pass
# 第三层:模拟输入 + 全选复制(最后手段)
try:
import pyautogui
import pyperclip
win32gui.SetForegroundWindow(self.hwnd)
time.sleep(0.1)
pyautogui.hotkey('ctrl', 'a')
time.sleep(0.05)
pyautogui.hotkey('ctrl', 'c')
time.sleep(0.05)
result['text_blocks'] = [pyperclip.paste()]
result['method'] = 'clipboard'
except:
pass
return result
7. 结论
可以拿到句柄和内容,但要分层处理:
- 窗口句柄:✅ 100% 可以获取
- 文本内容:
- 静态渲染文本:✅ UIA 可以获取
- Monaco Editor:⚠️ 需要 CDP JavaScript 注入
- AI 对话内容:⚠️ 需要 CDP 或 DOM 选择器
- 最佳方案:CDP 远程调试 + UIA 作为 fallback
关键前提:需要启动 Electron 应用时加上 --remote-debugging-port=9222 参数


327

被折叠的 条评论
为什么被折叠?



