@windsland52/maafw-live 0.0.0-stage → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +278 -2
- package/bin/maafw-live.mjs +25 -0
- package/docs/daemon-protocol.md +116 -0
- package/docs/keyframe-evidence-contract-v0.md +61 -0
- package/docs/keyframe-retention-contract-v0.md +236 -0
- package/lib/client/daemon.js +234 -0
- package/lib/client/daemon.js.map +1 -0
- package/lib/commands/annotate.js +172 -0
- package/lib/commands/annotate.js.map +1 -0
- package/lib/commands/calibrate.js +61 -0
- package/lib/commands/calibrate.js.map +1 -0
- package/lib/commands/color.js +73 -0
- package/lib/commands/color.js.map +1 -0
- package/lib/commands/crop.js +228 -0
- package/lib/commands/crop.js.map +1 -0
- package/lib/commands/env.js +215 -0
- package/lib/commands/env.js.map +1 -0
- package/lib/commands/index.js +64 -0
- package/lib/commands/index.js.map +1 -0
- package/lib/commands/input.js +219 -0
- package/lib/commands/input.js.map +1 -0
- package/lib/commands/kf.js +131 -0
- package/lib/commands/kf.js.map +1 -0
- package/lib/commands/reco.js +211 -0
- package/lib/commands/reco.js.map +1 -0
- package/lib/commands/repl.js +698 -0
- package/lib/commands/repl.js.map +1 -0
- package/lib/commands/runtime.js +514 -0
- package/lib/commands/runtime.js.map +1 -0
- package/lib/commands/skill.js +169 -0
- package/lib/commands/skill.js.map +1 -0
- package/lib/commands/timing.js +229 -0
- package/lib/commands/timing.js.map +1 -0
- package/lib/commands/version.js +66 -0
- package/lib/commands/version.js.map +1 -0
- package/lib/daemon/framed.mjs +2850 -0
- package/lib/daemon/reco_child.mjs +146 -0
- package/lib/exec.js +96 -0
- package/lib/exec.js.map +1 -0
- package/lib/flags.js +42 -0
- package/lib/flags.js.map +1 -0
- package/lib/image.js +90 -0
- package/lib/image.js.map +1 -0
- package/lib/index.js +139 -0
- package/lib/index.js.map +1 -0
- package/lib/interface/index.js +12 -0
- package/lib/interface/index.js.map +1 -0
- package/lib/interface/jsonc.js +95 -0
- package/lib/interface/jsonc.js.map +1 -0
- package/lib/interface/load.js +307 -0
- package/lib/interface/load.js.map +1 -0
- package/lib/interface/override.js +103 -0
- package/lib/interface/override.js.map +1 -0
- package/lib/interface/plan.js +188 -0
- package/lib/interface/plan.js.map +1 -0
- package/lib/pkg.js +18 -0
- package/lib/pkg.js.map +1 -0
- package/lib/protocol.js +90 -0
- package/lib/protocol.js.map +1 -0
- package/lib/runtime/actions.js +110 -0
- package/lib/runtime/actions.js.map +1 -0
- package/lib/runtime/keyframes.js +73 -0
- package/lib/runtime/keyframes.js.map +1 -0
- package/lib/runtime/session.js +86 -0
- package/lib/runtime/session.js.map +1 -0
- package/lib/types/client/daemon.d.ts +86 -0
- package/lib/types/commands/annotate.d.ts +3 -0
- package/lib/types/commands/calibrate.d.ts +7 -0
- package/lib/types/commands/color.d.ts +9 -0
- package/lib/types/commands/crop.d.ts +3 -0
- package/lib/types/commands/env.d.ts +2 -0
- package/lib/types/commands/index.d.ts +13 -0
- package/lib/types/commands/input.d.ts +17 -0
- package/lib/types/commands/kf.d.ts +10 -0
- package/lib/types/commands/reco.d.ts +23 -0
- package/lib/types/commands/repl.d.ts +2 -0
- package/lib/types/commands/runtime.d.ts +48 -0
- package/lib/types/commands/skill.d.ts +17 -0
- package/lib/types/commands/timing.d.ts +64 -0
- package/lib/types/commands/version.d.ts +3 -0
- package/lib/types/exec.d.ts +31 -0
- package/lib/types/flags.d.ts +58 -0
- package/lib/types/image.d.ts +27 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/interface/index.d.ts +11 -0
- package/lib/types/interface/jsonc.d.ts +9 -0
- package/lib/types/interface/load.d.ts +113 -0
- package/lib/types/interface/override.d.ts +33 -0
- package/lib/types/interface/plan.d.ts +47 -0
- package/lib/types/pkg.d.ts +6 -0
- package/lib/types/protocol.d.ts +108 -0
- package/lib/types/runtime/actions.d.ts +166 -0
- package/lib/types/runtime/keyframes.d.ts +43 -0
- package/lib/types/runtime/session.d.ts +53 -0
- package/lib/types/update-check.d.ts +20 -0
- package/lib/types/usage.d.ts +3 -0
- package/lib/update-check.js +97 -0
- package/lib/update-check.js.map +1 -0
- package/lib/usage.js +50 -0
- package/lib/usage.js.map +1 -0
- package/package.json +81 -4
- package/skills/README.md +51 -0
- package/skills/maafw-live/SKILL.md +263 -0
- package/skills/maafw-live/references/examples.md +238 -0
- package/skills/maafw-live/references/pitfalls.md +118 -0
- package/skills/maafw-live/references/workflows.md +159 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Windsland52
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,279 @@
|
|
|
1
|
-
#
|
|
1
|
+
# maafw-live
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
给 AI agent 的 MaaFramework **实时观测与设备交互底座**:常驻 daemon 提供连续帧流、变化/稳定检测与
|
|
4
|
+
环形缓冲;确定性 CLI 以稳定 JSON 输出观测与交互能力,节点和识别参数靠实际运行测量。
|
|
5
|
+
|
|
6
|
+
解决 AI agent 使用 MaaFramework 的两大痛点:
|
|
7
|
+
|
|
8
|
+
- **看不到现场。** 此前没有面向模型的实时观测方案,agent 只能「截一帧、猜、再截一帧」。daemon 持续采样:帧流随时可取,画面变化/稳定有事件可订阅,历史帧有环形缓冲兜底。观测以事件与数字交付,像素默认不出 daemon——纯文本模型同样可用,要看画面时再按 seq / ROI 精确获取。
|
|
9
|
+
- **写不准识别。** 模板、roi、阈值、wait_freezes/delay 靠猜多半不准,而它们都能实测:识别单测与阈值扫描(`reco`)、模板裁剪(`crop`,裁后自匹配复核——判据为 best 落回裁剪处优先于得分,纯色模板的 CCOEFF_NORMED 得分是噪声)、探色(`color`)、实测节点时长与稳定时间(`timing`);没有模板时,候选区域编号回画、选号即得坐标(`annotate`)。
|
|
10
|
+
|
|
11
|
+
CLI 面向 agent、脚本与人:命令是确定性的,输出有稳定 JSON 信封;daemon 协议与客户端不绑定任何
|
|
12
|
+
宿主,编辑器插件、自动化平台、CI 都能直接接。连续观测到的帧可选择性地留存为跨工作会话证据——
|
|
13
|
+
契约见 [`docs/keyframe-retention-contract-v0.md`](docs/keyframe-retention-contract-v0.md)。
|
|
14
|
+
|
|
15
|
+
## 三件东西
|
|
16
|
+
|
|
17
|
+
| 部分 | 位置 | 做什么 |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| daemon | `src/daemon/framed.mjs` | 独立子进程,持有 Controller / Resource / Tasker、帧流与环形缓冲;说 JSON 行协议 |
|
|
20
|
+
| client | `@windsland52/maafw-live/client` | 可复用客户端:spawn、init 握手(拿 framesDir / daemonId / kfQuota)、应答配对、超时硬杀自愈、帧 / 事件 / 原生日志汇聚 |
|
|
21
|
+
| CLI | `bin/maafw-live.mjs` | 命令面:探针、设备、输入、识别、会话 |
|
|
22
|
+
|
|
23
|
+
协议的完整契约(消息形状、命令表、坐标系与稳定性约定)见 [`docs/daemon-protocol.md`](docs/daemon-protocol.md)。
|
|
24
|
+
|
|
25
|
+
## 安装
|
|
26
|
+
|
|
27
|
+
从 registry(包名 `@windsland52/maafw-live`,命令名 `maafw-live`——前者是装的那个,后者是敲的那个):
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install --global @windsland52/maafw-live
|
|
31
|
+
maafw-live version
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
本地开发(checkout 里):
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install
|
|
38
|
+
npm run build
|
|
39
|
+
npm link # 之后可直接 maafw-live --help
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
需要 Node >= 22.13;`@maaxyz/maa-node` 会在安装时按平台取对应的原生包。
|
|
43
|
+
|
|
44
|
+
## 命令
|
|
45
|
+
|
|
46
|
+
| 命令 | 说明 |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| `maafw-live env` | 探测环境与能力:项目根、pipeline 目录、运行时绑定、外部工具(adb / python / git) |
|
|
49
|
+
| `maafw-live version` | 打印 CLI 与依赖版本 |
|
|
50
|
+
| `maafw-live probe` | 自检运行时:绑定版本、adb / win32 设备发现数 |
|
|
51
|
+
| `maafw-live device` | 列出可连接的设备(adb 设备与 win32 窗口) |
|
|
52
|
+
| `maafw-live connect` | 连接设备(进程态;多步操作用 repl) |
|
|
53
|
+
| `maafw-live disconnect` | 断开并销毁 Tasker / Controller |
|
|
54
|
+
| `maafw-live screencap` | 截一帧并落盘(同时进 L0 缓存,有捕获身份可升格) |
|
|
55
|
+
| `maafw-live frame` | 帧流状态与历史帧取用(可带 ROI,按该帧捕获时尺寸换算);`--src full` 取控制器分辨率原图,降级为小图会明说并判 findings |
|
|
56
|
+
| `maafw-live wait` | 状态谓词:`stable`(画面静下来)/ `change`(画面动了)——取代调用方硬睡时钟;**超时不是失败**,是观测结果(退出 3) |
|
|
57
|
+
| `maafw-live run` | 运行 pipeline;项目模式合成 pipeline_override 四级链;`--timeout 0` 不自动停;退出码按任务级 `record.ok` |
|
|
58
|
+
| `maafw-live stop` | 停止运行中的任务 |
|
|
59
|
+
| `maafw-live click` / `swipe` / `key` / `keys` / `press` / `dbclick` / `scroll` / `move` / `text` | 输入注入(经 maafw 控制器本体;动作边界帧自动入 L0 锚区) |
|
|
60
|
+
| `maafw-live reco` | 识别单测(子进程隔离):`--param`/`--sweep` 阈值扫描,`--node` 整节点透传(V1/V2),`--act` 识别拿框 → 真机执行动作半 |
|
|
61
|
+
| `maafw-live color` | 探色:ROI 实测均值 / HSV / 主色,选色后用 reco ColorMatch 出框 |
|
|
62
|
+
| `maafw-live crop` | 模板裁剪:宽松框或点 → snap 收紧 → 原图裁剪(L0 热缓存 / `--from-kf` 库内留存帧)→ 同帧自匹配逐边精修 + 跨帧验证;产出 L2 派生图与 `<out>.prov.json` 出处 |
|
|
63
|
+
| `maafw-live annotate` | 轻量 SoM:OCR / diff / 连通域 / 边缘密度候选区域 + 编号回画(模型选号 → ctrl 坐标);`--from-kf` 可在关键帧库留存帧上离线跑 |
|
|
64
|
+
| `maafw-live calibrate` | 变化检测阈值校准:静止画面定噪声地板,推荐 blockThresh / changeGlobal |
|
|
65
|
+
| `maafw-live timing` | 跑任务反推 timeout / wait_freezes / delay 建议:节点时长 + 动作后画面稳定时间;`--runs n`(1-10)多次采样取 P50/P95 分布 |
|
|
66
|
+
| `maafw-live kf` | 关键帧:status 看 L0 缓存,promote 升格原图进本地库,list / resolve 离线解析(库帧可直接作 crop 的源) |
|
|
67
|
+
| `maafw-live repl` | 交互 / 管道会话:连接一次,命令复用 |
|
|
68
|
+
| `maafw-live skill` | 随包 agent skill:看包内副本与指纹、`--check` 比对已装副本(有漂移退出 3)、`--install` 逐字节写出 |
|
|
69
|
+
|
|
70
|
+
`env` 是前置命令:动手前先问它「现在有什么」,而不是各自写一遍环境检测。它有一条硬要求——
|
|
71
|
+
**自身永远不能因为环境残缺而失败**,缺依赖都是探测结果,不是错误。
|
|
72
|
+
|
|
73
|
+
它按**应用开发者视角**探测:写 Maa 应用的人不需要 clone 框架源码,所以默认结果里没有框架源码项。
|
|
74
|
+
只有显式给 `--checkout <MaaFramework 源码目录>`(或环境变量 `MAAFW_CHECKOUT`)时才追加一项版本
|
|
75
|
+
对账(native 绑定 vs checkout 里的 schema),供框架开发与静态校验使用。
|
|
76
|
+
|
|
77
|
+
## 连接:优先 `--project`
|
|
78
|
+
|
|
79
|
+
两种连法:
|
|
80
|
+
|
|
81
|
+
- `--project <dir>`(推荐):读项目 `interface.json` 的 `controller[]` —— 控制器类型、窗口类名与标题
|
|
82
|
+
正则、截图与输入方法、识别缩放(`display_short_side`)全部按项目声明来。**不猜设备**:猜错不只是连错
|
|
83
|
+
窗口,还会把 roi 与模板图的坐标系一起猜错。
|
|
84
|
+
- `--kind win32|adb|gamepad [--target ...]`:手动指定,窗口可用标题片段或 hwnd。
|
|
85
|
+
|
|
86
|
+
连接是**进程态**的:命令退出即断开。多步操作走 `repl`(连接一次、命令复用,也能被管道驱动):
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
maafw-live repl --project ./my-maa-project
|
|
90
|
+
maa> stream start --fps 10
|
|
91
|
+
maa> frame get --roi 0,0,200,120
|
|
92
|
+
maa> click 100 200
|
|
93
|
+
maa> run StartUp --timeout 60000
|
|
94
|
+
maa> quit
|
|
95
|
+
|
|
96
|
+
printf "probe\nquit\n" | maafw-live repl # 脚本 / agent 用法
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## 坐标系(最容易出错的一条)
|
|
100
|
+
|
|
101
|
+
识别与输入共用**控制器分辨率空间**:默认按项目的 `display_short_side` 缩放(缺省短边 720)。
|
|
102
|
+
`roi`、模板图与 `click` 坐标都在这一空间。只有显式传 `--short-side` / `--long-side` / `--raw`
|
|
103
|
+
才会改变它——那意味着项目里所有 roi 与模板都要按新尺寸重算。
|
|
104
|
+
|
|
105
|
+
## 从留存帧裁模板(不需要设备)
|
|
106
|
+
|
|
107
|
+
观测到不可复现状态时**当场升格**——L0 热缓存会淘汰,攒到最后就没有像素了(契约 §3):
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
maafw-live repl --project ./my-maa-project
|
|
111
|
+
maa> stream start --fps 10
|
|
112
|
+
maa> kf promote latest --note "活动弹窗"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
事后(可以换一天、设备早已不在那个画面、甚至**根本没连设备**)再从库帧裁模板:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
maafw-live crop --from-kf kf:<库UUID>:0142 --roi 640,300,220,80 \
|
|
119
|
+
--project ./my-maa-project --out assets/tpl_popup.png
|
|
120
|
+
# → assets/tpl_popup.png L2 派生图(提交进仓库的那个)
|
|
121
|
+
# → assets/tpl_popup.png.prov.json 出处:来源帧身份 + 裁剪变换 + 自匹配结论
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
几条边界,都是有意为之:
|
|
125
|
+
|
|
126
|
+
- 库帧是 **L0 原图本体**(契约 §2):控制器分辨率、捕获时刻的整帧,与当时在 `repl` 里看到的是同一份字节。
|
|
127
|
+
- 裁剪前校 **sha256 与像素尺寸**:解析之后被替换的字节、库记录与文件不同源的条目,一律拒绝,不拿"另一个字节"当依据。
|
|
128
|
+
- **模板空间 = 该帧捕获时尺寸**。它不等于"当前控制器尺寸"——改过 `shortSide` / 换过设备时会警告(契约 §5)。
|
|
129
|
+
- **跨帧验证缺省关**:留存帧对应的状态通常已不在画面上,拿当前帧比只会得到假警告;状态可复现时给 `--cross` 才做。
|
|
130
|
+
- 裁剪**只读**库帧:L2 与出处落在 `--out` 指定处,L0 与库目录不被改动。
|
|
131
|
+
|
|
132
|
+
## 复用 daemon
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
import { spawnDaemon } from "@windsland52/maafw-live/client"
|
|
136
|
+
|
|
137
|
+
const c = spawnDaemon({ runDir: "/tmp/maa-run" })
|
|
138
|
+
try {
|
|
139
|
+
await c.call("connect", { kind: "win32", target: "记事本" })
|
|
140
|
+
await c.call("stream_start", { fps: 10 })
|
|
141
|
+
c.subscribe("event", (ev) => console.log(ev))
|
|
142
|
+
const shot = await c.call("frame_get", { roi: [0, 0, 200, 120] })
|
|
143
|
+
console.log(shot.path)
|
|
144
|
+
} finally {
|
|
145
|
+
c.close()
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
一个 daemon 子进程 = 一个设备会话。**像素默认不出 daemon**:先拿帧元数据,需要画面时再按 seq / ROI
|
|
150
|
+
精确取——这是为了让模型上下文不被像素挤爆。
|
|
151
|
+
|
|
152
|
+
## 全局约定
|
|
153
|
+
|
|
154
|
+
### JSON 信封
|
|
155
|
+
|
|
156
|
+
`--json` 时 stdout 只输出信封,可被管道直接解析:
|
|
157
|
+
|
|
158
|
+
```jsonc
|
|
159
|
+
{
|
|
160
|
+
"schemaVersion": 1,
|
|
161
|
+
"command": "env",
|
|
162
|
+
"ok": true,
|
|
163
|
+
"exitCode": 0,
|
|
164
|
+
"root": "/path/to/project",
|
|
165
|
+
"written": [], "removed": [], "skipped": [], "pending": [],
|
|
166
|
+
"suggestedCommands": [], "warnings": [],
|
|
167
|
+
"data": { },
|
|
168
|
+
"error": null
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**信封字段只增不改。** 消费方按字段名读取,改名等于静默破坏所有调用方。
|
|
173
|
+
|
|
174
|
+
### 退出码
|
|
175
|
+
|
|
176
|
+
| 码 | 含义 |
|
|
177
|
+
| --- | --- |
|
|
178
|
+
| 0 | 正常完成,未发现问题(run 的任务级 `record.ok=true`) |
|
|
179
|
+
| 1 | 命令自身失败(未预期异常、IO 错误);**run 的任务失败**(`record.ok=false`,含超时被停止) |
|
|
180
|
+
| 2 | 参数 / 用法错误 |
|
|
181
|
+
| 3 | 跑成了,但发现了问题:run 的**调用级**失败(未连接 / 资源缺失 / 已有任务在跑)、reco 识别未命中(含 `--act` 单测失败)、crop / color / annotate / calibrate 的失败或警告、kf manifest / promote 失败、device 未发现设备、frame 取不到帧(帧流未开 / 环形缓冲为空)与**带 `roi` 时帧降级为小图**(`--src auto/full` 下 L0 已淘汰;显式 `--src ring` 与不带 `roi` 的整帧小图都是预期行为,不算)、wait **超时未成立**("没等到"是观测结果,不是命令失败)、timing 采样未跑起来(资源缺失 / 任务没跑起来 / `--runs` 次全未成功)、`version --check` 查到新版、`skill --check` 检出已装副本漂移 |
|
|
182
|
+
| 4 | 前置环境缺失 |
|
|
183
|
+
|
|
184
|
+
关键是 **1 与 3 的区分**:1 是「没跑成」(含任务失败),3 是「跑成了但有问题」。任务级结果看
|
|
185
|
+
JSON 信封的 `data.record.ok`,不是外层 `ok`——外层只代表调用完成。
|
|
186
|
+
|
|
187
|
+
### 全局选项
|
|
188
|
+
|
|
189
|
+
`--json` / `--yes` / `--no-interactive` / `--no-color` / `--cwd <dir>` / `-h` / `-V`。`--no-color` 是自动化
|
|
190
|
+
场景的硬要求:ANSI 转义会混进 JSON(非 TTY 下自动无色)。`--no-interactive` 与 `--yes` 当前无门控——
|
|
191
|
+
本工具没有交互式提问与确认步骤,照传即可,防后续版本引入交互提问挂住自动化调用。
|
|
192
|
+
|
|
193
|
+
`--dry-run` / `--limit <n>` / `--verbose` 是**预留位、当前未实现**:传了不报错,但不改变任何命令的行为
|
|
194
|
+
(`--dry-run` 照样落盘)——别把它们写进脚本当保证。
|
|
195
|
+
|
|
196
|
+
## 代码结构
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
src/
|
|
200
|
+
index.ts 入口:解析 → 分发 → 输出。领域逻辑一律不许进来
|
|
201
|
+
protocol.ts 信封 / 命令契约 / 退出码
|
|
202
|
+
flags.ts 全局选项与默认值
|
|
203
|
+
exec.ts 外部命令执行(永不抛异常)
|
|
204
|
+
usage.ts 帮助文本
|
|
205
|
+
pkg.ts 读取自身包信息
|
|
206
|
+
daemon/
|
|
207
|
+
framed.mjs 设备 daemon(独立子进程;逐字节发布,不经打包器)
|
|
208
|
+
reco_child.mjs 识别单测的一次性子进程(崩溃隔离)
|
|
209
|
+
client/daemon.ts 可复用的 daemon 客户端
|
|
210
|
+
runtime/
|
|
211
|
+
actions.ts 动作层:把 daemon 命令包成有类型 / 超时 / 默认值的函数
|
|
212
|
+
session.ts 一次性命令如何先连接再干活
|
|
213
|
+
interface/ interface.json(ProjectInterface v2)的窄解析:控制器规划与资源路径
|
|
214
|
+
commands/ 命令面(index 是注册表;env / runtime / input / reco / repl / version)
|
|
215
|
+
bin/maafw-live.mjs 启动器(未构建时给人话提示,而不是堆栈)
|
|
216
|
+
docs/ 协议与留存契约
|
|
217
|
+
scripts/ 回归与构建辅助
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**加一个新命令**:在 `src/commands/` 写一个导出 `Command` 的文件,在 `commands/index.ts` 注册。
|
|
221
|
+
选项解析、cwd 校验、错误兜底、JSON 输出都由入口统一处理。注册表只放能跑通的命令,未实现的能力
|
|
222
|
+
不挂空壳也不进 help——挂空壳会让失败原因从「没这个命令」变成「没实现」,help 描述未来就会过期。
|
|
223
|
+
|
|
224
|
+
## 回归
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
npm test # 纯函数单测:变化检测/L0 淘汰/snap 收紧/SoM 候选/interface 四级 override 链/库配额
|
|
228
|
+
npm run verify # 装配级回归:全部通过真实子进程断言外部契约
|
|
229
|
+
node scripts/accept-kf-offline.mjs # 契约验收(离线):库读侧——同 ID 跨库、manifest 各类坏法、半写
|
|
230
|
+
node scripts/accept-emulator.mjs --target 127.0.0.1:16384 # 契约验收(需模拟器在线):改分辨率旧帧 ROI、升格路径、磁盘失败注入
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
断言的是**外部契约**(stdout / stderr / 退出码),不是内部函数——消费方看到的正是前者,内部重构不该
|
|
234
|
+
影响它。最要紧的两条:信封字段集与文档逐字一致;`--json` 时 stdout 必须是纯 JSON。单测与验收脚本
|
|
235
|
+
补的是另一层:信封级回归测不到的数值行为与契约语义(验收记录见
|
|
236
|
+
[`docs/keyframe-retention-contract-v0.md`](docs/keyframe-retention-contract-v0.md) §9)。
|
|
237
|
+
|
|
238
|
+
## 依赖
|
|
239
|
+
|
|
240
|
+
| 依赖 | 用途 |
|
|
241
|
+
| --- | --- |
|
|
242
|
+
| `@maaxyz/maa-node` | 官方 Node 绑定:设备连接、截图、识别、输入。原生能力全走它,不走旁路 |
|
|
243
|
+
|
|
244
|
+
interface.json 的解析与控制器规划内建在 `src/interface/`(无外部依赖)。
|
|
245
|
+
|
|
246
|
+
## 路线图
|
|
247
|
+
|
|
248
|
+
已落地的大件:**关键帧留存与引用**(L0 原图缓存
|
|
249
|
+
滚动区 + 锚区、输入 / run 边界帧、升格与本地关键帧库、离线帧解析、**从库帧裁出带出处的 L2 派生图**——
|
|
250
|
+
离线可用、不需要设备;契约与验收用例在
|
|
251
|
+
[`docs/keyframe-retention-contract-v0.md`](docs/keyframe-retention-contract-v0.md))、PI v2 解析补齐
|
|
252
|
+
(import 合并、pipeline_override 四级链、preset 默认值)、**agent 完整桥接**(run 时 spawn agent
|
|
253
|
+
子进程并经 `maa.Client` 接入;注意 maa-node 与 agent 侧 maa 库须同版本,协议握手要求)、
|
|
254
|
+
变化检测双阈值(分块亮度差 + 事件携带变化区域 bbox、阈值校准命令)、输入原语补齐、探色、
|
|
255
|
+
节点级单测(reco `--act`)、模板裁剪(crop,位置优先判据 + 跨帧验证)、轻量 SoM(annotate)与
|
|
256
|
+
timing 反推(节点时长 + 动作后稳定时间 → delay/timeout 建议,`--runs n` 取分布:P95 × 余量,失败 run 不计入建议)。
|
|
257
|
+
|
|
258
|
+
不在本包范围(各有归属):
|
|
259
|
+
|
|
260
|
+
| 域 | 归属 |
|
|
261
|
+
| --- | --- |
|
|
262
|
+
| 静态知识(validate / graph / lookup / project) | MaaLLMWiki 技能 |
|
|
263
|
+
| pipeline 改写(node / migrate / interface) | maafw-pipeline 技能 + agent 对文件的直接编辑 |
|
|
264
|
+
| 模板资产(template audit / crop) | pipeline 编写动线;裁剪派生物(L2)在留存契约内 |
|
|
265
|
+
| 日志与诊断证据(log / evidence) | MaaEvidenceKit + maa-evidence 技能 |
|
|
266
|
+
|
|
267
|
+
## Agent Skill(随包发布)
|
|
268
|
+
|
|
269
|
+
给 agent 用的使用动线在本仓 `skills/`:**纪律、判据与反模式**(怎么观测、怎么实测识别、怎么裁模板、
|
|
270
|
+
怎么在状态消失前留档),字段与协议仍以 `docs/` 为唯一出处——skill 不复述协议,避免出现第二份。
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
npx skills add https://github.com/Windsland52/maafw-live --skill maafw-live --global
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
- [`skills/maafw-live/SKILL.md`](skills/maafw-live/SKILL.md):硬护栏、四条动线、判定与退出码、反模式。
|
|
277
|
+
- [`skills/maafw-live/references/workflows.md`](skills/maafw-live/references/workflows.md):命令级选择与实测经验值。
|
|
278
|
+
- [`skills/maafw-live/references/pitfalls.md`](skills/maafw-live/references/pitfalls.md):真实教训(判定、会话、坐标、原生坑)。
|
|
279
|
+
- [`skills/maafw-live/references/examples.md`](skills/maafw-live/references/examples.md):真机样例(一次性状态留档 → 离线裁模板;分辨率空间守卫)。
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* maafw-live CLI 启动器。
|
|
4
|
+
*
|
|
5
|
+
* 为什么不让 package.json 的 bin 直接指向 lib/index.js:
|
|
6
|
+
* - tsc 不会生成 shebang,Windows 下也没有可执行位概念;
|
|
7
|
+
* - 未构建时要给出可操作的中文提示,而不是一个 MODULE_NOT_FOUND 堆栈。
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync } from 'node:fs'
|
|
10
|
+
import { dirname, join } from 'node:path'
|
|
11
|
+
import { fileURLToPath } from 'node:url'
|
|
12
|
+
|
|
13
|
+
const here = dirname(fileURLToPath(import.meta.url))
|
|
14
|
+
const entry = join(here, '..', 'lib', 'index.js')
|
|
15
|
+
|
|
16
|
+
if (!existsSync(entry)) {
|
|
17
|
+
process.stderr.write(
|
|
18
|
+
'maafw-live: 尚未构建。\n' +
|
|
19
|
+
' 请在 maafw-live 目录执行:npm install && npm run build\n',
|
|
20
|
+
)
|
|
21
|
+
process.exit(1)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { main } = await import(new URL('../lib/index.js', import.meta.url))
|
|
25
|
+
process.exitCode = await main(process.argv.slice(2))
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# daemon 协议(maafw-live core)
|
|
2
|
+
|
|
3
|
+
这份文档是**为了被复用而存在**的:任何宿主(编辑器插件、自动化平台、CI 脚本、
|
|
4
|
+
其他语言写的壳)都可以直接跟这个 daemon 说话,而不必依赖本仓库的 CLI 或 TypeScript 客户端。
|
|
5
|
+
|
|
6
|
+
实现:`src/daemon/framed.mjs`(随包发布为 `lib/daemon/framed.mjs`)。
|
|
7
|
+
客户端参考实现:`src/client/daemon.ts`(`@windsland52/maafw-live/client` 导出)。
|
|
8
|
+
|
|
9
|
+
## 传输
|
|
10
|
+
|
|
11
|
+
node <daemon>/framed.mjs --child
|
|
12
|
+
|
|
13
|
+
- **stdin**:每行一条 JSON 请求 `{ id, cmd, ...args }`。
|
|
14
|
+
- **stdout**:每行一条 JSON 消息(见下)。**非 JSON 行不是错误**——它是 maa 框架的原生日志,
|
|
15
|
+
照原样收进日志环(识别失败的根因常常只在这里出现)。
|
|
16
|
+
- **stderr**:同样是原生日志。
|
|
17
|
+
- 不带 `--child` 时该模块无副作用,可直接 import(`__test` 导出纯函数用于无设备回归)。
|
|
18
|
+
|
|
19
|
+
一进程一会话:**一个 daemon 子进程 = 一个设备会话**(一个 Controller + Resource + Tasker + 帧流)。
|
|
20
|
+
同一台设备同一时刻只允许一个所有者;需要多设备就起多个子进程,但占用规则由调用方负责。
|
|
21
|
+
|
|
22
|
+
## 消息
|
|
23
|
+
|
|
24
|
+
| 形状 | 方向 | 含义 |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| `{ id, cmd, ...args }` | 调用方 → daemon | 请求;`id` 由调用方自增,`-1` 可作一次性通知 |
|
|
27
|
+
| `{ kind:'reply', id, ok, data }` | daemon → 调用方 | 应答成功 |
|
|
28
|
+
| `{ kind:'reply', id, ok:false, error }` | daemon → 调用方 | 应答失败(业务失败也是这一形状,不抛异常) |
|
|
29
|
+
| `{ kind:'frame', meta, preview? }` | daemon → 调用方 | 每接受一帧的元数据;`preview` 是预览帧 PNG 路径 |
|
|
30
|
+
| `{ kind:'event', ev }` | daemon → 调用方 | 画面变化/稳定事件与 Tasker 节点消息(按帧序对齐) |
|
|
31
|
+
| `{ kind:'stream_error', error }` | daemon → 调用方 | 帧流自身出错(设备掉线等),流可能已停 |
|
|
32
|
+
| `{ kind:'stream_stopped', reason }` | daemon → 调用方 | daemon 主动停流(控制器被销毁,如 `connect` 重建):流已不在跑,要看帧得重新 `stream_start` |
|
|
33
|
+
|
|
34
|
+
`meta` 至少含 `seq`(帧序,单调递增)、`t`(毫秒时间戳);差异检测命中时含 `diff`。
|
|
35
|
+
`ev.type` 取值:`change` / `stable`(画面)、`run`(节点级消息,含 `node`、`msg`、`id`)。
|
|
36
|
+
|
|
37
|
+
**像素默认不出 daemon。** 调用方先拿 `frame` 元数据,需要画面时再用 `frame_get` 精确取
|
|
38
|
+
(可带 `roi`)。这条是为了让 LLM 的上下文不被像素挤爆,也是面板与模型共用同一份缓冲的原因。
|
|
39
|
+
|
|
40
|
+
## 命令
|
|
41
|
+
|
|
42
|
+
| cmd | 参数 | 返回要点 |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `init` | `runDir`、`kfQuotaBytes?`(字节,>0 生效;缺省 1GiB,env `MAAFW_KF_QUOTA_BYTES` 亦可覆盖;env `MAAFW_NOTIFY_DUMP=<file>` 把框架原始通知逐行 JSON 落盘,用于诊断"记录字段到底是谁填的",默认关) | `{ ok, previewPath, daemonId, framesDir, kfQuota }`;设定预览帧落盘路径与关键帧库磁盘配额。应在 spawn 后立刻发一次——本仓客户端把它当**握手**(`client.init()`):回执里的 framesDir / daemonId / kfQuota 对宿主可见,无应答或失败会如实回报并进错误环,不再被静默丢弃 |
|
|
45
|
+
| `probe` | — | `{ version, adb, win32, errors[] }`:绑定版本与设备发现数 |
|
|
46
|
+
| `device_list` | `kind: all\|adb\|win32` | 设备数组:`{ kind, id, name, cls?, adbPath? }` |
|
|
47
|
+
| `connect` | `kind: adb\|win32\|gamepad`,`target`,可选 `screencap`/`mouse`/`keyboard`/`gamepadType`(枚举名),可选 `shortSide`/`longSide`/`rawSize` | `{ ok, session, streamStopped? }`;`session` 含 `kind/target/name/cls/method/resolution/warns`。**会先销毁已有 Controller**——因此若上一轮在跑帧流,本次连接会**显式停掉它并回 `streamStopped:true`**,同时向订阅者推一条 `stream_stopped`(要看帧就重新 `stream_start`;旧行为是流循环静默死掉而状态仍报"在跑") |
|
|
48
|
+
| `disconnect` | — | `{ ok }`;停流、销毁 Tasker/Controller。adb 的 destroy 偶发阻塞 → 客户端超时后硬杀自愈 |
|
|
49
|
+
| `screencap` | `out` | `{ ok, path, bytes }` 或 `{ error }` |
|
|
50
|
+
| `stream_start` | `fps`(1-30)、`scale`(160-1280)、`maxFrames`(20-600);可选 `l0Roll`/`l0Anchor`/`l0Bytes`/`blockThresh`/`changeGlobal`(L0 与变化检测调优) | `{ ok, fps, scale, maxFrames }` |
|
|
51
|
+
| `stream_stop` / `stream_status` | — | 状态含 `running, fps, scale, maxFrames, seq, ring, events, full, session` |
|
|
52
|
+
| `frame_get` | `seq?`(缺省最新)、`roi?: [x,y,w,h]`、`out?`、**`src?: auto\|full\|ring`**(缺省 `auto`) | `{ ok, path, bytes, w, h, seq, t, diff, ctrlW, ctrlH, source, sourceResolution, degraded, requested?, warn? }`。`roi` 是**控制器分辨率坐标**,与 pipeline 里的 roi 同空间。<br>**带 `roi` 时**:`auto` 先从该 seq 的 L0 全分辨率原图 1:1 裁(`source:"l0-full"`、`sourceResolution:1`),L0 已淘汰才退环形缓冲小图(`source:"l1-ring"`)并**附 warn**;`full` 只要原图,淘汰即报错——**绝不拿一张缩小图冒充**;`ring` 强制小图(显式选择,不附警告)。<br>**不带 `roi`(整帧)时**:**只有显式 `full` 才是 L0 原图**;`auto` / `ring` 一律给环形缓冲那一帧(既有契约,`sourceResolution` 如实标注)——整帧小图是缺省行为而非降级,**不警告、不判 findings**,否则最普通的 `frame get`(不带 roi)会时不时退 3。<br>**`degraded`** 是**相对"调用方要什么"**的判断,不是"分辨率 < 1":带 `roi` 的 `auto` 退成小图才算降级(`true` + warn,CLI 退出 3);显式 `ring` 与整帧小图都是预期行为(`false`)。消费方按这个字段决定要不要报警,别拿 `sourceResolution` 自己反推。<br>**⚠ 行为变更(0.2.0 → 未发布)**:**仅**带 `roi` 的缺省路径由"必走 L1 小图"变为"有 L0 就用控制器分辨率原图";不带 `roi` 的整帧行为**未变**。依赖 `w/h` 恒为小图尺寸的消费方请显式传 `src=ring`,或改读 `sourceResolution` |
|
|
53
|
+
| `frame_diff` | `a`、`b`(两个缓冲 seq)、`rois: [[x,y,w,h],…]`(控制器分辨率) | `{ ok, a, b, dtMs, rois[{roi,meanDiff,maxDiff,pixels}], ranked, sourceResolution, note }`。**回答"这一步究竟改了哪几块 / 哪几格"**——帧流事件只给整帧 bbox,而离散网格(棋盘 / 背包 / 技能格)要的是每格变了多少。`ranked` 按 `meanDiff` 降序给 roi;`meanDiff` 是 0..255 的平均亮度差,**判"变了/没变"的闸门要先用静止画面实测**(同 calibrate 的思路),不当普适常数用 |
|
|
54
|
+
| `wait` | `mode: stable\|change`、`timeout?`(ms,200–120000,默认 10000)、`quiet?`(ms,默认 600)、`roi?`、`threshold?`(默认 3) | `{ ok, mode, satisfied, waitedMs, seq, quietMs, roi?, threshold?, lastRoiMeanDiff?, warn? }`。**状态谓词:等到条件成立再往下读,取代调用方硬睡时钟**。判据复用帧流同一套(最近 `quietMs` 内有无 `change` 事件 + 可选 ROI 内逐帧均值差 `>= threshold` 记为"在动"),不另立阈值口径。**超时不是命令失败**:`ok:true` + `satisfied:false`(CLI 退出码 3)——"没等到"是观测结果。环形缓冲一帧都没有时**直接报错**,不空转到超时再给"画面持续变化"这种假原因。`change` 的回看窗口含命令开始前 `quietMs` 内的事件,所以紧接 `click` 之后的 `wait change` 可能秒满足——它说的是"**动了**",不说明"到了目标界面";到了哪一屏要用识别或留存帧复核 |
|
|
55
|
+
| `l0_status` | — | `{ ok, seq, roll, anchor, bytes, framesDir, library }`:L0 滚动区/锚区条目与字节用量(两区共享帧只计一次)、关键帧库用量与配额 |
|
|
56
|
+
| `kf_promote` | `seq`(数字)或 `latest:true` | `{ ok, id, record, path, sha256, idempotent? }`;捕获身份在接受请求时固定,L0 已淘汰则失败(不改取新帧冒充);同一捕获重试幂等返回原对象。契约见 [keyframe-retention-contract-v0.md](keyframe-retention-contract-v0.md) |
|
|
57
|
+
| `tpl_crop` | `roi:[x,y,w,h]` 或 `point:[x,y]` + `pad?`、`resourceDir`(自匹配要加载资源)、`seq?`(缺省最新 L0 原图)、**`kfSource?`**(关键帧库留存帧:`{id,path,sha256,ctrlW,ctrlH,captureSeq,capturedAt}`,与 `seq` 二选一)、`out?`、`cross?`(`false` 关跨帧验证;库帧路径缺省关,`true` 才做)、`prov?`(`false` 不写 L2 出处侧车)、**`provOut?`**(出处侧车改写到该目录下、保持同名;缺省写在模板旁——用于"模板进资源包、出处留包外"的项目,此时不变量是同一相对路径而非同一目录)、**`keepSource?`**(热缓存路径下,把**本次裁剪实际用的那一帧**升格进关键帧库,出处随即带上可复核的 `kf:` 身份;不取最新帧。来源本就是 `kfSource` 时无意义)、**`snapTol?`/`snapFrac?`**(snap 收紧阈值的**测量用覆盖**,默认即生产常数 90 / 0.12;定标时 A/B 用)、**`edge?`**(**边缘收紧那一路,默认 `false`**:`true` 时把"局部对比度"判据也放进候选池,常数可再覆盖 `edgeZ?`(环上对比度 p90 的倍数,默认 2)/`edgeMin?`(绝对地板,默认 24)/`edgeFrac?`(行/列命中比例门槛,默认 0.02)。**定标结论:不采用为默认**——在 `scripts/truth/1999-720p.json`(12 例)上它单独用 IoU 均值只有 0.50–0.59,而背景差分是 0.804;边缘判据找到的是**元素内部结构**(文字笔画/图标细节)而不是元素范围,所以"取更紧者"会把框收进元素内部(实测 0005 物品卡片 0.866 → 0.449,严口径 precision 91.7% → 83.3%)。旋钮留着是为了可复跑这张对照表) | `{ ok, path, seq, source, provPath, provenance, spaceCheck, box, loose, tighten, snapped, score, positionOk, selfMatchBox?, cross?, tries, w, h, ctrlW, ctrlH, keptSource?, warn?, warns? }`;snap 收紧 + 同帧自匹配逐边精修,判据**位置正确优先于得分**(纯色模板的 CCOEFF_NORMED 得分是噪声)。`kfSource` 路径**不需要设备**,裁剪前校 sha256 与像素尺寸(不符即拒),`seq` 为 `null`;`source.kind` 是 `kf` / `l0-cache`;`spaceCheck` 是源帧空间与当前控制器尺寸的比对(未连接时为 `{current:null,match:null}`);`provenance` 同时写 `<out>.prov.json`(契约 §5)。**`tighten`** 是贴合度信号:`{from:[w,h], to:[w,h], areaRatio, grew, looseMatch?}`——`grew:true` 表示"收紧反而放大"(背景差分没收到元素边界,留白仍在)并会带一条警告;`looseMatch` 是宽松框自己的自匹配结果,**有才带**(池内提前命中就会跳过它)。**别拿"宽松框 vs 最终框的得分"判贴合度**:自匹配是自洽指标,实测同一元素在 40×61 / 80×80 / 120×120 三种窗口下都是 1.0 且都落回自己——几何比例才分辨得出 |
|
|
58
|
+
| `color_probe` | `roi?: [x,y,w,h]`(控制器分辨率,缺省全帧)、`seq?` | `{ ok, seq, roi?, pixelRoi, count, mean, hsv, dominant, space }` |
|
|
59
|
+
| `annotate` | `resourceDir?`(OCR 候选源需要)、`seq?`(会话内环)、**`kfSource?`**(关键帧库留存帧,与 `seq` 二选一:控制器分辨率、**不需要设备**)、`out?`、**`roi?: [x,y,w,h]`**(只保留落在该区域内的候选,**在取上限之前过滤**——区域内的候选只跟自己竞争)、**`somScale?: full\|small`**(检测面;缺省 `full`)、**`somLimit?`/`somMaxAreaRatio?`/`somEdgeZ?`/`somEdgeMin?`/`somIoU?`**(候选上限、巨框剔除阈值、边缘 z 门槛、边缘绝对地板、合并 IoU 的**测量用覆盖**,默认 30 / 0.12 / 1.2 / 2000 / 0.6) | `{ ok, out, seq, source, detectionSource, count, limit, mergedTotal, sources, filtered, availability, sourcesUnavailable?, small, ctrl, candidates[] }`;候选 = OCR / 连通域 / 边缘密度 / diff 区域(IoU>0.6 去重合并),`candidates[].ctrl` 是控制器坐标,另带 `areaRatio`(控制器空间面积占比,供排序:越小越像小按钮)与 OCR 的 `score`。<br>**检测面分辨率决定小目标能不能被找到**:缺省优先用同一 seq 的 **L0 全分辨率原图**做检测(`detectionSource:"l0-full"`),L0 已淘汰才退降级小图(`"l1-ring"` + `source.degraded` 与说明)。**为什么必须这样**:实测主页面顶部一个 42×55 的图标,在 480 宽的 L1 上四个源一个都没探到(合并池仅 30 条、零截断却仍然没有它),在 1280 宽原图上连通域一次就命中——**漏报的根因是分辨率,不是上限**。代价是耗时上升(单次约 20–40 秒),要旧行为给 `somScale:"small"`。<br>**上限截断按横向分带轮转,带内再按来源轮转**(旧行为是带内保持源优先级,OCR 几十条会把 conn/edge 挤光,而图标类目标恰多来自 conn/edge)。`mergedTotal` 是去重后的真实规模,与 `count` 不等即表示有截断。`filtered` 是被门槛拒掉的计数(`conn: {passed,tooSmall,tooLarge,tooNarrow}`、`edge: {passed,belowZ,belowMin,singleBlock}`)。<br>**`availability` 是"看起来有结果 ≠ 有结果"的护栏**:`{limit, mergedTotal, truncated, dropped:{tooSmall,tooBig,duplicated}, maxAreaRatio, roi?, byBand[{band,yFrom,yTo,inPool,inTable}], outResolution, detectionResolution}`——分带三列一起读才分得清"这一带真没有"与"这一带被门槛/上限拿掉了"。**注意 `tooSmall` 的 8px 门槛在检测面空间**:检测面从 480 升到 1280 后,同一门槛在控制器空间相当于松了约 2.7×,跨检测面的 `dropped`/`filtered` 数字不可直接比(`detectionResolution` 就是用来对冲这件事的)。库帧路径 `seq` 为 `null`、`small` 与 `ctrl` 同为帧尺寸(换算系数 1),且 **diff 源不可用**(没有会话事件,如实报在 `sourcesUnavailable`,不拿别的区域顶替) |
|
|
60
|
+
| `calibrate` | `frames?`(6-60,默认 24)、`interval?`(ms,默认 300) | `{ ok, frames, block, global, recommended{blockThresh,changeGlobal}, apply, warn? }`;静止画面定噪声地板 |
|
|
61
|
+
| `run` | `entry`、`resourceDir` 或 `resourceDirs[]`、`override?`、`agents?`(PI 声明的 agent 桥接)、`timeoutMs?`(缺省 30000,下限 500;`0` 不自动停,停止权交调用方) | `{ ok, record }`;`record.nodes[]` 含每节点 `id/name/nameSource/status/ms/seq/msgs`,`startSeq/endSeq` 用于把节点事件对回帧序,`record.retention` 是任务边界帧。`name` 取自框架的 `node_details.name`(`nameSource: "node_details"` = 权威);框架在 `PipelineNode.*` 顶层填的是**任务入口名**,所以完成前该字段可能是 `entry` 兜底值(`nameSource: "entry"` = 未确认,此时别拿它当"跑了哪个节点")。`record.nextCandidates` 是框架本轮**宣布过的候选序列**(`{seq, name, polls, jumpBack?, anchor?}`,连续重复折叠成 `polls` 计数,上限 50 条;`nextCandidateCount` 是公告总次数)——分支可见性用它,配合节点名即可还原"轮询了哪些出口、最后走了哪个"。超时内置 `post_stop`,防 JumpBack 死循环 |
|
|
62
|
+
| `run_stop` | — | `{ ok }` |
|
|
63
|
+
| `input` | `kind: click\|dbclick\|press\|swipe\|key\|keys\|scroll\|move\|text\|app\|touch_down\|touch_move\|touch_up` + 对应参数(见下) | `{ ok, kind, ms, retention, path?, viaCount?, stepMs? }`;`retention.before/.after` 是动作边界帧,自动入 L0 锚区。**after 帧统一在 `job.wait()` 之后抓**,所以它含动作落地后的画面(手势类尤其要紧:要验的是"抬手之后变成什么样") |
|
|
64
|
+
| `reco_test` | `resourceDir`、`type`、`image` / `seq` / **`kfSource?`**(关键帧库留存帧:`{id,path,sha256,ctrlW,ctrlH,captureSeq,capturedAt}`,控制器分辨率、**不需要设备**,与 `image`/`seq` 三选一)、`param?`、`sweep?{key,min,max,step}`、`node?`(整节点 JSON 透传,V1/V2 由框架解析)、`act?`(配 `node`:识别拿框 → 真机执行动作半)、`templateImage?`(调用方裁好的模板,不写资源目录) | `{ ok, type, meta, results[] }`;`meta.source` 是 kf ID / 文件路径 / `seq`(库帧路径如实报 kf 身份)。`act` 时含 `reco/action/stage/retention`。除 `act` 外在**一次性子进程**里执行:beta 绑定遇到无效模板会原生崩溃,子进程炸掉只损失一次测试。**识别跑了但一个置信度都没带回来**(`results[].detail.detail.best` 空、`all` 为空)与"分数低的未命中"是两种形态:前者是模板/资源当时读不到,别当画面不匹配处置 |
|
|
65
|
+
| `shutdown` | — | 停流 + 断开 + 退出进程;回执 `{ ok:true }` **先落地再退**(`process.exit` 不冲 stdout,而调用方在等这条应答) |
|
|
66
|
+
|
|
67
|
+
`input` 的参数:`click{x,y,contact?,pressure?}`、`dbclick{x,y,gap?}`、`press{x,y,duration?,contact?,pressure?}`、
|
|
68
|
+
`swipe{x1,y1,x2,y2,duration?,contact?,pressure?,via?}`、`key{code}`(Android KeyEvent 码)、
|
|
69
|
+
`keys{codes,hold?}`(数组或逗号/加号分隔)、`scroll{dx,dy}`(格数,建议 120 的倍数)、
|
|
70
|
+
`move{dx,dy}`(相对移动,Win32/MacOS)、`text{text}`、`app{action:start\|stop,intent}`、
|
|
71
|
+
`touch_down{x,y,contact?,pressure?}` / `touch_move{x,y,contact?,pressure?}` / `touch_up{contact?}`。
|
|
72
|
+
全部经 maafw 控制器本体注入,不直接调 adb。
|
|
73
|
+
|
|
74
|
+
**`swipe` 的 `via`**:中途路径点(`[[x,y],…]` 或 `"x,y;x,y"`)。给了它就走
|
|
75
|
+
`touch_down → 逐点 touch_move → touch_up`,也就是**一条笔画画出折线**(回执带 `path`/`viaCount`/`stepMs`);
|
|
76
|
+
不给则走原生 `post_swipe`(只有两个端点,只能画直线)。**为什么需要**:连线 / 谱曲 / 拖拽排序这类玩法
|
|
77
|
+
要求"按住 → 经过若干点 → 抬起",且**中途不能抬手**——一旦抬手就是新笔画,很多玩法会把新笔画当成
|
|
78
|
+
另一次操作或直接静默忽略。需要跨多条命令维持同一次按压时,用裸原语
|
|
79
|
+
`touch_down` / `touch_move` / `touch_up` 自己拼序列。
|
|
80
|
+
|
|
81
|
+
## 坐标系(最容易出错的一条)
|
|
82
|
+
|
|
83
|
+
识别与输入共用**控制器分辨率空间**:默认按 interface.json 的 `display_short_side`(缺省短边 720)
|
|
84
|
+
缩放。`roi`、模板图、`click` 坐标全在同一空间。只有显式传 `shortSide`/`longSide`/`rawSize`
|
|
85
|
+
才会改变它——那意味着项目里所有 roi 与模板都要按新尺寸重算。
|
|
86
|
+
|
|
87
|
+
## 落盘约定(`out` 类参数)
|
|
88
|
+
|
|
89
|
+
会产出文件的命令(`screencap` / `frame_get` / `tpl_crop` / `annotate`)都接受 `out` 指定路径。
|
|
90
|
+
**不传 `out` 时落在 daemon 的 run 目录下 `out/`(一次性命令给的是临时目录),不写调用方的 cwd**——
|
|
91
|
+
调用方的 cwd 通常就是项目仓库,而这些命令本质是"看/量",产物只是顺便落盘(实测踩过两次:
|
|
92
|
+
`maa_frame_*.png`、`maa_som_*.png` 进了仓库根)。要留住产物就显式给 `out`。
|
|
93
|
+
|
|
94
|
+
## 稳定性约定
|
|
95
|
+
|
|
96
|
+
- 命令**只增不改**:参数名与返回字段名一律不重命名,删字段等于静默破坏所有消费者。
|
|
97
|
+
- 新增命令请同时更新本文件与 `src/client/daemon.ts` 的类型。
|
|
98
|
+
- 帧元数据与事件的字段同样只增不改(面板与 timing 反推都按字段名读取)。
|
|
99
|
+
|
|
100
|
+
## 复用示例
|
|
101
|
+
|
|
102
|
+
import { spawnDaemon } from '@windsland52/maafw-live/client'
|
|
103
|
+
|
|
104
|
+
const c = spawnDaemon({ runDir: '/tmp/maa-run' })
|
|
105
|
+
try {
|
|
106
|
+
const info = await c.call('probe')
|
|
107
|
+
await c.call('connect', { kind: 'win32', target: '记事本' })
|
|
108
|
+
await c.call('stream_start', { fps: 10 })
|
|
109
|
+
c.subscribe('event', (ev) => console.log(ev))
|
|
110
|
+
const shot = await c.call('frame_get', { roi: [0, 0, 200, 120] })
|
|
111
|
+
console.log(shot.path)
|
|
112
|
+
} finally {
|
|
113
|
+
c.close()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
CLI 侧的等价物是 `maafw-live repl`(一次连接、多步操作),以及 `maafw-live probe|device|connect|run|click|reco` 等一次性命令。
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# 关键帧工具 — 源码核查与对齐备注
|
|
2
|
+
|
|
3
|
+
> 2026-10-03,只读源码核查,无设备运行或实机复现;行号对应重构前基线 commit `85dae0e`,仅用于定位。
|
|
4
|
+
>
|
|
5
|
+
> **主契约唯一入口:[关键帧留存与引用契约](keyframe-retention-contract-v0.md)。** 本文件曾是并行草案,现收敛为核查记录,不定义第二套 schema 或命令。技能侧配套(MaaTutorial 仓):`skills/maafw-pipeline/references/state-plan.md` · `skills/docs/state-plan-review-brief.md`。
|
|
6
|
+
|
|
7
|
+
## 1. 核查基线与结论
|
|
8
|
+
|
|
9
|
+
本仓 commit `85dae0e7f075f9e9eb6af3253759d7050d88a5e1`(重构前基线),核查时工作树干净。已实现“常驻设备会话+降采样帧环+同源节点事件”,**未实现耐久关键帧库**。主契约中的 L0 原图缓存、稳定 ID、升格与解析均为待补能力。
|
|
10
|
+
|
|
11
|
+
| 能力 | 已核查行为 | 对齐影响 |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| 会话 | 单 daemon 持有一个 Controller / Resource / Tasker | 宿主保持生命周期及设备独占,不另开采集链 |
|
|
14
|
+
| 采样 | 默认动态 8fps,稳定后约 1Hz | 可在 LLM 思考期间继续采样,但不保证捕获全部瞬态 |
|
|
15
|
+
| 帧环 | 默认 120 帧、可设 20–600;长边默认 480;FIFO | 仅保留小图,无同次捕获原图留存 |
|
|
16
|
+
| 预览 | preview.png 原子替换 | 固定路径不是稳定帧身份 |
|
|
17
|
+
| frame_get | 指定 seq 导出尚存小图,支持 roi / out | 可复用导出机制,但不能恢复已丢弃原图 |
|
|
18
|
+
| screencap | 另拍控制器分辨率 PNG,无 seq / t | 是另一次捕获,不能补回消失状态 |
|
|
19
|
+
| input | 返回 ok / kind / ms | 无动作 ID、前后图关联,不触发抓帧或提频 |
|
|
20
|
+
| 节点事件 | 回调时记录最新 S.seq | 时间邻近,不是引擎识别图的精确绑定 |
|
|
21
|
+
| 重生 | 超时杀进程、下次调用重生,需要重新 connect | 不恢复帧环、资源、现场或 checkpoint |
|
|
22
|
+
|
|
23
|
+
裸 seq 重启归零,同进程重连又可能混入旧帧;客户端元数据存在也不代表像素仍可取。历史 ROI 用当前全局 fullW/fullH 换算,缺少每帧原尺寸。主契约据此要求完整捕获身份及 per-frame 坐标依据。
|
|
24
|
+
|
|
25
|
+
## 2. 现有接口消费注意事项
|
|
26
|
+
|
|
27
|
+
已并入主契约 §7「消费注意(现有接口,核查基线)」,此处不再重复。
|
|
28
|
+
|
|
29
|
+
## 3. 接入前问题(静态确认,未修复)
|
|
30
|
+
|
|
31
|
+
三处已证实缺陷(识别参数透传、CLI 成功语义、旧帧 ROI 尺寸)以主契约 §7 的待修表为唯一一份,此处不再重复。原图留存、动作边界采集、帧身份属于新增能力,不混称为已有接口的 bug。
|
|
32
|
+
|
|
33
|
+
## 4. 原始讨论与合并裁决
|
|
34
|
+
|
|
35
|
+
- 2026-09-11 的讨论提出常驻帧流、变化检测与环形缓冲,支持等稳定和历史回看。
|
|
36
|
+
- 2026-09-12 定案废弃独立轨迹层,改为帧流+节点事件同源;本次不重新建设全量轨迹系统。
|
|
37
|
+
- 早期评估曾否决独立 CLI,但当前仓库已提供 CLI;接口以当前代码为准。
|
|
38
|
+
|
|
39
|
+
并行草案已收敛:
|
|
40
|
+
|
|
41
|
+
| 分歧 | 采用方案 | 理由 / 边界 |
|
|
42
|
+
| --- | --- | --- |
|
|
43
|
+
| 小图是否可作正式 state 证据 | 保留主契约的严格 L0-only 策略 | L1 仅参考,不自动满足正式证据门;未声称这是所有观测场景唯一选择 |
|
|
44
|
+
| checkpoint 是否改名 focus | 保留 checkpoint,收窄为开发断点 | 不为概念澄清引入额外字段迁移;本地现场单独确认 |
|
|
45
|
+
| 自动留存还是人工升格 | 自动入有界锚区,显式升格 | 沿用最新草案的责任划分;仍有等待过久被淘汰的边界 |
|
|
46
|
+
| 裸 kf 序号 | 补库 UUID,完整 ID 不透明 | 防止不同库同序号误解析;副本 v0 不支持多写者合并 |
|
|
47
|
+
| 事件 seq 能否证明当前位置 / 转移 | 不能,分别补本地确认及人工证据核对 | 时间邻近与执行历史不是业务状态断言 |
|
|
48
|
+
|
|
49
|
+
主契约规定新能力的语义;精确 JSON schema、命令名和预算仍待实施前确定。本文不再列出另一套候选 API。
|
|
50
|
+
|
|
51
|
+
## 5. 来源定位
|
|
52
|
+
|
|
53
|
+
行号对应 §1 基线(重构前 commit `85dae0e`),路径相对本仓根。
|
|
54
|
+
|
|
55
|
+
- `src/daemon/framed.mjs:188-203,275-278,357-362`:状态、默认值与连接。
|
|
56
|
+
- `src/daemon/framed.mjs:365-499`:截图、采样、帧环、导出与 ROI。
|
|
57
|
+
- `src/daemon/framed.mjs:550-618,630-714`:节点事件、运行、输入、识别调用。
|
|
58
|
+
- `src/daemon/reco_child.mjs:25-36,50-57,70-85`:识别参数与坐标。
|
|
59
|
+
- `src/client/daemon.ts:52-78,128-169,189-194`:订阅、回复、生命周期。
|
|
60
|
+
- `src/commands/runtime.ts:323-336`:CLI 退出码。
|
|
61
|
+
- `src/commands/repl.ts:46-49,163-164,200-203`:输出截断。
|