@clawos-dev/clawd 0.2.553 → 0.2.555

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,13 +1,13 @@
1
1
  ---
2
2
  name: scan-persona-for-deploy
3
- description: 老板要把某个 persona 发布到云上时触发——把这个 persona 读一遍,弄清它挪到一台干净的云上机器要预备什么(命令行工具、凭据、随行资产、连接器),边查边填进它的部署文件。收到形如「扫描 persona-xxx 的部署环境,personaId=persona-xxx」的任务时使用。只负责扫,不负责部署。
3
+ description: 老板要把某个 persona 发布到云上时触发——把这个 persona 读一遍,弄清它挪到一台干净的云上机器要预备什么(镜像里要装的东西、连接器、凭据),边查边填进这次扫描的清单。收到形如「扫描 persona-xxx 的部署环境,personaId=persona-xxx,scanId=xxxx」的任务时使用。只负责扫,不负责部署。
4
4
  ---
5
5
 
6
6
  # 扫 persona 的上云环境
7
7
 
8
- 老板在界面上点了「发布到云上」,这个 persona 的部署文件(`.clawd/deploy.json`,会随 persona
9
- 一起进 git 仓库)等着你填。**页面是随你干活一点点长出来的**——他正看着,你查明白一条
10
- 就报一条,别攒到最后一次性倒出来。
8
+ 老板在上云页面点了「开始扫描」,这次扫描的清单(扫描历史 `.clawd/deploy-scan.json` 里
9
+ 以任务给的 `scanId` 为准的那一条;发布时它会被写成 `.clawd/deploy.json` 进 git 仓库)等着你填。
10
+ **页面是随你干活一点点长出来的**——他正看着,你查明白一条就报一条,别攒到最后一次性倒出来。
11
11
 
12
12
  ## 先看这次是硬扫还是软扫
13
13
 
@@ -35,9 +35,6 @@ git diff --name-only <任务里给的 sha> -- .
35
35
  - **diff 给的是入口,不是边界。** 改的可能只是 `CLAUDE.md` 里一行命令,而那行命令背后的脚本
36
36
  带着一整串工具依赖。**改动文件引出去的那一跳照样要跟**,规则同下面「读什么」那节。
37
37
 
38
- 两档都一样:任务里列出的**共享资产都要照判据过一遍**——那部分判据就写在任务里,不用读
39
- persona 目录,很便宜,漏了的话资产进不了镜像,容器里那条能力就是空的。
40
-
41
38
  ## 你要回答的问题
42
39
 
