mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-15 12:15:16 +00:00
## 拆解 Coopanion(用户指出的参考项目)
新增 DESKTOP-PET-NOTES.md,记录 Pal-AI-Lab/Coopanion 的桌宠动作系统
(packages/cortico-world-desktop-pet):
· 4 个工具:pet_say(气泡说话,可夹带动作标记) / pet_ask(提问,异步回答) /
pet_walk_to(走到 0-1 比例或 left/center/right/cursor,含走到鼠标位置) /
pet_act(只做动作; sit/sleep 是持续态)
· 动作词表 VOCAB:10 个表情 + 13 个动作,每个词多个中文别名,
英文 id 与中文别名进同一张 Map;未知词丢弃而非报错
· 脚本标记:【词,词】= 先做动作再换新气泡;<词> = 打字到那里时做、不换气泡。
让文字与动作在时间轴上对齐 —— 实现成本低但表现力强
· 设计要点:身体是 agent 唯一可见输出通道(用户看不见消息正文);
互动事件不单独唤醒模型,跟着下批事件到
可借鉴(已写入文档待办):姿态表达状态、动作与文字对齐、持续态、
动作解析容错、待机自主行为。不建议照搬:桌宠承载全部对话输出、ASR、戳摸甩。
## 注入层改 SendInput(参考它的实现)
原 PostMessage 路径只能操作原生 Win32 程序 —— Chromium/Electron 自绘界面
与游戏会忽略合成消息。这不是「平台硬限制」,是所选 API 的限制。
Coopanion 用 SendInput 注入系统输入队列,可靠性高得多。
新增三档模式 gui-prefs.deviceBridge.agentCursor:
sendinput(默认) SendInput,可靠;会动真光标,故先等用户停手
overlay PostMessage,不动用户鼠标;只能操作原生程序
real 保留旧代码路径
另有 deferMs:用户活动时最多等多久(默认 3000,上限 10000)
★ SendInput 绝对坐标是 0–65535 归一化值,不是像素。按虚拟桌面归一化
并带 MOUSEEVENTV_VIRTUALDESK,多屏下也正确(Coopanion 只按主屏归一化)。
★ 文本用 KEYEVENTF.UNICODE 逐字符发,不受键盘布局与输入法影响(比 WM_CHAR 可靠)。
## 修「用户活动感知」把自己也算进去的 bug
GetLastInputInfo 是**全局**的 —— agent 自己的 SendInput 也被算作「用户输入」。
实测:deferMs 设 0 仍被暂缓,回执写「开始时空闲仅 0ms」,即上一条命令的
注入刚把 idleMs 清零。若不排除自身注入,会永远处于「用户正在操作」状态。
按 Coopanion 的三重判定修正:
1. ownTick 自己最后一次注入时刻,之后 40ms 内的输入忽略
2. ownCursor 自己放下的光标位置,偏离 >2px 说明是用户动的
3. 无符号差值 GetTickCount 每 2^32ms 回绕,必须按无符号比较
## 修同一条命令被执行两遍
sendinput 分支把逻辑包进 runAction 箭头函数,里面的 return 只退出它自己,
于是执行完继续 fall through 到旧 real 路径。实测 8 条命令回了 16 个
cmd_result,且真光标被额外抢一次。补上外层 return。
## 判据
computeruse.test.mjs 47 条(npm test 共 131 条全绿),新增 4 条钉住本轮
踩过的坑,均做变异验证:
· 三档模式存在(overlay/real 只在条件里,不能要求赋值形式)
· 用户活动排除自身注入;且盯 send() 里的**调用点** —— 只查函数名时
删掉调用仍会误报绿(已实测并改正)
· SendInput 坐标归一化 + VIRTUALDESK
· sendinput 分支必须 return(防双执行)
## 待办(已写入 GUI-PLAN.md)
· loadGuiPrefs 不透传 agentCursor,导致 deferMs 配置读取不生效(实测)
· 显式调 SetProcessDpiAwarenessContext(-4),不依赖 Electron 默认值
· mascot 姿态:思考/等待/出错
· 真实点击效果仍未验证(当前环境无可注入的可见窗口)
229 lines
9.7 KiB
Markdown
229 lines
9.7 KiB
Markdown
# 桌宠动作系统:Coopanion 拆解与可借鉴点
|
||
|
||
> 来源:`Pal-AI-Lab/Coopanion`(本机 clone 于 `/tmp/coop`)
|
||
> 相关包:`packages/cortico-world-desktop-pet/`
|
||
>
|
||
> 本文只记录**桌宠的动作/表情/互动系统**,以及本仓(HomeAgent GUI)可借鉴什么。
|
||
> 输入注入层(SendInput)的对比见 `GUI-PLAN.md` 与下文第七节。
|
||
|
||
---
|
||
|
||
## 一、它是什么
|
||
|
||
一个 C 形小生物,站在屏幕底边(任务栏上沿)。**用户看得见它的身体,看不见 agent 的消息正文** ——
|
||
想让用户看到内容,只能通过它冒气泡。
|
||
|
||
这句话是整个设计的关键:
|
||
|
||
> 桌宠身体是 agent 的**唯一可见输出通道**,而不是装饰。
|
||
|
||
这和我们 GUI 的定位不同:我们的 mascot 只是「agent 光标」,不承载信息。这是一个可以借的点。
|
||
|
||
---
|
||
|
||
## 二、核心工具(4 个)
|
||
|
||
| 工具 | 作用 | 关键设计 |
|
||
|---|---|---|
|
||
| `pet_say` | 头顶冒气泡说话 | 脚本里可夹带**动作标记**,边说边做 |
|
||
| `pet_ask` | 冒提问气泡 + 最多 3 个选项 | **立即返回**,回答通过 `[回答]` 事件异步送达 |
|
||
| `pet_walk_to` | 沿屏幕底边走到某处 | 目标是 0–1 比例或 `left/center/right/cursor` |
|
||
| `pet_act` | 不说话,只做一串动作 | `sit`/`sleep` 会**持续**到下个动作 |
|
||
|
||
### 值得注意的三个设计
|
||
|
||
1. **`pet_ask` 是异步的**。agent 提完问不阻塞,用户答了再通过事件唤醒。
|
||
避免 agent 干等,也允许用户先不答。
|
||
实用细节:新问题会**替换**还没回答的旧问题;关掉不答也会回一个事件,
|
||
agent 不会永远等下去。
|
||
|
||
2. **`pet_walk_to` 支持 `cursor`** —— 走到鼠标当前横向位置。
|
||
这是个很自然的"到我这儿来"表达,不需要坐标。
|
||
|
||
3. **`sit`/`sleep` 是持续态**,其他动作是一次性。
|
||
这让 agent 能表达"待机"与"我在休息",而不只是瞬间动画。
|
||
|
||
---
|
||
|
||
## 三、动作词表(`src/script.ts` 的 `VOCAB`)
|
||
|
||
它把动作分成两类:**表情**(持续几秒后回到平常脸)与**动作**。
|
||
|
||
### 表情(expression,10 个)
|
||
|
||
| id | 中文 | 表现 |
|
||
|---|---|---|
|
||
| `neutral` | 平静 | 默认的脸 |
|
||
| `happy` | 开心 / 高兴 | 眼睛弯成 ^ ^ |
|
||
| `wink` | 眨眼 | 一只眼 ^ |
|
||
| `love` | 喜欢 / 爱心 | 眼睛变心形,冒小心心 |
|
||
| `shy` | 害羞 | 脸红,眼神躲开 |
|
||
| `surprised` | 惊讶 / 吃惊 | 眼睛放大,头顶感叹号 |
|
||
| `angry` | 生气 | 皱眉,头顶怒气符号,身体发抖 |
|
||
| `sad` | 难过 / 伤心 | 八字眉,掉眼泪 |
|
||
| `sleepy` | 犯困 / 困 | 眯眼打哈欠 |
|
||
| `thinking` | 思考 / 想想 | 眼睛往上看,头顶冒圈 |
|
||
|
||
### 动作(motion,13 个)
|
||
|
||
| id | 中文 | 表现 |
|
||
|---|---|---|
|
||
| `stand` | 站起 / 站 | 站起来(坐着、睡着时) |
|
||
| `jump` | 跳 / 跳起来 | 原地起跳 |
|
||
| `hop` | 小跳 / 蹦 | 小小蹦一下 |
|
||
| `look` | 张望 / 看看 | 左右张望 |
|
||
| `turn` | 转身 | 转向另一边 |
|
||
| `nod` | 点头 | 点两下头 |
|
||
| `shake` | 摇头 | 摇头 |
|
||
| `spin` | 转圈 | 原地转一圈 |
|
||
| `sit` | 坐下 / 坐 | **坐下,持续到下个动作** |
|
||
| `sleep` | 睡觉 / 睡 | **躺下睡觉,持续到下个动作** |
|
||
| `dizzy` | 晕 / 转晕 | 头晕眼花几秒 |
|
||
| `walk` | 走走 / 散步 | 随便走一段 |
|
||
| `run` | 跑 / 跑起来 | 跑到屏幕另一头 |
|
||
|
||
### 词表设计的可借鉴处
|
||
|
||
- **每个词有多个中文别名**(`开心`/`高兴` 同一个 id)。LLM 输出自然语言时容错率更高。
|
||
- **英文 id 与中文别名都进同一张 Map**(`BY_WORD`),不区分来源。
|
||
- **未知词被丢弃而非报错**(`dropped` 数组收集但不中断)。
|
||
对 LLM 输出的容错策略,值得学。
|
||
- 词表通过 `pet.vocab` 变量**注入到 prompt**,让模型知道能做什么 ——
|
||
而不是把 30 个动作硬编码进 tool schema 的 enum。
|
||
|
||
---
|
||
|
||
## 四、脚本标记(说明与动作混排)
|
||
|
||
`pet_say` 的 `script` 参数里可以夹带两种标记:
|
||
|
||
| 标记 | 作用 |
|
||
|---|---|
|
||
| `【词, 词】` | 先做这些动作,然后**换一个新气泡**继续说 |
|
||
| `<词>` | 打字打到这里时做,**不换气泡** |
|
||
|
||
示例(源码里的原例):
|
||
|
||
```
|
||
【开心】今天也辛苦啦!<眨眼>要不要歇一会儿?
|
||
```
|
||
|
||
这个设计解决了一个真实问题:**让文字与动作在时间轴上对齐**。
|
||
`<眨眼>` 落在句子中间,让"眨眼"发生在念到那个字的时候,而不是整句之前。
|
||
比"先做动作再说话"生动得多,实现成本却很低(解析时切分即可)。
|
||
|
||
相关实现:`parseScript` / `parseActions` / `estimateSeconds`(`src/script.ts`),
|
||
`estimateSeconds` 用于让回执报告"大约显示多久"。
|
||
|
||
---
|
||
|
||
## 五、自主行为与互动
|
||
|
||
### 待机(没有指令时)
|
||
|
||
> 没收到指令时身体会自己眨眼、张望、散步、坐下打盹;你的指令会先执行。
|
||
|
||
注意最后半句:**agent 指令优先于自主行为**。自主行为是填充,不是竞争。
|
||
|
||
### 用户互动(事件形式回给 agent)
|
||
|
||
| 事件 | 含义 |
|
||
|---|---|
|
||
| `[语音] 用户:…` | 麦克风听到并转写的话(可能同音错字) |
|
||
| `[打字] 用户:…` | 在桌宠上双击或点悬停按钮打的字 |
|
||
| `[回答] …` | 对 `pet_ask` 的回答 |
|
||
| `[互动] …` | 戳、摸、拎起来甩出去,以及落地摔晕 |
|
||
|
||
细节值得注意:
|
||
|
||
- 互动事件**不单独唤醒 agent**,"跟着下一批事件到,可能已经过去一会儿"
|
||
—— 避免用户随手一戳就把模型叫起来烧钱。
|
||
- `asleep` 状态下被摸,文案不同(`摸了摸睡着的你`)。
|
||
状态影响事件语义,而不只是影响动画。
|
||
|
||
---
|
||
|
||
## 六、对本仓(HomeAgent GUI)的可借鉴点
|
||
|
||
### 6.1 可以借鉴(成本低、收益明确)
|
||
|
||
| 点 | 现状 | 建议 |
|
||
|---|---|---|
|
||
| **动作与文字时间轴对齐**(`【】`/`<>`) | 我们的 mascot 只有移动/点击两种状态 | 若要表达"思考/等待/出错",可引入轻量标记 |
|
||
| **姿态表达状态** | 「忙碌呼吸」一种 | 可区分:思考中(转圈)、等待用户(张望)、出错(晕) |
|
||
| **持续态 vs 一次性**(`sit`/`sleep`) | 无持续态 | agent 空闲/被禁用时用持续态表达,而非静止 |
|
||
| **LLM 输出容错**(未知词丢弃不报错) | 未处理 | 动作解析应容错,不能因一个错词整条失败 |
|
||
| **待机自主行为** | 无 | 光标闲置时可轻微眨眼/呼吸,让"活着"可感知 |
|
||
|
||
### 6.2 不建议照搬
|
||
|
||
| 点 | 原因 |
|
||
|---|---|
|
||
| **桌宠承载全部对话输出** | 我们是桌面客户端,有完整聊天界面,不该退化到只能冒气泡 |
|
||
| **语音输入(ASR)** | 与本期目标无关,且引入麦克风权限问题 |
|
||
| **戳/摸/甩出去** | 需要模型 + 物理引擎,投入产出比低 |
|
||
| **`pet.vocab` 注入 prompt** | 我们没把 mascot 交给 LLM 控,动作由 computeruse 隐式驱动 |
|
||
|
||
### 6.3 一个更重要的判断
|
||
|
||
**Coopanion 的桌宠和我们的"独立光标"解决的是不同问题:**
|
||
|
||
- 它的桌宠 = **输出通道**(让用户看见 agent 想说什么)
|
||
- 我们的 mascot 光标 = **过程指示**(让用户看见 agent 在操作哪里)
|
||
|
||
两者不冲突,甚至可以合并 —— 让我们的 mascot 在 agent 思考时表现"思考"姿态,
|
||
在执行 computeruse 时表现"移动/点击"。这正是 6.1 表格里那几条的价值所在。
|
||
|
||
---
|
||
|
||
## 七、接口层对比(顺带记录,详见 GUI-PLAN.md)
|
||
|
||
它和我们在**输入注入**上的关键分歧:
|
||
|
||
| | Coopanion | 本仓(HomeAgent GUI) |
|
||
|---|---|---|
|
||
| 注入 API | `SendInput` | `SendInput`(默认)/ `PostMessage`(可选) |
|
||
| 能否操作 Chromium/游戏 | ✅ 能 | ✅ 能(sendinput 模式) |
|
||
| 是否移动真实光标 | ✅ 会移动 | ✅ 会移动(sendinput)/ ❌ 不动(overlay) |
|
||
| 用户操作时 | 等待空闲再注入 | 等待空闲再注入(**借鉴自它**) |
|
||
| 自绘光标 | ❌ 没有 | ✅ 有(mascot + 点击脉冲) |
|
||
|
||
### 从它学到的关键一条
|
||
|
||
`GetLastInputInfo` 是**全局**的 —— agent 自己的 `SendInput` 也会被算作"用户输入"。
|
||
若不排除自己的注入,`idleMs()` 每次注入后归零,于是**永远处于"用户正在操作"状态**,
|
||
所有命令都被暂缓。
|
||
|
||
它的做法(我们已照此实现)是三重判定:
|
||
|
||
1. `ownTick` —— 自己最后一次注入的时刻,之后 40ms 内的输入忽略
|
||
2. `ownCursor` —— 自己放下的光标位置,偏离 >2px 说明是用户动的
|
||
3. 无符号差值 —— `GetTickCount` 每 2^32 ms 回绕,必须按无符号比较
|
||
|
||
这个坑我们实测踩到了(`deferMs:0` 仍被暂缓),教训已写进 `agent-inject.js` 注释。
|
||
|
||
---
|
||
|
||
## 八、其他独立印证
|
||
|
||
Coopanion 独立印证了我们上一轮的两个判断:
|
||
|
||
1. **坐标就是物理像素,不要换算** —— 它注释明写:
|
||
> All coordinates are physical pixels of the primary display. The process declares
|
||
> itself per-monitor DPI aware (v2) before anything else, so no API here returns scaled values.
|
||
|
||
我们曾错误地加了 `/scaleFactor`,已改回并在判据中钉住。
|
||
|
||
2. **必须显式声明 DPI 感知** —— 它显式调用 `SetProcessDpiAwarenessContext(-4)`,
|
||
并注明 "before anything else"。我们是**依赖 Electron 默认值**,未显式设。
|
||
虽然 Electron 默认已是 per-monitor aware,但显式声明更稳。**建议补上**(见待办)。
|
||
|
||
---
|
||
|
||
## 九、待办(由本文档派生)
|
||
|
||
- [ ] `main.js` 显式调用 `SetProcessDpiAwarenessContext(-4)`,不依赖 Electron 默认值
|
||
- [ ] 修 `deferMs` 配置读取不生效的问题(实测 `deferMs:0` 仍走 3000ms 默认)
|
||
- [ ] mascot 增加姿态:思考 / 等待 / 出错(参考第三节词表)
|
||
- [ ] 动作解析容错:未知动作丢弃而非整条失败
|
||
- [ ] (可选)mascot 待机自主行为:闲置时轻微眨眼/呼吸 |