@clawos-dev/clawd 0.2.549 → 0.2.551

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
@@ -59723,14 +59723,27 @@ function toClawdError(err) {
59723
59723
  if (err instanceof RepoError || err instanceof GithubApiError) return new ClawdError(ERROR_CODES.INTERNAL, err.message);
59724
59724
  return new ClawdError(ERROR_CODES.INTERNAL, err instanceof Error ? err.message : String(err));
59725
59725
  }
59726
- function mustBeScanning(scan, method) {
59727
- if (scan?.status !== "scanning") {
59728
- throw invalid(`${method} \u53EA\u5728\u626B\u63CF\u671F\u53EF\u7528\uFF08\u5F53\u524D ${scan?.status ?? "\u6CA1\u6709\u626B\u63CF\u8BB0\u5F55"}\uFF09`);
59729
- }
59730
- return scan;
59726
+ function isLiveScan(live, scan) {
59727
+ if (!live) return false;
59728
+ return live.sessionId === void 0 || live.sessionId === scan.scanSessionId;
59731
59729
  }
59732
59730
  function buildDeployHandlers(deps) {
59733
59731
  const scannableAssets = deps.scannableAssets ?? SCANNABLE_ASSETS;
59732
+ const liveScans = /* @__PURE__ */ new Map();
59733
+ const settleStale = async (dir, scan) => {
59734
+ 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" };
59735
+ await writeScan(dir, settled);
59736
+ return settled;
59737
+ };
59738
+ const mustBeLiveScan = (personaId2, scan, method) => {
59739
+ if (scan?.status !== "scanning") {
59740
+ throw invalid(`${method} \u53EA\u5728\u626B\u63CF\u671F\u53EF\u7528\uFF08\u5F53\u524D ${scan?.status ?? "\u6CA1\u6709\u626B\u63CF\u8BB0\u5F55"}\uFF09`);
59741
+ }
59742
+ if (!isLiveScan(liveScans.get(personaId2), scan)) {
59743
+ 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`);
59744
+ }
59745
+ return scan;
59746
+ };
59734
59747
  const cacheMs = deps.secretsCacheMs ?? SECRETS_CACHE_MS;
59735
59748
  const secretsCache = /* @__PURE__ */ new Map();
59736
59749
  const runCache = /* @__PURE__ */ new Map();
@@ -59767,6 +59780,7 @@ function buildDeployHandlers(deps) {
59767
59780
  fileError = err instanceof Error ? err.message : String(err);
59768
59781
  }
59769
59782
  let scan = await readScan(dir);
59783
+ if (scan?.status === "scanning" && !isLiveScan(liveScans.get(personaId2), scan)) scan = await settleStale(dir, scan);
59770
59784
  if (scan && scan.status === "done") {
59771
59785
  const stale = describeStaleAssets(staleScannerAssets(scan, scannableAssets));
59772
59786
  const kept = scan.notes.filter((n2) => !n2.startsWith(STALE_ASSETS_NOTE_PREFIX));
@@ -59803,6 +59817,7 @@ function buildDeployHandlers(deps) {
59803
59817
  response: { type: "deploy:state", ...await stateOf(personaId2) }
59804
59818
  });
59805
59819
  async function beginScan(personaId2, dir, current, head, want) {
59820
+ liveScans.set(personaId2, {});
59806
59821
  const prevManifest = await readDeployFile(dir).catch(() => void 0);
59807
59822
  const scanBase = pickScanBase({
59808
59823
  // **文件在 ≠ 清单能用**:硬扫一开头就写了一份空清单,那一轮没跑完(OTA 重启 / butler 崩,
@@ -59847,8 +59862,23 @@ function buildDeployHandlers(deps) {
59847
59862
  };
59848
59863
  const started = deps.startScan(personaId2, scanBase);
59849
59864
  if ("sessionId" in started) {
59865
+ liveScans.set(personaId2, { sessionId: started.sessionId });
59850
59866
  await writeScan(dir, { status: "scanning", scanSessionId: started.sessionId, ...withMcp });
59867
+ const mine = () => liveScans.get(personaId2)?.sessionId === started.sessionId;
59868
+ void started.ended.then(async (outcome) => {
59869
+ if (mine()) liveScans.delete(personaId2);
59870
+ const current2 = await readScan(dir).catch(() => void 0);
59871
+ if (liveScans.has(personaId2) || current2?.scanSessionId !== started.sessionId) return;
59872
+ const settled = settleEndedScan(outcome, current2, deps.now());
59873
+ if (!settled) return;
59874
+ await writeScan(dir, settled);
59875
+ deps.warn?.("deploy scan ended without finishScan", { personaId: personaId2, why: settled.error });
59876
+ }).catch((err) => {
59877
+ if (mine()) liveScans.delete(personaId2);
59878
+ deps.warn?.("deploy scan settle failed", { personaId: personaId2, error: String(err) });
59879
+ });
59851
59880
  } else {
59881
+ liveScans.delete(personaId2);
59852
59882
  await writeScan(dir, {
59853
59883
  status: "failed",
59854
59884
  ...withMcp,
@@ -59901,7 +59931,7 @@ function buildDeployHandlers(deps) {
59901
59931
  ensureOwner(ctx);
59902
59932
  const args = parseArgs2(DeployReportFindingArgsSchema, frame, "deploy:reportFinding");
59903
59933
  const dir = mustPersona(args.personaId);
59904
- const scan = mustBeScanning(await readScan(dir), "deploy:reportFinding");
59934
+ const scan = mustBeLiveScan(args.personaId, await readScan(dir), "deploy:reportFinding");
59905
59935
  let item;
59906
59936
  try {
59907
59937
  item = parseFinding(args.kind, args.item);
@@ -59922,7 +59952,7 @@ function buildDeployHandlers(deps) {
59922
59952
  ensureOwner(ctx);
59923
59953
  const args = parseArgs2(DeployFinishScanArgsSchema, frame, "deploy:finishScan");
59924
59954
  const dir = mustPersona(args.personaId);
59925
- const scan = mustBeScanning(await readScan(dir), "deploy:finishScan");
59955
+ const scan = mustBeLiveScan(args.personaId, await readScan(dir), "deploy:finishScan");
59926
59956
  await writeScan(dir, {
59927
59957
  ...scan,
59928
59958
  status: "done",
@@ -59956,8 +59986,13 @@ function buildDeployHandlers(deps) {
59956
59986
  if (!repo2) throw new ClawdError(ERROR_CODES.INTERNAL, "\u4ED3\u5E93\u5EFA\u597D\u4E86\u4F46\u8BFB\u4E0D\u5230 origin");
59957
59987
  }
59958
59988
  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`);
59959
- const current = await readScan(dir);
59960
- if (current?.status === "scanning") return { response: { type: "deploy:release", outcome: "scanning" } };
59989
+ const starting = liveScans.get(personaId2);
59990
+ if (starting && starting.sessionId === void 0) return { response: { type: "deploy:release", outcome: "scanning" } };
59991
+ let current = await readScan(dir);
59992
+ if (current?.status === "scanning") {
59993
+ if (isLiveScan(liveScans.get(personaId2), current)) return { response: { type: "deploy:release", outcome: "scanning" } };
59994
+ current = await settleStale(dir, current);
59995
+ }
59961
59996
  const fileUsable = await readDeployFile(dir).then((f2) => f2 !== void 0).catch(() => false);
59962
59997
  if (scanBase !== void 0 || !fileUsable || !isScanFresh(current, repo2)) {
59963
59998
  let head;
@@ -59967,7 +60002,12 @@ function buildDeployHandlers(deps) {
59967
60002
  throw toClawdError(err);
59968
60003
  }
59969
60004
  runCache.delete(personaId2);
59970
- await beginScan(personaId2, dir, current, head, scanBase);
60005
+ try {
60006
+ await beginScan(personaId2, dir, current, head, scanBase);
60007
+ } catch (err) {
60008
+ liveScans.delete(personaId2);
60009
+ throw err;
60010
+ }
59971
60011
  return { response: { type: "deploy:release", outcome: "scanning" } };
59972
60012
  }
59973
60013
  const file = await readDeployFile(dir);
@@ -70930,7 +70970,7 @@ function computeMethodAccess(args) {
70930
70970
  }
70931
70971
 
70932
70972
  // src/version.ts
70933
- var version = "0.2.549".length > 0 ? "0.2.549" : "dev";
70973
+ var version = "0.2.551".length > 0 ? "0.2.551" : "dev";
70934
70974
 
70935
70975
  // src/cli-probe/probe.ts
70936
70976
  var fs78 = __toESM(require("fs"), 1);
@@ -74592,6 +74632,7 @@ async function startDaemon(config) {
74592
74632
  githubApi: (token) => createGithubApi({ token }),
74593
74633
  org: PERSONA_REPO_ORG,
74594
74634
  now: () => Date.now(),
74635
+ warn: (msg, meta) => logger.warn(msg, meta),
74595
74636
  startScan: (personaId2, scanBase) => {
74596
74637
  const assetLines = SCANNABLE_ASSETS.map(
74597
74638
  (a2) => ` - ${a2.name}${a2.path ? `\uFF08${a2.path(config.dataDir)}\uFF09` : ""}\uFF1A${a2.what}`
@@ -74623,16 +74664,7 @@ async function startDaemon(config) {
74623
74664
  logger.error("deploy scan session failed to start", { personaId: personaId2, error: String(err) });
74624
74665
  return { error: String(err) };
74625
74666
  }
74626
- void handle.ended.then(async (outcome) => {
74627
- const dir = personaStore.personaDirPath(personaId2);
74628
- const settled = settleEndedScan(outcome, await readScan(dir).catch(() => void 0), Date.now());
74629
- if (!settled) return;
74630
- await writeScan(dir, settled);
74631
- logger.warn("deploy scan ended without finishScan", { personaId: personaId2, why: settled.error });
74632
- }).catch((err) => {
74633
- logger.warn("deploy scan settle failed", { personaId: personaId2, error: String(err) });
74634
- });
74635
- return { sessionId: handle.sessionId };
74667
+ return { sessionId: handle.sessionId, ended: handle.ended };
74636
74668
  }
74637
74669
  });
74638
74670
  Object.assign(handlers, deployHandlers);
@@ -0,0 +1,7 @@
1
+ # 本目录就是一个独立的 pnpm 项目(packages 只列自己:pnpm 10 没有这一段会报 "packages field missing",12 不强求;
2
+ # 同时也让 pnpm 在这里止步、不往上爬进 clawos 的 workspace)。
3
+ packages:
4
+ - '.'
5
+ # 同 web/pnpm-workspace.yaml:pnpm 12 起 build 脚本要显式允许,@nestjs/core 有 postinstall。
6
+ allowBuilds:
7
+ "@nestjs/core": true
@@ -0,0 +1,9 @@
1
+ # 本目录就是一个独立的 pnpm 项目(packages 只列自己:pnpm 10 没有这一段会报 "packages field missing",12 不强求;
2
+ # 同时也让 pnpm 在这里止步、不往上爬进 clawos 的 workspace)。
3
+ packages:
4
+ - '.'
5
+ # pnpm 12 起「依赖带 build 脚本但没被显式允许」= install 直接失败(ERR_PNPM_IGNORED_BUILDS),且 pnpm 11 起不再读 package.json 的 pnpm 字段,
6
+ # 允许名单只能写在这里(写法同 clawd/pnpm-workspace.yaml)。vite 拖的 esbuild 有 postinstall。
7
+ # 以后加依赖 install 又报这个错 = 把它点名进来(本机 pnpm 10 的 warning 会列出来)。
8
+ allowBuilds:
9
+ esbuild: true
@@ -270,7 +270,12 @@ GitHub release / apt,你得告诉我它从哪装」。
270
270
 
271
271
  ## 纪律
272
272
 
273
- - **只读不写**:不要修改被扫 persona 目录里的任何文件,不要执行它的脚本。
273
+ - **只读不写**:不要修改被扫 persona 目录里的任何文件,不要执行它的脚本。**`.clawd/deploy.json` 和
274
+ `.clawd/deploy-scan.json` 也在内**——它们只能经 `deploy:reportFinding` / `deploy:patch` / `deploy:finishScan` 改。
275
+ 把扫描记录手改回 `scanning` 没有用:daemon 只认它自己起的、还活着的扫描,会话一结束记录就不归你了,
276
+ 改回去只会被翻成 failed。
277
+ - **扫完之后老板在这个会话里让你「重新扫」「改成硬扫 / 软扫」:不要动。** 不改文件、不重跑流程、不再上报。
278
+ 回一句让他去页面重新点「发布」——按钮右边的下拉能选硬扫 / 软扫,daemon 会起新的扫描会话。
274
279
  - **宁缺毋滥**:拿不准的去查清,不要编一条依赖进清单;查清了、且过得了 `notes` 那三道门槛的才是 note。
275
280
  多报一条假依赖会让镜像装一堆没用的东西;漏报一条真依赖,云上试跑和用起来会发现,补一行再发一次就行。
276
281
  - **没调 `finishScan` 就等于没扫完**——中途卡住也要调,把卡在哪写进 `notes`,别让页面一直停在
@@ -0,0 +1,7 @@
1
+ # 本目录就是一个独立的 pnpm 项目(packages 只列自己:pnpm 10 没有这一段会报 "packages field missing",12 不强求;
2
+ # 同时也让 pnpm 在这里止步、不往上爬进 clawos 的 workspace)。
3
+ packages:
4
+ - '.'
5
+ # 同 web/pnpm-workspace.yaml:pnpm 12 起 build 脚本要显式允许,@nestjs/core 有 postinstall。
6
+ allowBuilds:
7
+ "@nestjs/core": true
@@ -0,0 +1,9 @@
1
+ # 本目录就是一个独立的 pnpm 项目(packages 只列自己:pnpm 10 没有这一段会报 "packages field missing",12 不强求;
2
+ # 同时也让 pnpm 在这里止步、不往上爬进 clawos 的 workspace)。
3
+ packages:
4
+ - '.'
5
+ # pnpm 12 起「依赖带 build 脚本但没被显式允许」= install 直接失败(ERR_PNPM_IGNORED_BUILDS),且 pnpm 11 起不再读 package.json 的 pnpm 字段,
6
+ # 允许名单只能写在这里(写法同 clawd/pnpm-workspace.yaml)。vite 拖的 esbuild 有 postinstall。
7
+ # 以后加依赖 install 又报这个错 = 把它点名进来(本机 pnpm 10 的 warning 会列出来)。
8
+ allowBuilds:
9
+ esbuild: true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawos-dev/clawd",
3
- "version": "0.2.549",
3
+ "version": "0.2.551",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",