@honor-claw/yoyo 1.4.1-alpha.1 → 1.4.1-alpha.10

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.
@@ -1,6 +1,6 @@
1
1
  import { useClawLogger as e } from "../utils/logger.mjs";
2
2
  import { StatusEventType as t } from "../services/connection/status-tracker/events.mjs";
3
- import { processDeviceToolSet as n } from "../modules/device-toolset/processor.mjs";
3
+ import { createDeviceToolSetUpdateAck as n } from "../modules/device-toolset/ack.mjs";
4
4
  import "../modules/device-toolset/index.mjs";
5
5
  //#region src/cloud-channel/message-handler.ts
6
6
  var r = { style: "SOUL.md" }, i = "[yoyoclaw-channel]", a = class {
@@ -167,7 +167,7 @@ var r = { style: "SOUL.md" }, i = "[yoyoclaw-channel]", a = class {
167
167
  this.sendMessage("deviceControl", t, n, "", void 0, o), e().info(`${i} auto pair is completed, requestId: ${r}`);
168
168
  return;
169
169
  }
170
- e().debug?.(`${i} trans gateway msg to cloud, session: ${t}, data: ${r.slice(0, 3e3)}`), this.sendMessage("userMessage", t, n, r) || (e().error(`${i} failed to send message, cloud socket closed, session ${t}, device ${s}`), this.sessionManager.closeNodeGatewayClient(t));
170
+ this.sendMessage("userMessage", t, n, r) || (e().error(`${i} failed to send message, cloud socket closed, session ${t}, device ${s}`), this.sessionManager.closeNodeGatewayClient(t));
171
171
  } catch (t) {
172
172
  e().error(`${i} failed to handle gateway message: ${String(t)}`);
173
173
  }
@@ -206,10 +206,10 @@ var r = { style: "SOUL.md" }, i = "[yoyoclaw-channel]", a = class {
206
206
  e().info(`${i} ${o ? "update" : "fetch"} context '${u}' processed (no reply)`);
207
207
  return;
208
208
  }
