@honor-claw/yoyo 2026.7.1-beta.13 → 2026.7.1-beta.14
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/dist/core/prompt/builder.mjs +8 -10
- package/package.json +1 -1
- package/skills/yoyo-file-transfer/SKILL.md +17 -8
- package/skills/yoyo-file-transfer/references/pc-to-phone.md +3 -5
- package/skills/yoyo-file-transfer/references/phone-to-pc.md +3 -5
- package/skills/yoyo-phone-control/SKILL.md +8 -8
- package/skills/yoyo-phone-control/references/local-user-file-search.md +7 -0
- package/dist/core/prompt/location.mjs +0 -7
- package/skills/yoyo-file-transfer/references/phone-to-phone.md +0 -189
|
@@ -1,26 +1,24 @@
|
|
|
1
1
|
import { resolveAgentPromptContribution as e } from "./agent.mjs";
|
|
2
2
|
import { resolveCloudToolPromptContribution as t } from "./cloud-tool.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import { resolveSkillPromptContribution as r } from "./skill.mjs";
|
|
3
|
+
import { resolveSkillPromptContribution as n } from "./skill.mjs";
|
|
5
4
|
//#region src/core/prompt/builder.ts
|
|
6
|
-
function
|
|
5
|
+
function r(e) {
|
|
7
6
|
let t = e.filter((e) => !!e);
|
|
8
7
|
return t.length > 0 ? t.join("\n\n") : void 0;
|
|
9
8
|
}
|
|
10
|
-
function
|
|
11
|
-
let t =
|
|
9
|
+
function i(e) {
|
|
10
|
+
let t = r(e.map((e) => e.prependSystemContext)), n = r(e.map((e) => e.prependContext));
|
|
12
11
|
return {
|
|
13
12
|
...t ? { prependSystemContext: t } : {},
|
|
14
13
|
...n ? { prependContext: n } : {}
|
|
15
14
|
};
|
|
16
15
|
}
|
|
17
|
-
async function
|
|
18
|
-
return
|
|
16
|
+
async function a() {
|
|
17
|
+
return i([
|
|
19
18
|
t(),
|
|
20
19
|
e(),
|
|
21
|
-
await
|
|
22
|
-
n()
|
|
20
|
+
await n()
|
|
23
21
|
]);
|
|
24
22
|
}
|
|
25
23
|
//#endregion
|
|
26
|
-
export {
|
|
24
|
+
export { a as resolvePromptBuildContribution };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yoyo-file-transfer
|
|
3
|
-
description: 通过 YOYO 控虾在同账号已连接设备之间远场传输文件(PC
|
|
3
|
+
description: 通过 YOYO 控虾在同账号已连接设备之间远场传输文件(PC↔手机,如"把电脑上的XX传到手机")。用户用自然语言指定源端和目标端,skill 按"上传→跨端引用→下载"串联完成传输。跨端文件交付走本 skill;设备能力操作(音量/截屏/打开App)走 yoyo-phone-control。不处理:手机→手机(暂不支持)、仅生成/搜索/预览、发给联系人或 App、上传用户自有网盘、非同账号设备、本地文件改名/移动等单端文件管理。
|
|
4
4
|
metadata: { "openclaw": { "emoji": "📤", "always": false } }
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -20,8 +20,8 @@ metadata: { "openclaw": { "emoji": "📤", "always": false } }
|
|
|
20
20
|
|
|
21
21
|
## 触发条件
|
|
22
22
|
|
|
23
|
-
- **正例**:用户要把文件"发送/传到/下载到/保存到/放到"某个同账号可校验的目标设备(含 PC→手机、手机→PC
|
|
24
|
-
-
|
|
23
|
+
- **正例**:用户要把文件"发送/传到/下载到/保存到/放到"某个同账号可校验的目标设备(含 PC→手机、手机→PC,如"把电脑上的XX传到手机")。
|
|
24
|
+
- **反例**:手机→手机(暂不支持,如"把这台手机的照片传到另一台手机"→ 告知用户暂不支持该方向);仅生成/搜索/预览/要链接;发给联系人/App/网盘;非同账号;本地改名/移动;本地处理但不交付。
|
|
25
25
|
- **目标设备是手机 ≠ 手机控制**:只要动作是"把文件交付到某设备"(传/发/同步/导 + 跨设备目标)就走本 skill,**不走 yoyo-phone-control**。yoyo-phone-control 处理的是设备能力(音量/截屏/打开 App/本机单端文件操作),**不处理跨端文件交付**。query 含"手机"二字不等于手机控制任务。
|
|
26
26
|
- 源/目标不明确 → 先追问。
|
|
27
27
|
|
|
@@ -31,7 +31,6 @@ metadata: { "openclaw": { "emoji": "📤", "always": false } }
|
|
|
31
31
|
|---|---|---|---|
|
|
32
32
|
| PC → 手机 | `references/pc-to-phone.md` | PC | 手机 |
|
|
33
33
|
| 手机 → PC | `references/phone-to-pc.md` | 手机 | PC |
|
|
34
|
-
| 手机 → 手机 | `references/phone-to-phone.md` | 手机 | 手机 |
|
|
35
34
|
|
|
36
35
|
> 平板按注册的工具族判定:openclaw 当 PC、YOYO 当手机。不支持 PC → PC。
|
|
37
36
|
|
|
@@ -46,14 +45,11 @@ metadata: { "openclaw": { "emoji": "📤", "always": false } }
|
|
|
46
45
|
| 多候选文件 | 找到以下多个文件,请选择:1. {名1}({路径1})… |
|
|
47
46
|
| 重名 | 存在重名文件,请选择(带格式、带路径):… |
|
|
48
47
|
| 多设备候选 | 找到多台{设备类型}(如 {设备1}、{设备2}),要传到哪一台? |
|
|
49
|
-
|
|
|
50
|
-
| 泛指一类文件("照片"/"文档"/"录音"等) | 要传哪些?最近一张 / 最近几张 / 全部 / 指定某些?(先搜后问还是直接问,按该方向 reference) |
|
|
48
|
+
| 泛指一类文件("照片"/"文档"/"录音"等) | **先搜后问**:按方向 reference 搜出候选后,走上方「多候选文件」话术列出候选让用户选;仅方向 reference 无搜索步骤或搜索不可用时,才直接问:要传哪些?最近一张 / 最近几张 / 全部 / 指定某些? |
|
|
51
49
|
| 源/目标不明确 | 您要把文件传到哪个设备?(手机/电脑/平板) |
|
|
52
50
|
|
|
53
51
|
## 提醒话术
|
|
54
52
|
|
|
55
|
-
- 上传中:发送端({设备名})文件上传中,请勿发送新指令,否则会中断传输。
|
|
56
|
-
- 下载中:目标端({设备名})文件下载中,请勿发送新指令,否则会中断传输。
|
|
57
53
|
- 收尾(用户取消时用一句话,其余场景一律走「Summary 模板」表格):用户取消 → 已取消本次传输。
|
|
58
54
|
|
|
59
55
|
**发提醒的时机**(上传、下载各提醒一次,不刷屏):
|
|
@@ -62,6 +58,17 @@ metadata: { "openclaw": { "emoji": "📤", "always": false } }
|
|
|
62
58
|
- **下载提醒**:上传成功后,**调下载工具的同一条消息里先输出提醒、再调工具**。上传全失败 → 不调下载、不发下载提醒;跳过上传(已在云侧中转)→ 只发下载提醒。
|
|
63
59
|
- 提醒是「执行过程不展示给用户」的**唯一例外**;仍受「`nodes_status` 返回前不得承诺」约束——设备未定向前不发任何提醒。
|
|
64
60
|
|
|
61
|
+
## 轮询契约(task-result-query)
|
|
62
|
+
|
|
63
|
+
两方向轮询统一照此执行,references 不各自展开:
|
|
64
|
+
|
|
65
|
+
- `isFinal:false` = 任务仍在执行,是**正常中间态,不是失败信号**。大文件/多文件传输耗时长、轮询几十上百次都正常——**必须持续轮询直到 `isFinal:true`**;**唯一终止例外是「轮询超限」**:累计 `task-result-query` **轮询满 300 次**仍 `isFinal:false` → 停止轮询、如实报告(原因写"任务长时间未完成")。
|
|
66
|
+
- **轮询节奏**:每次轮询**间隔 1-3 秒**再发下一次(建议 2 秒),避免高频空转。
|
|
67
|
+
- **次数口径**:以实际发出的 `task-result-query` 调用次数计,逐次累加从 1 数到 300,**不依赖时间戳、不做时间估算**。
|
|
68
|
+
- 未满 300 次时不得以"轮询次数多 / 无进度更新 / 任务疑似卡住"为由中止或报告失败;**超限判定只能按上面 300 次规则**,**严禁**编造其他"超时/卡住/无响应"类原因。
|
|
69
|
+
- 仅在工具明确返回失败信号、或触发上述「轮询超限」时报告失败:`ok:false`、`isFinal:true`+`errorMessage`、文件级 `status:"FAILED"/"CANCELLED"`、或轮询满 300 次;原因复述工具返回文本 / 轮询超限原因。
|
|
70
|
+
- **`isFinal` 是唯一停止信号**:只要 `false` 就继续轮询(建议间隔 1-3 秒,轮询次数上限 300);一旦 `true` 即终态,取 `files[]`。任务不存在/已取消 → 返 `isFinal:true`+`errorMessage`+空 `files[]`,终止不卡死。
|
|
71
|
+
|
|
65
72
|
## 异常 → 回话(按工具返回的失败原因语义对表,不依赖错误码)
|
|
66
73
|
|
|
67
74
|
| 错误(工具返回) | 回话 |
|
|
@@ -75,6 +82,7 @@ metadata: { "openclaw": { "emoji": "📤", "always": false } }
|
|
|
75
82
|
| 空间不足 / 权限不足 / 路径无效 | 目标设备空间不足… / 需授权… / 路径无效… |
|
|
76
83
|
| 重名 | (工具自动 rename;必要时告知新名) |
|
|
77
84
|
| 用户主动取消("取消"/"不用传了") | 已取消本次传输(即「提醒话术·收尾」的取消回话) |
|
|
85
|
+
| 轮询超限(skill 侧判定,非工具返回:累计轮询满 300 次仍 `isFinal:false`) | {方向}任务长时间未完成,已中止 |
|
|
78
86
|
|
|
79
87
|
> 数量/大小上限由云侧权威判定,skill 不预检。
|
|
80
88
|
> 被新指令/abort 打断 → 不向用户提旧任务,直接转新任务(见「全局规则·任务中断即丢弃」)。
|
|
@@ -127,6 +135,7 @@ metadata: { "openclaw": { "emoji": "📤", "always": false } }
|
|
|
127
135
|
**失败处理**
|
|
128
136
|
|
|
129
137
|
- **失败即报告、严禁假装成功**:工具返回任何失败信号(`ok:false`、`error`/`errorMessage`、`status:"error"`、`status:"FAILED"/"CANCELLED"`,或报"unknown"/"not found"等)→ 立即停止、如实报告用户。**严禁假装成功、严禁编造**文件名/路径/fileId/accessUrl/结果——没拿到就是没拿到,失败就是失败,不粉饰。
|
|
138
|
+
- **轮询中间态不是失败、持续等待不推测**:`isFinal:false` 是正常中间态、不是失败信号,持续轮询直到 `isFinal:true`;轮询节奏 / 300 次超限判定等契约见「轮询契约」。任务执行中收到用户新指令 → 按「任务中断即丢弃」处理。
|
|
130
139
|
- **失败重试规则**:失败仅可在**同一工具、仅修正入参**后重调(如路径写错改对);**严禁换工具/换方法、严禁探查文件系统另寻出路**(见 `Never try a different approach`)。**最多重试 3 次**,仍失败则如实报告。**例外——以下属服务端权威判定(按失败原因语义识别,如"数量超限/文件过大/空间不足/未授权"),改入参无效,1 次失败即如实报告、零重试**:数量超限、单文件过大、空间不足、登录态缺失、权限不足(云侧/权限硬限制,重试结果不变)。
|
|
131
140
|
|
|
132
141
|
## 禁止操作
|
|
@@ -56,7 +56,7 @@ PC 源端用户在 PC。三种情况:
|
|
|
56
56
|
| `totalCount` / `successCount` / `failCount` | 总数 / 成功数 / 失败数 |
|
|
57
57
|
| `errorMessage?` | 整批级异常时 |
|
|
58
58
|
|
|
59
|
-
记下每个成功文件的 `fileId`+`accessUrl`。**调 `remote_file_upload` 前按 SKILL「提醒话术·时机」输出上传提醒**(同条消息先提醒再调工具;PC
|
|
59
|
+
记下每个成功文件的 `fileId`+`accessUrl`。**调 `remote_file_upload` 前按 SKILL「提醒话术·时机」输出上传提醒**(同条消息先提醒再调工具;PC 直调同步返回快,提醒仍要在调用前发出)。本方向发送端是 PC(本机 openclaw),上传提醒固定为"发送端(PC)文件上传中,请勿发送新指令,否则会中断传输。"。上传全失败 → 不下载直接报;部分成功 → 只下载成功项。
|
|
60
60
|
|
|
61
61
|
### 步骤 4:目标端下载(手机 node_invoke + 轮询)
|
|
62
62
|
|
|
@@ -96,11 +96,9 @@ PC 源端用户在 PC。三种情况:
|
|
|
96
96
|
❌ 错: files: { item: [ { ... } ] } // dict 包数组(贴合工具 schema 默认包装,最易写错)
|
|
97
97
|
❌ 错: files: { accessUrl, fileName, ... } // 裸对象
|
|
98
98
|
```
|
|
99
|
-
2. **查进度**:反复 `node_invoke({ nodeId, name:"task-result-query", params:{ task_id } })`,看顶层 `isFinal`——`false` → 继续轮询;`true` → 停,取 `files[]
|
|
99
|
+
2. **查进度**:反复 `node_invoke({ nodeId, name:"task-result-query", params:{ task_id } })`,看顶层 `isFinal`——`false` → 继续轮询;`true` → 停,取 `files[]`。**轮询契约(isFinal 判定 / 1-3 秒节奏 / 300 次超限 / 严禁自行推测结论)见 SKILL「轮询契约」,此处不重复**。
|
|
100
100
|
3. **拿结果**:`isFinal:true` 停止 → `files[]` 每文件终态,顶层带 `successCount`/`failCount`,整批级异常在 `errorMessage`
|
|
101
101
|
|
|
102
|
-
> **`isFinal` 是唯一停止信号**。任务不存在/已取消 → 返 `isFinal:true`+`errorMessage`+空 `files[]`,终止不卡死。
|
|
103
|
-
|
|
104
102
|
`task-result-query` 顶层字段:
|
|
105
103
|
|
|
106
104
|
| 字段 | 何时填 | 说明 |
|
|
@@ -123,7 +121,7 @@ PC 源端用户在 PC。三种情况:
|
|
|
123
121
|
| `fileName` | 文件名 |
|
|
124
122
|
| `failReason` | `FAILED` / `CANCELLED` 必带 |
|
|
125
123
|
|
|
126
|
-
`accessUrl`/`fileName`/`accessExpireTime` 原样来自 upload 出参,不得编造/丢失;`savePath` 每文件项自带(见下)。**派活 `remote_file_download` 前按 SKILL
|
|
124
|
+
`accessUrl`/`fileName`/`accessExpireTime` 原样来自 upload 出参,不得编造/丢失;`savePath` 每文件项自带(见下)。**派活 `remote_file_download` 前按 SKILL「提醒话术·时机」输出下载提醒**(同条消息先提醒再调工具),轮询过程中不再重复。本方向目标端是手机,下载提醒为"目标端({目标手机 nodeName})文件下载中,请勿发送新指令,否则会中断传输。",`nodeName` 取步骤 2 中选中目标手机的 `nodeName`。
|
|
127
125
|
|
|
128
126
|
**savePath**:每文件项各自的保存目录(不是批级)。用户指定 → 每项 `savePath` 都填该目录;未指定 → 每项填 `Download/OpenClaw`,不追问保存位置。
|
|
129
127
|
|
|
@@ -82,11 +82,9 @@ description: 手机 → PC 方向的完整执行流程。源端手机 node_invok
|
|
|
82
82
|
❌ 错: filePaths: "/storage/emulated/0/..." // 裸字符串
|
|
83
83
|
❌ 错: filePaths: { "item": [ "/storage/emulated/0/..." ] } // 对象包数组
|
|
84
84
|
```
|
|
85
|
-
2. **查进度**:反复 `node_invoke({ nodeId, name:"task-result-query", params:{ task_id } })`,看顶层 `isFinal`——`false` → 继续轮询;`true` → 停,取 `files[]
|
|
85
|
+
2. **查进度**:反复 `node_invoke({ nodeId, name:"task-result-query", params:{ task_id } })`,看顶层 `isFinal`——`false` → 继续轮询;`true` → 停,取 `files[]`。**轮询契约(isFinal 判定 / 1-3 秒节奏 / 300 次超限 / 严禁自行推测结论)见 SKILL「轮询契约」,此处不重复**。
|
|
86
86
|
3. **拿结果**:`isFinal:true` 停止 → `files[]` 每文件终态,顶层带 `successCount`/`failCount`,整批级异常在 `errorMessage`
|
|
87
87
|
|
|
88
|
-
> **`isFinal` 是唯一停止轮询信号**:只要 `false` 就继续查;一旦 `true`,`files[]` 必须齐全。任务不存在/已取消 → 端侧返 `isFinal:true`+`errorMessage`+空 `files[]`,终止不卡死。
|
|
89
|
-
|
|
90
88
|
`task-result-query` 顶层字段:
|
|
91
89
|
|
|
92
90
|
| 字段 | 何时填 | 说明 |
|
|
@@ -111,7 +109,7 @@ description: 手机 → PC 方向的完整执行流程。源端手机 node_invok
|
|
|
111
109
|
| `fileSize` | 文件大小 |
|
|
112
110
|
| `failReason` | `FAILED` / `CANCELLED` 必带 |
|
|
113
111
|
|
|
114
|
-
记下每个成功文件的 `accessUrl`。**派活 `remote_file_upload` 前按 SKILL
|
|
112
|
+
记下每个成功文件的 `accessUrl`。**派活 `remote_file_upload` 前按 SKILL「提醒话术·时机」输出上传提醒**(同条消息先提醒再调工具),轮询过程中不再重复。本方向发送端是手机,上传提醒为"发送端({源手机 nodeName})文件上传中,请勿发送新指令,否则会中断传输。",`nodeName` 取步骤 1 中选中源手机的 `nodeName`。上传全失败 → 不下载直接报;部分成功 → 只下载成功项。
|
|
115
113
|
|
|
116
114
|
### 步骤 4:目标端下载(PC 直调,同步)
|
|
117
115
|
|
|
@@ -133,7 +131,7 @@ description: 手机 → PC 方向的完整执行流程。源端手机 node_invok
|
|
|
133
131
|
| `totalCount` / `successCount` / `failCount` | 总数 / 成功数 / 失败数 |
|
|
134
132
|
| `errorMessage?` | 整批级异常时 |
|
|
135
133
|
|
|
136
|
-
`accessUrl`/`fileName` 原样作入参,不得编造/丢失。**调 `remote_file_download` 前按 SKILL「提醒话术·时机」输出下载提醒**(同条消息先提醒再调工具;PC
|
|
134
|
+
`accessUrl`/`fileName` 原样作入参,不得编造/丢失。**调 `remote_file_download` 前按 SKILL「提醒话术·时机」输出下载提醒**(同条消息先提醒再调工具;PC 直调同步返回快,提醒仍要在调用前发出)。本方向目标端是 PC(本机 openclaw),下载提醒固定为"目标端(PC)文件下载中,请勿发送新指令,否则会中断传输。"。
|
|
137
135
|
|
|
138
136
|
**saveDir**:用户指定 → 传 `saveDir`(PC 插件可创建任意目录);未指定 → 不传,默认 `~/.openclaw/downloads`,不追问保存位置。
|
|
139
137
|
|
|
@@ -167,14 +167,14 @@ metadata: { "openclaw": { "emoji": "📱", "always": true } }
|
|
|
167
167
|
|
|
168
168
|
最大重试次数:3 次,每次间隔 1 秒。
|
|
169
169
|
|
|
170
|
-
| 错误场景
|
|
171
|
-
|
|
172
|
-
| 参数无效
|
|
173
|
-
| 连接错误 / 超时
|
|
174
|
-
| 无会员权益或权益不足
|
|
175
|
-
| 节点处理中 / 挂起
|
|
176
|
-
| 设备离线
|
|
177
|
-
| 用户拒绝设备被执行
|
|
170
|
+
| 错误场景 | 处理方式 | 可重试 |
|
|
171
|
+
|--------------| ------------------------------------------------------------- | --------------- |
|
|
172
|
+
| 参数无效 | 重新核对 `name` / `params` 与文档定义后修正重试 | 是(最多 3 次) |
|
|
173
|
+
| 连接错误 / 超时 / 服务异常 | 等待后重试 | 是(最多 3 次) |
|
|
174
|
+
| 无会员权益或权益不足 | `node_invoke` 返回 `ok:false` 且 `result.resultContent` 表明无会员权益或权益不足(权益/积分不足类账号状态)。重试、改参数、换工具均无法改变结果:不得重试,直接进入步骤 5 向用户总结 | 否 |
|
|
175
|
+
| 节点处理中 / 挂起 | 节点仍在执行或等待用户介入,提示用户并按需重试 | 否 |
|
|
176
|
+
| 设备离线 | 重新调用 `nodes_status` 确认 `status` | 否 |
|
|
177
|
+
| 用户拒绝设备被执行 | 收到"用户拒绝设备被执行"后不再通过 `node_invoke` 调用**该节点**;对其他节点/其他工具的调用不受影响,可继续。作用域仅限当前这轮会话,下一轮新对话可再次对该节点调用 | 否(仅该节点,仅本轮会话) |
|
|
178
178
|
|
|
179
179
|
如果参数错误**必须强制执行**重新读取 `SKILL.md` 和对应的工具定义文件,并重新执行。
|
|
180
180
|
3 次重试均失败后,停止重试,向用户报告错误诊断并建议手动处理。
|
|
@@ -136,6 +136,13 @@ description: >
|
|
|
136
136
|
2. **属性词约束**: 严禁剥离 `query` 末尾的媒介、格式或属性词(如图片、PDF、笔记、文档等),仅允许剥离与检索无关的后续操作指令(如“并打印出来”)。
|
|
137
137
|
3. **默认值行为**: 当未明确提及搜索范围时,`domain` 传空数组表示全量搜索;`isSearchImage` 缺省时默认为 `false`。
|
|
138
138
|
4. **参数独立性**: `isSearchImage` 与 `domain` 相互独立,即使指定了 `domain` 为非图库类型,只要语义包含搜图意图即可设为 `true`。
|
|
139
|
+
5. **domain 必须是数组本体**: `domain` 直接填字符串数组,单选也写成单元素数组,不得用对象包装:
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
✅ 对: domain: ["图库"] // 数组本体,单选也写成单元素数组
|
|
143
|
+
✅ 对: domain: ["图库", "YOYO记忆"] // 多选
|
|
144
|
+
❌ 错: domain: { "item": "图库" } // 对象包装(误把 schema 的 items 当字段名)
|
|
145
|
+
```
|
|
139
146
|
|
|
140
147
|
## Query 示例及输出
|
|
141
148
|
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
//#region src/core/prompt/location.ts
|
|
2
|
-
var e = "## 位置前置获取规则(get_user_location)\n\n用户指令需要当前位置而未提供地点时(如查天气、空气质量、找附近美食/加油站、本地资讯、规划路线),必须先路由到 yoyo_phone_control 技能调用 get_user_location 获取位置,再以返回的城市/经纬度继续执行原任务,一次性输出最终答复。\n1. 中间步骤静默,位置信息仅以工具真实返回为准,禁止编造。\n2. 禁止向用户追问所在城市,禁止以\"需要你的城市\"为由拒绝执行。\n3. 用户已提供地点则直接执行;获取失败时说明原因并请用户告知城市,可执行部分继续。";
|
|
3
|
-
function t() {
|
|
4
|
-
return { prependSystemContext: e };
|
|
5
|
-
}
|
|
6
|
-
//#endregion
|
|
7
|
-
export { t as resolveLocationPromptContribution };
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: phone-to-phone
|
|
3
|
-
description: 手机 → 手机 方向的完整执行流程。两端都 node_invoke 异步轮询:源手机 remote_file_upload,目标手机 remote_file_download。本文件自包含(工作流每步自带详细内容);追问·提醒·异常话术、Summary 模板、全局规则见 SKILL。
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# phone-to-phone(手机 → 手机)
|
|
7
|
-
|
|
8
|
-
源端手机、目标端另一台手机(同账号)。**两端都远端无长链,都走 `node_invoke` 异步轮询。** 本文件自包含;追问/提醒/异常话术、Summary 模板、全局规则见 SKILL。
|
|
9
|
-
|
|
10
|
-
> **入参以本文为权威**:`remote_file_upload` / `remote_file_download` / `task-result-query` 的入参形状以本文为唯一来源,直接按本文调用。
|
|
11
|
-
|
|
12
|
-
## 工作流(按序执行)
|
|
13
|
-
|
|
14
|
-
### 步骤 1:拿源手机 nodeId
|
|
15
|
-
|
|
16
|
-
调用 `nodes_status` 取所有节点,选源手机 nodeId。返回 `{ ok, nodes:[...], errorMessage? }`(失败时 `ok:false` + 空 `nodes` + `errorMessage`)。`nodes[]` 每项:
|
|
17
|
-
|
|
18
|
-
| 字段 | 说明 | 示例 |
|
|
19
|
-
|---|---|---|
|
|
20
|
-
| `nodeId` | 节点唯一标识符,即 `node_invoke.nodeId` 的取值来源 | `f5f8916028aa5` |
|
|
21
|
-
| `nodeName` | 节点名称,展示给用户 | `Honor Magic6 Pro` |
|
|
22
|
-
| `deviceType` | 设备类型:`phone` / `pad` / `pc` | `phone` |
|
|
23
|
-
| `deviceModel` | 设备型号,多设备区分时展示 | `Magic6 Pro` |
|
|
24
|
-
| `status` | 连接状态:`connected` / `disconnected` | `connected` |
|
|
25
|
-
|
|
26
|
-
**设备选择规则**:
|
|
27
|
-
- 仅选 `status = "connected"` 的**手机角色**节点(`deviceType=phone`;pad 按角色判定),不得操作离线设备
|
|
28
|
-
- 单设备连接 → 自动选择
|
|
29
|
-
- 多个 connected 且无法区分 → 询问用户
|
|
30
|
-
- 无 connected 设备 → 告知"当前无设备连接",止步
|
|
31
|
-
|
|
32
|
-
> **双向多设备特例(本方向专属)**:手机→手机时源与目标都是手机。若两台 connected 且用户用指代词("这台/那台")**无法确定哪台是源**,按 SKILL「追问话术·双向多设备」**一次性合并问清源/目标/范围**,再进入步骤 2 搜索。**不要因源未定就停在追问又不告诉用户该怎么答**,也不要因源未定就跳过搜索——先问定向、拿到答案立刻搜。
|
|
33
|
-
|
|
34
|
-
### 步骤 2:拿源端路径
|
|
35
|
-
|
|
36
|
-
按用户是否给出源手机完整路径,分两支拿 `filePaths`(手机路径形如 `/storage/emulated/0/...`):
|
|
37
|
-
|
|
38
|
-
- **用户给了完整源手机绝对路径** → 直接用,跳过搜索。
|
|
39
|
-
- **没给完整路径**(泛指"照片"/"文档"、或只给文件名)→ **加载 `yoyo-phone-control` skill,用其手机本地搜索工具搜出完整绝对路径**(搜索的调用参数在该 skill 的 `local-user-file-search.md` reference 里,照它执行)。
|
|
40
|
-
- **搜索轮询与取路径**:
|
|
41
|
-
```
|
|
42
|
-
loop: task-result-query(task_id)
|
|
43
|
-
result.isFinal == false → 继续轮询
|
|
44
|
-
result.isFinal == true →
|
|
45
|
-
data = JSON.parse(result.answer) // answer 终态是 JSON 字符串
|
|
46
|
-
list = JSON.parse(data.dataSource) // dataSource 也是 JSON 字符串
|
|
47
|
-
files = [ item for group in list if group.source == "file"
|
|
48
|
-
for item in group.items if item.path ]
|
|
49
|
-
filePaths = [ item.path for item in files ] // item.title = 文件名
|
|
50
|
-
list 为空 / files 为空 → 搜不到,按 SKILL 追问,严禁编造路径
|
|
51
|
-
注意:无 path 的 item(如 notepad 笔记项只有 url)不是文件,跳过
|
|
52
|
-
```
|
|
53
|
-
- 搜不到 / 空结果 → 按 SKILL「追问话术」追问;**严禁编造路径**。
|
|
54
|
-
|
|
55
|
-
路径齐全即直接传(不确认、不寒暄);泛指/多候选/源目标不明才按 SKILL「追问话术」追问。
|
|
56
|
-
|
|
57
|
-
### 步骤 3:源端上传(源手机 node_invoke + 轮询)
|
|
58
|
-
|
|
59
|
-
源端手机走异步三步:
|
|
60
|
-
|
|
61
|
-
1. **派活**:`node_invoke({ nodeId:<源>, name:"remote_file_upload", params })` → `{ task_id }`。**`params` 必须严格符合以下 JSON Schema。`node_invoke.params` 是透传对象;无论上游搜索结果、历史上下文或框架内部使用何种包装结构,调用 `remote_file_upload` 时,`params.filePaths` 最终必须是非空裸字符串数组,不得包含 `item`、`items`、`data` 等包装层。**
|
|
62
|
-
|
|
63
|
-
```json
|
|
64
|
-
{
|
|
65
|
-
"type": "object",
|
|
66
|
-
"properties": {
|
|
67
|
-
"filePaths": {
|
|
68
|
-
"type": "array",
|
|
69
|
-
"items": { "type": "string" },
|
|
70
|
-
"minItems": 1,
|
|
71
|
-
"description": "源手机绝对路径,单文件也写成单元素数组"
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
"required": ["filePaths"],
|
|
75
|
-
"additionalProperties": false
|
|
76
|
-
}
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
写错形状手机侧派活会失败,认准下面正/反例:
|
|
80
|
-
|
|
81
|
-
```
|
|
82
|
-
✅ 对: filePaths: [ "/storage/emulated/0/Pictures/.../summer.png" ] // 裸数组,单文件也写成单元素数组
|
|
83
|
-
❌ 错: filePaths: { "item": "/storage/emulated/0/..." } // 对象包值(贴合工具 schema 默认包装,最易写错)
|
|
84
|
-
❌ 错: filePaths: "/storage/emulated/0/..." // 裸字符串
|
|
85
|
-
❌ 错: filePaths: { "item": [ "/storage/emulated/0/..." ] } // 对象包数组
|
|
86
|
-
```
|
|
87
|
-
2. **查进度**:反复 `node_invoke({ nodeId:<源>, name:"task-result-query", params:{ task_id } })`,看顶层 `isFinal`——`false` → 继续轮询;`true` → 停,取 `files[]`。
|
|
88
|
-
3. **拿结果**:`isFinal:true` 停止 → `files[]` 每文件终态,顶层带 `successCount`/`failCount`,整批级异常在 `errorMessage`
|
|
89
|
-
|
|
90
|
-
> **`isFinal` 是唯一停止信号**:只要 `false` 就继续查;一旦 `true`,`files[]` 必须齐全。任务不存在/已取消 → 端侧返 `isFinal:true`+`errorMessage`+空 `files[]`,终止不卡死。
|
|
91
|
-
|
|
92
|
-
`task-result-query` 顶层字段:
|
|
93
|
-
|
|
94
|
-
| 字段 | 何时填 | 说明 |
|
|
95
|
-
|---|---|---|
|
|
96
|
-
| `task_id` | 必填 | 任务号,回显 |
|
|
97
|
-
| `isFinal` | 必填 | 唯一停止信号,`true` = 终态 |
|
|
98
|
-
| `taskType` | 必填 | `file_upload` / `file_download` |
|
|
99
|
-
| `totalCount` | 终态必填 | 这批文件总数 |
|
|
100
|
-
| `files[]` | 终态必填 | 每文件一条,见下 |
|
|
101
|
-
| `successCount`/`failCount` | 终态必填 | 与 `files[]` 一致(权威仍是 `files[]`) |
|
|
102
|
-
| `errorMessage` | 异常时 | 整批级,如“任务不存在或已过期”“未检测到有效登录态”“已取消本次传输” |
|
|
103
|
-
|
|
104
|
-
**upload**(步骤 3 源端上传返回):
|
|
105
|
-
|
|
106
|
-
| 字段 | 说明 |
|
|
107
|
-
|---|---|
|
|
108
|
-
| `status` | 文件级状态(大写):`SUCCEEDED` / `FAILED` / `CANCELLED` |
|
|
109
|
-
| `fileId` | 成功必带 |
|
|
110
|
-
| `accessUrl` | 成功必带 |
|
|
111
|
-
| `accessExpireTime` | 成功必带,ISO8601 |
|
|
112
|
-
| `fileName` | 文件名 |
|
|
113
|
-
| `fileSize` | 文件大小 |
|
|
114
|
-
| `failReason` | `FAILED` / `CANCELLED` 必带 |
|
|
115
|
-
|
|
116
|
-
**download**(步骤 5 目标端下载返回):
|
|
117
|
-
|
|
118
|
-
| 字段 | 说明 |
|
|
119
|
-
|---|---|
|
|
120
|
-
| `status` | 文件级状态(大写):`SUCCEEDED` / `FAILED` / `CANCELLED` |
|
|
121
|
-
| `localPath` | 成功必带(下载落盘路径) |
|
|
122
|
-
| `fileId` | 文件标识 |
|
|
123
|
-
| `fileName` | 文件名 |
|
|
124
|
-
| `failReason` | `FAILED` / `CANCELLED` 必带 |
|
|
125
|
-
|
|
126
|
-
记下成功文件的 `fileId`+`accessUrl`。**派活 `remote_file_upload` 前按 SKILL「提醒话术·时机」输出上传提醒**(同条消息先提醒再调工具),轮询过程中不再重复。上传全失败 → 不下载直接报;部分成功 → 只下载成功项。
|
|
127
|
-
|
|
128
|
-
### 步骤 4:拿目标手机 nodeId
|
|
129
|
-
|
|
130
|
-
再调 `nodes_status`(字段同步骤 1),选**另一台**手机(与源 nodeId 不同;多候选问用户)。
|
|
131
|
-
|
|
132
|
-
### 步骤 5:目标端下载(目标手机 node_invoke + 轮询)
|
|
133
|
-
|
|
134
|
-
按步骤 3 的轮询契约,用**目标手机 nodeId**:`node_invoke({ nodeId:<目标>, name:"remote_file_download", params })` → `{ task_id }` → 轮询到 `isFinal:true` → 拿 `localPath`。`accessUrl`/`fileName`/`accessExpireTime` 原样来自 upload 出参,不得编造/丢失。**`params` 必须严格符合以下 JSON Schema。`node_invoke.params` 是透传对象;无论上游 upload 出参、历史上下文或框架内部使用何种包装结构,调用 `remote_file_download` 时,`params.files` 最终必须是非空裸对象数组,不得包含 `item`、`items`、`data` 等包装层。**
|
|
135
|
-
|
|
136
|
-
```json
|
|
137
|
-
{
|
|
138
|
-
"type": "object",
|
|
139
|
-
"properties": {
|
|
140
|
-
"files": {
|
|
141
|
-
"type": "array",
|
|
142
|
-
"minItems": 1,
|
|
143
|
-
"items": {
|
|
144
|
-
"type": "object",
|
|
145
|
-
"properties": {
|
|
146
|
-
"accessUrl": { "type": "string" },
|
|
147
|
-
"fileName": { "type": "string" },
|
|
148
|
-
"savePath": { "type": "string" },
|
|
149
|
-
"accessExpireTime":{ "type": "string" }
|
|
150
|
-
},
|
|
151
|
-
"required": ["accessUrl", "fileName", "savePath", "accessExpireTime"],
|
|
152
|
-
"additionalProperties": false
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
},
|
|
156
|
-
"required": ["files"],
|
|
157
|
-
"additionalProperties": false
|
|
158
|
-
}
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
写错形状手机侧派活会失败,认准下面正/反例:
|
|
162
|
-
|
|
163
|
-
```
|
|
164
|
-
✅ 对: files: [ { accessUrl, fileName, savePath, accessExpireTime } ] // 裸数组,单文件也写成单元素数组
|
|
165
|
-
❌ 错: files: { item: [ { ... } ] } // 对象包数组(贴合工具 schema 默认包装,最易写错)
|
|
166
|
-
❌ 错: files: { accessUrl, fileName, ... } // 裸对象
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
**派活 `remote_file_download` 前按 SKILL「提醒话术·时机」输出下载提醒**(同条消息先提醒再调工具),轮询过程中不再重复。
|
|
170
|
-
|
|
171
|
-
**savePath**:每文件项各自的保存目录(不是批级)。用户指定 → 每项 `savePath` 都填该目录;未指定 → 每项填 `Download/OpenClaw`,不追问保存位置。
|
|
172
|
-
|
|
173
|
-
> 两端各一个 nodeId、各一个 task_id,别混。
|
|
174
|
-
|
|
175
|
-
### 步骤 6:回传 Summary
|
|
176
|
-
|
|
177
|
-
回读本 SKILL「Summary 模板」,最终回复固定为该表格(单文件也用表格),禁止用"✅ XX 已传到 XX"一句话收尾。
|
|
178
|
-
|
|
179
|
-
## 示例
|
|
180
|
-
|
|
181
|
-
**用户**:"把这台手机上的照片传到另一台手机"
|
|
182
|
-
|
|
183
|
-
1. 定方向:手机 → 手机,按本文件执行。
|
|
184
|
-
2. `nodes_status` → 选源手机 nodeId。
|
|
185
|
-
3. 拿源端路径:用户泛指"照片" → 加载 `yoyo-phone-control` skill 用搜索工具搜出 `/storage/emulated/0/DCIM/Camera/IMG_2026.jpg`(搜出多张则按 SKILL「追问话术」问"要传哪些"),**路径齐全直接上传(不确认)**。
|
|
186
|
-
4. 源端上传:`node_invoke({ nodeId:<源>, name:"remote_file_upload", params:{ filePaths:["/storage/emulated/0/DCIM/Camera/IMG_2026.jpg"] } })` → `{ task_id }` → 轮询到 `isFinal:true` → `{ files:[{ fileId:"obs-5e6f7a8b", accessUrl:"https://files.honor.com/s/Ef6yZ9qQ", status:"SUCCEEDED" }] }`。
|
|
187
|
-
5. `nodes_status` → 选目标手机 nodeId(与源不同)。
|
|
188
|
-
6. 目标端下载:`node_invoke({ nodeId:<目标>, name:"remote_file_download", params:{ files:[{ accessUrl:"https://files.honor.com/s/Ef6yZ9qQ", fileName:"IMG_2026.jpg", savePath:"Download/OpenClaw", accessExpireTime:"2026-12-31T23:59:59Z" }] } })` → `{ task_id }` → 轮询到 `isFinal:true` → `{ files:[{ fileName:"IMG_2026.jpg", localPath:"Download/OpenClaw/IMG_2026.jpg", status:"SUCCEEDED" }] }`。
|
|
189
|
-
7. 回传:按 SKILL「Summary 模板」输出表格。
|