帮我构思一个项目: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. 结论

可以拿到句柄和内容,但要分层处理:

  1. 窗口句柄:✅ 100% 可以获取
  2. 文本内容
    • 静态渲染文本:✅ UIA 可以获取
    • Monaco Editor:⚠️ 需要 CDP JavaScript 注入
    • AI 对话内容:⚠️ 需要 CDP 或 DOM 选择器
  3. 最佳方案:CDP 远程调试 + UIA 作为 fallback

关键前提:需要启动 Electron 应用时加上 --remote-debugging-port=9222 参数

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