209
- d.ok ? (e().info(`${i} ${o ? "update" : "fetch"} context successfully`), this.sendContextResponse(a, n, r, s, {
209
+ d.ok ? (e().info(`${i} ${o ? "update" : "fetch"} context successfully`), this.sendContextResponse(a, n, r, s, d.responsePayload ?? {
210
210
  ok: !0,
211
211
  ...d.data
212
- })) : (e().error(`${i} failed to ${o ? "update" : "fetch"} context: ${d.error}`), this.sendContextResponse(a, n, r, s, {
212
+ })) : (e().error(`${i} failed to ${o ? "update" : "fetch"} context: ${d.error}`), this.sendContextResponse(a, n, r, s, d.responsePayload ?? {
213
213
  ok: !1,
214
214
  error: d.error,
215
215
  ...d?.data,
@@ -220,20 +220,20 @@ var r = { style: "SOUL.md" }, i = "[yoyoclaw-channel]", a = class {
220
220
  e().error(`${i} error ${o ? "updating" : "fetching"} context '${u}': ${c}`), this.replyContextError(a, n, r, s, c);
221
221
  }
222
222
  }
223
- sendMessage(e, t, n, r, i, a) {
224
- let o = {
225
- msgType: e,
223
+ sendMessage(t, n, r, a, o, s) {
224
+ let c = {
225
+ msgType: t,
226
226
  sourceRole: "yoyoclaw",
227
227
  sourceDeviceId: this.config.deviceInfo.deviceId,
228
- traceInfo: n,
228
+ traceInfo: r,
229
229
  targetRole: "node",
230
- targetDeviceId: t,
230
+ targetDeviceId: n,
231
231
  port: this.config.deviceInfo.port,
232
- data: typeof r == "string" ? r : JSON.stringify(r),
233
- contexts: i,
234
- bizExtInfo: a
232
+ data: typeof a == "string" ? a : JSON.stringify(a),
233
+ contexts: o,
234
+ bizExtInfo: s
235
235
  };
236
- return this.config.onReply(o, t);
236
+ return e().debug?.(`${i} sending message to cloud, msgType: ${c.msgType}, targetDeviceId: ${c.targetDeviceId}, data: ${JSON.stringify(c).slice(0, 3e3)}`), this.config.onReply(c, n);
237
237
  }
238
238
  validateContext(e) {
239
239
  return e?.header?.name ? {
@@ -367,13 +367,14 @@ var r = { style: "SOUL.md" }, i = "[yoyoclaw-channel]", a = class {
367
367
  return !e || t !== "DeviceToolSet" && t !== "UniversalToolSet";
368
368
  }
369
369
  async updateDeviceToolSetContext(e, t, r) {
370
- return r?.header.namespace === "yoyoclaw" ? {
371
- ok: !0,
372
- data: await n(t),
373
- noReply: !0
374
- } : {
375
- ok: !1,
376
- error: `Unsupported DeviceToolSet namespace: ${r?.header.namespace ?? "unknown"}`
370
+ let i = await n({
371
+ namespace: r?.header.namespace,
372
+ payload: t
373
+ });
374
+ return {
375
+ ok: i.ok,
376
+ error: i.error,
377
+ responsePayload: i.payload
377
378
  };
378
379
  }
379
380
  };
@@ -0,0 +1,54 @@
1
+ import { processDeviceToolSet as e } from "./processor.mjs";
2
+ //#region src/modules/device-toolset/ack.ts
3
+ var t = "200", n = "500", r = "DeviceToolSet payload missing nodeId";
4
+ async function i(t) {
5
+ let n = a(t.payload);
6
+ if (t.namespace !== "yoyoclaw") {
7
+ let e = `Unsupported DeviceToolSet namespace: ${t.namespace ?? "unknown"}`;
8
+ return {
9
+ ok: !1,
10
+ error: e,
11
+ payload: s(n, e)
12
+ };
13
+ }
14
+ if (!n) return {
15
+ ok: !1,
16
+ error: r,
17
+ payload: s("", r)
18
+ };
19
+ try {
20
+ return {
21
+ ok: !0,
22
+ payload: o(await e(t.payload))
23
+ };
24
+ } catch (e) {
25
+ let t = e instanceof Error ? e.message : String(e);
26
+ return {
27
+ ok: !1,
28
+ error: t,
29
+ payload: s(n, t)
30
+ };
31
+ }
32
+ }
33
+ function a(e) {
34
+ let t = e?.nodeId;
35
+ return typeof t == "string" && t.trim() ? t : "";
36
+ }
37
+ function o(e) {
38
+ return {
39
+ nodeId: e.nodeId,
40
+ errorCode: t,
41
+ changed: e.changed,
42
+ rawCount: e.rawCount,
43
+ validCount: e.validCount
44
+ };
45
+ }
46
+ function s(e, t) {
47
+ return {
48
+ nodeId: e,
49
+ errorCode: n,
50
+ errorMessage: t
51
+ };
52
+ }
53
+ //#endregion
54
+ export { i as createDeviceToolSetUpdateAck };
@@ -3,25 +3,20 @@ import { INVALID_TOOL_FILENAME_CHAR_PATTERN as t, TOOL_ARCHIVE_DIR as n } from "
3
3
  import r from "node:path";
4
4
  //#region src/modules/device-toolset/archive.ts
5
5
  async function i(t, i) {
6
- let s = 0, c = 0;
6
+ let s = 0;
7
7
  for (let a of i) {
8
8
  let i = o(a.name);
9
- if (!i) {
10
- c += 1;
11
- continue;
12
- }
13
- await e({
9
+ i && (await e({
14
10
  rootDir: t,
15
11
  relativePath: r.join(n, i),
16
12
  data: JSON.stringify(a, null, 2),
17
13
  encoding: "utf8",
18
14
  mkdir: !0
19
- }), s += 1;
15
+ }), s += 1);
20
16
  }
21
17
  return {
22
18
  archiveDir: a(t),
23
- writtenCount: s,
24
- skippedCount: c
19
+ writtenCount: s
25
20
  };
26
21
  }
27
22
  function a(e) {
@@ -1,2 +1,3 @@
1
1
  import "./normalize.mjs";
2
2
  import "./processor.mjs";
3
+ import "./ack.mjs";
@@ -10,15 +10,14 @@ function t(t) {
10
10
  return t;
11
11
  }
12
12
  function n(e) {
13
- let t = e.nodeId, n = /* @__PURE__ */ new Map(), i = 0;
13
+ let t = e.nodeId, n = /* @__PURE__ */ new Map();
14
14
  for (let t of e.toolSet) {
15
15
  let e = r(t);
16
- e ? n.set(e.name, e) : i += 1;
16
+ e && n.set(e.name, e);
17
17
  }
18
18
  return {
19
19
  nodeId: t,
20
- tools: Array.from(n.values()),
21
- skippedCount: i
20
+ tools: Array.from(n.values())
22
21
  };
23
22
  }
24
23
  function r(t) {
@@ -36,8 +36,6 @@ async function s(e, t, r) {
36
36
  mkdir: !0
37
37
  }), {
38
38
  changed: !0,
39
- nodeCount: d(a.tools),
40
- toolCount: Object.keys(a.tools).length,
41
39
  cache: a
42
40
  };
43
41
  }
@@ -72,10 +70,5 @@ function l(e, t) {
72
70
  function u(e, t) {
73
71
  return [...new Set([...e, t])];
74
72
  }
75
- function d(e) {
76
- let t = /* @__PURE__ */ new Set();
77
- for (let n of Object.values(e)) for (let e of n.supportedNodeIds) t.add(e);
78
- return t.size;
79
- }
80
73
  //#endregion
81
74
  export { o as loadDeviceToolSetCache, s as mergeDeviceToolSetCache };
@@ -14,29 +14,29 @@ async function g(n) {
14
14
  let g = p(n), _ = t();
15
15
  if (!_) throw Error("Unable to resolve home directory for DeviceToolSet persistence");
16
16
  let v = m(g);
17
- e().info(`[yoyoclaw-device-toolset] process start, nodeId: ${v.nodeId}, tools: ${v.tools.length}, skipped: ${v.skippedCount}`);
17
+ e().info(`[yoyoclaw-device-toolset] process start, nodeId: ${v.nodeId}, raw: ${g.toolSet.length}, valid: ${v.tools.length}`);
18
18
  let y = u(v.tools);
19
19
  if (e().debug?.(`[yoyoclaw-device-toolset] computed md5, nodeId: ${v.nodeId}, md5: ${y}`), !(await d(_, v.nodeId, y)).changed) {
20
20
  let t = await l(_, h);
21
21
  return e().info(`[yoyoclaw-device-toolset] unchanged, skip refresh, nodeId: ${v.nodeId}, artifactsRestored: ${t.restored}`), {
22
22
  changed: !1,
23
23
  nodeId: v.nodeId,
24
- skippedCount: v.skippedCount
24
+ rawCount: g.toolSet.length,
25
+ validCount: v.tools.length
25
26
  };
26
27
  }
27
28
  let b = await s(_, v.nodeId, v.tools);
28
- e().info(`[yoyoclaw-device-toolset] cache merged, nodeId: ${v.nodeId}, supportedNodes: ${b.nodeCount}, tools: ${b.toolCount}`);
29
+ e().info(`[yoyoclaw-device-toolset] cache merged, nodeId: ${v.nodeId}, processed: ${v.tools.length}`);
29
30
  let x = b.cache.tools, S = await i(_, v.tools);
30
- e().info(`[yoyoclaw-device-toolset] archive refreshed, nodeId: ${v.nodeId}, written: ${S.writtenCount}, skipped: ${S.skippedCount}`);
31
+ e().info(`[yoyoclaw-device-toolset] archive refreshed, nodeId: ${v.nodeId}, written: ${S.writtenCount}`);
31
32
  let C = await c(h, x);
32
33
  e().info(`[yoyoclaw-device-toolset] skill refreshed, nodeId: ${v.nodeId}, rows: ${C.rowCount}, updated: ${C.updated}`), C.updated && await r(h, v.nodeId);
33
34
  let w = await a(S.archiveDir, o(h));
34
35
  return e().info(`[yoyoclaw-device-toolset] mapping ready, nodeId: ${v.nodeId}, mode: ${w.mode}`), await f(_, v.nodeId, y, Date.now()), e().info(`[yoyoclaw-device-toolset] process complete, nodeId: ${v.nodeId}`), {
35
36
  changed: !0,
36
37
  nodeId: v.nodeId,
37
- nodeCount: b.nodeCount,
38
- toolCount: b.toolCount,
39
- skippedCount: v.skippedCount
38
+ rawCount: g.toolSet.length,
39
+ validCount: v.tools.length
40
40
  };
41
41
  }
42
42
  //#endregion
@@ -1,7 +1,7 @@
1
1
  //#region src/utils/version.ts
2
2
  var e = null, t = null;
3
3
  function n() {
4
- return e === null && (e = "1.4.1-alpha.1".includes("beta") || "1.4.1-alpha.1".includes("alpha")), !!e;
4
+ return e === null && (e = "1.4.1-alpha.10".includes("beta") || "1.4.1-alpha.10".includes("alpha")), !!e;
5
5
  }
6
6
  function r(e) {
7
7
  if (!e) return 0;
@@ -10,7 +10,7 @@ function r(e) {
10
10
  }
11
11
  function i() {
12
12
  if (t !== null) return t;
13
- let [e, n, i] = "1.4.1-alpha.1".split("-", 1)[0].split("."), a = r(e), o = r(n), s = r(i);
13
+ let [e, n, i] = "1.4.1-alpha.10".split("-", 1)[0].split("."), a = r(e), o = r(n), s = r(i);
14
14
  return t = a * 1e6 + o * 1e3 + s, t;
15
15
  }
16
16
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@honor-claw/yoyo",
3
- "version": "1.4.1-alpha.1",
3
+ "version": "1.4.1-alpha.10",
4
4
  "description": "OpenClaw Honor Yoyo connection plugin",
5
5
  "keywords": [
6
6
  "ai",
@@ -15,8 +15,24 @@ metadata: { "openclaw": { "emoji": "🔄", "always": true } }
15
15
  - [MANDATORY] **严禁卸载插件** — 不得执行 `openclaw plugins uninstall`,否则用户配置将丢失
16
16
  - [MANDATORY] 出错时仅汇总错误并给出建议,不得尝试自动修复(尤其是不得通过卸载重装来解决问题)
17
17
  - [MANDATORY] 所有面向用户的输出必须使用中文
18
+ - [MANDATORY] 不支持精确版本号安装的插件自动升级;检测到精确版本号时必须停止流程并提示用户自行更新插件
19
+ - [MANDATORY] 执行 `openclaw plugins update yoyo` 会更新 `plugins.installs` 配置并触发网关自动重启;检测到存在可更新版本后直接进入更新流程,不得向用户二次确认或输出重启风险说明
20
+ - [MANDATORY] 更新后的最终判断必须以实际更新版本为准,不得只依赖步骤 2 查询到的目标版本
21
+ - [MANDATORY] 本文件中的"步骤 1/2/3/4/5"、小节标题、处理规则、命令说明均为内部流程,不得原样输出给用户
22
+ - [MANDATORY] 正常更新流程下,面向用户的输出必须严格使用本文指定的可见文案;不得额外输出"步骤1"、"步骤2"、"正在检查版本"、"我将执行"、命令说明、内部判断过程或自由发挥的状态描述
18
23
  - 所有命令在 Windows (bash) 和 Unix (bash) 环境下格式一致,无需区分平台
19
24
 
25
+ ## 用户可见输出约束
26
+
27
+ 正常更新流程只允许输出以下几类内容:
28
+
29
+ 1. 步骤 3 中的更新摘要文案
30
+ 2. 步骤 4.1 的升级流程开头和阶段 1 进度
31
+ 3. 步骤 4.3 的阶段 2 进度
32
+ 4. 步骤 5 的成功文案,以及实际版本高于预检查目标版本时的追加说明
33
+
34
+ 除上述内容外,不得向用户展示内部步骤名称、命令执行计划、版本检查过程、字段解析过程或监控输出。异常、无需更新、未更新、无法确认版本等停止流程场景,按对应规则输出简短结果即可,也不得展示内部步骤编号。
35
+
20
36
  ## 步骤 1:预检查
21
37
 
22
38
  执行以下命令获取当前插件信息:
@@ -45,11 +61,13 @@ openclaw plugins info yoyo
45
61
 
46
62
  ### 2.1 确定当前发布渠道
47
63
 
48
- 根据步骤 1 中 `Spec` 字段判断当前发布渠道:
64
+ 根据步骤 1 中 `Spec` 字段判断当前发布渠道。判断时先提取 `@honor-claw/yoyo@` 后面的安装目标,再按以下规则处理:
49
65
 
50
- - `Spec` 包含 `@alpha` 后缀 → 渠道为 `alpha`
51
- - `Spec` 包含 `@beta` 后缀 → 渠道为 `beta`
52
- - `Spec` 无预发布后缀 → 渠道为 `latest`
66
+ - 安装目标为 `alpha` → 渠道为 `alpha`
67
+ - 安装目标为 `beta` → 渠道为 `beta`
68
+ - 安装目标为 `latest` 或没有安装目标 → 渠道为 `latest`
69
+ - `Spec` 包含精确版本号(如 `@honor-claw/yoyo@1.3.0-beta.8`)→ 告知用户"当前 YOYO 插件使用精确版本号安装,自动升级流程暂不支持判断升级渠道。后续安装 YOYO 插件时请不要携带精确版本号;本次请自行更新插件。"并停止流程
70
+ - `Spec` 包含其他未知标签 → 告知用户"当前 YOYO 插件安装来源无法识别,请自行更新插件或联系开发团队确认升级渠道"并停止流程
53
71
 
54
72
  ### 2.2 查询最新版本
55
73
 
@@ -59,7 +77,7 @@ openclaw plugins info yoyo
59
77
  npm view @honor-claw/yoyo dist-tags --json
60
78
  ```
61
79
 
62
- 从 JSON 输出中提取对应渠道的版本号。示例输出:
80
+ 从 JSON 输出中提取对应渠道的版本号。若 JSON 中不存在对应渠道,按当前渠道回退查询对应版本。示例输出:
63
81
 
64
82
  ```json
65
83
  {
@@ -69,24 +87,28 @@ npm view @honor-claw/yoyo dist-tags --json
69
87
  }
70
88
  ```
71
89
 
72
- 若 `npm view` 命令失败,回退使用以下命令获取 `latest` 版本:
90
+ 若 `npm view @honor-claw/yoyo dist-tags --json` 命令失败,按当前渠道回退查询对应版本:
73
91
 
74
92
  ```bash
75
- npm view @honor-claw/yoyo version
93
+ npm view @honor-claw/yoyo@{渠道名} version
76
94
  ```
77
95
 
96
+ 其中 `latest` 渠道也可以使用 `npm view @honor-claw/yoyo version`。若回退命令仍失败,告知用户"无法获取 YOYO 插件最新版本,请稍后重试或自行更新插件"并停止流程。
97
+
78
98
  ### 2.3 版本对比
79
99
 
80
- 将目标版本(对应渠道最新版)与步骤 1 获取的当前版本进行对比:
100
+ 使用 semver 语义化版本规则,将目标版本(对应渠道最新版)与步骤 1 获取的当前版本进行对比:
81
101
 
82
102
  - **版本相同** → 告知用户"当前已是最新版本(v{version}),无需更新"并停止流程
83
- - **版本不同**记录当前版本和目标版本,进入步骤 3
103
+ - **当前版本高于目标版本**告知用户"当前版本(v{当前版本})高于当前渠道目标版本(v{目标版本}),无需更新"并停止流程
104
+ - **目标版本高于当前版本** → 记录当前版本和目标版本,进入步骤 3
105
+ - **无法可靠比较版本** → 告知用户"无法可靠比较当前版本与目标版本,请自行更新插件或联系开发团队确认"并停止流程
84
106
 
85
- ## 步骤 3:展示版本差异
107
+ ## 步骤 3:展示版本差异并进入更新
86
108
 
87
- 向用户展示更新摘要后直接进入步骤 4:
109
+ 向用户展示更新摘要,不输出重启说明,不等待用户确认,展示后直接进入步骤 4:
88
110
 
89
- ```
111
+ ```markdown
90
112
  🔄 YOYO 插件更新可用
91
113
 
92
114
  当前版本:v{当前版本}
@@ -96,7 +118,20 @@ npm view @honor-claw/yoyo version
96
118
 
97
119
  ## 步骤 4:执行更新
98
120
 
99
- ### 4.1 执行更新命令
121
+ ### 4.1 先通知用户进入升级流程
122
+
123
+ 在执行更新命令前,必须先向用户输出升级流程开头和阶段 1 进度:
124
+
125
+ ```markdown
126
+ 正在执行升级流程:
127
+
128
+ 1. 正在下载插件
129
+ 状态:正在下载插件,请耐心等待...
130
+ ```
131
+
132
+ 后续阶段进度不得重复输出"正在执行升级流程:"标题。
133
+
134
+ ### 4.2 执行更新命令
100
135
 
101
136
  ```bash
102
137
  openclaw plugins update yoyo
@@ -104,21 +139,28 @@ openclaw plugins update yoyo
104
139
 
105
140
  注意:该命令可能需要较长时间执行(下载和安装),请使用足够的超时时间。
106
141
 
107
- ### 4.2 监控更新进度
142
+ ### 4.3 监控更新进度
108
143
 
109
144
  实时监控命令输出,根据不同阶段向用户报告进度:
110
145
 
111
146
  | 输出关键词 | 向用户报告 | 内部处理 |
112
147
  | -------------------------------------- | ----------------- | ------------------------------ |
113
- | `Downloading @honor-claw/yoyo@` | "正在下载插件..." | |
148
+ | `Downloading @honor-claw/yoyo@` | 不输出 | 阶段 1 已在命令执行前输出 |
114
149
  | `Extracting` | 不输出 | 静默跟踪 |
115
- | `Installing to` | "正在更新插件..." | — |
150
+ | `Installing to` | 见下方阶段 2 | — |
116
151
  | `Installing plugin dependencies` | 不输出 | 静默跟踪 |
117
- | `Updated yoyo:` | 不输出 | 捕获成功版本信息 |
152
+ | `Updated yoyo:` | 不输出 | 捕获实际更新版本 |
118
153
  | `Config overwrite:` / `Config backup:` | 不输出 | 记录配置备份路径 |
119
154
  | `Restart the gateway to load plugins` | 不输出 | 更新成功的确认信号,进入步骤 5 |
120
155
 
121
- ### 4.3 错误处理
156
+ 阶段 2 的进度通知必须使用以下文案:
157
+
158
+ ```markdown
159
+ 2. 正在更新插件
160
+ 状态:正在安装并配置新版本插件,请勿中断操作
161
+ ```
162
+
163
+ ### 4.4 错误处理
122
164
 
123
165
  若命令执行过程中出现错误(非零退出码或输出中包含错误信息):
124
166
 
@@ -128,19 +170,43 @@ openclaw plugins update yoyo
128
170
 
129
171
  ## 步骤 5:更新后处理
130
172
 
173
+ ### 版本校验
174
+
175
+ 仅在更新命令成功完成并出现成功信号时执行版本校验。若命令非零退出或已判定为更新失败,跳过版本校验并直接进入更新失败处理。
176
+
177
+ 优先从 `openclaw plugins update yoyo` 输出中的 `Updated yoyo:` 行提取实际更新版本。若命令执行成功但没有捕获到实际更新版本,可再次执行:
178
+
179
+ ```bash
180
+ openclaw plugins info yoyo
181
+ ```
182
+
183
+ 从 `Version` 字段提取最终版本。若此时网关正在自动重启导致命令暂时失败,不要把它直接判定为更新失败;说明"插件更新命令已完成,但网关正在重启,暂时无法确认最终版本"。
184
+
185
+ 将实际更新版本与步骤 1 的当前版本、步骤 2 的目标版本进行 semver 比较:
186
+
187
+ - **实际版本等于当前版本** → 告知用户"插件版本未发生变化,需要稍后再次尝试更新",不要让用户手动触发命令行
188
+ - **实际版本高于目标版本** → 按更新成功处理,并额外提示"本次实际升级到 v{实际版本},高于预检查获取的目标版本 v{目标版本}"
189
+ - **实际版本等于目标版本** → 按更新成功处理
190
+ - **实际版本高于当前版本但低于目标版本** → 告知用户"插件已升级到 v{实际版本},但低于预检查目标版本 v{目标版本},需要稍后再次尝试更新",不要让用户手动触发命令行
191
+ - **无法可靠比较实际版本** → 告知用户"插件更新命令已完成,但无法可靠确认最终版本,请稍后再次检查插件版本"
192
+
131
193
  ### 更新成功
132
194
 
133
195
  向用户报告:
134
196
 
197
+ ```markdown
198
+ 3. 插件更新完成
199
+ Claw 插件更新已完成
200
+ 网关即将自动重启,请稍候...
135
201
  ```
136
- ✅ 插件更新完成
137
202
 
138
- 更新版本:v{旧版本} v{新版本}
203
+ 若实际版本高于步骤 2 获取的目标版本,在成功文案后追加:
139
204
 
140
- 插件更新完成,重启网关后生效。是否立即重启?
205
+ ```markdown
206
+ 说明:本次实际升级到 v{实际版本},高于预检查获取的目标版本 v{目标版本}。
141
207
  ```
142
208
 
143
- **[MANDATORY] 不得在此步骤中执行网关重启操作。** 仅向用户提问,由用户在下一轮对话中自行决定是否重启。
209
+ **[MANDATORY] 不得在此步骤中询问用户是否重启。** `openclaw plugins update yoyo` 已通过配置变更触发网关自动重启。
144
210
 
145
211
  ### 更新失败
146
212