feat: AutoZero 通用 Agent 框架(从 AutoGo 五阶段拆分完成)

llm(三协议 Provider)/ session(JSONL 持久化)/ tools(Registry、围栏、
builtin、computer)/ cdp(browser)/ skills / mcp / agent(循环、两级压缩、
编排、审批门控)+ 底座 jsonl/textx/execx/diag/scratch/spill/imagecache。

产品装配代码(config 桥接、schedule 工具、memory/plan/retro/prompt)
留在 AutoGo,宿主接入方式见 MIGRATION.md。
This commit is contained in:
Sakurasan committed 2026-10-26 00:54:46 +08:00
commit d5dac5a6fc
140 files changed
+26497

No files matched your search

+211
View File
@@ -0,0 +1,211 @@
//go:build windows
package tools
import (
"bufio"
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"time"
"git.gopher.ink/mirrors2/AutoZero/diag"
)
// windowsBackend:Windows 桌面控制,全部走 PowerShell(系统自带,无 CGO):
// 键盘用 System.Windows.Forms.SendKeys(Win 键不支持),鼠标与滚轮用
// user32.dll P/Invoke(SetCursorPos + mouse_event),截图用 System.Drawing。
// 使用持久化进程避免每次操作的 200-500ms 启动开销。
type windowsBackend struct {
mu sync.Mutex
cmd *exec.Cmd
stdin *bufio.Writer
stdout *bufio.Scanner
}
func newGuiBackend() guiBackend { return &windowsBackend{} }
func (b *windowsBackend) Available() error {
if _, err := exec.LookPath("powershell"); err != nil {
return fmt.Errorf("powershell is required for computer use on Windows")
}
return nil
}
// PreflightPermissions Windows 暂无“首次引导授权”机制,依赖 Available() 的依赖检查。
func (b *windowsBackend) PreflightPermissions() []string { return nil }
// ensureStarted 确保持久化 PowerShell 进程已启动。
func (b *windowsBackend) ensureStarted() error {
b.mu.Lock()
defer b.mu.Unlock()
if b.cmd != nil && b.cmd.Process != nil {
return nil
}
cmd := exec.Command("powershell", "-NoProfile", "-NoLogo", "-NonInteractive")
cmd.SysProcAttr = nil // 使用默认
stdin, err := cmd.StdinPipe()
if err != nil {
return err
}
stdout, err := cmd.StdoutPipe()
if err != nil {
return err
}
cmd.Stderr = diag.Indirect() // 不直连终端:TUI 在 alt-screen 下会被打穿
if err := cmd.Start(); err != nil {
return err
}
b.cmd = cmd
b.stdin = bufio.NewWriter(stdin)
b.stdout = bufio.NewScanner(stdout)
// 注入 user32 类定义(只需一次)
if _, err := b.exec(psUser32); err != nil {
_ = cmd.Process.Kill()
b.cmd = nil
return fmt.Errorf("failed to inject user32: %w", err)
}
return nil
}
// exec 向持久化 PS 进程发送命令并读取输出。
// 使用唯一分隔符区分输出边界。
const psDelim = "__AG_END_"
func (b *windowsBackend) exec(script string) (string, error) {
delim := fmt.Sprintf("Write-Output '%s'", psDelim)
full := script + "; " + delim
if _, err := b.stdin.WriteString(full + "\n"); err != nil {
return "", err
}
if err := b.stdin.Flush(); err != nil {
return "", err
}
var lines []string
for b.stdout.Scan() {
line := b.stdout.Text()
if strings.Contains(line, psDelim) {
break
}
lines = append(lines, line)
}
return strings.Join(lines, "\n"), nil
}
// restart 重启 PowerShell 进程(进程崩溃时恢复)。
func (b *windowsBackend) restart() {
if b.cmd != nil && b.cmd.Process != nil {
_ = b.cmd.Process.Kill()
_, _ = b.cmd.Process.Wait()
}
b.cmd = nil
_ = b.ensureStarted()
}
// runScript 在持久化进程失败时自动重启一次。
func (b *windowsBackend) runScript(script string) (string, error) {
if err := b.ensureStarted(); err != nil {
return "", err
}
out, err := b.exec(script)
if err != nil {
// 进程可能已死,重启后重试一次
b.restart()
if err2 := b.ensureStarted(); err2 != nil {
return "", err
}
out, err = b.exec(script)
}
return out, err
}
// psUser32 是注入 user32 P/Invoke 的 PowerShell 片段(在 ensureStarted 时注入一次)。
const psUser32 = "Add-Type -TypeDefinition 'using System;using System.Runtime.InteropServices;public class AGUser32{[DllImport(\"user32.dll\")]public static extern bool SetCursorPos(int x,int y);[DllImport(\"user32.dll\")]public static extern void mouse_event(uint f,uint dx,uint dy,uint d,UIntPtr e);}' -ErrorAction SilentlyContinue"
// mouse_event flags:2=LEFTDOWN 4=LEFTUP 8=RIGHTDOWN 16=RIGHTUP 2048=WHEEL
const (
meLeftDown = "2"
meLeftUp = "4"
meRightDown = "8"
meRightUp = "16"
meWheel = "2048"
wheelNotch = "120"
)
func (b *windowsBackend) Click(_ context.Context, x, y int, kind string) error {
var bld strings.Builder
fmt.Fprintf(&bld, "[AGUser32]::SetCursorPos(%d,%d); Start-Sleep -Milliseconds 80; ", x, y)
switch kind {
case "right":
fmt.Fprintf(&bld, "[AGUser32]::mouse_event(%s,0,0,0,[UIntPtr]::Zero); Start-Sleep -Milliseconds 40; [AGUser32]::mouse_event(%s,0,0,0,[UIntPtr]::Zero);", meRightDown, meRightUp)
case "double":
fmt.Fprintf(&bld, "[AGUser32]::mouse_event(%s,0,0,0,[UIntPtr]::Zero); Start-Sleep -Milliseconds 40; [AGUser32]::mouse_event(%s,0,0,0,[UIntPtr]::Zero); Start-Sleep -Milliseconds 120; [AGUser32]::mouse_event(%s,0,0,0,[UIntPtr]::Zero); Start-Sleep -Milliseconds 40; [AGUser32]::mouse_event(%s,0,0,0,[UIntPtr]::Zero);", meLeftDown, meLeftUp, meLeftDown, meLeftUp)
default:
fmt.Fprintf(&bld, "[AGUser32]::mouse_event(%s,0,0,0,[UIntPtr]::Zero); Start-Sleep -Milliseconds 40; [AGUser32]::mouse_event(%s,0,0,0,[UIntPtr]::Zero);", meLeftDown, meLeftUp)
}
_, err := b.runScript(bld.String())
return err
}
func (b *windowsBackend) TypeText(_ context.Context, text string) error {
seq := strings.ReplaceAll(sendKeysEscape(text), "'", "''")
script := "Add-Type -AssemblyName System.Windows.Forms; " +
"[System.Windows.Forms.SendKeys]::SendWait('" + seq + "');"
_, err := b.runScript(script)
return err
}
func (b *windowsBackend) KeyChord(_ context.Context, chordStr string) error {
mods, main, err := parseChord(chordStr)
if err != nil {
return err
}
seq, err := chordToSendKeys(mods, main, chordStr)
if err != nil {
return err
}
seq = strings.ReplaceAll(seq, "'", "''")
script := "Add-Type -AssemblyName System.Windows.Forms; " +
"[System.Windows.Forms.SendKeys]::SendWait('" + seq + "');"
_, err = b.runScript(script)
return err
}
func (b *windowsBackend) Scroll(_ context.Context, direction string, amount int) error {
delta := wheelNotch // 上滚为正
if direction == "down" {
delta = "-" + wheelNotch
}
var bld strings.Builder
for i := 0; i < amount; i++ {
fmt.Fprintf(&bld, "Start-Sleep -Milliseconds 90; [AGUser32]::mouse_event(%s,0,0,%s,[UIntPtr]::Zero); ", meWheel, delta)
}
_, err := b.runScript(bld.String())
return err
}
func (b *windowsBackend) Screenshot(_ context.Context, region string) (string, error) {
path := filepath.Join(os.TempDir(), fmt.Sprintf("autogo-shot-%d.png", time.Now().UnixNano()))
psPath := strings.ReplaceAll(path, "'", "''")
var bld strings.Builder
bld.WriteString("Add-Type -AssemblyName System.Windows.Forms,System.Drawing; ")
if region != "" {
x, y, w, h, err := parseRegion(region)
if err != nil {
return "", err
}
fmt.Fprintf(&bld, "$bmp=New-Object System.Drawing.Bitmap(%d,%d); $g=[System.Drawing.Graphics]::FromImage($bmp); $g.CopyFromScreen(%d,%d,0,0,$bmp.Size); ", w, h, x, y)
} else {
bld.WriteString("$b=[System.Windows.Forms.SystemInformation]::VirtualScreen; $bmp=New-Object System.Drawing.Bitmap($b.Width,$b.Height); $g=[System.Drawing.Graphics]::FromImage($bmp); $g.CopyFromScreen($b.X,$b.Y,0,0,$bmp.Size); ")
}
bld.WriteString("$g.Dispose(); $bmp.Save('" + psPath + "',[System.Drawing.Imaging.ImageFormat]::Png); $bmp.Dispose();")
if out, err := b.runScript(bld.String()); err != nil {
_ = os.Remove(path)
return "", fmt.Errorf("screenshot failed: %w: %s", err, out)
}
return path, nil
}