@clawos-dev/clawd 0.2.673 → 0.2.674

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
@@ -6498,6 +6498,10 @@ function isScanFresh(scan, repo2, released) {
6498
6498
  if (scan.status !== "done") return false;
6499
6499
  return scan.head === contentHeadOf(repo2, released) && !repo2.dirty;
6500
6500
  }
6501
+ function isScanRollbackable(scan, repo2, released) {
6502
+ if (!scan || !repo2 || scan.status !== "done" || !scan.head) return false;
6503
+ return scan.head !== contentHeadOf(repo2, released);
6504
+ }
6501
6505
  function cloudEngineOk(tool) {
6502
6506
  return CLOUD_ENGINES.includes(tool || "claude");
6503
6507
  }
@@ -62867,6 +62871,25 @@ async function commitAll(exec, args) {
62867
62871
  }
62868
62872
  return mustGit(exec, args.dir, ["rev-parse", "HEAD"], "\u8BFB\u63D0\u4EA4");
62869
62873
  }
62874
+ async function restoreContent(exec, dir, sha) {
62875
+ if (!/^[a-f0-9]{40}$/.test(sha)) throw new RepoError("\u626B\u63CF\u7ED1\u7684\u63D0\u4EA4\u4E0D\u662F\u5B8C\u6574 SHA\uFF0C\u65E0\u6CD5\u56DE\u5230\u90A3\u4E2A\u7248\u672C");
62876
+ if (!await hasCommit(exec, dir, sha)) throw new RepoError("\u8FD9\u6B21\u626B\u63CF\u5BF9\u5E94\u7684\u7248\u672C\u5DF2\u4E0D\u5728\u672C\u673A\u4ED3\u5E93\u91CC\uFF0C\u65E0\u6CD5\u56DE\u5230\u5B83\u2014\u2014\u91CD\u65B0\u626B\u63CF");
62877
+ const ancestor = await git(exec, dir, ["merge-base", "--is-ancestor", sha, "HEAD"]);
62878
+ if (ancestor.code !== 0) throw new RepoError("\u8FD9\u6B21\u626B\u63CF\u5BF9\u5E94\u7684\u7248\u672C\u4E0D\u5728\u5F53\u524D\u5206\u652F\u7684\u5386\u53F2\u91CC\uFF0C\u65E0\u6CD5\u56DE\u5230\u5B83\u2014\u2014\u91CD\u65B0\u626B\u63CF");
62879
+ const saved = await commitAll(exec, { dir, message: "clawd: \u4FDD\u5B58\u56DE\u5230\u5386\u53F2\u7248\u672C\u524D\u7684\u4FEE\u6539" });
62880
+ try {
62881
+ await mustGit(exec, dir, ["restore", `--source=${sha}`, "--staged", "--worktree", "--", ":/", `:(exclude)${PERSONA_FILE_REL}`], "\u56DE\u5230\u6240\u9009\u7248\u672C");
62882
+ const identity = await identityArgs(exec, dir);
62883
+ await mustGit(exec, dir, [...identity, "commit", "--allow-empty", "-m", `clawd: \u56DE\u5230 ${sha.slice(0, 7)} \u7248\u672C\u7684\u5185\u5BB9`], "\u63D0\u4EA4\u56DE\u6EDA");
62884
+ } catch (err) {
62885
+ await resetTo(exec, dir, saved);
62886
+ throw err;
62887
+ }
62888
+ return saved;
62889
+ }
62890
+ async function resetTo(exec, dir, sha) {
62891
+ await mustGit(exec, dir, ["reset", "--hard", sha], "\u64A4\u56DE\u56DE\u6EDA");
62892
+ }
62870
62893
  async function release(exec, api, args) {
62871
62894
  const info = await repoInfo(exec, args.dir);
62872
62895
  if (!info) throw new RepoError("\u8FD8\u6CA1\u6709\u4ED3\u5E93\uFF0C\u5148\u70B9\u4E00\u6B21\u300C\u53D1\u5E03\u5230\u4E91\u4E0A\u300D\u5EFA\u4ED3");
@@ -63034,6 +63057,16 @@ var import_node_stream3 = require("stream");
63034
63057
  var import_promises6 = require("stream/promises");
63035
63058
  var ARTIFACT_MAX_BYTES = 200 * 1024 * 1024;
63036
63059
  var ARTIFACT_TIMEOUT_MS = 12e4;
63060
+ var ARTIFACT_ATTEMPTS = 2;
63061
+ var TransientDownloadError = class extends Error {
63062
+ };
63063
+ function isTransient(err) {
63064
+ if (err instanceof TransientDownloadError) return true;
63065
+ const e = err;
63066
+ if (e?.name === "TimeoutError" || e?.name === "AbortError") return true;
63067
+ if (err instanceof TypeError) return true;
63068
+ return /^(ECONNRESET|ETIMEDOUT|ECONNREFUSED|EAI_AGAIN|UND_ERR_)/.test(e?.code ?? e?.cause?.code ?? "");
63069
+ }
63037
63070
  function artifactKind(url, bin) {
63038
63071
  const u2 = new URL(url);
63039
63072
  if (u2.protocol !== "https:") throw new Error(`\u8D44\u4EA7\u53EA\u6536 https \u76F4\u94FE\uFF1A${url}`);
@@ -63058,37 +63091,49 @@ async function verifyArtifact(fetchArtifact, a2) {
63058
63091
  return { sha256: got.sha256, size: got.size };
63059
63092
  }
63060
63093
  function downloadArtifact(exec) {
63061
- return async (url, { listEntries }) => {
63062
- const res = await fetch(url, { signal: AbortSignal.timeout(ARTIFACT_TIMEOUT_MS), redirect: "follow" });
63063
- if (!res.ok || !res.body) throw new Error(`\u4E0B\u8F7D\u5931\u8D25 HTTP ${res.status}\uFF1A${url}`);
63064
- const declared = Number(res.headers.get("content-length") ?? 0);
63065
- if (declared > ARTIFACT_MAX_BYTES) throw new Error(`\u8D44\u4EA7 ${declared} \u5B57\u8282\uFF0C\u8D85\u8FC7\u4E0A\u9650 ${ARTIFACT_MAX_BYTES}\uFF1A${url}`);
63066
- const dir = await import_node_fs37.default.promises.mkdtemp(import_node_path44.default.join(import_node_os14.default.tmpdir(), "clawd-artifact-"));
63067
- const file = import_node_path44.default.join(dir, "pkg");
63068
- const hash = (0, import_node_crypto13.createHash)("sha256");
63069
- let size = 0;
63070
- const meter = new import_node_stream3.Transform({
63071
- transform(chunk, _enc, cb) {
63072
- size += chunk.length;
63073
- if (size > ARTIFACT_MAX_BYTES) return cb(new Error(`\u8D44\u4EA7\u8D85\u8FC7\u4E0A\u9650 ${ARTIFACT_MAX_BYTES} \u5B57\u8282\uFF1A${url}`));
63074
- hash.update(chunk);
63075
- cb(null, chunk);
63094
+ return async (url, opts) => {
63095
+ for (let attempt = 1; ; attempt++) {
63096
+ try {
63097
+ return await downloadOnce(exec, url, opts);
63098
+ } catch (err) {
63099
+ if (attempt >= ARTIFACT_ATTEMPTS || !isTransient(err)) throw err;
63076
63100
  }
63077
- });
63078
- try {
63079
- await (0, import_promises6.pipeline)(import_node_stream3.Readable.fromWeb(res.body), meter, import_node_fs37.default.createWriteStream(file));
63080
- let entries;
63081
- if (listEntries) {
63082
- const r2 = await exec("tar", ["-tzf", file], { timeoutMs: 3e4 });
63083
- if (r2.code !== 0) throw new Error(`\u4E0D\u662F\u5408\u6CD5\u7684 tar.gz\uFF1A${url}\uFF08${r2.stderr.trim()}\uFF09`);
63084
- entries = r2.stdout.split("\n").filter(Boolean);
63085
- }
63086
- return { sha256: hash.digest("hex"), size, ...entries ? { entries } : {} };
63087
- } finally {
63088
- await import_node_fs37.default.promises.rm(dir, { recursive: true, force: true });
63089
63101
  }
63090
63102
  };
63091
63103
  }
63104
+ async function downloadOnce(exec, url, { listEntries }) {
63105
+ const res = await fetch(url, { signal: AbortSignal.timeout(ARTIFACT_TIMEOUT_MS), redirect: "follow" });
63106
+ if (!res.ok || !res.body) {
63107
+ const msg = `\u4E0B\u8F7D\u5931\u8D25 HTTP ${res.status}\uFF1A${url}`;
63108
+ throw res.status >= 500 || res.status === 429 ? new TransientDownloadError(msg) : new Error(msg);
63109
+ }
63110
+ const declared = Number(res.headers.get("content-length") ?? 0);
63111
+ if (declared > ARTIFACT_MAX_BYTES) throw new Error(`\u8D44\u4EA7 ${declared} \u5B57\u8282\uFF0C\u8D85\u8FC7\u4E0A\u9650 ${ARTIFACT_MAX_BYTES}\uFF1A${url}`);
63112
+ const dir = await import_node_fs37.default.promises.mkdtemp(import_node_path44.default.join(import_node_os14.default.tmpdir(), "clawd-artifact-"));
63113
+ const file = import_node_path44.default.join(dir, "pkg");
63114
+ const hash = (0, import_node_crypto13.createHash)("sha256");
63115
+ let size = 0;
63116
+ const meter = new import_node_stream3.Transform({
63117
+ transform(chunk, _enc, cb) {
63118
+ size += chunk.length;
63119
+ if (size > ARTIFACT_MAX_BYTES) return cb(new Error(`\u8D44\u4EA7\u8D85\u8FC7\u4E0A\u9650 ${ARTIFACT_MAX_BYTES} \u5B57\u8282\uFF1A${url}`));
63120
+ hash.update(chunk);
63121
+ cb(null, chunk);
63122
+ }
63123
+ });
63124
+ try {
63125
+ await (0, import_promises6.pipeline)(import_node_stream3.Readable.fromWeb(res.body), meter, import_node_fs37.default.createWriteStream(file));
63126
+ let entries;
63127
+ if (listEntries) {
63128
+ const r2 = await exec("tar", ["-tzf", file], { timeoutMs: 3e4 });
63129
+ if (r2.code !== 0) throw new Error(`\u4E0D\u662F\u5408\u6CD5\u7684 tar.gz\uFF1A${url}\uFF08${r2.stderr.trim()}\uFF09`);
63130
+ entries = r2.stdout.split("\n").filter(Boolean);
63131
+ }
63132
+ return { sha256: hash.digest("hex"), size, ...entries ? { entries } : {} };
63133
+ } finally {
63134
+ await import_node_fs37.default.promises.rm(dir, { recursive: true, force: true });
63135
+ }
63136
+ }
63092
63137
 
63093
63138
  // src/deploy/debian-apt.ts
63094
63139
  init_protocol();
@@ -63186,6 +63231,7 @@ async function requireIntegrationPublishingConfig(api, slug) {
63186
63231
  }
63187
63232
 
63188
63233
  // src/deploy/scan-base.ts
63234
+ init_protocol();
63189
63235
  function pickScanBase(args) {
63190
63236
  if (args.want === "hard") return { kind: "hard" };
63191
63237
  if (!args.hasUsableManifest || !args.releasedHead) return { kind: "hard" };
@@ -63195,6 +63241,12 @@ var SCAN_BASE_LABEL = {
63195
63241
  hard: "\u786C\u626B",
63196
63242
  soft: "\u8F6F\u626B"
63197
63243
  };
63244
+ function packageNeedsRereport(pkg, probed) {
63245
+ if (probed === null || probed !== pkg.localVersion) return true;
63246
+ if (packageBlockers(pkg).length > 0) return true;
63247
+ const rootProvided = CLOUD_ROOT_PROVIDES.includes(pkg.name);
63248
+ return !!pkg.from?.startsWith("apt:") && !rootProvided && !pkg.aptVersion && !pkg.aptMissing;
63249
+ }
63198
63250
 
63199
63251
  // src/deploy/run-status.ts
63200
63252
  init_protocol();
@@ -63754,6 +63806,10 @@ function buildDeployHandlers(deps) {
63754
63806
  const engineChannels = withChannels ? (await readHistory(dir)).engineChannels : void 0;
63755
63807
  return { response: { type: "deploy:secrets", environment, ...secrets ? { secrets } : {}, ...engineChannels ? { engineChannels } : {} } };
63756
63808
  }
63809
+ async function stalePackageNames(packages) {
63810
+ const probed = await Promise.all(packages.map((p3) => deps.probeLocalVersion(p3).catch(() => null)));
63811
+ return packages.filter((p3, i2) => packageNeedsRereport(p3, probed[i2])).map((p3) => p3.name);
63812
+ }
63757
63813
  async function beginScan(personaId2, dir, file, contentHead, want) {
63758
63814
  liveScans.set(personaId2, {});
63759
63815
  const released = latestProductionRelease(file.releases);
@@ -63784,7 +63840,8 @@ function buildDeployHandlers(deps) {
63784
63840
  ...scanBase.base ? { base: scanBase.base } : {}
63785
63841
  };
63786
63842
  await writeScanFile(dir, replaceScan(file, entry));
63787
- const started = deps.startScan(personaId2, scanBase, entry.id);
63843
+ const rereport = scanBase.kind === "soft" ? await stalePackageNames(entry.manifest.packages) : [];
63844
+ const started = deps.startScan(personaId2, scanBase, entry.id, rereport);
63788
63845
  if ("sessionId" in started) {
63789
63846
  liveScans.set(personaId2, { sessionId: started.sessionId });
63790
63847
  await writeScanFile(dir, replaceScan(await readHistory(dir), { ...entry, scanSessionId: started.sessionId }));
@@ -63909,7 +63966,7 @@ function buildDeployHandlers(deps) {
63909
63966
  }
63910
63967
  return secretsResult(args.personaId, dir, api, args.environment, true);
63911
63968
  };
63912
- async function settlePackage(pkg) {
63969
+ async function settlePackage(pkg, existing) {
63913
63970
  const localVersion = await deps.probeLocalVersion(pkg);
63914
63971
  const { artifact: reported, ...rest } = pkg;
63915
63972
  if (pkg.from?.startsWith("apt:")) {
@@ -63921,6 +63978,10 @@ function buildDeployHandlers(deps) {
63921
63978
  if (!reported) return out;
63922
63979
  if (!pkg.version) throw invalid(`\u9489\u8D44\u4EA7\u8981\u5148\u8BF4\u6E05\u662F\u54EA\u4E2A\u7248\u672C\u7684\uFF1A\`${pkg.name}\` \u5E26 artifact \u7684\u4E0A\u62A5\u5FC5\u987B\u540C\u65F6\u5E26 version`);
63923
63980
  if (pkg.version !== version2) throw invalid(`\u672C\u673A\u88C5\u7684 \`${pkg.name}\` \u662F ${version2}\uFF0C\u4F60\u6309 ${pkg.version} \u627E\u7684\u8D44\u4EA7\uFF1B\u6309 ${version2} \u91CD\u627E`);
63981
+ const prior = existing?.artifact;
63982
+ if (prior && existing?.version === version2 && prior.url === reported.url && prior.bin === reported.bin) {
63983
+ return { ...out, artifact: prior };
63984
+ }
63924
63985
  let verified;
63925
63986
  try {
63926
63987
  verified = await verifyArtifact(deps.fetchArtifact, reported);
@@ -63966,7 +64027,7 @@ function buildDeployHandlers(deps) {
63966
64027
  const previous = scan.manifest.connectors.some((c2) => c2.name === incoming.name) ? void 0 : [...file.scans].reverse().find((s2) => s2.status === "done")?.manifest.connectors.find((c2) => c2.name === incoming.name && (isFeishuBotConnector(c2) || isFeishuPublisherConnector(c2)));
63967
64028
  item = mergeConnector(previous, incoming);
63968
64029
  }
63969
- if (args.kind === "package") item = await settlePackage(item);
64030
+ if (args.kind === "package") item = await settlePackage(item, scan.manifest.packages.find((p3) => p3.name === item.name));
63970
64031
  const target = `${args.kind}:${item.name}`;
63971
64032
  await writeScanFile(dir, replaceScan(file, {
63972
64033
  ...scan,
@@ -64055,7 +64116,18 @@ function buildDeployHandlers(deps) {
64055
64116
  const changed = changedConfigKeys(configOfPersona(deps.personaEngine(personaId2) ?? {}), await testedConfigOf(mustPersona(personaId2), record4));
64056
64117
  if (changed?.length) throw invalid(`\u914D\u7F6E\u6539\u8FC7\u4E86\uFF08${changed.map((k3) => CONFIG_KEY_LABEL[k3]).join("\u3001")}\uFF09\uFF0C\u5148\u300C\u5E94\u7528\u5230\u6D4B\u8BD5\u73AF\u5883\u300D\u5E76\u786E\u8BA4\uFF0C\u518D\u4E0A\u7EBF`);
64057
64118
  };
64058
- const releaseHandler = async (frame, _client, ctx) => {
64119
+ const releaseHandler = async (frame, client, ctx) => {
64120
+ let undoRollback;
64121
+ try {
64122
+ return await releaseScan(frame, client, ctx, (undo) => {
64123
+ undoRollback = undo;
64124
+ });
64125
+ } catch (err) {
64126
+ if (undoRollback) await undoRollback();
64127
+ throw err;
64128
+ }
64129
+ };
64130
+ const releaseScan = async (frame, _client, ctx, armUndo) => {
64059
64131
  ensureOwner(ctx);
64060
64132
  const { personaId: personaId2, scanId } = parseArgs2(DeployReleaseArgsSchema, frame, "deploy:release");
64061
64133
  const dir = mustPersona(personaId2);
@@ -64067,12 +64139,17 @@ function buildDeployHandlers(deps) {
64067
64139
  if (repo2.branch !== "integration") throw invalid("\u8BF7\u91CD\u65B0\u626B\u63CF\uFF0C\u5207\u5230\u6D4B\u8BD5\u53D1\u5E03\u5206\u652F\u540E\u518D\u53D1\u5E03");
64068
64140
  const file = await readHistory(dir);
64069
64141
  const entry = mustScan(file, scanId);
64070
- const publisherTest = discoverPublisherTest(dir);
64071
- const testBlockers = [...publisherTest.problems, ...entry.notes.filter((note) => note.startsWith(TEST_SCAN_NOTE_PREFIX))];
64072
- if (JSON.stringify(publisherTest.test) !== JSON.stringify(entry.manifest.test)) testBlockers.push("\u6D4B\u8BD5\u58F0\u660E\u5DF2\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u626B\u63CF");
64073
- if (testBlockers.length) throw invalid("\u8FD8\u5DEE\uFF1A" + testBlockers.join("\uFF1B"));
64142
+ const checkPublisherTest = () => {
64143
+ const publisherTest = discoverPublisherTest(dir);
64144
+ const testBlockers = [...publisherTest.problems, ...entry.notes.filter((note) => note.startsWith(TEST_SCAN_NOTE_PREFIX))];
64145
+ if (JSON.stringify(publisherTest.test) !== JSON.stringify(entry.manifest.test)) testBlockers.push("\u6D4B\u8BD5\u58F0\u660E\u5DF2\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u626B\u63CF");
64146
+ if (testBlockers.length) throw invalid("\u8FD8\u5DEE\uFF1A" + testBlockers.join("\uFF1B"));
64147
+ };
64074
64148
  const pending = file.releases.find((r2) => r2.environment === "integration" && r2.scanId === scanId && !r2.pushed);
64075
- if (pending) {
64149
+ const releasedAtHead = file.releases.find((r2) => r2.head === repo2.head);
64150
+ const rollback2 = isScanRollbackable(entry, repo2, releasedAtHead);
64151
+ if (!rollback2) checkPublisherTest();
64152
+ if (pending && !rollback2) {
64076
64153
  try {
64077
64154
  await configureTestIdentity(api, repoSlug(repo2.url));
64078
64155
  await pushRelease(deps.exec, dir, auth, pending.head);
@@ -64089,7 +64166,9 @@ function buildDeployHandlers(deps) {
64089
64166
  return { response: { type: "deploy:release", sha: already.head, actionsUrl: "https://github.com/" + repoSlug(repo2.url) + "/actions" } };
64090
64167
  }
64091
64168
  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");
64092
- if (!isScanFresh(entry, repo2, file.releases.find((r2) => r2.head === repo2.head))) {
64169
+ if (rollback2) {
64170
+ if (file.scans.some((s2) => s2.status === "scanning" && isLiveScan(liveScans.get(personaId2), s2))) throw invalid("\u6709\u4E00\u6B21\u626B\u63CF\u6B63\u5728\u8DD1\uFF0C\u7B49\u5B83\u626B\u5B8C\u518D\u56DE\u5230\u65E7\u7248\u672C\u53D1\u5E03");
64171
+ } else if (!isScanFresh(entry, repo2, releasedAtHead)) {
64093
64172
  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");
64094
64173
  }
64095
64174
  const blockers = [];
@@ -64123,8 +64202,19 @@ function buildDeployHandlers(deps) {
64123
64202
  return isFeishuPublisherConnector(c2) ? { ...connector, publisherUnionId } : connector;
64124
64203
  }) };
64125
64204
  await configureTestIdentity(api, repoSlug(repo2.url));
64205
+ if (rollback2) {
64206
+ let saved;
64207
+ try {
64208
+ saved = await restoreContent(deps.exec, dir, entry.head);
64209
+ } catch (err) {
64210
+ throw toClawdError(err);
64211
+ }
64212
+ armUndo(() => resetTo(deps.exec, dir, saved));
64213
+ checkPublisherTest();
64214
+ }
64126
64215
  if (await deps.claimPublisher(cloudPersonaIdOf(repo2.url), "integration") === "taken") throw invalid("\u6D4B\u8BD5\u73AF\u5883\u91CC\u7684\u8FD9\u4E2A\u540D\u5B57\u5DF2\u7531\u522B\u4EBA\u53D1\u5E03");
64127
64216
  const releaseId = (0, import_node_crypto14.randomUUID)();
64217
+ armUndo(void 0);
64128
64218
  try {
64129
64219
  const result = await release(deps.exec, api, { dir, now: deps.now, auth, manifest, onPrepared: async (head) => {
64130
64220
  const latest2 = await readHistory(dir);
@@ -74948,7 +75038,7 @@ function computeMethodAccess(args) {
74948
75038
  }
74949
75039
 
74950
75040
  // src/version.ts
74951
- var version = "0.2.673".length > 0 ? "0.2.673" : "dev";
75041
+ var version = "0.2.674".length > 0 ? "0.2.674" : "dev";
74952
75042
 
74953
75043
  // src/cli-probe/probe.ts
74954
75044
  var fs86 = __toESM(require("fs"), 1);
@@ -78643,12 +78733,13 @@ async function startDaemon(config) {
78643
78733
  personaRegistry.setCloudSnapshot([...personaRegistry.list().filter((p3) => p3.cloud?.environment === "integration" && p3.personaId !== id), test], "integration");
78644
78734
  return id;
78645
78735
  },
78646
- startScan: (personaId2, scanBase, scanId) => {
78736
+ startScan: (personaId2, scanBase, scanId, rereportPackages) => {
78647
78737
  const baseLines = scanBase.base ? [
78648
78738
  `\u672C\u6B21\u662F**${SCAN_BASE_LABEL[scanBase.kind]}**\uFF1A\u53EA\u770B\u8DDF\u7EBF\u4E0A\u6B63\u8DD1\u90A3\u7248\u7684\u5DEE\u522B\uFF0C`,
78649
78739
  `\u90A3\u4E00\u7248\u662F commit ${scanBase.base}\u3002`,
78650
78740
  `\u5148\u5728 persona \u76EE\u5F55\u8DD1 \`git diff --name-only ${scanBase.base} -- .\` \u62FF\u5230\u6539\u52A8\u6E05\u5355\uFF0C`,
78651
- "\u53EA\u91CD\u770B\u8FD9\u4E9B\u6587\u4EF6\uFF08\u53CA\u5B83\u4EEC\u5F15\u51FA\u53BB\u7684\u90A3\u4E00\u8DF3\uFF09\uFF1B\u6E05\u5355\u91CC\u5176\u4F59\u6761\u76EE\u6309 skill \u8F6F\u626B\u90A3\u8282\u5904\u7406\u3002"
78741
+ "\u53EA\u91CD\u770B\u8FD9\u4E9B\u6587\u4EF6\uFF08\u53CA\u5B83\u4EEC\u5F15\u51FA\u53BB\u7684\u90A3\u4E00\u8DF3\uFF09\uFF1B\u6E05\u5355\u91CC\u5176\u4F59\u6761\u76EE\u6309 skill \u8F6F\u626B\u90A3\u8282\u5904\u7406\u3002",
78742
+ rereportPackages.length ? `\u6E05\u5355\u91CC\u7684 package\uFF0Cdaemon \u5DF2\u63A2\u8FC7\u672C\u673A\u7248\u672C\uFF1B\u4E3A\u91CD\u63A2\u7248\u672C\u800C\u8981\u4F60\u91CD\u62A5\u7684\u53EA\u6709\u8FD9\u51E0\u6761\uFF08\u7248\u672C\u53D8\u4E86\u6216\u8D44\u4EA7\u6CA1\u9489\u5168\uFF09\uFF1A${rereportPackages.join("\u3001")}\u3002\u5176\u4F59 package \u4E0D\u5FC5\u4E3A\u4E86\u91CD\u63A2\u7248\u672C\u800C\u91CD\u62A5\uFF1Bdiff \u91CC\u6539\u5230\u7684\u4F9D\u8D56\u7167\u5E38\u4E0A\u62A5\u3002` : "\u6E05\u5355\u91CC\u7684 package\uFF0Cdaemon \u5DF2\u63A2\u8FC7\u672C\u673A\u7248\u672C\uFF0C\u5168\u90FD\u6CA1\u53D8\uFF0C\u4E0D\u5FC5\u4E3A\u4E86\u91CD\u63A2\u7248\u672C\u800C\u91CD\u62A5\uFF1Bdiff \u91CC\u6539\u5230\u7684\u4F9D\u8D56\u7167\u5E38\u4E0A\u62A5\u3002"
78652
78743
  ] : [
78653
78744
  `\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`
78654
78745
  ];
@@ -17,7 +17,7 @@ description: 老板要把某个 persona 发布到云上时触发——把这个
17
17
  | 档 | 任务里会说 | 你要做什么 |
18
18
  |---|---|---|
19
19
  | **硬扫** | 「从零把 persona 整个读一遍」 | 清单是空的,从零填 |
20
- | **软扫** | 「只看跟线上正跑那版的差别,那一版是 commit `<sha>`」 | 先把已有清单的 `package` 重报一遍让 daemon 重探版本,再在**已有清单**上打补丁 |
20
+ | **软扫** | 「只看跟线上正跑那版的差别,那一版是 commit `<sha>`」 | 在**已有清单**上打补丁;清单里的 `package` 由 daemon 先探过版本,只有任务点名的几条要你重报 |
21
21
 
22
22
  软扫的第一件事,在 persona 目录里跑:
23
23
 
@@ -27,29 +27,35 @@ git diff --name-only <任务里给的 sha> -- .
27
27
 
28
28
  拿到的就是要重看的文件——**这是已跟踪内容与给定 commit 的差异入口;未跟踪文件、外部依赖和运行配置不在此命令覆盖内**。清单里其余的 `secret` / `connector`
29
29
  条目原样有效,不用重报:重报没变的东西不但白花时间,还会让本来稳定的字段(比如凭据的命名引用)无谓地变来变去。
30
- **`package` 条目例外——每次软扫都要重报一遍**,见下一节。
30
+ `package` 条目同理,只是多了一道 daemon 的预检,见下一节。
31
31
 
32
- ### 软扫先把清单里的 package 重报一遍
32
+ ### 软扫里的 package:只重报任务点名的
33
33
 
34
- 版本是 daemon 在你每次报 `package` 时探本机得来的(见「边查边报」那节),**不重报就不探**:
35
- 本机把某个工具升了级、或者上一版单据是没有探测那阵扫的,清单上的版本都不会自己变。所以拿到 diff 之后、
36
- 读改动文件之前,先做这一步:
34
+ 软扫开始时 daemon 已经在本机把清单里每个 `package` 的版本重探了一遍:版本没变、资产也钉全的,原样保留,
35
+ **不用为了重探版本而重报**——重报带 `artifact` 的 `github:` 包会让 daemon 再下载一次 release 资产(几十 MB),
36
+ 网络一抖整个扫描就失败,而这条本来没有任何变化。
37
+
38
+ 任务里会写「为重探版本而要你重报的只有这几条:……」(或「全都没变」)。**下面这套重报流程只对点名的那几条做,没点名的不为它重报。**
39
+ daemon 只比本机版本和资产是否钉全,看不见别的:如果 diff 里的改动动到了某个依赖本身(换了安装源 `from`、抬了 `min`、
40
+ 换了资产 url 之类),那一条照常按「边查边报」重新上报,不论点没点名。
37
41
 
38
42
  1. 调 `deploy:get`,在 `scans` 里找 `id` 等于任务给的 scanId 那条,拿它的 `manifest.packages` 和 `evidence`。
39
- 2. 对 `packages` 里的每一条调一次 `deploy:reportFinding`(`kind: "package"`):
40
- - `item` 只带 `name` / `from` / `min` / `version`;有 `artifact` 的只带 `{ url, bin }`。
43
+ 2. 对点名的每一条调一次 `deploy:reportFinding`(`kind: "package"`):
44
+ - `item` 只带 `name` / `from` / `min` / `version`;有 `artifact` 的只带 `{ url, bin }`
45
+ (url 和 bin 没变就不用换,daemon 会沿用已核过的 sha256 / size,不再下载)。**已有 `artifact` 的包一次报完,
46
+ 别走「先报 `{ name, from }`、再带资产重报」的两步**——第一步没带 `artifact`,会把已钉的资产覆盖掉,第二步只能重新下载。
41
47
  `localVersion`、`artifact.sha256`、`artifact.size` 是 daemon 写的,**不要带**(带了 sha256 会被按形状拒)。
42
48
  - `evidence` 用记录里 `target` 等于 `package:<name>` 那条的 `reason` / `file` / `quote` 原样回传。
43
49
  - 记录里**没有**这条 evidence 的(老板在页面上手工补的条目)跳过不报——上报口要求 evidence,编一条就是造假。
44
50
  - 被拒就按错误里说的做:`github:` 包本机版本变了,daemon 会告诉你新版本,按它重找资产再报(规则见 package 那节第 2 步)。
45
- - **记录里没有 `artifact` 的 `github:` 包(早先扫的旧单据),重报之后接着把资产钉上**:按 package 那节「`github:` 要钉资产」
51
+ - **点名的 `github:` 包若记录里没有 `artifact`(早先扫的旧单据),重报之后接着把资产钉上**:按 package 那节「`github:` 要钉资产」
46
52
  第 2 步,用这次返回的 `version` 找 linux-amd64 资产再报一次。没钉资产的 `github:` 包云上装不了,页面会拦着不让发布。
47
53
  3. 然后再去读改动文件。改动涉及的条目在那一趟会再报一次,新结论盖过这里的,顺序不能反。
48
54
 
49
55
  两件最容易做错的事:
50
56
 
51
- - **改动列表是空的,不等于没活干。** 老板点了重扫,页面正卡在「分析中」,你照样要把上面那步重报做完、
52
- 走到收尾那步调 `deploy:finishScan`,只是中间不用再报别的。
57
+ - **改动列表是空的,不等于没活干。** 老板点了重扫,页面正卡在「分析中」,你照样要走完收尾(任务点名了要重报的 package 也要先报完),
58
+ 调 `deploy:finishScan`,只是中间不用再报别的。
53
59
  - **diff 给的是入口,不是边界。** 改的可能只是 `CLAUDE.md` 里一行命令,而那行命令背后的脚本
54
60
  带着一整串工具依赖。**改动文件引出去的那一跳照样要跟**,规则同下面「读什么」那节。
55
61
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawos-dev/clawd",
3
- "version": "0.2.673",
3
+ "version": "0.2.674",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",