@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.
package/dist/cli.cjs CHANGED
@@ -5989,13 +5989,13 @@ function sharedConnectorSecrets(m3) {
5989
5989
  function requiredSecretEnvNames(m3) {
5990
5990
  return [.../* @__PURE__ */ new Set([...m3.secrets.flatMap((s2) => s2.env), ...sharedConnectorSecrets(m3).flatMap((s2) => s2.env)])];
5991
5991
  }
5992
- var EnvCliSchema, EnvSecretSchema, EnvAssetSchema, ConnectorNameSchema, CONNECTOR_KINDS, CONNECTOR_RUNNERS, CONNECTOR_PROTOCOLS, CREDENTIAL_SOURCES, CONNECTOR_AUTHS, ConnectorFieldSchema, EnvConnectorSchema, MANIFEST_BODY, EnvManifestBodySchema, EnvironmentManifestSchema, EnvEvidenceSchema;
5992
+ var EnvPackageSchema, EnvSecretSchema, ConnectorNameSchema, CONNECTOR_KINDS, CONNECTOR_RUNNERS, CONNECTOR_PROTOCOLS, CREDENTIAL_SOURCES, CONNECTOR_AUTHS, ConnectorFieldSchema, EnvConnectorSchema, normalizeLegacyManifest, MANIFEST_BODY, EnvManifestBodySchema, EnvironmentManifestSchema, EnvEvidenceSchema;
5993
5993
  var init_environment_schemas = __esm({
5994
5994
  "../protocol/src/environment-schemas.ts"() {
5995
5995
  "use strict";
5996
5996
  init_zod();
5997
- EnvCliSchema = external_exports.object({
5998
- // 可执行文件名,装配后要能在 PATH 里直接调到('aliyun' / 's' / 'pnpm' / 'python3')
5997
+ EnvPackageSchema = external_exports.object({
5998
+ // 装完后在 PATH 里能直接调到的名字('aliyun' / 's' / 'pnpm' / 'python3');npm 包报 bin 名,包名进 from
5999
5999
  name: external_exports.string().min(1),
6000
6000
  /**
6001
6001
  * 从哪装,约定 `<kind>:<ref>`:`npm:@serverless-devs/s` / `github:aliyun/aliyun-cli` /
@@ -6028,9 +6028,6 @@ var init_environment_schemas = __esm({
6028
6028
  // supabase 是 url/anon/service-role/jwt 四个),所以是数组不是单值。
6029
6029
  env: external_exports.array(external_exports.string().min(1)).min(1)
6030
6030
  });
6031
- EnvAssetSchema = external_exports.object({
6032
- name: external_exports.string().min(1)
6033
- });
6034
6031
  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");
6035
6032
  CONNECTOR_KINDS = ["proxy", "env"];
6036
6033
  CONNECTOR_RUNNERS = ["mcp", "cli"];
@@ -6049,10 +6046,23 @@ var init_environment_schemas = __esm({
6049
6046
  fields: external_exports.array(ConnectorFieldSchema).default([]),
6050
6047
  http: external_exports.object({ url: external_exports.string().min(1), headers: external_exports.record(external_exports.string(), external_exports.string()) }).optional()
6051
6048
  });
6049
+ normalizeLegacyManifest = (v3) => {
6050
+ if (!v3 || typeof v3 !== "object") return v3;
6051
+ let o2 = v3;
6052
+ if ("clis" in o2 && Array.isArray(o2.clis)) {
6053
+ const { clis, ...rest } = o2;
6054
+ o2 = "packages" in rest ? rest : { ...rest, packages: clis };
6055
+ }
6056
+ if ("assets" in o2 && Array.isArray(o2.assets) && o2.assets.length === 0) {
6057
+ const { assets: _dropped, ...rest } = o2;
6058
+ o2 = rest;
6059
+ }
6060
+ return o2;
6061
+ };
6052
6062
  MANIFEST_BODY = {
6053
- clis: external_exports.array(EnvCliSchema),
6063
+ // 构镜像时要装进去的所有东西(工具 / 运行时 / 包)。底座已有的构建器会跳过;persona 目录整个进镜像,不在这里列
6064
+ packages: external_exports.array(EnvPackageSchema),
6054
6065
  secrets: external_exports.array(EnvSecretSchema),
6055
- assets: external_exports.array(EnvAssetSchema),
6056
6066
  /**
6057
6067
  * 装了哪些连接器(design 2026-09-11-mcp-connector §3.1)。一条一个对象,形状见 `EnvConnectorSchema`。
6058
6068
  *
@@ -6064,14 +6074,14 @@ var init_environment_schemas = __esm({
6064
6074
  connectors: external_exports.array(EnvConnectorSchema).default([])
6065
6075
  };
6066
6076
  EnvManifestBodySchema = external_exports.object(MANIFEST_BODY);
6067
- EnvironmentManifestSchema = external_exports.object({
6077
+ EnvironmentManifestSchema = external_exports.preprocess(normalizeLegacyManifest, external_exports.object({
6068
6078
  // 格式版本。manifest 要跨进程(daemon 扫 → 镜像构建器读)也跨时间(部署记录留档),
6069
6079
  // 加字段兼容但改语义不兼容,留一个显式版本位。
6070
6080
  version: external_exports.literal(1),
6071
6081
  ...MANIFEST_BODY
6072
- }).strict();
6082
+ }).strict());
6073
6083
  EnvEvidenceSchema = external_exports.object({
6074
- // 这条证据支持的是哪一项:`${kind}:${name}`,如 'cli:aliyun' / 'secret:aliyun-ak'
6084
+ // 这条证据支持的是哪一项:`${kind}:${name}`,如 'package:aliyun' / 'secret:aliyun-ak' / 'connector:feishu'
6075
6085
  target: external_exports.string().min(1),
6076
6086
  // 一句人话:为什么需要它 + 从哪看出来的。单据默认显示这句。
6077
6087
  reason: external_exports.string().min(1),
@@ -6087,10 +6097,13 @@ var init_environment_schemas = __esm({
6087
6097
  });
6088
6098
 
6089
6099
  // ../protocol/src/deploy-schemas.ts
6090
- function isScanFresh(scan, repo2) {
6100
+ function contentHeadOf(repo2, released) {
6101
+ return released && repo2.head === released.head ? released.contentHead : repo2.head;
6102
+ }
6103
+ function isScanFresh(scan, repo2, released) {
6091
6104
  if (!scan || !repo2) return false;
6092
6105
  if (scan.status !== "done") return false;
6093
- return scan.head === repo2.head && !repo2.dirty;
6106
+ return scan.head === contentHeadOf(repo2, released) && !repo2.dirty;
6094
6107
  }
6095
6108
  function cloudEngineOk(tool) {
6096
6109
  return (tool || "claude") === "codex";
@@ -6098,7 +6111,7 @@ function cloudEngineOk(tool) {
6098
6111
  function repoSlug(url) {
6099
6112
  return url.trim().replace(/^git@[^:]+:/, "").replace(/^https?:\/\/[^/]+\//, "").replace(/\.git$/, "");
6100
6113
  }
6101
- var DeployFileSchema, SCAN_BASE_KINDS, ScanBaseKindSchema, DeployScanStatusSchema, DeployScanSchema, DeployRepoSchema, DeploySecretPresenceSchema, DEPLOY_STAGE_STATUSES, DeployStageStatusSchema, DEPLOY_STAGE_KEYS, DeployStageKeySchema, DeployJobSchema, DeployRunSchema, DeployStateSchema, DeployReleaseResultSchema, DeployPersonaArgsSchema, DeployReleaseArgsSchema, DeployPatchArgsSchema, DeployPutSecretArgsSchema, DeployRerunJobArgsSchema, DeployReportFindingArgsSchema, DeployFinishScanArgsSchema, PERSONA_REPO_ORG;
6114
+ var DeployFileSchema, SCAN_BASE_KINDS, ScanBaseKindSchema, DeployScanStatusSchema, DeployScanSchema, DeployReleasedSchema, DeployScanFileSchema, DeployRepoSchema, DeploySecretPresenceSchema, DEPLOY_STAGE_STATUSES, DeployStageStatusSchema, DEPLOY_STAGE_KEYS, DeployStageKeySchema, DeployJobSchema, DeployRunSchema, DeployStateSchema, DeployReleaseResultSchema, DeployPersonaArgsSchema, DeployScanArgsSchema, DeployReleaseArgsSchema, DeployPatchArgsSchema, DeployPutSecretArgsSchema, DeployRerunJobArgsSchema, DeployReportFindingArgsSchema, DeployFinishScanArgsSchema, PERSONA_REPO_ORG;
6102
6115
  var init_deploy_schemas = __esm({
6103
6116
  "../protocol/src/deploy-schemas.ts"() {
6104
6117
  "use strict";
@@ -6113,33 +6126,30 @@ var init_deploy_schemas = __esm({
6113
6126
  ScanBaseKindSchema = external_exports.enum(SCAN_BASE_KINDS);
6114
6127
  DeployScanStatusSchema = external_exports.enum(["scanning", "done", "failed"]);
6115
6128
  DeployScanSchema = external_exports.object({
6129
+ /** 历史里指名用(发布 / patch / putSecret 都按它找)。daemon 生成;存量单条记录迁过来的叫 `legacy` */
6130
+ id: external_exports.string().min(1),
6131
+ /**
6132
+ * 这次扫描的清单——原来在 `deploy.json` 里的那份,现在每次扫描各存一份。
6133
+ * 扫描期由 `deploy:reportFinding` 逐条长出来,扫完人在页面上改(`deploy:patch`)也改这份;
6134
+ * 发布时选中的那条被写成 `deploy.json` 进仓库。
6135
+ */
6136
+ manifest: EnvironmentManifestSchema,
6116
6137
  /** 干这次扫描的 butler 会话,「看它怎么干的」入口 */
6117
6138
  scanSessionId: external_exports.string().optional(),
6118
6139
  status: DeployScanStatusSchema,
6119
6140
  evidence: external_exports.array(EnvEvidenceSchema).default([]),
6120
6141
  notes: external_exports.array(external_exports.string()).default([]),
6121
- /** 扫描时被告知的共享资产名单(判「扫描器后来长了新本事」用) */
6122
- scannedAssets: external_exports.array(external_exports.string()).default([]),
6123
6142
  /**
6124
- * 这次扫描是**对哪个 commit 的断言**。`deploy:release` 在起扫描之前会先把 persona 目录
6125
- * 提交(只提交不推,推是发布末尾那一下的事),所以这个值一定是仓库里真实存在的一版。
6143
+ * 这次扫描是**对哪个内容版本的断言**:起扫描前先把 persona 目录提交到本地(只提交不推,推是发布的事),
6144
+ * 落存那一下的 HEAD 就是它——发布 commit 不算内容版本,见 `contentHeadOf`。所以这个值一定是仓库里真实存在的一版,
6145
+ * 同一内容上扫几次 `head` 都相同。**从此不动**:发布不再把它挪成发布 commit(线上是哪版记在 `DeployReleased` 里)。
6126
6146
  *
6127
- * 有了它,「清单过没过时」才是个可判定的等式(`head === repo.head`),
6147
+ * 有了它,「清单过没过时」才是个可判定的等式(`head === contentHeadOf(repo, released)`),
6128
6148
  * 而不是从 `dirty` / `ahead` 里猜——那两个字段判不出「清单是好几版之前扫的」。
6129
6149
  *
6130
6150
  * 缺席 = 旧版 clawd 扫的记录。**一律当过时**:没有第三种情况,重扫一次就有了。
6131
6151
  */
6132
6152
  head: external_exports.string().min(1).optional(),
6133
- /**
6134
- * 上次**发布**推的那个 commit ——「线上正跑的是哪一版」在本机唯一的答案,
6135
- * 也就是软扫的基准。只有 `deploy:release` 写它,扫描不动。
6136
- *
6137
- * 为什么不能用 `head` 顶替:`head` 被扫描和发布轮流覆盖(扫描前落存一版、发布后又挪一版),
6138
- * 它回答的是「清单绑在哪个 commit 上」,回答不了「上次发布是哪一版」。
6139
- *
6140
- * 缺席 = 这个 persona 还从没发布过,没有线上版本可比,软扫退硬扫。
6141
- */
6142
- releasedHead: external_exports.string().min(1).optional(),
6143
6153
  /**
6144
6154
  * 这次**实际**怎么扫的。档位由 daemon 定(有线上版本可比 + 有跑完的清单 → 软扫,否则硬扫),
6145
6155
  * 页面要显示的是真正发生的那个。
@@ -6148,12 +6158,23 @@ var init_deploy_schemas = __esm({
6148
6158
  * 定成必填会让 `readScan` 当场抛、上云页面直接打不开。而老记录确实都是从零扫的。
6149
6159
  */
6150
6160
  baseKind: ScanBaseKindSchema.default("hard"),
6151
- /** 软扫比的那个 commit(= `releasedHead`)。硬扫时缺席 */
6161
+ /** 软扫比的那个 commit(= 当时 `DeployReleased.head`)。硬扫时缺席 */
6152
6162
  base: external_exports.string().min(1).optional(),
6153
6163
  startedAt: external_exports.number(),
6154
6164
  finishedAt: external_exports.number().optional(),
6155
6165
  error: external_exports.string().optional()
6156
6166
  }).strict();
6167
+ DeployReleasedSchema = external_exports.object({
6168
+ head: external_exports.string().min(1),
6169
+ contentHead: external_exports.string().min(1),
6170
+ scanId: external_exports.string().min(1),
6171
+ at: external_exports.number()
6172
+ }).strict();
6173
+ DeployScanFileSchema = external_exports.object({
6174
+ version: external_exports.literal(2),
6175
+ scans: external_exports.array(DeployScanSchema),
6176
+ released: DeployReleasedSchema.optional()
6177
+ }).strict();
6157
6178
  DeployRepoSchema = external_exports.object({
6158
6179
  url: external_exports.string().min(1),
6159
6180
  branch: external_exports.string().min(1),
@@ -6202,8 +6223,10 @@ var init_deploy_schemas = __esm({
6202
6223
  /** 本机 GitHub 身份(机器级,githubAuth:* 的同一份;上云页顶上那张卡用)。老 daemon 不给(可选只为兼容),UI 缺席时按未知处理 */
6203
6224
  github: GithubAuthStatusSchema.optional(),
6204
6225
  repo: DeployRepoSchema.optional(),
6205
- file: DeployFileSchema.optional(),
6206
- scan: DeployScanSchema.optional(),
6226
+ /** 扫描历史,升序,最后一条最新。没扫过就是空数组 */
6227
+ scans: external_exports.array(DeployScanSchema).default([]),
6228
+ /** 线上正跑的是哪一版;从没发布过就缺席 */
6229
+ released: DeployReleasedSchema.optional(),
6207
6230
  /** 没仓库时缺席(查不了) */
6208
6231
  secrets: external_exports.array(DeploySecretPresenceSchema).optional(),
6209
6232
  /**
@@ -6212,27 +6235,28 @@ var init_deploy_schemas = __esm({
6212
6235
  */
6213
6236
  run: DeployRunSchema.optional(),
6214
6237
  /**
6215
- * `.clawd/deploy.json` 读不出来(人手改坏了)时的人话。有它就没有 `file`。
6216
- * 页面据此给一条能走的路(重新扫描会整份重写),而不是白屏。
6238
+ * `.clawd/deploy-scan.json` 读不出来(坏 JSON / 不合形状)时的人话。有它 `scans` 为空。
6239
+ * 页面据此给一条能走的路(重新扫描会重建这个文件),而不是白屏。
6217
6240
  */
6218
- fileError: external_exports.string().optional()
6241
+ historyError: external_exports.string().optional()
6242
+ }).strict();
6243
+ DeployReleaseResultSchema = external_exports.object({
6244
+ sha: external_exports.string().min(1),
6245
+ actionsUrl: external_exports.string().min(1)
6219
6246
  }).strict();
6220
- DeployReleaseResultSchema = external_exports.discriminatedUnion("outcome", [
6221
- external_exports.object({
6222
- outcome: external_exports.literal("pushed"),
6223
- sha: external_exports.string().min(1),
6224
- actionsUrl: external_exports.string().min(1)
6225
- }).strict(),
6226
- external_exports.object({ outcome: external_exports.literal("scanning") }).strict()
6227
- ]);
6228
6247
  DeployPersonaArgsSchema = external_exports.object({
6229
6248
  personaId: external_exports.string().min(1)
6230
6249
  }).strict();
6231
- DeployReleaseArgsSchema = DeployPersonaArgsSchema.extend({
6250
+ DeployScanArgsSchema = DeployPersonaArgsSchema.extend({
6232
6251
  scanBase: ScanBaseKindSchema.optional()
6233
6252
  }).strict();
6253
+ DeployReleaseArgsSchema = DeployPersonaArgsSchema.extend({
6254
+ scanId: external_exports.string().min(1)
6255
+ }).strict();
6234
6256
  DeployPatchArgsSchema = external_exports.object({
6235
6257
  personaId: external_exports.string().min(1),
6258
+ /** 改的是历史里哪次扫描的清单。扫描期 butler 删条目也走这条(scanId 在它的任务提示里) */
6259
+ scanId: external_exports.string().min(1),
6236
6260
  patch: external_exports.object({
6237
6261
  // 人工补依赖;整份替换 manifest
6238
6262
  manifest: EnvironmentManifestSchema.optional()
@@ -6240,6 +6264,8 @@ var init_deploy_schemas = __esm({
6240
6264
  }).strict();
6241
6265
  DeployPutSecretArgsSchema = external_exports.object({
6242
6266
  personaId: external_exports.string().min(1),
6267
+ /** 按哪次扫描的清单校验凭据名 / env 名。值本身是仓库级的(GitHub Secrets),跟扫描无关 */
6268
+ scanId: external_exports.string().min(1),
6243
6269
  // 对应 manifest.secrets[].name
6244
6270
  name: external_exports.string().min(1),
6245
6271
  // 环境变量名 → 值。key 必须是该 secret 声明过的 env 之一,handler 校验。
@@ -6252,7 +6278,7 @@ var init_deploy_schemas = __esm({
6252
6278
  }).strict();
6253
6279
  DeployReportFindingArgsSchema = external_exports.object({
6254
6280
  personaId: external_exports.string().min(1),
6255
- kind: external_exports.enum(["cli", "secret", "asset", "connector"]),
6281
+ kind: external_exports.enum(["package", "secret", "connector"]),
6256
6282
  // 该条目在 manifest 对应数组里的内容(形状随 kind 变,handler 按 kind 收窄校验)
6257
6283
  // connector 的 item 是 butler 报的 CLI 连接器({ name, fields? }),daemon 补齐 kind / runner / id 后落进清单;
6258
6284
  // MCP 连接器不经这里——由 daemon 从 .mcp.json 机械列出(design 2026-09-11-mcp-connector §3.2)
@@ -7578,7 +7604,7 @@ var init_persona_schemas = __esm({
7578
7604
  "../protocol/src/persona-schemas.ts"() {
7579
7605
  "use strict";
7580
7606
  init_zod();
7581
- PERSONA_SIZES = ["small", "medium", "large"];
7607
+ PERSONA_SIZES = ["tiny", "small", "medium", "large"];
7582
7608
  PersonaSizeSchema = external_exports.enum(PERSONA_SIZES);
7583
7609
  PersonaFileSchema = external_exports.object({
7584
7610
  personaId: external_exports.string(),
@@ -7596,7 +7622,7 @@ var init_persona_schemas = __esm({
7596
7622
  // 与 model 平行:persona 新建 session 的默认 tool,可平滑切换。
7597
7623
  tool: external_exports.string().optional(),
7598
7624
  /**
7599
- * 云上任务 Pod 的规格档(design 2026-09-18-persona-size):`small` / `medium` / `large`,没写按 medium。
7625
+ * 云上任务 Pod 的规格档(design 2026-09-18-persona-size):`tiny` / `small` / `medium` / `large`,没写按 medium。
7600
7626
  * 只有发布上云的流水线读它(cloud-host/pipeline/deploy-file.mjs 收窄值域,不认识的拒发);本机跑不看。
7601
7627
  * 协议层同 model / effort 保持 string(文件层向前兼容);改它的入口是上云页标题行的规格下拉
7602
7628
  * (design 2026-09-20-persona-size-ui),`persona:update` 那头按 PersonaSizeSchema 收窄。
@@ -7713,7 +7739,7 @@ var init_persona_schemas = __esm({
7713
7739
  model: external_exports.string().optional(),
7714
7740
  effort: external_exports.string().optional(),
7715
7741
  tool: external_exports.string().optional(),
7716
- // 云上任务 Pod 的规格档:只收三档之一(PersonaFile 层是 string,边界在这里收窄)
7742
+ // 云上任务 Pod 的规格档:只收四档之一(PersonaFile 层是 string,边界在这里收窄)
7717
7743
  size: PersonaSizeSchema.optional(),
7718
7744
  personality: external_exports.string().optional(),
7719
7745
  // 人格补充层正文(见 PersonaInfoResponseSchema.personalityLocal)。与 personality 各写
@@ -8987,15 +9013,19 @@ var init_methods = __esm({
8987
9013
  },
8988
9014
  // ---- deploy:*(persona 上云:persona 目录 = git 仓库工作树,发布 = 推送触发仓库流水线)----
8989
9015
  "deploy:get": {
8990
- 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",
9016
+ 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",
8991
9017
  args: DeployPersonaArgsSchema
8992
9018
  },
9019
+ "deploy:scan": {
9020
+ 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",
9021
+ args: DeployScanArgsSchema
9022
+ },
8993
9023
  "deploy:patch": {
8994
- 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",
9024
+ 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",
8995
9025
  args: DeployPatchArgsSchema
8996
9026
  },
8997
9027
  "deploy:putSecret": {
8998
- 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",
9028
+ 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",
8999
9029
  args: DeployPutSecretArgsSchema
9000
9030
  },
9001
9031
  "deploy:reportFinding": {
@@ -9007,11 +9037,11 @@ var init_methods = __esm({
9007
9037
  args: DeployFinishScanArgsSchema
9008
9038
  },
9009
9039
  "deploy:release": {
9010
- 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",
9040
+ 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",
9011
9041
  args: DeployReleaseArgsSchema
9012
9042
  },
9013
9043
  "deploy:rerunJob": {
9014
- 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",
9044
+ 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",
9015
9045
  args: DeployRerunJobArgsSchema
9016
9046
  },
9017
9047
  // ---- githubAuth:*(本机 GitHub 身份,机器级;spec 2026-09-15-github-auth-unify-design §4.3)----
@@ -32802,7 +32832,7 @@ var require_websocket = __commonJS({
32802
32832
  var http2 = require("http");
32803
32833
  var net2 = require("net");
32804
32834
  var tls = require("tls");
32805
- var { randomBytes, createHash: createHash2 } = require("crypto");
32835
+ var { randomBytes: randomBytes2, createHash: createHash2 } = require("crypto");
32806
32836
  var { Duplex, Readable: Readable3 } = require("stream");
32807
32837
  var { URL: URL2 } = require("url");
32808
32838
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -33332,7 +33362,7 @@ var require_websocket = __commonJS({
33332
33362
  }
33333
33363
  }
33334
33364
  const defaultPort = isSecure ? 443 : 80;
33335
- const key = randomBytes(16).toString("base64");
33365
+ const key = randomBytes2(16).toString("base64");
33336
33366
  const request = isSecure ? https.request : http2.request;
33337
33367
  const protocolSet = /* @__PURE__ */ new Set();
33338
33368
  let perMessageDeflate;
@@ -44495,7 +44525,7 @@ Env (advanced):
44495
44525
  var import_node_path78 = __toESM(require("path"), 1);
44496
44526
  var import_node_fs60 = __toESM(require("fs"), 1);
44497
44527
  var import_node_os28 = __toESM(require("os"), 1);
44498
- var import_node_crypto22 = require("crypto");
44528
+ var import_node_crypto23 = require("crypto");
44499
44529
  var import_node_url5 = require("url");
44500
44530
 
44501
44531
  // src/logger.ts
@@ -55306,6 +55336,7 @@ function redactEvidence(evidence) {
55306
55336
  // src/deploy/deploy-file.ts
55307
55337
  var import_promises4 = __toESM(require("fs/promises"), 1);
55308
55338
  var import_node_path33 = __toESM(require("path"), 1);
55339
+ var import_node_crypto8 = require("crypto");
55309
55340
  init_protocol();
55310
55341
 
55311
55342
  // src/deploy/connectors.ts
@@ -55495,26 +55526,79 @@ async function readDeployFile(dir) {
55495
55526
  async function writeDeployFile(dir, file) {
55496
55527
  await writeJson(import_node_path33.default.join(dir, DEPLOY_FILE), DeployFileSchema.parse(file));
55497
55528
  }
55498
- async function readScan(dir) {
55499
- const raw = await readJson(import_node_path33.default.join(dir, SCAN_FILE));
55529
+ async function readDeployFileRaw(dir) {
55530
+ try {
55531
+ return await import_promises4.default.readFile(import_node_path33.default.join(dir, DEPLOY_FILE), "utf8");
55532
+ } catch (err) {
55533
+ if (err.code === "ENOENT") return void 0;
55534
+ throw err;
55535
+ }
55536
+ }
55537
+ async function restoreDeployFileRaw(dir, raw) {
55538
+ const file = import_node_path33.default.join(dir, DEPLOY_FILE);
55539
+ if (raw === void 0) await import_promises4.default.rm(file, { force: true });
55540
+ else await import_promises4.default.writeFile(file, raw);
55541
+ }
55542
+ var emptyScanFile = () => ({ version: 2, scans: [] });
55543
+ var LEGACY_SCAN_ID = "legacy";
55544
+ var newScanId = () => (0, import_node_crypto8.randomBytes)(4).toString("hex");
55545
+ var z1 = DeployScanSchema.omit({ id: true, manifest: true }).extend({ releasedHead: DeployScanSchema.shape.head }).strict();
55546
+ function migrateV1(v1, manifest) {
55547
+ const { releasedHead, ...rest } = v1;
55548
+ const entry = { ...rest, id: LEGACY_SCAN_ID, manifest: manifest ?? emptyDeployFile().manifest };
55549
+ return {
55550
+ version: 2,
55551
+ scans: [entry],
55552
+ ...releasedHead ? { released: { head: releasedHead, contentHead: releasedHead, scanId: LEGACY_SCAN_ID, at: v1.head === releasedHead ? v1.finishedAt ?? v1.startedAt : 0 } } : {}
55553
+ };
55554
+ }
55555
+ function normalizeLegacyScan(raw) {
55556
+ const fix = (o2) => {
55557
+ if (!o2 || typeof o2 !== "object") return o2;
55558
+ const { scannedAssets: _dropped, ...rest } = o2;
55559
+ if (Array.isArray(rest.evidence)) {
55560
+ rest.evidence = rest.evidence.map((e) => {
55561
+ if (!e || typeof e !== "object" || typeof e.target !== "string") return e;
55562
+ const target = e.target;
55563
+ return target.startsWith("cli:") ? { ...e, target: `package:${target.slice("cli:".length)}` } : e;
55564
+ });
55565
+ }
55566
+ return rest;
55567
+ };
55568
+ if (raw && typeof raw === "object" && Array.isArray(raw.scans)) {
55569
+ return { ...raw, scans: raw.scans.map(fix) };
55570
+ }
55571
+ return fix(raw);
55572
+ }
55573
+ async function readScanFile(dir) {
55574
+ const raw = normalizeLegacyScan(await readJson(import_node_path33.default.join(dir, SCAN_FILE)));
55500
55575
  if (raw === void 0) return void 0;
55501
- const parsed = DeployScanSchema.safeParse(raw);
55502
- if (!parsed.success) {
55503
- const first = parsed.error.issues[0];
55504
- throw new Error(`${SCAN_FILE} \u4E0D\u5408\u5F62\u72B6\uFF1A${first ? `${first.path.join(".")} ${first.message}` : ""}`);
55576
+ const badShape = (issues) => {
55577
+ const first = issues[0];
55578
+ return new Error(`${SCAN_FILE} \u4E0D\u5408\u5F62\u72B6\uFF1A${first ? `${first.path.join(".")} ${first.message}` : ""}`);
55579
+ };
55580
+ if (typeof raw === "object" && raw !== null && !("version" in raw)) {
55581
+ const v1 = z1.safeParse(raw);
55582
+ if (!v1.success) throw badShape(v1.error.issues);
55583
+ return migrateV1(v1.data, (await readDeployFile(dir).catch(() => void 0))?.manifest);
55505
55584
  }
55585
+ const parsed = DeployScanFileSchema.safeParse(raw);
55586
+ if (!parsed.success) throw badShape(parsed.error.issues);
55506
55587
  return parsed.data;
55507
55588
  }
55508
- async function writeScan(dir, scan) {
55509
- await writeJson(import_node_path33.default.join(dir, SCAN_FILE), DeployScanSchema.parse(scan));
55589
+ async function writeScanFile(dir, file) {
55590
+ await writeJson(import_node_path33.default.join(dir, SCAN_FILE), DeployScanFileSchema.parse(file));
55591
+ }
55592
+ function replaceScan(file, entry) {
55593
+ const idx = file.scans.findIndex((s2) => s2.id === entry.id);
55594
+ return { ...file, scans: idx === -1 ? [...file.scans, entry] : file.scans.map((s2, i2) => i2 === idx ? entry : s2) };
55510
55595
  }
55511
55596
  function emptyDeployFile() {
55512
- return { version: 1, manifest: { version: 1, clis: [], secrets: [], assets: [], connectors: [] } };
55597
+ return { version: 1, manifest: { version: 1, packages: [], secrets: [], connectors: [] } };
55513
55598
  }
55514
55599
  var FINDING_SCHEMAS = {
55515
- cli: EnvCliSchema,
55600
+ package: EnvPackageSchema,
55516
55601
  secret: EnvSecretSchema,
55517
- asset: EnvAssetSchema,
55518
55602
  // butler 报的是 CLI 连接器的「名字 + 它读的环境变量名」,kind / runner / id 由 daemon 补
55519
55603
  //(handlers/deploy.ts 的 cliConnectorFromFinding)。MCP 连接器不经上报口,由 scanMcpConnectors 机械列出。
55520
55604
  connector: CliConnectorFindingSchema
@@ -55527,26 +55611,26 @@ function parseFinding(kind, item) {
55527
55611
  }
55528
55612
  return parsed.data;
55529
55613
  }
55530
- function upsertFinding(file, kind, item) {
55614
+ function upsertFinding(manifest, kind, item) {
55531
55615
  if (kind === "connector") {
55532
55616
  const incoming = item;
55533
- const have = file.manifest.connectors;
55617
+ const have = manifest.connectors;
55534
55618
  const idx2 = have.findIndex((c2) => c2.name === incoming.name);
55535
- if (idx2 !== -1 && have[idx2].runner === "mcp" && incoming.runner === "cli") return file;
55619
+ if (idx2 !== -1 && have[idx2].runner === "mcp" && incoming.runner === "cli") return manifest;
55536
55620
  const merged = mergeConnector(idx2 === -1 ? void 0 : have[idx2], incoming);
55537
55621
  const connectors = idx2 === -1 ? [...have, merged] : have.map((c2, i2) => i2 === idx2 ? merged : c2);
55538
- return { ...file, manifest: { ...file.manifest, connectors } };
55622
+ return { ...manifest, connectors };
55539
55623
  }
55540
- const key = kind === "cli" ? "clis" : kind === "secret" ? "secrets" : "assets";
55541
- const list = [...file.manifest[key]];
55624
+ const key = kind === "package" ? "packages" : "secrets";
55625
+ const list = [...manifest[key]];
55542
55626
  const idx = list.findIndex((x2) => x2.name === item.name);
55543
55627
  if (idx === -1) list.push(item);
55544
55628
  else list[idx] = item;
55545
- return { ...file, manifest: { ...file.manifest, [key]: list } };
55629
+ return { ...manifest, [key]: list };
55546
55630
  }
55547
- function requiredEnvNames(file) {
55548
- if (!file) return [];
55549
- return requiredSecretEnvNames(file.manifest);
55631
+ function requiredEnvNames(manifest) {
55632
+ if (!manifest) return [];
55633
+ return requiredSecretEnvNames(manifest);
55550
55634
  }
55551
55635
 
55552
55636
  // src/deploy/repo.ts
@@ -59313,20 +59397,20 @@ var L2 = [
59313
59397
  " --persona-id '${{ steps.plan.outputs.persona_id }}' \\",
59314
59398
  ' --tag "$REGISTRY/$IMAGE_NAME:${{ steps.plan.outputs.tag }}"',
59315
59399
  "",
59316
- " - name: \u955C\u50CF\u65AD\u8A00\uFF08\u6E05\u5355\u91CC\u7684 CLI \u90FD\u5728\u3001bundle \u91CC persona \u5728\uFF09",
59317
- " # /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",
59318
- " # 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",
59400
+ " - name: \u955C\u50CF\u65AD\u8A00\uFF08\u6E05\u5355\u91CC\u8981\u88C5\u7684\u90FD\u5728\u3001bundle \u91CC persona \u5728\uFF09",
59401
+ " # /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",
59402
+ " # \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",
59319
59403
  " run: |",
59320
59404
  ' IMG="$REGISTRY/$IMAGE_NAME:${{ steps.plan.outputs.tag }}"',
59321
59405
  ` OUT=$(docker run --rm "$IMG" '/opt/versions.sh')`,
59322
59406
  ' echo "$OUT"',
59323
- ` WANT=$(node -e 'const m=JSON.parse(require("fs").readFileSync("persona/.clawd/deploy.json","utf8"));console.log(((m.manifest??m).clis??[]).length)')`,
59407
+ ` 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)')`,
59324
59408
  ` echo "$OUT" | awk -v want="$WANT" '`,
59325
59409
  ' /^--- / { name=$2; if ((getline v) <= 0) v=""; n++',
59326
59410
  ' if (v !~ /[0-9]+\\.[0-9]+/) { printf " \u2717 %s \u6CA1\u62A5\u51FA\u7248\u672C\u53F7\uFF1A%s\\n", name, v > "/dev/stderr"; bad++ } }',
59327
- ' 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++ }',
59411
+ ' 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++ }',
59328
59412
  " exit bad ? 1 : 0 }'",
59329
- ' echo " \u2713 $WANT \u4E2A CLI \u90FD\u5728\u4E14\u62A5\u5F97\u51FA\u7248\u672C"',
59413
+ ' echo " \u2713 \u6E05\u5355\u91CC\u8981\u88C5\u7684 $WANT \u4E2A\u90FD\u5728\u4E14\u62A5\u5F97\u51FA\u7248\u672C"',
59330
59414
  ` docker run --rm "$IMG" 'test -d /clawd/personas/\${{ steps.plan.outputs.persona_id }}' \\`,
59331
59415
  ' || { echo " \u2717 /clawd/personas/${{ steps.plan.outputs.persona_id }} \u4E0D\u5728" >&2; exit 1; }',
59332
59416
  ' echo " \u2713 persona \u76EE\u5F55\u5728"',
@@ -59600,7 +59684,7 @@ async function release(exec, api, args) {
59600
59684
  await mustGit(exec, args.dir, [...identity, "commit", "--allow-empty", "-m", `clawd: \u53D1\u5E03 ${stamp}`], "\u63D0\u4EA4");
59601
59685
  await gitPush(exec, args.dir, args.auth);
59602
59686
  const sha = await mustGit(exec, args.dir, ["rev-parse", "HEAD"], "\u8BFB\u63D0\u4EA4");
59603
- return { outcome: "pushed", sha, actionsUrl: `https://github.com/${repoSlug(info.url)}/actions` };
59687
+ return { sha, actionsUrl: `https://github.com/${repoSlug(info.url)}/actions` };
59604
59688
  }
59605
59689
 
59606
59690
  // src/deploy/scan-base.ts
@@ -59679,17 +59763,6 @@ async function rerunJob(api, slug, jobId) {
59679
59763
 
59680
59764
  // src/deploy/scan-task.ts
59681
59765
  var BUTLER_PERSONA_ID = "persona-clawd-butler";
59682
- var SCANNABLE_ASSETS = [];
59683
- function staleScannerAssets(scan, known = SCANNABLE_ASSETS) {
59684
- const knew = new Set(scan.scannedAssets ?? []);
59685
- return known.filter((a2) => !knew.has(a2.name));
59686
- }
59687
- var STALE_ASSETS_NOTE_PREFIX = "\u3010\u4E0A\u4E91\u626B\u63CF\u3011";
59688
- function describeStaleAssets(stale) {
59689
- if (stale.length === 0) return void 0;
59690
- const plain = (s2) => s2.replace(/\*\*/g, "").replace(/\s+/g, " ").trim();
59691
- return `${STALE_ASSETS_NOTE_PREFIX}\u8FD9\u6B21\u626B\u63CF\u7684\u65F6\u5019\uFF0C\u626B\u63CF\u5668\u8FD8\u4E0D\u77E5\u9053\u6709${stale.map((a2) => `\u300C${a2.name}\u300D`).join("\u3001")}\u8FD9${stale.length > 1 ? "\u51E0" : ""}\u9879\u5171\u4EAB\u8D44\u4EA7\uFF0C\u6240\u4EE5\u6E05\u5355\u91CC\u53EF\u80FD\u6F0F\u4E86\u58F0\u660E\u3002${stale.map((a2) => ` ${a2.name} \u662F\uFF1A${plain(a2.what)}`).join("")} \u7528\u5F97\u4E0A\u7684\u8BDD\uFF0C\u5728\u6E05\u5355\u91CC\u8865\u4E00\u6761 asset\u3002`;
59692
- }
59693
59766
  function settleEndedScan(outcome, current, now) {
59694
59767
  if (current?.status !== "scanning") return void 0;
59695
59768
  return {
@@ -59701,13 +59774,11 @@ function settleEndedScan(outcome, current, now) {
59701
59774
  }
59702
59775
  function reconcileInterruptedScans(deps) {
59703
59776
  const flipped = [];
59704
- for (const { personaId: personaId2, scan } of deps.scans) {
59705
- if (scan?.status !== "scanning") continue;
59777
+ for (const { personaId: personaId2, file } of deps.files) {
59778
+ if (!file || !file.scans.some((s2) => s2.status === "scanning")) continue;
59706
59779
  deps.write(personaId2, {
59707
- ...scan,
59708
- status: "failed",
59709
- finishedAt: deps.now,
59710
- error: "daemon \u91CD\u542F\uFF0C\u626B\u63CF\u4F1A\u8BDD\u5DF2\u4E2D\u65AD\u2014\u2014\u518D\u70B9\u4E00\u6B21\u300C\u53D1\u5E03\u300D\u4F1A\u91CD\u65B0\u626B\u63CF"
59780
+ ...file,
59781
+ scans: file.scans.map((s2) => s2.status === "scanning" ? { ...s2, status: "failed", finishedAt: deps.now, error: "daemon \u91CD\u542F\uFF0C\u626B\u63CF\u4F1A\u8BDD\u5DF2\u4E2D\u65AD\u2014\u2014\u518D\u70B9\u4E00\u6B21\u300C\u91CD\u65B0\u626B\u63CF\u300D" } : s2)
59711
59782
  });
59712
59783
  flipped.push(personaId2);
59713
59784
  }
@@ -59737,21 +59808,33 @@ function isLiveScan(live, scan) {
59737
59808
  return live.sessionId === void 0 || live.sessionId === scan.scanSessionId;
59738
59809
  }
59739
59810
  function buildDeployHandlers(deps) {
59740
- const scannableAssets = deps.scannableAssets ?? SCANNABLE_ASSETS;
59741
59811
  const liveScans = /* @__PURE__ */ new Map();
59742
- const settleStale = async (dir, scan) => {
59743
- const settled = { ...scan, status: "failed", finishedAt: deps.now(), error: "\u626B\u63CF\u8BB0\u5F55\u505C\u5728 scanning\uFF0C\u4F46\u6CA1\u6709\u6B63\u5728\u8DD1\u7684\u626B\u63CF\u4F1A\u8BDD" };
59744
- await writeScan(dir, settled);
59812
+ const readHistory = async (dir) => await readScanFile(dir) ?? emptyScanFile();
59813
+ const settleStale = async (personaId2, dir, file) => {
59814
+ const live = liveScans.get(personaId2);
59815
+ const stale = file.scans.filter((s2) => s2.status === "scanning" && !isLiveScan(live, s2));
59816
+ if (stale.length === 0) return file;
59817
+ const settled = {
59818
+ ...file,
59819
+ scans: file.scans.map((s2) => stale.includes(s2) ? { ...s2, status: "failed", finishedAt: deps.now(), error: "\u626B\u63CF\u8BB0\u5F55\u505C\u5728 scanning\uFF0C\u4F46\u6CA1\u6709\u6B63\u5728\u8DD1\u7684\u626B\u63CF\u4F1A\u8BDD" } : s2)
59820
+ };
59821
+ await writeScanFile(dir, settled);
59745
59822
  return settled;
59746
59823
  };
59747
- const mustBeLiveScan = (personaId2, scan, method) => {
59748
- if (scan?.status !== "scanning") {
59749
- throw invalid(`${method} \u53EA\u5728\u626B\u63CF\u671F\u53EF\u7528\uFF08\u5F53\u524D ${scan?.status ?? "\u6CA1\u6709\u626B\u63CF\u8BB0\u5F55"}\uFF09`);
59750
- }
59751
- if (!isLiveScan(liveScans.get(personaId2), scan)) {
59752
- throw invalid(`${method} \u53EA\u5728\u626B\u63CF\u671F\u53EF\u7528\uFF08\u8BB0\u5F55\u505C\u5728 scanning\uFF0C\u4F46\u6CA1\u6709\u6B63\u5728\u8DD1\u7684\u626B\u63CF\u4F1A\u8BDD\uFF09`);
59753
- }
59754
- return scan;
59824
+ const mustBeLiveScan = (personaId2, file, method) => {
59825
+ const scanning = file.scans.filter((s2) => s2.status === "scanning");
59826
+ if (scanning.length === 0) {
59827
+ const last = file.scans[file.scans.length - 1];
59828
+ throw invalid(`${method} \u53EA\u5728\u626B\u63CF\u671F\u53EF\u7528\uFF08\u5F53\u524D ${last?.status ?? "\u6CA1\u6709\u626B\u63CF\u8BB0\u5F55"}\uFF09`);
59829
+ }
59830
+ const live = scanning.find((s2) => isLiveScan(liveScans.get(personaId2), s2));
59831
+ if (!live) throw invalid(`${method} \u53EA\u5728\u626B\u63CF\u671F\u53EF\u7528\uFF08\u8BB0\u5F55\u505C\u5728 scanning\uFF0C\u4F46\u6CA1\u6709\u6B63\u5728\u8DD1\u7684\u626B\u63CF\u4F1A\u8BDD\uFF09`);
59832
+ return live;
59833
+ };
59834
+ const mustScan = (file, scanId) => {
59835
+ const entry = file.scans.find((s2) => s2.id === scanId);
59836
+ if (!entry) throw invalid(`\u626B\u63CF\u5386\u53F2\u91CC\u6CA1\u6709 ${scanId} \u8FD9\u4E00\u6761\uFF0C\u5237\u65B0\u770B\u770B`);
59837
+ return entry;
59755
59838
  };
59756
59839
  const cacheMs = deps.secretsCacheMs ?? SECRETS_CACHE_MS;
59757
59840
  const secretsCache = /* @__PURE__ */ new Map();
@@ -59781,20 +59864,15 @@ function buildDeployHandlers(deps) {
59781
59864
  const github = await deps.githubIdentity.status();
59782
59865
  const auth = deps.githubIdentity.cached();
59783
59866
  const api = auth ? deps.githubApi(auth.token) : void 0;
59784
- let file;
59785
- let fileError;
59867
+ let file = emptyScanFile();
59868
+ let historyError;
59786
59869
  try {
59787
- file = await readDeployFile(dir);
59870
+ file = await readHistory(dir);
59788
59871
  } catch (err) {
59789
- fileError = err instanceof Error ? err.message : String(err);
59790
- }
59791
- let scan = await readScan(dir);
59792
- if (scan?.status === "scanning" && !isLiveScan(liveScans.get(personaId2), scan)) scan = await settleStale(dir, scan);
59793
- if (scan && scan.status === "done") {
59794
- const stale = describeStaleAssets(staleScannerAssets(scan, scannableAssets));
59795
- const kept = scan.notes.filter((n2) => !n2.startsWith(STALE_ASSETS_NOTE_PREFIX));
59796
- scan = { ...scan, notes: stale ? [...kept, stale] : kept };
59872
+ historyError = err instanceof Error ? err.message : String(err);
59797
59873
  }
59874
+ file = await settleStale(personaId2, dir, file);
59875
+ const { scans } = file;
59798
59876
  let secrets;
59799
59877
  let run;
59800
59878
  if (repo2 && api && github.status === "authorized") {
@@ -59815,72 +59893,56 @@ function buildDeployHandlers(deps) {
59815
59893
  personaId: personaId2,
59816
59894
  github,
59817
59895
  ...repo2 ? { repo: repo2 } : {},
59818
- ...file ? { file } : {},
59819
- ...scan ? { scan } : {},
59896
+ scans,
59897
+ ...file.released ? { released: file.released } : {},
59820
59898
  ...secrets ? { secrets } : {},
59821
59899
  ...run ? { run } : {},
59822
- ...fileError ? { fileError } : {}
59900
+ ...historyError ? { historyError } : {}
59823
59901
  };
59824
59902
  }
59825
59903
  const view = async (personaId2) => ({
59826
59904
  response: { type: "deploy:state", ...await stateOf(personaId2) }
59827
59905
  });
59828
- async function beginScan(personaId2, dir, current, head, want) {
59906
+ async function beginScan(personaId2, dir, file, contentHead, want) {
59829
59907
  liveScans.set(personaId2, {});
59830
- const prevManifest = await readDeployFile(dir).catch(() => void 0);
59908
+ const releasedEntry = file.released ? file.scans.find((s2) => s2.id === file.released.scanId) : void 0;
59831
59909
  const scanBase = pickScanBase({
59832
- // **文件在 ≠ 清单能用**:硬扫一开头就写了一份空清单,那一轮没跑完(OTA 重启 / butler 崩,
59833
- // 见 scan-task.ts 的收尾判定)的话文件在、内容是空的。所以判据连上「上一轮扫完了」。
59834
- hasUsableManifest: prevManifest !== void 0 && current?.status === "done",
59835
- releasedHead: await hasCommit(deps.exec, dir, current?.releasedHead) ? current?.releasedHead : void 0,
59910
+ // **有记录 ≠ 清单能用**:被发那条要是没跑完(OTA 重启 / butler 崩,见 scan-task.ts 的收尾判定)清单是半截的。
59911
+ hasUsableManifest: releasedEntry?.status === "done",
59912
+ releasedHead: await hasCommit(deps.exec, dir, file.released?.head) ? file.released?.head : void 0,
59836
59913
  ...want ? { want } : {}
59837
59914
  });
59838
- const startedAt = deps.now();
59839
- const base = {
59840
- evidence: scanBase.kind === "hard" ? [] : current?.evidence ?? [],
59841
- // notes 反过来不沿用:它是「这次扫描要老板处理的事」,沿用会把上次的事项一直复读
59842
- notes: [],
59843
- scannedAssets: [],
59844
- startedAt,
59845
- head,
59846
- // 发布记的那个 commit 跟扫描无关,原样带过去,别让重扫把软扫的基准弄丢。
59847
- //
59848
- // **哪怕上面 hasCommit 刚判定它已经不在仓库里,也照样留着**:探测失败也包括 git 没跑起来
59849
- // 这种一过性情况,据此把它抹掉就等于「一次 git 抽风 → 永久失去软扫,直到下次发布」,
59850
- // 比留一个死 sha 糟得多。留着的代价只是 UI 上「从零重扫」多显示一阵子(那期间两个按钮
59851
- // 都退硬扫、行为一致不出错),下次发布就自愈。
59852
- ...current?.releasedHead ? { releasedHead: current.releasedHead } : {},
59853
- baseKind: scanBase.kind,
59854
- ...scanBase.base ? { base: scanBase.base } : {}
59855
- };
59856
- await writeScan(dir, { status: "scanning", ...base });
59857
- if (scanBase.kind === "hard") await writeDeployFile(dir, emptyDeployFile());
59915
+ const lastDone = [...file.scans].reverse().find((s2) => s2.status === "done");
59916
+ const seed = scanBase.kind === "soft" && releasedEntry ? releasedEntry.manifest : { ...emptyDeployFile().manifest, connectors: (lastDone?.manifest.connectors ?? []).filter((c2) => c2.runner === "mcp") };
59858
59917
  const mcp = scanMcpConnectors(dir);
59859
- const current0 = await readDeployFile(dir) ?? emptyDeployFile();
59860
- const carried = scanBase.kind === "hard" ? (prevManifest?.manifest.connectors ?? []).filter((c2) => c2.runner === "mcp") : current0.manifest.connectors;
59861
- await writeDeployFile(dir, {
59862
- ...current0,
59863
- // 文件读不出来(坏 JSON)时保持原样:当成「零个 MCP」会把清单里的连接器清光,而发布闸对零连接器是放行的
59864
- manifest: { ...current0.manifest, connectors: mcp.readable ? mergeScannedConnectors(carried, mcp.connectors) : carried }
59865
- });
59866
59918
  const mcpTargets = new Set(mcp.evidence.map((e) => e.target));
59867
- const withMcp = {
59868
- ...base,
59869
- evidence: [...base.evidence.filter((e) => !mcpTargets.has(e.target)), ...mcp.evidence.map(redactEvidence)],
59870
- notes: mcp.warnings.map((w3) => `${MCP_SCAN_NOTE_PREFIX}${w3}`)
59919
+ const carriedEvidence = scanBase.kind === "soft" && releasedEntry ? releasedEntry.evidence : [];
59920
+ const entry = {
59921
+ id: newScanId(),
59922
+ status: "scanning",
59923
+ manifest: { ...seed, connectors: mcp.readable ? mergeScannedConnectors(seed.connectors, mcp.connectors) : seed.connectors },
59924
+ evidence: [...carriedEvidence.filter((e) => !mcpTargets.has(e.target)), ...mcp.evidence.map(redactEvidence)],
59925
+ // notes 不沿用:它是「这次扫描要老板处理的事」,沿用会把上次的事项一直复读
59926
+ notes: mcp.warnings.map((w3) => `${MCP_SCAN_NOTE_PREFIX}${w3}`),
59927
+ startedAt: deps.now(),
59928
+ head: contentHead,
59929
+ baseKind: scanBase.kind,
59930
+ ...scanBase.base ? { base: scanBase.base } : {}
59871
59931
  };
59872
- const started = deps.startScan(personaId2, scanBase);
59932
+ await writeScanFile(dir, replaceScan(file, entry));
59933
+ const started = deps.startScan(personaId2, scanBase, entry.id);
59873
59934
  if ("sessionId" in started) {
59874
59935
  liveScans.set(personaId2, { sessionId: started.sessionId });
59875
- await writeScan(dir, { status: "scanning", scanSessionId: started.sessionId, ...withMcp });
59936
+ await writeScanFile(dir, replaceScan(await readHistory(dir), { ...entry, scanSessionId: started.sessionId }));
59876
59937
  const mine = () => liveScans.get(personaId2)?.sessionId === started.sessionId;
59877
59938
  void started.ended.then(async (outcome) => {
59878
59939
  if (mine()) liveScans.delete(personaId2);
59879
- const current2 = await readScan(dir).catch(() => void 0);
59880
- if (liveScans.has(personaId2) || current2?.scanSessionId !== started.sessionId) return;
59881
- const settled = settleEndedScan(outcome, current2, deps.now());
59940
+ const current = await readHistory(dir).catch(() => void 0);
59941
+ const own = current?.scans.find((s2) => s2.id === entry.id);
59942
+ if (!current || !own || liveScans.has(personaId2) || own.scanSessionId !== started.sessionId) return;
59943
+ const settled = settleEndedScan(outcome, own, deps.now());
59882
59944
  if (!settled) return;
59883
- await writeScan(dir, settled);
59945
+ await writeScanFile(dir, replaceScan(current, settled));
59884
59946
  deps.warn?.("deploy scan ended without finishScan", { personaId: personaId2, why: settled.error });
59885
59947
  }).catch((err) => {
59886
59948
  if (mine()) liveScans.delete(personaId2);
@@ -59888,12 +59950,12 @@ function buildDeployHandlers(deps) {
59888
59950
  });
59889
59951
  } else {
59890
59952
  liveScans.delete(personaId2);
59891
- await writeScan(dir, {
59953
+ await writeScanFile(dir, replaceScan(await readHistory(dir), {
59954
+ ...entry,
59892
59955
  status: "failed",
59893
- ...withMcp,
59894
59956
  finishedAt: deps.now(),
59895
59957
  error: `\u8D77\u626B\u63CF\u4F1A\u8BDD\u5931\u8D25\uFF1A${started.error}`
59896
- });
59958
+ }));
59897
59959
  }
59898
59960
  }
59899
59961
  const get = async (frame, _client, ctx) => {
@@ -59906,20 +59968,21 @@ function buildDeployHandlers(deps) {
59906
59968
  ensureOwner(ctx);
59907
59969
  const args = parseArgs2(DeployPatchArgsSchema, frame, "deploy:patch");
59908
59970
  const dir = mustPersona(args.personaId);
59909
- const file = await readDeployFile(dir) ?? emptyDeployFile();
59910
- const next = {
59911
- ...file,
59912
- ...args.patch.manifest ? { manifest: args.patch.manifest } : {}
59913
- };
59914
- await writeDeployFile(dir, next);
59971
+ const file = await readHistory(dir);
59972
+ const entry = mustScan(file, args.scanId);
59973
+ if (entry.status === "failed") throw invalid("\u8FD9\u6B21\u626B\u63CF\u6CA1\u8DD1\u5B8C\uFF0C\u6E05\u5355\u4E0D\u5B8C\u6574\uFF0C\u6539\u5B83\u6CA1\u610F\u4E49\u2014\u2014\u91CD\u65B0\u626B\u63CF");
59974
+ if (entry.status === "scanning" && mustBeLiveScan(args.personaId, file, "deploy:patch").id !== entry.id) {
59975
+ throw invalid("deploy:patch \u6307\u540D\u7684\u4E0D\u662F\u6B63\u5728\u8DD1\u7684\u90A3\u6B21\u626B\u63CF");
59976
+ }
59977
+ if (args.patch.manifest) await writeScanFile(dir, replaceScan(file, { ...entry, manifest: args.patch.manifest }));
59915
59978
  return view(args.personaId);
59916
59979
  };
59917
59980
  const putSecret = async (frame, _client, ctx) => {
59918
59981
  ensureOwner(ctx);
59919
59982
  const args = parseArgs2(DeployPutSecretArgsSchema, frame, "deploy:putSecret");
59920
59983
  const dir = mustPersona(args.personaId);
59921
- const file = await readDeployFile(dir);
59922
- const declared = file ? [...file.manifest.secrets, ...sharedConnectorSecrets(file.manifest)].find((s2) => s2.name === args.name) : void 0;
59984
+ const { manifest } = mustScan(await readHistory(dir), args.scanId);
59985
+ const declared = [...manifest.secrets, ...sharedConnectorSecrets(manifest)].find((s2) => s2.name === args.name);
59923
59986
  if (!declared) throw invalid(`\u6E05\u5355\u91CC\u6CA1\u6709\u53EB\u300C${args.name}\u300D\u7684\u51ED\u636E`);
59924
59987
  const allowed = new Set(declared.env);
59925
59988
  for (const key of Object.keys(args.values)) {
@@ -59940,7 +60003,8 @@ function buildDeployHandlers(deps) {
59940
60003
  ensureOwner(ctx);
59941
60004
  const args = parseArgs2(DeployReportFindingArgsSchema, frame, "deploy:reportFinding");
59942
60005
  const dir = mustPersona(args.personaId);
59943
- const scan = mustBeLiveScan(args.personaId, await readScan(dir), "deploy:reportFinding");
60006
+ const file = await readHistory(dir);
60007
+ const scan = mustBeLiveScan(args.personaId, file, "deploy:reportFinding");
59944
60008
  let item;
59945
60009
  try {
59946
60010
  item = parseFinding(args.kind, args.item);
@@ -59948,37 +60012,34 @@ function buildDeployHandlers(deps) {
59948
60012
  throw invalid(err instanceof Error ? err.message : String(err));
59949
60013
  }
59950
60014
  if (args.kind === "connector") item = cliConnectorFromFinding(item);
59951
- const file = await readDeployFile(dir) ?? emptyDeployFile();
59952
- await writeDeployFile(dir, upsertFinding(file, args.kind, item));
59953
60015
  const target = `${args.kind}:${item.name}`;
59954
- await writeScan(dir, {
60016
+ await writeScanFile(dir, replaceScan(file, {
59955
60017
  ...scan,
60018
+ manifest: upsertFinding(scan.manifest, args.kind, item),
59956
60019
  evidence: [...scan.evidence.filter((e) => e.target !== target), redactEvidence({ target, ...args.evidence })]
59957
- });
60020
+ }));
59958
60021
  return view(args.personaId);
59959
60022
  };
59960
60023
  const finishScan = async (frame, _client, ctx) => {
59961
60024
  ensureOwner(ctx);
59962
60025
  const args = parseArgs2(DeployFinishScanArgsSchema, frame, "deploy:finishScan");
59963
60026
  const dir = mustPersona(args.personaId);
59964
- const scan = mustBeLiveScan(args.personaId, await readScan(dir), "deploy:finishScan");
59965
- await writeScan(dir, {
60027
+ const file = await readHistory(dir);
60028
+ const scan = mustBeLiveScan(args.personaId, file, "deploy:finishScan");
60029
+ await writeScanFile(dir, replaceScan(file, {
59966
60030
  ...scan,
59967
60031
  status: "done",
59968
60032
  finishedAt: deps.now(),
59969
60033
  // 存疑项也是 agent 的自由文本,跟证据同一道涂抹。daemon 自己扫 .mcp.json 时发现的问题
59970
60034
  //(带 MCP_SCAN_NOTE_PREFIX)留着——butler 不知道那些,它的那份不该把它们冲掉
59971
60035
  notes: [...scan.notes.filter((n2) => n2.startsWith(MCP_SCAN_NOTE_PREFIX)), ...args.notes.map(redactSecretValues)],
59972
- // 记下这次扫描**被告知有哪些共享资产**。以后资产表长出新条目时,只有扫的时候还不知道
59973
- // 它的那些记录会被提示一次(见 staleScannerAssets)
59974
- scannedAssets: scannableAssets.map((a2) => a2.name),
59975
60036
  error: void 0
59976
- });
60037
+ }));
59977
60038
  return view(args.personaId);
59978
60039
  };
59979
- const releaseHandler = async (frame, _client, ctx) => {
60040
+ const scanHandler = async (frame, _client, ctx) => {
59980
60041
  ensureOwner(ctx);
59981
- const { personaId: personaId2, scanBase } = parseArgs2(DeployReleaseArgsSchema, frame, "deploy:release");
60042
+ const { personaId: personaId2, scanBase } = parseArgs2(DeployScanArgsSchema, frame, "deploy:scan");
59982
60043
  const dir = mustPersona(personaId2);
59983
60044
  const wrong = wrongEngine(deps.personaEngine(personaId2)?.tool);
59984
60045
  if (wrong) throw invalid(wrong);
@@ -59994,33 +60055,43 @@ function buildDeployHandlers(deps) {
59994
60055
  repo2 = await repoInfo(deps.exec, dir);
59995
60056
  if (!repo2) throw new ClawdError(ERROR_CODES.INTERNAL, "\u4ED3\u5E93\u5EFA\u597D\u4E86\u4F46\u8BFB\u4E0D\u5230 origin");
59996
60057
  }
60058
+ if (repo2.branch !== "main") throw invalid(`\u5F53\u524D\u5728\u5206\u652F ${repo2.branch}\uFF0C\u4ED3\u5E93\u91CC\u7684\u6D41\u6C34\u7EBF\u53EA\u8BA4 main\uFF1B\u5207\u56DE main \u518D\u626B\u63CF`);
60059
+ const live = liveScans.get(personaId2);
60060
+ if (live && live.sessionId === void 0) return view(personaId2);
60061
+ let file = await readHistory(dir).catch(() => emptyScanFile());
60062
+ if (file.scans.some((s2) => s2.status === "scanning" && isLiveScan(live, s2))) return view(personaId2);
60063
+ file = await settleStale(personaId2, dir, file);
60064
+ let head;
60065
+ try {
60066
+ head = await commitAll(deps.exec, { dir, message: `clawd: \u626B\u63CF\u524D\u843D\u5B58 ${new Date(deps.now()).toISOString()}` });
60067
+ } catch (err) {
60068
+ throw toClawdError(err);
60069
+ }
60070
+ runCache.delete(personaId2);
60071
+ try {
60072
+ await beginScan(personaId2, dir, file, contentHeadOf({ head }, file.released), scanBase);
60073
+ } catch (err) {
60074
+ liveScans.delete(personaId2);
60075
+ throw err;
60076
+ }
60077
+ return view(personaId2);
60078
+ };
60079
+ const releaseHandler = async (frame, _client, ctx) => {
60080
+ ensureOwner(ctx);
60081
+ const { personaId: personaId2, scanId } = parseArgs2(DeployReleaseArgsSchema, frame, "deploy:release");
60082
+ const dir = mustPersona(personaId2);
60083
+ const wrong = wrongEngine(deps.personaEngine(personaId2)?.tool);
60084
+ if (wrong) throw invalid(wrong);
60085
+ const { auth, api } = await requireGithub();
60086
+ const repo2 = await repoInfo(deps.exec, dir);
60087
+ if (!repo2) throw invalid("\u8FD8\u6CA1\u6709\u4ED3\u5E93\u2014\u2014\u5148\u70B9\u300C\u5F00\u59CB\u626B\u63CF\u300D\uFF08\u4F1A\u5EFA\u4ED3\uFF09");
59997
60088
  if (repo2.branch !== "main") throw invalid(`\u5F53\u524D\u5728\u5206\u652F ${repo2.branch}\uFF0C\u4ED3\u5E93\u91CC\u7684\u6D41\u6C34\u7EBF\u53EA\u8BA4 main\uFF1B\u5207\u56DE main \u518D\u53D1\u5E03`);
59998
- const starting = liveScans.get(personaId2);
59999
- if (starting && starting.sessionId === void 0) return { response: { type: "deploy:release", outcome: "scanning" } };
60000
- let current = await readScan(dir);
60001
- if (current?.status === "scanning") {
60002
- if (isLiveScan(liveScans.get(personaId2), current)) return { response: { type: "deploy:release", outcome: "scanning" } };
60003
- current = await settleStale(dir, current);
60004
- }
60005
- const fileUsable = await readDeployFile(dir).then((f2) => f2 !== void 0).catch(() => false);
60006
- if (scanBase !== void 0 || !fileUsable || !isScanFresh(current, repo2)) {
60007
- let head;
60008
- try {
60009
- head = await commitAll(deps.exec, { dir, message: `clawd: \u626B\u63CF\u524D\u843D\u5B58 ${new Date(deps.now()).toISOString()}` });
60010
- } catch (err) {
60011
- throw toClawdError(err);
60012
- }
60013
- runCache.delete(personaId2);
60014
- try {
60015
- await beginScan(personaId2, dir, current, head, scanBase);
60016
- } catch (err) {
60017
- liveScans.delete(personaId2);
60018
- throw err;
60019
- }
60020
- return { response: { type: "deploy:release", outcome: "scanning" } };
60089
+ const file = await readHistory(dir);
60090
+ const entry = mustScan(file, scanId);
60091
+ if (entry.status !== "done") throw invalid(entry.status === "scanning" ? "\u8FD9\u6B21\u626B\u63CF\u8FD8\u5728\u8DD1\uFF0C\u7B49\u5B83\u626B\u5B8C" : "\u8FD9\u6B21\u626B\u63CF\u6CA1\u8DD1\u5B8C\uFF0C\u6E05\u5355\u4E0D\u5B8C\u6574\u2014\u2014\u91CD\u65B0\u626B\u63CF");
60092
+ if (!isScanFresh(entry, repo2, file.released)) {
60093
+ throw invalid(repo2.dirty ? "\u6709\u672A\u63D0\u4EA4\u7684\u6539\u52A8\uFF0C\u6E05\u5355\u662F\u6539\u52A8\u524D\u626B\u7684\u2014\u2014\u91CD\u65B0\u626B\u63CF" : "\u8FD9\u6B21\u626B\u63CF\u5BF9\u5E94\u7684\u4E0D\u662F\u5F53\u524D\u8FD9\u4E00\u7248 persona\u2014\u2014\u91CD\u65B0\u626B\u63CF");
60021
60094
  }
60022
- const file = await readDeployFile(dir);
60023
- if (!file) throw invalid("\u8FD8\u6CA1\u626B\u63CF\u51FA\u6E05\u5355");
60024
60095
  const blockers = [];
60025
60096
  let present;
60026
60097
  try {
@@ -60028,18 +60099,28 @@ function buildDeployHandlers(deps) {
60028
60099
  } catch (err) {
60029
60100
  throw toClawdError(err);
60030
60101
  }
60031
- const missing = requiredEnvNames(file).filter((e) => !present.has(e));
60102
+ const missing = requiredEnvNames(entry.manifest).filter((e) => !present.has(e));
60032
60103
  if (missing.length > 0) blockers.push(`${missing.join(" / ")} \u8FD8\u6CA1\u586B`);
60033
- for (const c2 of file.manifest.connectors) blockers.push(...connectorBlockers(c2));
60104
+ for (const c2 of entry.manifest.connectors) blockers.push(...connectorBlockers(c2));
60034
60105
  if (!deps.personaEngine(personaId2)?.model) {
60035
60106
  blockers.push("\u8FD8\u6CA1\u9009\u6A21\u578B\uFF08\u5207\u5F15\u64CE\u65F6\u4F1A\u6E05\u7A7A\uFF09\u2014\u2014\u53BB persona \u8BBE\u7F6E\u91CC\u9009\u4E00\u4E2A");
60036
60107
  }
60037
60108
  if (blockers.length > 0) throw invalid(`\u8FD8\u5DEE\uFF1A${blockers.join("\uFF1B")}`);
60109
+ const before = await readDeployFileRaw(dir);
60038
60110
  try {
60039
- const result = await release(deps.exec, api, { dir, now: deps.now, auth });
60111
+ await writeDeployFile(dir, { version: 1, manifest: entry.manifest });
60112
+ let result;
60113
+ try {
60114
+ result = await release(deps.exec, api, { dir, now: deps.now, auth });
60115
+ } catch (err) {
60116
+ if ((await repoInfo(deps.exec, dir))?.head === repo2.head) await restoreDeployFileRaw(dir, before);
60117
+ throw err;
60118
+ }
60040
60119
  runCache.delete(personaId2);
60041
- const scanned = await readScan(dir);
60042
- if (scanned) await writeScan(dir, { ...scanned, head: result.sha, releasedHead: result.sha });
60120
+ await writeScanFile(dir, {
60121
+ ...await readHistory(dir),
60122
+ released: { head: result.sha, contentHead: entry.head, scanId: entry.id, at: deps.now() }
60123
+ });
60043
60124
  return { response: { type: "deploy:release", ...result } };
60044
60125
  } catch (err) {
60045
60126
  throw toClawdError(err);
@@ -60050,7 +60131,7 @@ function buildDeployHandlers(deps) {
60050
60131
  const { personaId: personaId2, jobId } = parseArgs2(DeployRerunJobArgsSchema, frame, "deploy:rerunJob");
60051
60132
  const dir = mustPersona(personaId2);
60052
60133
  const repo2 = await repoInfo(deps.exec, dir);
60053
- if (!repo2) throw invalid("\u8FD8\u6CA1\u6709\u4ED3\u5E93\uFF0C\u5148\u70B9\u4E00\u6B21\u300C\u53D1\u5E03\u5230\u4E91\u4E0A\u300D\u5EFA\u4ED3");
60134
+ if (!repo2) throw invalid("\u8FD8\u6CA1\u6709\u4ED3\u5E93\u2014\u2014\u5148\u70B9\u300C\u5F00\u59CB\u626B\u63CF\u300D\uFF08\u4F1A\u5EFA\u4ED3\uFF09");
60054
60135
  const slug = repoSlug(repo2.url);
60055
60136
  const { api } = await requireGithub();
60056
60137
  const run = await latestRun(api, slug);
@@ -60068,6 +60149,7 @@ function buildDeployHandlers(deps) {
60068
60149
  };
60069
60150
  return {
60070
60151
  "deploy:get": get,
60152
+ "deploy:scan": scanHandler,
60071
60153
  "deploy:patch": patch,
60072
60154
  "deploy:putSecret": putSecret,
60073
60155
  "deploy:reportFinding": reportFinding,
@@ -61106,7 +61188,7 @@ ${formatLarkSpeakerReminder(speaker)}`
61106
61188
  }
61107
61189
 
61108
61190
  // src/lark-bot-connector/envelope.ts
61109
- var import_node_crypto8 = require("crypto");
61191
+ var import_node_crypto9 = require("crypto");
61110
61192
  var LARK_ENVELOPE_PREFIX = "clawdlbe1";
61111
61193
  function verifyLarkEventEnvelope(args) {
61112
61194
  const { token, publicKeyPem } = args;
@@ -61125,7 +61207,7 @@ function verifyLarkEventEnvelope(args) {
61125
61207
  const data = Buffer.from(`${prefix}.${payloadB64}`, "utf8");
61126
61208
  let sigOk = false;
61127
61209
  try {
61128
- sigOk = (0, import_node_crypto8.verify)(null, data, publicKeyPem, Buffer.from(sigB64, "base64url"));
61210
+ sigOk = (0, import_node_crypto9.verify)(null, data, publicKeyPem, Buffer.from(sigB64, "base64url"));
61129
61211
  } catch {
61130
61212
  sigOk = false;
61131
61213
  }
@@ -63136,7 +63218,7 @@ var PiRpcClient = class {
63136
63218
 
63137
63219
  // src/tools/pi-session.ts
63138
63220
  var import_node_child_process10 = require("child_process");
63139
- var import_node_crypto9 = require("crypto");
63221
+ var import_node_crypto10 = require("crypto");
63140
63222
  var import_node_fs35 = __toESM(require("fs"), 1);
63141
63223
  var import_node_path39 = __toESM(require("path"), 1);
63142
63224
 
@@ -63314,7 +63396,7 @@ ${dirs.map((d2) => `- ${d2}`).join("\n")}`;
63314
63396
  }
63315
63397
  function createPiSession(ctx, sink, deps = {}) {
63316
63398
  const cmd = process.env.PI_BIN ?? "pi";
63317
- const sessionId = ctx.toolSessionId ?? (0, import_node_crypto9.randomUUID)();
63399
+ const sessionId = ctx.toolSessionId ?? (0, import_node_crypto10.randomUUID)();
63318
63400
  const skillsDir = import_node_path39.default.join(ctx.cwd, ".claude", "skills");
63319
63401
  const exists = deps.skillsDirExists ?? ((d2) => import_node_fs35.default.existsSync(d2));
63320
63402
  const args = buildPiArgs(ctx, {
@@ -64761,7 +64843,7 @@ var wrapper_default = import_websocket.default;
64761
64843
 
64762
64844
  // src/transport/local-ws-server.ts
64763
64845
  var import_node_http = __toESM(require("http"), 1);
64764
- var import_node_crypto10 = require("crypto");
64846
+ var import_node_crypto11 = require("crypto");
64765
64847
  var LocalWsServer = class {
64766
64848
  constructor(opts) {
64767
64849
  this.opts = opts;
@@ -64978,7 +65060,7 @@ var LocalWsServer = class {
64978
65060
  }
64979
65061
  }
64980
65062
  onConnection(socket, remoteAddress) {
64981
- const id = (0, import_node_crypto10.randomUUID)();
65063
+ const id = (0, import_node_crypto11.randomUUID)();
64982
65064
  const subscribedSessions = /* @__PURE__ */ new Map();
64983
65065
  const handle = {
64984
65066
  id,
@@ -65943,7 +66025,7 @@ var import_node_path49 = __toESM(require("path"), 1);
65943
66025
  // src/attachment/upload.ts
65944
66026
  var import_node_fs43 = __toESM(require("fs"), 1);
65945
66027
  var import_node_path47 = __toESM(require("path"), 1);
65946
- var import_node_crypto11 = __toESM(require("crypto"), 1);
66028
+ var import_node_crypto12 = __toESM(require("crypto"), 1);
65947
66029
  var import_promises7 = require("stream/promises");
65948
66030
  var UploadError = class extends Error {
65949
66031
  constructor(code, message) {
@@ -65967,11 +66049,11 @@ async function writeUploadedAttachment(args) {
65967
66049
  } catch (err) {
65968
66050
  throw new UploadError("STORAGE_ERROR", `mkdir failed: ${err.message}`);
65969
66051
  }
65970
- const hasher = import_node_crypto11.default.createHash("sha256");
66052
+ const hasher = import_node_crypto12.default.createHash("sha256");
65971
66053
  let actualSize = 0;
65972
66054
  const tmpPath = import_node_path47.default.join(
65973
66055
  attachmentsRoot,
65974
- `.upload-${process.pid}-${Date.now()}-${import_node_crypto11.default.randomBytes(4).toString("hex")}`
66056
+ `.upload-${process.pid}-${Date.now()}-${import_node_crypto12.default.randomBytes(4).toString("hex")}`
65975
66057
  );
65976
66058
  try {
65977
66059
  await (0, import_promises7.pipeline)(
@@ -66815,7 +66897,7 @@ function runAttachmentGc(args) {
66815
66897
  // src/attachment/group.ts
66816
66898
  var import_node_fs46 = __toESM(require("fs"), 1);
66817
66899
  var import_node_path51 = __toESM(require("path"), 1);
66818
- var import_node_crypto12 = __toESM(require("crypto"), 1);
66900
+ var import_node_crypto13 = __toESM(require("crypto"), 1);
66819
66901
  init_protocol();
66820
66902
  var GroupFileStore = class {
66821
66903
  dataDir;
@@ -66904,7 +66986,7 @@ var GroupFileStore = class {
66904
66986
  entries[idx] = next;
66905
66987
  } else {
66906
66988
  next = {
66907
- id: `gf-${import_node_crypto12.default.randomBytes(6).toString("base64url")}`,
66989
+ id: `gf-${import_node_crypto13.default.randomBytes(6).toString("base64url")}`,
66908
66990
  relPath: input.relPath,
66909
66991
  from: input.from,
66910
66992
  label: input.label,
@@ -67050,7 +67132,7 @@ function readSpawnedByDesktopFromEnv(env = process.env) {
67050
67132
  // src/tunnel/tunnel-manager.ts
67051
67133
  var import_node_fs51 = __toESM(require("fs"), 1);
67052
67134
  var import_node_path56 = __toESM(require("path"), 1);
67053
- var import_node_crypto13 = __toESM(require("crypto"), 1);
67135
+ var import_node_crypto14 = __toESM(require("crypto"), 1);
67054
67136
  var import_node_child_process15 = require("child_process");
67055
67137
 
67056
67138
  // src/tunnel/tunnel-store.ts
@@ -67549,7 +67631,7 @@ var TunnelManager = class {
67549
67631
  override: this.deps.frpcBinaryOverride ?? void 0
67550
67632
  });
67551
67633
  const tomlPath = import_node_path56.default.join(this.deps.dataDir, "frpc.toml");
67552
- const proxyName = `clawd-${t2.subdomain}-${localPort}-${import_node_crypto13.default.randomBytes(3).toString("hex")}`;
67634
+ const proxyName = `clawd-${t2.subdomain}-${localPort}-${import_node_crypto14.default.randomBytes(3).toString("hex")}`;
67553
67635
  const toml = buildFrpcToml({
67554
67636
  serverAddr: t2.frpsHost,
67555
67637
  serverPort: t2.frpsPort,
@@ -67650,7 +67732,7 @@ async function waitForFrpcReady(proc, timeoutMs) {
67650
67732
  // src/tunnel/device-key.ts
67651
67733
  var import_node_os18 = __toESM(require("os"), 1);
67652
67734
  var import_node_path57 = __toESM(require("path"), 1);
67653
- var import_node_crypto14 = __toESM(require("crypto"), 1);
67735
+ var import_node_crypto15 = __toESM(require("crypto"), 1);
67654
67736
  var DERIVE_SALT = "clawd-tunnel-device-v1";
67655
67737
  function deriveStableDeviceKey(opts = {}) {
67656
67738
  const hostname = opts.hostname ?? import_node_os18.default.hostname();
@@ -67660,13 +67742,13 @@ function deriveStableDeviceKey(opts = {}) {
67660
67742
  const normalizedDataDir = opts.dataDir ? import_node_path57.default.resolve(opts.dataDir) : null;
67661
67743
  const isDefaultDir = normalizedDataDir == null || normalizedDataDir === defaultDataDir;
67662
67744
  const input = isDefaultDir ? `${hostname}::${uid}` : `${hostname}::${uid}::${normalizedDataDir}`;
67663
- return import_node_crypto14.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
67745
+ return import_node_crypto15.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
67664
67746
  }
67665
67747
 
67666
67748
  // src/auth-store.ts
67667
67749
  var import_node_fs52 = __toESM(require("fs"), 1);
67668
67750
  var import_node_path58 = __toESM(require("path"), 1);
67669
- var import_node_crypto15 = __toESM(require("crypto"), 1);
67751
+ var import_node_crypto16 = __toESM(require("crypto"), 1);
67670
67752
  var AUTH_FILE_NAME = "auth.json";
67671
67753
  function authFilePath(dataDir) {
67672
67754
  return import_node_path58.default.join(dataDir, AUTH_FILE_NAME);
@@ -67703,10 +67785,10 @@ function loadOrCreateTunnelDeviceKey(opts) {
67703
67785
  return tunnelDeviceKey;
67704
67786
  }
67705
67787
  function defaultGenerateToken() {
67706
- return import_node_crypto15.default.randomBytes(32).toString("base64url");
67788
+ return import_node_crypto16.default.randomBytes(32).toString("base64url");
67707
67789
  }
67708
67790
  function defaultGenerateOwnerPrincipalId() {
67709
- return `owner-${import_node_crypto15.default.randomUUID()}`;
67791
+ return `owner-${import_node_crypto16.default.randomUUID()}`;
67710
67792
  }
67711
67793
  function readAuthFile(file) {
67712
67794
  try {
@@ -67838,7 +67920,7 @@ var OwnerIdentityStore = class {
67838
67920
  };
67839
67921
 
67840
67922
  // src/feishu-auth/login-flow.ts
67841
- var import_node_crypto16 = __toESM(require("crypto"), 1);
67923
+ var import_node_crypto17 = __toESM(require("crypto"), 1);
67842
67924
  var STATE_TTL_MS = 5 * 60 * 1e3;
67843
67925
  var LoginFlow = class {
67844
67926
  constructor(deps) {
@@ -67847,7 +67929,7 @@ var LoginFlow = class {
67847
67929
  deps;
67848
67930
  pendingStates = /* @__PURE__ */ new Map();
67849
67931
  start() {
67850
- const state = import_node_crypto16.default.randomBytes(16).toString("base64url");
67932
+ const state = import_node_crypto17.default.randomBytes(16).toString("base64url");
67851
67933
  const now = (this.deps.now ?? Date.now)();
67852
67934
  this.pendingStates.set(state, now);
67853
67935
  this.gcExpired(now);
@@ -68378,7 +68460,7 @@ init_protocol();
68378
68460
  var import_node_fs55 = __toESM(require("fs"), 1);
68379
68461
  var import_node_os20 = __toESM(require("os"), 1);
68380
68462
  var import_node_path61 = __toESM(require("path"), 1);
68381
- var import_node_crypto17 = require("crypto");
68463
+ var import_node_crypto18 = require("crypto");
68382
68464
  init_claude_history();
68383
68465
  function readJsonlEntries(file) {
68384
68466
  const raw = import_node_fs55.default.readFileSync(file, "utf8");
@@ -68415,7 +68497,7 @@ function forkSession(input) {
68415
68497
  throw new Error(`fork: messageUuid not found in transcript: ${input.messageUuid}`);
68416
68498
  }
68417
68499
  const sliced = mainEntries.slice(0, targetIdx + 1);
68418
- const forkedToolSessionId = (0, import_node_crypto17.randomUUID)();
68500
+ const forkedToolSessionId = (0, import_node_crypto18.randomUUID)();
68419
68501
  let parentUuid = null;
68420
68502
  const forkedLines = [];
68421
68503
  for (const entry of sliced) {
@@ -70741,6 +70823,7 @@ var METHOD_GRANT_MAP = {
70741
70823
  // deploy:*(persona 上云):全部 owner-only。里面有凭据录入口、建仓和推送,
70742
70824
  // guest 不该看见更不该动。
70743
70825
  "deploy:get": ADMIN_ANY,
70826
+ "deploy:scan": ADMIN_ANY,
70744
70827
  "deploy:patch": ADMIN_ANY,
70745
70828
  "deploy:putSecret": ADMIN_ANY,
70746
70829
  "deploy:reportFinding": ADMIN_ANY,
@@ -70954,6 +71037,7 @@ var METHOD_GRANT_MAP = {
70954
71037
  "device:connections": ADMIN_ANY
70955
71038
  };
70956
71039
  var HUMAN_ONLY_METHODS = [
71040
+ "deploy:scan",
70957
71041
  "deploy:release",
70958
71042
  "deploy:putSecret",
70959
71043
  "deploy:rerunJob",
@@ -71001,7 +71085,7 @@ function computeMethodAccess(args) {
71001
71085
  }
71002
71086
 
71003
71087
  // src/version.ts
71004
- var version = "0.2.553".length > 0 ? "0.2.553" : "dev";
71088
+ var version = "0.2.555".length > 0 ? "0.2.555" : "dev";
71005
71089
 
71006
71090
  // src/cli-probe/probe.ts
71007
71091
  var fs78 = __toESM(require("fs"), 1);
@@ -71398,7 +71482,7 @@ init_protocol();
71398
71482
  // src/extension/bundle-zip.ts
71399
71483
  var import_promises11 = __toESM(require("fs/promises"), 1);
71400
71484
  var import_node_path67 = __toESM(require("path"), 1);
71401
- var import_node_crypto18 = __toESM(require("crypto"), 1);
71485
+ var import_node_crypto19 = __toESM(require("crypto"), 1);
71402
71486
  var import_jszip2 = __toESM(require_lib3(), 1);
71403
71487
  async function bundleExtensionDir(dir) {
71404
71488
  const entries = await listFilesSorted(dir);
@@ -71413,7 +71497,7 @@ async function bundleExtensionDir(dir) {
71413
71497
  compression: "DEFLATE",
71414
71498
  compressionOptions: { level: 6 }
71415
71499
  });
71416
- const sha256 = import_node_crypto18.default.createHash("sha256").update(buffer).digest("hex");
71500
+ const sha256 = import_node_crypto19.default.createHash("sha256").update(buffer).digest("hex");
71417
71501
  return { buffer, sha256 };
71418
71502
  }
71419
71503
  var FIXED_DATE = /* @__PURE__ */ new Date("2020-01-01T00:00:00.000Z");
@@ -71481,7 +71565,7 @@ function computePublishCheck(args) {
71481
71565
  var import_promises12 = __toESM(require("fs/promises"), 1);
71482
71566
  var import_node_path69 = __toESM(require("path"), 1);
71483
71567
  var import_node_os25 = __toESM(require("os"), 1);
71484
- var import_node_crypto19 = __toESM(require("crypto"), 1);
71568
+ var import_node_crypto20 = __toESM(require("crypto"), 1);
71485
71569
  var import_jszip3 = __toESM(require_lib3(), 1);
71486
71570
 
71487
71571
  // src/extension/paths.ts
@@ -71510,7 +71594,7 @@ var InstallError = class extends Error {
71510
71594
  };
71511
71595
  async function installFromChannel(args, deps) {
71512
71596
  const { channelRef, snapshotHash, bundleZip } = args;
71513
- const computed = import_node_crypto19.default.createHash("sha256").update(bundleZip).digest("hex");
71597
+ const computed = import_node_crypto20.default.createHash("sha256").update(bundleZip).digest("hex");
71514
71598
  if (computed !== snapshotHash) {
71515
71599
  throw new InstallError(
71516
71600
  "HASH_MISMATCH",
@@ -71602,7 +71686,7 @@ async function installFromChannel(args, deps) {
71602
71686
  var import_promises13 = __toESM(require("fs/promises"), 1);
71603
71687
  var import_node_path70 = __toESM(require("path"), 1);
71604
71688
  var import_node_os26 = __toESM(require("os"), 1);
71605
- var import_node_crypto20 = __toESM(require("crypto"), 1);
71689
+ var import_node_crypto21 = __toESM(require("crypto"), 1);
71606
71690
  var import_jszip4 = __toESM(require_lib3(), 1);
71607
71691
  var UpdateError = class extends Error {
71608
71692
  constructor(code, message) {
@@ -71640,7 +71724,7 @@ async function updateFromChannel(args, deps) {
71640
71724
  if (e instanceof UpdateError) throw e;
71641
71725
  throw e;
71642
71726
  }
71643
- const computed = import_node_crypto20.default.createHash("sha256").update(bundleZip).digest("hex");
71727
+ const computed = import_node_crypto21.default.createHash("sha256").update(bundleZip).digest("hex");
71644
71728
  if (computed !== snapshotHash) {
71645
71729
  throw new UpdateError(
71646
71730
  "HASH_MISMATCH",
@@ -72769,7 +72853,7 @@ async function uninstall(deps) {
72769
72853
  }
72770
72854
 
72771
72855
  // src/handlers/index.ts
72772
- var import_node_crypto21 = require("crypto");
72856
+ var import_node_crypto22 = require("crypto");
72773
72857
 
72774
72858
  // src/handlers/peer-exec.ts
72775
72859
  init_protocol();
@@ -72924,7 +73008,7 @@ function buildMethodHandlers(deps) {
72924
73008
  const c2 = deps.contactStore.get(deviceId);
72925
73009
  return c2 ? { deviceId: c2.deviceId, remoteUrl: c2.remoteUrl, connectToken: c2.connectToken } : null;
72926
73010
  },
72927
- genId: () => (0, import_node_crypto21.randomUUID)(),
73011
+ genId: () => (0, import_node_crypto22.randomUUID)(),
72928
73012
  now: () => Date.now(),
72929
73013
  forwardInboxPostToPeer,
72930
73014
  logger: deps.logger
@@ -73653,7 +73737,7 @@ async function startDaemon(config) {
73653
73737
  const channelStore = createChannelStore({
73654
73738
  filePath: import_node_path78.default.join(config.dataDir, "channels.json"),
73655
73739
  now: () => Date.now(),
73656
- genId: () => `ch-${(0, import_node_crypto22.randomUUID)().slice(0, 8)}`,
73740
+ genId: () => `ch-${(0, import_node_crypto23.randomUUID)().slice(0, 8)}`,
73657
73741
  onFlushError: (err) => logger.error("channel store flush failed", {
73658
73742
  reason: err instanceof Error ? err.message : String(err)
73659
73743
  })
@@ -73662,7 +73746,7 @@ async function startDaemon(config) {
73662
73746
  const topicStore = createTopicStore({
73663
73747
  filePath: import_node_path78.default.join(config.dataDir, "topics.json"),
73664
73748
  now: () => Date.now(),
73665
- genId: () => `tp-${(0, import_node_crypto22.randomUUID)().slice(0, 8)}`,
73749
+ genId: () => `tp-${(0, import_node_crypto23.randomUUID)().slice(0, 8)}`,
73666
73750
  onFlushError: (err) => logger.error("topic store flush failed", {
73667
73751
  reason: err instanceof Error ? err.message : String(err)
73668
73752
  })
@@ -73670,7 +73754,7 @@ async function startDaemon(config) {
73670
73754
  await topicStore.load();
73671
73755
  let deliverPendingRef;
73672
73756
  const personaDispatchManager = new PersonaDispatchManager({
73673
- genId: () => (0, import_node_crypto22.randomUUID)(),
73757
+ genId: () => (0, import_node_crypto23.randomUUID)(),
73674
73758
  store: dispatchStore,
73675
73759
  now: () => Date.now(),
73676
73760
  onCompleted: (sid) => deliverPendingRef?.(sid),
@@ -74635,15 +74719,15 @@ async function startDaemon(config) {
74635
74719
  });
74636
74720
  Object.assign(handlers, shiftHandlers);
74637
74721
  {
74638
- const scans = await Promise.all(personaStore.list().map(async (personaId2) => ({
74722
+ const files = await Promise.all(personaStore.list().map(async (personaId2) => ({
74639
74723
  personaId: personaId2,
74640
- scan: await readScan(personaStore.personaDirPath(personaId2)).catch(() => void 0)
74724
+ file: await readScanFile(personaStore.personaDirPath(personaId2)).catch(() => void 0)
74641
74725
  })));
74642
74726
  const interrupted = reconcileInterruptedScans({
74643
- scans,
74727
+ files,
74644
74728
  now: Date.now(),
74645
- write: (personaId2, scan) => {
74646
- void writeScan(personaStore.personaDirPath(personaId2), scan).catch((err) => {
74729
+ write: (personaId2, file) => {
74730
+ void writeScanFile(personaStore.personaDirPath(personaId2), file).catch((err) => {
74647
74731
  logger.warn("deploy scan reconcile write failed", { personaId: personaId2, error: String(err) });
74648
74732
  });
74649
74733
  }
@@ -74664,10 +74748,7 @@ async function startDaemon(config) {
74664
74748
  org: PERSONA_REPO_ORG,
74665
74749
  now: () => Date.now(),
74666
74750
  warn: (msg, meta) => logger.warn(msg, meta),
74667
- startScan: (personaId2, scanBase) => {
74668
- const assetLines = SCANNABLE_ASSETS.map(
74669
- (a2) => ` - ${a2.name}${a2.path ? `\uFF08${a2.path(config.dataDir)}\uFF09` : ""}\uFF1A${a2.what}`
74670
- ).join("\n");
74751
+ startScan: (personaId2, scanBase, scanId) => {
74671
74752
  const baseLines = scanBase.base ? [
74672
74753
  `\u672C\u6B21\u662F**${SCAN_BASE_LABEL[scanBase.kind]}**\uFF1A\u53EA\u770B\u8DDF\u7EBF\u4E0A\u6B63\u8DD1\u90A3\u7248\u7684\u5DEE\u522B\uFF0C`,
74673
74754
  `\u90A3\u4E00\u7248\u662F commit ${scanBase.base}\u3002`,
@@ -74677,14 +74758,12 @@ async function startDaemon(config) {
74677
74758
  `\u672C\u6B21\u662F**${SCAN_BASE_LABEL[scanBase.kind]}**\uFF1A\u4ECE\u96F6\u628A persona \u6574\u4E2A\u8BFB\u4E00\u904D\uFF0C\u6E05\u5355\u662F\u7A7A\u7684\u3002`
74678
74759
  ];
74679
74760
  const prompt = [
74680
- `\u626B\u63CF persona\u300C${personaId2}\u300D\u7684\u90E8\u7F72\u73AF\u5883\uFF0CpersonaId=${personaId2}\u3002`,
74761
+ `\u626B\u63CF persona\u300C${personaId2}\u300D\u7684\u90E8\u7F72\u73AF\u5883\uFF0CpersonaId=${personaId2}\uFF0CscanId=${scanId}\u3002`,
74681
74762
  "",
74682
74763
  `persona \u76EE\u5F55\uFF1A${personaStore.personaDirPath(personaId2)}`,
74764
+ `\u8FD9\u6B21\u626B\u63CF\u5728\u5386\u53F2\u91CC\u7684 id \u662F ${scanId}\uFF08\u6536\u5C3E\u524D\u8981\u5220\u6E05\u5355\u6761\u76EE\u65F6 deploy:patch \u7528\u5B83\u6307\u540D\uFF09\u3002`,
74683
74765
  ...baseLines,
74684
74766
  "",
74685
- "\u53EF\u4EE5\u58F0\u660E\u7684 persona \u76EE\u5F55\u4E4B\u5916\u7684\u5171\u4EAB\u8D44\u4EA7\uFF08\u7528\u5230\u54EA\u6761\u5C31\u62A5\u54EA\u6761\uFF09\uFF1A",
74686
- assetLines || " \uFF08\u65E0\uFF09",
74687
- "",
74688
74767
  "\u6309 scan-persona-for-deploy skill \u5E72\u6D3B\uFF1A\u8FB9\u67E5\u8FB9\u7528 deploy:reportFinding \u4E0A\u62A5\uFF0C",
74689
74768
  "\u5168\u90E8\u67E5\u5B8C\u8C03 deploy:finishScan \u6536\u5C3E\u3002"
74690
74769
  ].join("\n");