43
40
  这个 persona 如果被搬到一台**干净的 Linux 容器**里跑(没有任何人的 home 目录、没有预装工具、
@@ -45,12 +42,12 @@ persona 目录,很便宜,漏了的话资产进不了镜像,容器里那条
45
42
 
46
43
  ## 云上容器已经有的
47
44
 
48
- 「干净」是相对的——底座镜像和容器本身已经带了下面这些。**这些一律不报 `cli`、不写进 `notes`**,
45
+ 「干净」是相对的——底座镜像和容器本身已经带了下面这些。**这些一律不报 `package`、不写进 `notes`**,
49
46
  也别在任何上报里替它们操心(「需要 node」「得有可写目录」「登录态怎么进去」这类话都不该出现):
50
47
 
51
48
  | 有什么 | 说明 |
52
49
  |---|---|
53
- | node 22、npm、npx | 底座镜像就是 `node:22-bookworm-slim`。npm 包型的 MCP server / CLI 只需报一条 `cli` 让它预装,node 本身不用管 |
50
+ | node 22、npm、npx | 底座镜像就是 `node:22-bookworm-slim`。npm 包型的 MCP server / CLI 只需报一条 `package` 让它预装,node 本身不用管 |
54
51
  | git | 底座自带 |
55
52
  | codex | 云上唯一的引擎,底座自带 |
56
53
  | lark-cli | 底座自带,带凭据隔离编译。飞书的登录态由**飞书连接器**在容器起来时带进去,不需要也不能在容器里 `auth login` / `config init`;persona 里写的交互式初始化步骤在云上不会走到 |
@@ -73,8 +70,6 @@ persona 目录,很便宜,漏了的话资产进不了镜像,容器里那条
73
70
  **关键:跟着引用往外走至少一跳。** 人格里出现的文件路径、脚本名、命令,都要去把那个文件读了
74
71
  再下结论。一句「发布走 publish.sh」背后可能藏着五六个命令行工具,只读人格发现不了。
75
72
 
76
- 任务里会告诉你还有哪些 persona 目录之外的共享资产可以读(如 `deploy-kit`)。
77
-
78
73
  ## 边查边报
79
74
 
80
75
  每查明白一条,立刻调 `clawd-rpc` 的 `call`:
@@ -82,7 +77,7 @@ persona 目录,很便宜,漏了的话资产进不了镜像,容器里那条
82
77
  ```
83
78
  call({ method: "deploy:reportFinding", args: {
84
79
  personaId: "<任务里给的>",
85
- kind: "cli" | "secret" | "asset" | "connector",
80
+ kind: "package" | "secret" | "connector",
86
81
  item: { ... }, // 见下面各类的字段
87
82
  evidence: { reason: "...", file: "...", quote: "..." }
88
83
  }})
@@ -90,9 +85,12 @@ call({ method: "deploy:reportFinding", args: {
90
85
 
91
86
  同一个名字重复上报即覆盖——先给初判、读到更硬的证据再修正,是允许的。
92
87
 
93
- ### kind: "cli" —— 需要预装的命令行工具
88
+ ### kind: "package" —— 镜像里要装的东西
94
89
 
95
- `item` 字段:`name`(必填,可执行名)、`from`(从哪装)、`version`(精确版本)、`min`(版本下限)。
90
+ 镜像里要装的**一切**都报这一种:命令行工具、运行时(`python3` / `node`)、npm 包(MCP server 的)……
91
+ 页面上这一类叫「镜像」;「CLI」这个词只用于连接器的一种(下面那节),报的时候别叫它 cli。
92
+
93
+ `item` 字段:`name`(必填,装完落在 PATH 上的可执行名)、`from`(从哪装)、`version`(精确版本)、`min`(版本下限)。
96
94
 
97
95
  只列**干净容器里不会自带**的:要列 `aliyun` / `s`(Serverless Devs) / `pnpm` / `python3` / `git`
98
96
  这类;不必列 `ls` `cat` `grep` `sed` `awk` `curl` `tar` `bash` 这些基础镜像自带的。
@@ -112,7 +110,7 @@ call({ method: "deploy:reportFinding", args: {
112
110
 
113
111
  版本:拿得准精确版本填 `version`,只知道下限填 `min`,都不确定就都不填。**不要猜版本号**。
114
112
 
115
- **`.mcp.json` 里 `npx` / `uvx` 现拉现跑的 MCP server,也要报一条 `cli`。** 它看着像一条配置
113
+ **`.mcp.json` 里 `npx` / `uvx` 现拉现跑的 MCP server,也要报一条 `package`。** 它看着像一条配置
116
114
  不像一个命令行工具,但语义是一样的:容器里没有这个包时,MCP 首次启动要连 registry 现拉,
117
115
  离线或抖动就直接起不来,persona 少一整块能力。所以它必须预装进镜像。
118
116
 
@@ -134,27 +132,12 @@ call({ method: "deploy:reportFinding", args: {
134
132
  机械列进「连接器」,谁出凭据老板在单据上点。同一个变量若还被别处用到(比如 `publish.sh` 拿
135
133
  `SUPABASE_URL` 渲成应用的环境变量),那条照报——它是另一个用途,不是 MCP 的。
136
134
 
137
- ### kind: "asset" —— persona 目录之外的共享资产
138
-
139
- `item` 字段只有 `name`。任务里会把可以声明的资产**逐条列出来,每条带一句判据**;
140
- 照那句判据看这个 persona 用没用到,用到才报。
141
-
142
- 资产是从本机整包带走的,**没有「装法」这回事**,不要给它编 `from`。
143
-
144
- 两件容易漏的:
145
-
146
- - **有的资产在盘上没有可读的路径**(比如 daemon 自己的构建产物)。任务里给了路径的可以读进去
147
- 查证,没给路径的照判据判——**不能因为「读不到就当它不存在」而不报**:没报的资产不会进镜像,
148
- 容器里那条能力就是空的。
149
- - 判据往往落在「这个动作在云上由谁提供」。persona 在本机走 daemon 提供的 MCP 干的活,
150
- 云上容器里没有 daemon,靠的就是那份随行资产。
151
-
152
135
  ### kind: "connector" —— persona 要连的第三方(只报 CLI 那种)
153
136
 
154
137
  **连接器分两种来源,你只报其中一种:**
155
138
 
156
139
  - **MCP 连接器**(`.mcp.json` 里的每个 server)——**不用报**。daemon 在扫描开始时已经从文件里机械列好
157
- (http 型、stdio 型、要填哪些 `${VAR}` 都读得出来),你报了也是重复。它们的 `npx` 包照样要报一条 `cli`(见上)。
140
+ (http 型、stdio 型、要填哪些 `${VAR}` 都读得出来),你报了也是重复。它们的 `npx` 包照样要报一条 `package`(见上)。
158
141
  - **CLI 连接器**——**要报**。判据:persona 里出现**以用户本人身份操作某个第三方的命令行工具**:
159
142
  `lark-cli … --as user` 操作飞书、`gh` 开 PR、`railway up`、`vercel deploy` 这类。这些工具没有配置文件可读,
160
143
  只有你读了文档才知道它从哪个环境变量拿凭据。
@@ -163,14 +146,14 @@ call({ method: "deploy:reportFinding", args: {
163
146
 
164
147
  - `name`:可执行名(小写字母、数字、短横线),如 `feishu`(lark-cli 的连接器名固定叫这个)、`gh`、`railway`。
165
148
  - `fields`:它读凭据的**环境变量名**数组,如 `gh` → `["GH_TOKEN"]`、`railway` → `["RAILWAY_TOKEN"]`。
166
- **查清楚是你的活**(同 `cli` 的 `from`):读官方文档,写工具真读的那个名字,别猜。飞书走 sidecar,`fields` 给 `[]`。
149
+ **查清楚是你的活**(同 `package` 的 `from`):读官方文档,写工具真读的那个名字,别猜。飞书走 sidecar,`fields` 给 `[]`。
167
150
 
168
151
  谁出凭据(部署者出一份共用 / 每个使用者各自一份)**不由你判**,老板在单据上点。
169
152
 
170
153
  - 跟 `secret` 的区别:`secret` 是脚本 / 应用直接读的一份值;`connector` 是「某个工具要用谁的身份」。
171
154
  报了 `connector: feishu` 就**不要**再给飞书报一条 `secret`。
172
- - 跟 `cli` 的区别:`lark-cli` 在底座镜像里,不用报 `cli`;`gh` / `railway` 这类不在底座里的,
173
- **既要报 `cli`(怎么装)也要报 `connector`(凭据从哪个变量来)**,两条各管一件事。
155
+ - 跟 `package` 的区别:`lark-cli` 在底座镜像里,不用报 `package`;`gh` / `railway` 这类不在底座里的,
156
+ **既要报 `package`(怎么装)也要报 `connector`(凭据从哪个变量来)**,两条各管一件事。
174
157
  - `evidence.quote` 引 skill 里那条命令行就够。
175
158
 
176
159
  ### evidence —— 每条的来历
@@ -182,7 +165,7 @@ call({ method: "deploy:reportFinding", args: {
182
165
  - 差:「用于 JSON 解析」(太短,看不出跟这个 persona 有什么关系)
183
166
  - 人格文件在 `reason` / `notes` 里叫「人格」(「人格里写着发布走 publish.sh」),**不写 `CLAUDE.md` / `AGENTS.md`**:
184
167
  两份是镜像,老板的 persona 跑的是 codex,看见 `CLAUDE.md` 会糊涂。`file` 照实填真路径,那是给展开核对用的
185
- - `file`:persona 目录内的相对路径,或资产名开头的路径
168
+ - `file`:persona 目录内的相对路径
186
169
  - `quote`:原文摘一两句,**引原文不要转述**(老板起疑时展开核对用)
187
170
 
188
171
  **引文里不要带凭据的值。** 这条对 `quote` 和 `reason` 都成立——引到 `KEY=值` 这种行时,
@@ -211,12 +194,13 @@ call({ method: "deploy:reportFinding", args: {
211
194
  ```
212
195
  call({ method: "deploy:patch", args: {
213
196
  personaId: "<任务里给的>",
214
- patch: { manifest: { version: 1, clis: [...], secrets: [...], assets: [...], connectors: [...] } }
197
+ scanId: "<任务里给的>",
198
+ patch: { manifest: { version: 1, packages: [...], secrets: [...], connectors: [...] } }
215
199
  }})
216
200
  ```
217
201
 
218
- 先把 `.clawd/deploy.json` 读出来,去掉该去的那几条,其余原样带上。**没有要删的就别调**——
219
- 整份改写是有风险的动作,没必要时不做。
202
+ 先调 `deploy:get`,在 `scans` 里找 `id` 等于任务给的 scanId 那条,拿它的 `manifest`,
203
+ 去掉该去的那几条,其余原样带上。**没有要删的就别调**——整份改写是有风险的动作,没必要时不做。
220
204
 
221
205
  判「该删」要有依据:来源文件在这次 diff 里被删了,或者你读了改动后的文件、确认它不再引用那个
222
206
  东西。**光是「这次没重报它」不是依据**——软扫下没重报是常态。
@@ -239,7 +223,7 @@ call({ method: "deploy:finishScan", args: {
239
223
 
240
224
  1. **不做就不行**:不解决,persona 在云上跑不起来或核心动作是断的。「可能不太顺」「建议优化」不算。
241
225
  2. **只有老板能做**:要他改 persona、换一个服务、去别处申请点什么。**你自己能查清的先查清**——读文件、
242
- 看 `--help`、上网查,查完要么进清单(cli / secret / asset / connector 各有位置),要么没事,都不是 note。
226
+ 看 `--help`、上网查,查完要么进清单(package / secret / connector 各有位置),要么没事,都不是 note。
243
227
  3. **确定,不是猜**:你真读到了、真查到了。带「如果 / 可能 / 大概率」的句子写不出来就对了——拿不准就去查,
244
228
  查不清就不写。
245
229
 
@@ -247,14 +231,14 @@ call({ method: "deploy:finishScan", args: {
247
231
 
248
232
  - 「云上容器已经有的」那节里的东西(node、可写目录、飞书登录态……)
249
233
  - 你自己的推理过程(「我按底座已有 node 处理」「我没报这条是因为…」)
250
- - 空结论(「两份共享资产都没用到」「没发现需要凭据的地方」)——没有就是没有,老板不需要知道你检查过
234
+ - 空结论(「没有 .mcp.json」「没发现需要凭据的地方」)——没有就是没有,老板不需要知道你检查过
251
235
  - 已经报进清单的东西再说一遍
252
236
 
253
237
  反例(一次真实扫描里的五条,全部不该写):
254
238
 
255
239
  | 写了什么 | 为什么不该 |
256
240
  |---|---|
257
- | 两份共享资产都没用到:persona 目录里没有 .mcp.json… | 空结论 |
241
+ | persona 目录里没有 .mcp.json,没有 MCP 连接器 | 空结论 |
258
242
  | 人格里的初始化流程(lark-cli config init / auth login)是交互式,得靠飞书连接器把登录态带进去 | 容器已有:飞书登录态本来就是连接器带进去的,交互式步骤不会走到 |
259
243
  | whiteboard-cli 是 npm 包运行需要 node,我按底座已有 node 处理、没单报一条 | 容器已有 + 自述推理 |
260
244
  | lark-event 是长连接,云上容器有执行时长上限或出网限制的话可能跑不完整 | 猜测;且 2 小时上限在「已有」那节 |
@@ -275,7 +259,7 @@ GitHub release / apt,你得告诉我它从哪装」。
275
259
  把扫描记录手改回 `scanning` 没有用:daemon 只认它自己起的、还活着的扫描,会话一结束记录就不归你了,
276
260
  改回去只会被翻成 failed。
277
261
  - **扫完之后老板在这个会话里让你「重新扫」「改成硬扫 / 软扫」:不要动。** 不改文件、不重跑流程、不再上报。
278
- 回一句让他去页面重新点「发布」——按钮右边的下拉能选硬扫 / 软扫,daemon 会起新的扫描会话。
262
+ 回一句让他去页面重新点「重新扫描」——按钮右边的下拉能选硬扫 / 软扫,daemon 会起新的扫描会话。
279
263
  - **宁缺毋滥**:拿不准的去查清,不要编一条依赖进清单;查清了、且过得了 `notes` 那三道门槛的才是 note。
280
264
  多报一条假依赖会让镜像装一堆没用的东西;漏报一条真依赖,云上试跑和用起来会发现,补一行再发一次就行。
281
265
  - **没调 `finishScan` 就等于没扫完**——中途卡住也要调,把卡在哪写进 `notes`,别让页面一直停在
@@ -21473,8 +21473,8 @@ var GithubAuthOauthStartArgsSchema = external_exports.object({
21473
21473
  }).strict();
21474
21474
 
21475
21475
  // ../protocol/src/environment-schemas.ts
21476
- var EnvCliSchema = external_exports.object({
21477
- // 可执行文件名,装配后要能在 PATH 里直接调到('aliyun' / 's' / 'pnpm' / 'python3')
21476
+ var EnvPackageSchema = external_exports.object({
21477
+ // 装完后在 PATH 里能直接调到的名字('aliyun' / 's' / 'pnpm' / 'python3');npm 包报 bin 名,包名进 from
21478
21478
  name: external_exports.string().min(1),
21479
21479
  /**
21480
21480
  * 从哪装,约定 `<kind>:<ref>`:`npm:@serverless-devs/s` / `github:aliyun/aliyun-cli` /
@@ -21507,9 +21507,6 @@ var EnvSecretSchema = external_exports.object({
21507
21507
  // supabase 是 url/anon/service-role/jwt 四个),所以是数组不是单值。
21508
21508
  env: external_exports.array(external_exports.string().min(1)).min(1)
21509
21509
  });
21510
- var EnvAssetSchema = external_exports.object({
21511
- name: external_exports.string().min(1)
21512
- });
21513
21510
  var ConnectorNameSchema = external_exports.string().regex(/^[a-z0-9-]+$/, "\u8FDE\u63A5\u5668\u540D\u53EA\u80FD\u662F\u5C0F\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u548C\u77ED\u6A2A\u7EBF");
21514
21511
  var CONNECTOR_KINDS = ["proxy", "env"];
21515
21512
  var CONNECTOR_RUNNERS = ["mcp", "cli"];
@@ -21528,10 +21525,23 @@ var EnvConnectorSchema = external_exports.object({
21528
21525
  fields: external_exports.array(ConnectorFieldSchema).default([]),
21529
21526
  http: external_exports.object({ url: external_exports.string().min(1), headers: external_exports.record(external_exports.string(), external_exports.string()) }).optional()
21530
21527
  });
21528
+ var normalizeLegacyManifest = (v) => {
21529
+ if (!v || typeof v !== "object") return v;
21530
+ let o = v;
21531
+ if ("clis" in o && Array.isArray(o.clis)) {
21532
+ const { clis, ...rest } = o;
21533
+ o = "packages" in rest ? rest : { ...rest, packages: clis };
21534
+ }
21535
+ if ("assets" in o && Array.isArray(o.assets) && o.assets.length === 0) {
21536
+ const { assets: _dropped, ...rest } = o;
21537
+ o = rest;
21538
+ }
21539
+ return o;
21540
+ };
21531
21541
  var MANIFEST_BODY = {
21532
- clis: external_exports.array(EnvCliSchema),
21542
+ // 构镜像时要装进去的所有东西(工具 / 运行时 / 包)。底座已有的构建器会跳过;persona 目录整个进镜像,不在这里列
21543
+ packages: external_exports.array(EnvPackageSchema),
21533
21544
  secrets: external_exports.array(EnvSecretSchema),
21534
- assets: external_exports.array(EnvAssetSchema),
21535
21545
  /**
21536
21546
  * 装了哪些连接器(design 2026-09-11-mcp-connector §3.1)。一条一个对象,形状见 `EnvConnectorSchema`。
21537
21547
  *
@@ -21543,14 +21553,14 @@ var MANIFEST_BODY = {
21543
21553
  connectors: external_exports.array(EnvConnectorSchema).default([])
21544
21554
  };
21545
21555
  var EnvManifestBodySchema = external_exports.object(MANIFEST_BODY);
21546
- var EnvironmentManifestSchema = external_exports.object({
21556
+ var EnvironmentManifestSchema = external_exports.preprocess(normalizeLegacyManifest, external_exports.object({
21547
21557
  // 格式版本。manifest 要跨进程(daemon 扫 → 镜像构建器读)也跨时间(部署记录留档),
21548
21558
  // 加字段兼容但改语义不兼容,留一个显式版本位。
21549
21559
  version: external_exports.literal(1),
21550
21560
  ...MANIFEST_BODY
21551
- }).strict();
21561
+ }).strict());
21552
21562
  var EnvEvidenceSchema = external_exports.object({
21553
- // 这条证据支持的是哪一项:`${kind}:${name}`,如 'cli:aliyun' / 'secret:aliyun-ak'
21563
+ // 这条证据支持的是哪一项:`${kind}:${name}`,如 'package:aliyun' / 'secret:aliyun-ak' / 'connector:feishu'
21554
21564
  target: external_exports.string().min(1),
21555
21565
  // 一句人话:为什么需要它 + 从哪看出来的。单据默认显示这句。
21556
21566
  reason: external_exports.string().min(1),
@@ -21572,33 +21582,30 @@ var SCAN_BASE_KINDS = ["hard", "soft"];
21572
21582
  var ScanBaseKindSchema = external_exports.enum(SCAN_BASE_KINDS);
21573
21583
  var DeployScanStatusSchema = external_exports.enum(["scanning", "done", "failed"]);
21574
21584
  var DeployScanSchema = external_exports.object({
21585
+ /** 历史里指名用(发布 / patch / putSecret 都按它找)。daemon 生成;存量单条记录迁过来的叫 `legacy` */
21586
+ id: external_exports.string().min(1),
21587
+ /**
21588
+ * 这次扫描的清单——原来在 `deploy.json` 里的那份,现在每次扫描各存一份。
21589
+ * 扫描期由 `deploy:reportFinding` 逐条长出来,扫完人在页面上改(`deploy:patch`)也改这份;
21590
+ * 发布时选中的那条被写成 `deploy.json` 进仓库。
21591
+ */
21592
+ manifest: EnvironmentManifestSchema,
21575
21593
  /** 干这次扫描的 butler 会话,「看它怎么干的」入口 */
21576
21594
  scanSessionId: external_exports.string().optional(),
21577
21595
  status: DeployScanStatusSchema,
21578
21596
  evidence: external_exports.array(EnvEvidenceSchema).default([]),
21579
21597
  notes: external_exports.array(external_exports.string()).default([]),
21580
- /** 扫描时被告知的共享资产名单(判「扫描器后来长了新本事」用) */
21581
- scannedAssets: external_exports.array(external_exports.string()).default([]),
21582
21598
  /**
21583
- * 这次扫描是**对哪个 commit 的断言**。`deploy:release` 在起扫描之前会先把 persona 目录
21584
- * 提交(只提交不推,推是发布末尾那一下的事),所以这个值一定是仓库里真实存在的一版。
21599
+ * 这次扫描是**对哪个内容版本的断言**:起扫描前先把 persona 目录提交到本地(只提交不推,推是发布的事),
21600
+ * 落存那一下的 HEAD 就是它——发布 commit 不算内容版本,见 `contentHeadOf`。所以这个值一定是仓库里真实存在的一版,
21601
+ * 同一内容上扫几次 `head` 都相同。**从此不动**:发布不再把它挪成发布 commit(线上是哪版记在 `DeployReleased` 里)。
21585
21602
  *
21586
- * 有了它,「清单过没过时」才是个可判定的等式(`head === repo.head`),
21603
+ * 有了它,「清单过没过时」才是个可判定的等式(`head === contentHeadOf(repo, released)`),
21587
21604
  * 而不是从 `dirty` / `ahead` 里猜——那两个字段判不出「清单是好几版之前扫的」。
21588
21605
  *
21589
21606
  * 缺席 = 旧版 clawd 扫的记录。**一律当过时**:没有第三种情况,重扫一次就有了。
21590
21607
  */
21591
21608
  head: external_exports.string().min(1).optional(),
21592
- /**
21593
- * 上次**发布**推的那个 commit ——「线上正跑的是哪一版」在本机唯一的答案,
21594
- * 也就是软扫的基准。只有 `deploy:release` 写它,扫描不动。
21595
- *
21596
- * 为什么不能用 `head` 顶替:`head` 被扫描和发布轮流覆盖(扫描前落存一版、发布后又挪一版),
21597
- * 它回答的是「清单绑在哪个 commit 上」,回答不了「上次发布是哪一版」。
21598
- *
21599
- * 缺席 = 这个 persona 还从没发布过,没有线上版本可比,软扫退硬扫。
21600
- */
21601
- releasedHead: external_exports.string().min(1).optional(),
21602
21609
  /**
21603
21610
  * 这次**实际**怎么扫的。档位由 daemon 定(有线上版本可比 + 有跑完的清单 → 软扫,否则硬扫),
21604
21611
  * 页面要显示的是真正发生的那个。
@@ -21607,12 +21614,23 @@ var DeployScanSchema = external_exports.object({
21607
21614
  * 定成必填会让 `readScan` 当场抛、上云页面直接打不开。而老记录确实都是从零扫的。
21608
21615
  */
21609
21616
  baseKind: ScanBaseKindSchema.default("hard"),
21610
- /** 软扫比的那个 commit(= `releasedHead`)。硬扫时缺席 */
21617
+ /** 软扫比的那个 commit(= 当时 `DeployReleased.head`)。硬扫时缺席 */
21611
21618
  base: external_exports.string().min(1).optional(),
21612
21619
  startedAt: external_exports.number(),
21613
21620
  finishedAt: external_exports.number().optional(),
21614
21621
  error: external_exports.string().optional()
21615
21622
  }).strict();
21623
+ var DeployReleasedSchema = external_exports.object({
21624
+ head: external_exports.string().min(1),
21625
+ contentHead: external_exports.string().min(1),
21626
+ scanId: external_exports.string().min(1),
21627
+ at: external_exports.number()
21628
+ }).strict();
21629
+ var DeployScanFileSchema = external_exports.object({
21630
+ version: external_exports.literal(2),
21631
+ scans: external_exports.array(DeployScanSchema),
21632
+ released: DeployReleasedSchema.optional()
21633
+ }).strict();
21616
21634
  var DeployRepoSchema = external_exports.object({
21617
21635
  url: external_exports.string().min(1),
21618
21636
  branch: external_exports.string().min(1),
@@ -21661,8 +21679,10 @@ var DeployStateSchema = external_exports.object({
21661
21679
  /** 本机 GitHub 身份(机器级,githubAuth:* 的同一份;上云页顶上那张卡用)。老 daemon 不给(可选只为兼容),UI 缺席时按未知处理 */
21662
21680
  github: GithubAuthStatusSchema.optional(),
21663
21681
  repo: DeployRepoSchema.optional(),
21664
- file: DeployFileSchema.optional(),
21665
- scan: DeployScanSchema.optional(),
21682
+ /** 扫描历史,升序,最后一条最新。没扫过就是空数组 */
21683
+ scans: external_exports.array(DeployScanSchema).default([]),
21684
+ /** 线上正跑的是哪一版;从没发布过就缺席 */
21685
+ released: DeployReleasedSchema.optional(),
21666
21686
  /** 没仓库时缺席(查不了) */
21667
21687
  secrets: external_exports.array(DeploySecretPresenceSchema).optional(),
21668
21688
  /**
@@ -21671,27 +21691,28 @@ var DeployStateSchema = external_exports.object({
21671
21691
  */
21672
21692
  run: DeployRunSchema.optional(),
21673
21693
  /**
21674
- * `.clawd/deploy.json` 读不出来(人手改坏了)时的人话。有它就没有 `file`。
21675
- * 页面据此给一条能走的路(重新扫描会整份重写),而不是白屏。
21694
+ * `.clawd/deploy-scan.json` 读不出来(坏 JSON / 不合形状)时的人话。有它 `scans` 为空。
21695
+ * 页面据此给一条能走的路(重新扫描会重建这个文件),而不是白屏。
21676
21696
  */
21677
- fileError: external_exports.string().optional()
21697
+ historyError: external_exports.string().optional()
21698
+ }).strict();
21699
+ var DeployReleaseResultSchema = external_exports.object({
21700
+ sha: external_exports.string().min(1),
21701
+ actionsUrl: external_exports.string().min(1)
21678
21702
  }).strict();
21679
- var DeployReleaseResultSchema = external_exports.discriminatedUnion("outcome", [
21680
- external_exports.object({
21681
- outcome: external_exports.literal("pushed"),
21682
- sha: external_exports.string().min(1),
21683
- actionsUrl: external_exports.string().min(1)
21684
- }).strict(),
21685
- external_exports.object({ outcome: external_exports.literal("scanning") }).strict()
21686
- ]);
21687
21703
  var DeployPersonaArgsSchema = external_exports.object({
21688
21704
  personaId: external_exports.string().min(1)
21689
21705
  }).strict();
21690
- var DeployReleaseArgsSchema = DeployPersonaArgsSchema.extend({
21706
+ var DeployScanArgsSchema = DeployPersonaArgsSchema.extend({
21691
21707
  scanBase: ScanBaseKindSchema.optional()
21692
21708
  }).strict();
21709
+ var DeployReleaseArgsSchema = DeployPersonaArgsSchema.extend({
21710
+ scanId: external_exports.string().min(1)
21711
+ }).strict();
21693
21712
  var DeployPatchArgsSchema = external_exports.object({
21694
21713
  personaId: external_exports.string().min(1),
21714
+ /** 改的是历史里哪次扫描的清单。扫描期 butler 删条目也走这条(scanId 在它的任务提示里) */
21715
+ scanId: external_exports.string().min(1),
21695
21716
  patch: external_exports.object({
21696
21717
  // 人工补依赖;整份替换 manifest
21697
21718
  manifest: EnvironmentManifestSchema.optional()
@@ -21699,6 +21720,8 @@ var DeployPatchArgsSchema = external_exports.object({
21699
21720
  }).strict();
21700
21721
  var DeployPutSecretArgsSchema = external_exports.object({
21701
21722
  personaId: external_exports.string().min(1),
21723
+ /** 按哪次扫描的清单校验凭据名 / env 名。值本身是仓库级的(GitHub Secrets),跟扫描无关 */
21724
+ scanId: external_exports.string().min(1),
21702
21725
  // 对应 manifest.secrets[].name
21703
21726
  name: external_exports.string().min(1),
21704
21727
  // 环境变量名 → 值。key 必须是该 secret 声明过的 env 之一,handler 校验。
@@ -21711,7 +21734,7 @@ var DeployRerunJobArgsSchema = external_exports.object({
21711
21734
  }).strict();
21712
21735
  var DeployReportFindingArgsSchema = external_exports.object({
21713
21736
  personaId: external_exports.string().min(1),
21714
- kind: external_exports.enum(["cli", "secret", "asset", "connector"]),
21737
+ kind: external_exports.enum(["package", "secret", "connector"]),
21715
21738
  // 该条目在 manifest 对应数组里的内容(形状随 kind 变,handler 按 kind 收窄校验)
21716
21739
  // connector 的 item 是 butler 报的 CLI 连接器({ name, fields? }),daemon 补齐 kind / runner / id 后落进清单;
21717
21740
  // MCP 连接器不经这里——由 daemon 从 .mcp.json 机械列出(design 2026-09-11-mcp-connector §3.2)
@@ -22848,7 +22871,7 @@ var EngineConnectPoolResponseSchema = external_exports.object({
22848
22871
  }).strict();
22849
22872
 
22850
22873
  // ../protocol/src/persona-schemas.ts
22851
- var PERSONA_SIZES = ["small", "medium", "large"];
22874
+ var PERSONA_SIZES = ["tiny", "small", "medium", "large"];
22852
22875
  var PersonaSizeSchema = external_exports.enum(PERSONA_SIZES);
22853
22876
  var PersonaFileSchema = external_exports.object({
22854
22877
  personaId: external_exports.string(),
@@ -22866,7 +22889,7 @@ var PersonaFileSchema = external_exports.object({
22866
22889
  // 与 model 平行:persona 新建 session 的默认 tool,可平滑切换。
22867
22890
  tool: external_exports.string().optional(),
22868
22891
  /**
22869
- * 云上任务 Pod 的规格档(design 2026-09-18-persona-size):`small` / `medium` / `large`,没写按 medium。
22892
+ * 云上任务 Pod 的规格档(design 2026-09-18-persona-size):`tiny` / `small` / `medium` / `large`,没写按 medium。
22870
22893
  * 只有发布上云的流水线读它(cloud-host/pipeline/deploy-file.mjs 收窄值域,不认识的拒发);本机跑不看。
22871
22894
  * 协议层同 model / effort 保持 string(文件层向前兼容);改它的入口是上云页标题行的规格下拉
22872
22895
  * (design 2026-09-20-persona-size-ui),`persona:update` 那头按 PersonaSizeSchema 收窄。
@@ -22983,7 +23006,7 @@ var PersonaUpdateArgsSchema = external_exports.object({
22983
23006
  model: external_exports.string().optional(),
22984
23007
  effort: external_exports.string().optional(),
22985
23008
  tool: external_exports.string().optional(),
22986
- // 云上任务 Pod 的规格档:只收三档之一(PersonaFile 层是 string,边界在这里收窄)
23009
+ // 云上任务 Pod 的规格档:只收四档之一(PersonaFile 层是 string,边界在这里收窄)
22987
23010
  size: PersonaSizeSchema.optional(),
22988
23011
  personality: external_exports.string().optional(),
22989
23012
  // 人格补充层正文(见 PersonaInfoResponseSchema.personalityLocal)。与 personality 各写
@@ -24161,15 +24184,19 @@ var METHOD_DOCS = {
24161
24184
  },
24162
24185
  // ---- deploy:*(persona 上云:persona 目录 = git 仓库工作树,发布 = 推送触发仓库流水线)----
24163
24186
  "deploy:get": {
24164
- summary: "\u4E0A\u4E91\u9875\u9762\u8981\u7684\u5168\u90E8\uFF1A\u4ED3\u5E93\u7ED1\u5B9A\u72B6\u6001\uFF08git \u73B0\u7B97\uFF09\u3001\u90E8\u7F72\u6587\u4EF6\u3001\u626B\u63CF\u8BB0\u5F55\u3001\u4ED3\u5E93 Secrets \u91CC\u5404\u53D8\u91CF\u540D\u586B\u8FC7\u6CA1\u6709\uFF08**\u53EA\u6709\u540D\u5B57\u548C\u65F6\u95F4\uFF0C\u6CA1\u6709\u503C**\uFF09\uFF1Bowner-only",
24187
+ summary: "\u4E0A\u4E91\u9875\u9762\u8981\u7684\u5168\u90E8\uFF1A\u4ED3\u5E93\u7ED1\u5B9A\u72B6\u6001\uFF08git \u73B0\u7B97\uFF09\u3001\u626B\u63CF\u5386\u53F2\uFF08\u6BCF\u6761\u5E26\u6E05\u5355\u5FEB\u7167\uFF09\u3001\u7EBF\u4E0A\u6B63\u8DD1\u7684\u662F\u54EA\u4E00\u7248\u3001\u4ED3\u5E93 Secrets \u91CC\u5404\u53D8\u91CF\u540D\u586B\u8FC7\u6CA1\u6709\uFF08**\u53EA\u6709\u540D\u5B57\u548C\u65F6\u95F4\uFF0C\u6CA1\u6709\u503C**\uFF09\uFF1Bowner-only",
24165
24188
  args: DeployPersonaArgsSchema
24166
24189
  },
24190
+ "deploy:scan": {
24191
+ summary: "\u8D77\u4E00\u6B21\u626B\u63CF\u3002**\u6CA1\u4ED3\u5E93\u5148\u5EFA\u4ED3**\uFF08GitHub REST \u5EFA\u5728\u7EC4\u7EC7\u4E0B\u3001\u79C1\u6709\uFF0Cgit \u63A8\u9996\u4E2A\u63D0\u4EA4\uFF1Btoken \u6765\u81EA\u672C\u673A git \u7684\u8EAB\u4EFD\uFF0C\u6CA1\u6388\u6743\u5148\u62D2\uFF09\u3002\u628A persona \u76EE\u5F55\u63D0\u4EA4\u5230\u672C\u5730\uFF08\u4E0D\u63A8\uFF09\u3001\u5728\u626B\u63CF\u5386\u53F2\u91CC\u8FFD\u52A0\u4E00\u6761\u3001\u8D77 persona-butler \u7684\u626B\u63CF\u4F1A\u8BDD\uFF1B\u6B63\u5728\u626B\u7684\u8BDD\u539F\u6837\u8FD4\u56DE\u4E0D\u8D77\u7B2C\u4E8C\u4E2A\u4F1A\u8BDD\u3002\u5E26 scanBase\uFF08hard / soft\uFF09\u6309\u8BE5\u6863\u4F4D\u626B\uFF08soft \u4E0D\u6210\u7ACB\u81EA\u52A8\u9000 hard\uFF09\uFF0C\u7F3A\u5E2D daemon \u81EA\u52A8\u5B9A\uFF1Bowner-only",
24192
+ args: DeployScanArgsSchema
24193
+ },
24167
24194
  "deploy:patch": {
24168
- summary: "\u7F16\u8F91\u90E8\u7F72\u6587\u4EF6\uFF1A\u624B\u5DE5\u8865\u4F9D\u8D56\u3002**\u4E0D\u80FD\u4ECE\u8FD9\u91CC\u4F20\u51ED\u636E\u7684\u503C**\uFF08\u503C\u8D70 deploy:putSecret\uFF09\uFF1Bowner-only",
24195
+ summary: "\u7F16\u8F91\u5386\u53F2\u91CC\u67D0\u6B21\u626B\u63CF\u7684\u6E05\u5355\uFF08scanId \u6307\u540D\uFF09\uFF1A\u624B\u5DE5\u8865 / \u5220\u4F9D\u8D56\uFF0C\u626B\u63CF\u671F butler \u5220\u6761\u76EE\u4E5F\u8D70\u8FD9\u6761\u3002**\u4E0D\u80FD\u4ECE\u8FD9\u91CC\u4F20\u51ED\u636E\u7684\u503C**\uFF08\u503C\u8D70 deploy:putSecret\uFF09\uFF1Bowner-only",
24169
24196
  args: DeployPatchArgsSchema
24170
24197
  },
24171
24198
  "deploy:putSecret": {
24172
- summary: "\u628A\u4E00\u6761\u51ED\u636E\u7684\u503C\u5199\u8FDB\u4ED3\u5E93\u7684 GitHub Secrets\uFF08GitHub REST\uFF0C\u503C\u7528\u4ED3\u5E93\u516C\u94A5\u5C01\u7BB1\u540E\u4E0A\u4F20\uFF09\u3002**\u8FD9\u662F\u503C\u552F\u4E00\u7684\u5165\u53E3**\uFF1A\u5165\u53C2\u4E0D\u8FDB\u65E5\u5FD7\u3001\u54CD\u5E94\u4E0D\u56DE\u663E\u503C\u3001\u672C\u673A\u4E0D\u843D\u76D8\u3002env \u540D\u5FC5\u987B\u662F\u8BE5\u51ED\u636E\u5728\u6E05\u5355\u91CC\u58F0\u660E\u8FC7\u7684\uFF1Bowner-only",
24199
+ summary: "\u628A\u4E00\u6761\u51ED\u636E\u7684\u503C\u5199\u8FDB\u4ED3\u5E93\u7684 GitHub Secrets\uFF08GitHub REST\uFF0C\u503C\u7528\u4ED3\u5E93\u516C\u94A5\u5C01\u7BB1\u540E\u4E0A\u4F20\uFF09\u3002**\u8FD9\u662F\u503C\u552F\u4E00\u7684\u5165\u53E3**\uFF1A\u5165\u53C2\u4E0D\u8FDB\u65E5\u5FD7\u3001\u54CD\u5E94\u4E0D\u56DE\u663E\u503C\u3001\u672C\u673A\u4E0D\u843D\u76D8\u3002env \u540D\u5FC5\u987B\u662F scanId \u90A3\u6B21\u6E05\u5355\u91CC\u8BE5\u51ED\u636E\u58F0\u660E\u8FC7\u7684\uFF1Bowner-only",
24173
24200
  args: DeployPutSecretArgsSchema
24174
24201
  },
24175
24202
  "deploy:reportFinding": {
@@ -24181,11 +24208,11 @@ var METHOD_DOCS = {
24181
24208
  args: DeployFinishScanArgsSchema
24182
24209
  },
24183
24210
  "deploy:release": {
24184
- summary: "\u53D1\u5E03\u2014\u2014\u4E0A\u4E91\u552F\u4E00\u7684\u52A8\u4F5C\uFF0C\u626B\u63CF\u662F\u5B83\u7684\u7B2C\u4E00\u6BB5\u3002**\u6CA1\u4ED3\u5E93\u5148\u5EFA\u4ED3**\uFF08GitHub REST \u5EFA\u5728\u7EC4\u7EC7\u4E0B\u3001\u79C1\u6709\uFF0Cgit \u63A8\u9996\u4E2A\u63D0\u4EA4\uFF1Btoken \u6765\u81EA\u672C\u673A git \u7684\u8EAB\u4EFD\uFF0C\u6CA1\u6388\u6743\u5148\u62D2\uFF09\u3002\u6E05\u5355\u8FC7\u65F6\uFF08persona \u6539\u8FC7 / \u6CA1\u626B\u8FC7 / \u4E0A\u6B21\u626B\u5931\u8D25\uFF09\uFF1A\u5148\u628A persona \u76EE\u5F55\u63D0\u4EA4\u5230\u672C\u5730\uFF08\u4E0D\u63A8\uFF09\uFF0C\u8D77 persona-butler \u7684\u626B\u63CF\u4F1A\u8BDD\uFF0C\u56DE outcome=scanning\u2014\u2014\u7ED3\u679C\u5199\u8FDB .clawd/deploy.json\uFF08\u6E05\u5355\uFF0C\u8FDB\u4ED3\u5E93\uFF09\u4E0E .clawd/deploy-scan.json\uFF08\u8BC1\u636E\uFF0C\u4E0D\u8FDB\u4ED3\u5E93\uFF09\uFF0C\u6B63\u5728\u626B\u7684\u8BDD\u539F\u6837\u8FD4\u56DE\u4E0D\u8D77\u7B2C\u4E8C\u4E2A\u4F1A\u8BDD\u3002\u6E05\u5355\u65B0\u9C9C\uFF1A\u8FC7\u95F8\uFF08\u6BCF\u4E2A\u53D8\u91CF\u540D\u90FD\u5728 Secrets \u91CC\u3001\u8FDE\u63A5\u5668\u6765\u6E90\u70B9\u8FC7\u3001\u9009\u4E86\u6A21\u578B\uFF09\u2192 \u91CD\u5199 workflow \u6A21\u677F \u2192 git add -A \u2192 \u7A7A\u63D0\u4EA4\u4E5F\u5141\u8BB8 \u2192 push\uFF0C\u56DE outcome=pushed \u5E26 commit \u4E0E Actions \u5730\u5740\uFF1B\u4ED3\u5E93\u91CC\u7684\u6D41\u6C34\u7EBF\u63A5\u7740\u6784\u955C\u50CF\u3001\u8BD5\u8DD1\u3001\u767B\u8BB0\u3002\u5E26 scanBase\uFF08hard / soft\uFF09= \u8FD9\u6B21\u4E00\u5B9A\u5148\u626B\u3001\u6309\u8BE5\u6863\u4F4D\u626B\uFF08soft \u4E0D\u6210\u7ACB\u81EA\u52A8\u9000 hard\uFF09\uFF0C\u6E05\u5355\u65B0\u9C9C\u4E5F\u56DE scanning\uFF1Bowner-only",
24211
+ summary: "\u628A\u5386\u53F2\u91CC scanId \u90A3\u6B21\u626B\u63CF\u7684\u6E05\u5355\u53D1\u4E0A\u53BB\u3002\u90A3\u6B21\u5FC5\u987B\u626B\u5B8C\u3001\u4E14\u5BF9\u5E94\u5F53\u524D\u5185\u5BB9\u7248\u672C\uFF08persona \u4E4B\u540E\u6CA1\u6539\u8FC7\uFF09\uFF0C\u5426\u5219\u62D2\u2014\u2014\u5148 deploy:scan\u3002\u8FC7\u95F8\uFF08\u6BCF\u4E2A\u53D8\u91CF\u540D\u90FD\u5728 Secrets \u91CC\u3001\u8FDE\u63A5\u5668\u6765\u6E90\u70B9\u8FC7\u3001\u9009\u4E86\u6A21\u578B\uFF09\u2192 \u628A\u90A3\u4EFD\u6E05\u5355\u5199\u6210 .clawd/deploy.json \u2192 \u91CD\u5199 workflow \u6A21\u677F \u2192 git add -A \u2192 \u7A7A\u63D0\u4EA4\u4E5F\u5141\u8BB8 \u2192 push\uFF0C\u56DE commit \u4E0E Actions \u5730\u5740\uFF1B\u4ED3\u5E93\u91CC\u7684\u6D41\u6C34\u7EBF\u63A5\u7740\u6784\u955C\u50CF\u3001\u8BD5\u8DD1\u3001\u767B\u8BB0\u3002\u540C\u4E00\u7248\u53EF\u4EE5\u53CD\u590D\u53D1\uFF08\u7528\u540C\u4E00\u4EFD\u6E05\u5355\u518D\u8D70\u4E00\u904D\uFF09\uFF1Bowner-only",
24185
24212
  args: DeployReleaseArgsSchema
24186
24213
  },
24187
24214
  "deploy:rerunJob": {
24188
- summary: "\u539F\u6837\u91CD\u8DD1\u6700\u65B0\u90A3\u6761\u6D41\u6C34\u7EBF\u91CC\u5931\u8D25\u7684\u67D0\u4E00\u6BB5\uFF08GitHub REST rerun job\uFF09\u3002**\u540C\u4E00\u4E2A commit\u3001\u540C\u4E00\u4EFD\u8F93\u5165**\uFF0C\u4E0D\u4EA7\u751F\u65B0\u63D0\u4EA4\u2014\u2014\u6539\u4E86 persona \u5185\u5BB9\u6216\u51ED\u636E\u8981\u8D70 deploy:release\u3002jobId \u5FC5\u987B\u5C5E\u4E8E\u6700\u65B0\u90A3\u6761 run\uFF1Bowner-only",
24215
+ summary: "\u539F\u6837\u91CD\u8DD1\u6700\u65B0\u90A3\u6761\u6D41\u6C34\u7EBF\u91CC\u5931\u8D25\u7684\u67D0\u4E00\u6BB5\uFF08GitHub REST rerun job\uFF09\u3002**\u540C\u4E00\u4E2A commit\u3001\u540C\u4E00\u4EFD\u8F93\u5165**\uFF0C\u4E0D\u4EA7\u751F\u65B0\u63D0\u4EA4\u2014\u2014\u6539\u4E86 persona \u5185\u5BB9\u8981 deploy:scan \u518D deploy:release\u3002jobId \u5FC5\u987B\u5C5E\u4E8E\u6700\u65B0\u90A3\u6761 run\uFF1Bowner-only",
24189
24216
  args: DeployRerunJobArgsSchema
24190
24217
  },
24191
24218
  // ---- githubAuth:*(本机 GitHub 身份,机器级;spec 2026-09-15-github-auth-unify-design §4.3)----
@@ -24722,6 +24749,7 @@ var CODEX_CLOUD_TOOL_ID = `codex${CLOUD_TOOL_SUFFIX}`;
24722
24749
  // src/deploy/deploy-file.ts
24723
24750
  var import_promises = __toESM(require("fs/promises"), 1);
24724
24751
  var import_node_path3 = __toESM(require("path"), 1);
24752
+ var import_node_crypto = require("crypto");
24725
24753
 
24726
24754
  // src/deploy/connectors.ts
24727
24755
  var import_node_fs2 = __toESM(require("fs"), 1);
@@ -24746,6 +24774,7 @@ var CliConnectorFindingSchema = external_exports.object({
24746
24774
  // src/deploy/deploy-file.ts
24747
24775
  var DEPLOY_FILE = import_node_path3.default.join(".clawd", "deploy.json");
24748
24776
  var SCAN_FILE = import_node_path3.default.join(".clawd", "deploy-scan.json");
24777
+ var z1 = DeployScanSchema.omit({ id: true, manifest: true }).extend({ releasedHead: DeployScanSchema.shape.head }).strict();
24749
24778
 
24750
24779
  // src/deploy/templates.ts
24751
24780
  var CLAWOS_REPO = "ottin4ttc/clawos";
@@ -24886,20 +24915,20 @@ var L = [
24886
24915
  " --persona-id '${{ steps.plan.outputs.persona_id }}' \\",
24887
24916
  ' --tag "$REGISTRY/$IMAGE_NAME:${{ steps.plan.outputs.tag }}"',
24888
24917
  "",
24889
- " - name: \u955C\u50CF\u65AD\u8A00\uFF08\u6E05\u5355\u91CC\u7684 CLI \u90FD\u5728\u3001bundle \u91CC persona \u5728\uFF09",
24890
- " # /opt/versions.sh \u6BCF\u884C\u90FD\u4EE5 `|| echo` \u6536\u5C3E\u3001\u6C38\u8FDC exit 0\uFF0C\u53EA\u80FD\u9A8C\u8F93\u51FA\uFF1A\u6BB5\u6570\u8981\u7B49\u4E8E\u6E05\u5355\u91CC\u7684",
24891
- " # CLI \u6570\uFF0C\u6BCF\u6BB5\u4E0B\u9762\u8981\u6709\u7248\u672C\u53F7\u3002\u955C\u50CF ENTRYPOINT \u662F sh -c\uFF0C\u547D\u4EE4\u8981\u4F5C\u4E3A\u4E00\u4E2A\u5B57\u7B26\u4E32\u4F20\u3002",
24918
+ " - name: \u955C\u50CF\u65AD\u8A00\uFF08\u6E05\u5355\u91CC\u8981\u88C5\u7684\u90FD\u5728\u3001bundle \u91CC persona \u5728\uFF09",
24919
+ " # /opt/versions.sh \u6BCF\u884C\u90FD\u4EE5 `|| echo` \u6536\u5C3E\u3001\u6C38\u8FDC exit 0\uFF0C\u53EA\u80FD\u9A8C\u8F93\u51FA\uFF1A\u6BB5\u6570\u8981\u7B49\u4E8E\u6E05\u5355 packages \u7684",
24920
+ " # \u6761\u6570\uFF0C\u6BCF\u6BB5\u4E0B\u9762\u8981\u6709\u7248\u672C\u53F7\u3002\u955C\u50CF ENTRYPOINT \u662F sh -c\uFF0C\u547D\u4EE4\u8981\u4F5C\u4E3A\u4E00\u4E2A\u5B57\u7B26\u4E32\u4F20\u3002",
24892
24921
  " run: |",
24893
24922
  ' IMG="$REGISTRY/$IMAGE_NAME:${{ steps.plan.outputs.tag }}"',
24894
24923
  ` OUT=$(docker run --rm "$IMG" '/opt/versions.sh')`,
24895
24924
  ' echo "$OUT"',
24896
- ` WANT=$(node -e 'const m=JSON.parse(require("fs").readFileSync("persona/.clawd/deploy.json","utf8"));console.log(((m.manifest??m).clis??[]).length)')`,
24925
+ ` WANT=$(node -e 'const m=JSON.parse(require("fs").readFileSync("persona/.clawd/deploy.json","utf8"));const b=m.manifest??m;console.log((b.packages??b.clis??[]).length)')`,
24897
24926
  ` echo "$OUT" | awk -v want="$WANT" '`,
24898
24927
  ' /^--- / { name=$2; if ((getline v) <= 0) v=""; n++',
24899
24928
  ' if (v !~ /[0-9]+\\.[0-9]+/) { printf " \u2717 %s \u6CA1\u62A5\u51FA\u7248\u672C\u53F7\uFF1A%s\\n", name, v > "/dev/stderr"; bad++ } }',
24900
- ' END { if (n != want) { printf " \u2717 \u955C\u50CF\u91CC\u53EA\u6709 %d \u4E2A CLI \u6BB5\uFF0C\u6E05\u5355\u8981\u6C42 %d \u4E2A\\n", n, want > "/dev/stderr"; bad++ }',
24929
+ ' END { if (n != want) { printf " \u2717 \u955C\u50CF\u91CC\u53EA\u6709 %d \u6BB5\u7248\u672C\u8F93\u51FA\uFF0C\u6E05\u5355\u8981\u88C5 %d \u4E2A\\n", n, want > "/dev/stderr"; bad++ }',
24901
24930
  " exit bad ? 1 : 0 }'",
24902
- ' echo " \u2713 $WANT \u4E2A CLI \u90FD\u5728\u4E14\u62A5\u5F97\u51FA\u7248\u672C"',
24931
+ ' echo " \u2713 \u6E05\u5355\u91CC\u8981\u88C5\u7684 $WANT \u4E2A\u90FD\u5728\u4E14\u62A5\u5F97\u51FA\u7248\u672C"',
24903
24932
  ` docker run --rm "$IMG" 'test -d /clawd/personas/\${{ steps.plan.outputs.persona_id }}' \\`,
24904
24933
  ' || { echo " \u2717 /clawd/personas/${{ steps.plan.outputs.persona_id }} \u4E0D\u5728" >&2; exit 1; }',
24905
24934
  ' echo " \u2713 persona \u76EE\u5F55\u5728"',