@clawos-dev/clawd 0.2.251 → 0.2.252

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
@@ -148,12 +148,6 @@ var init_methods = __esm({
148
148
  "attachment.groupAdd",
149
149
  "attachment.groupRemove",
150
150
  "attachment.groupList",
151
- // ---- capability:* (capability platform 鉴权底座) ----
152
- // 飞书统一身份 Phase 2 (2026-06-01, spec 决策 #12): personal token / 邀请码全链路删除——
153
- // device:connect 已完整替代(中心 server 签发自包含 connect token)。capability:list / capability:delete
154
- // 保留:调试 + whoami 反查 + 撤销级联 + 清旧 per-peer cap。personal-cap:get 已下线。
155
- "capability:list",
156
- "capability:delete",
157
151
  // ---- inbox:* (capability platform Phase 3 跨用户通知 + Phase 4 DM) ----
158
152
  // owner 接 guest 的 cross-principal 消息事件 + DM 双向私聊.
159
153
  // inbox:list / markRead: admin-only (Phase 3); inbox:postMessage: DM 自带能力,
@@ -5643,11 +5637,7 @@ var init_persona_mode = __esm({
5643
5637
  });
5644
5638
 
5645
5639
  // ../protocol/src/capability.ts
5646
- function stripSecretHash(cap) {
5647
- const { secretHash: _hash, ...wire } = cap;
5648
- return wire;
5649
- }
5650
- var ResourceSchema, ActionSchema, GrantSchema, CapabilitySchema, CapabilityWireSchema, CapabilityErrorCodeSchema, WhoamiResponseSchema, PERSONAL_CAP_GRANTS;
5640
+ var ResourceSchema, ActionSchema, GrantSchema, CapabilitySchema, CapabilityWireSchema, WhoamiResponseSchema, PERSONAL_CAP_GRANTS;
5651
5641
  var init_capability = __esm({
5652
5642
  "../protocol/src/capability.ts"() {
5653
5643
  "use strict";
@@ -5688,12 +5678,6 @@ var init_capability = __esm({
5688
5678
  peerDisplayName: external_exports.string().optional()
5689
5679
  }).strict();
5690
5680
  CapabilityWireSchema = CapabilitySchema.omit({ secretHash: true });
5691
- CapabilityErrorCodeSchema = external_exports.enum([
5692
- "TOKEN_INVALID",
5693
- "TOKEN_REVOKED",
5694
- "TOKEN_EXPIRED",
5695
- "TOKEN_EXHAUSTED"
5696
- ]);
5697
5681
  WhoamiResponseSchema = external_exports.object({
5698
5682
  type: external_exports.literal("whoami:ok"),
5699
5683
  owner: external_exports.object({
@@ -6099,9 +6083,6 @@ var init_feishu_auth = __esm({
6099
6083
  "use strict";
6100
6084
  init_zod();
6101
6085
  FEISHU_GATED_METHODS = [
6102
- // capability(调试 / 撤销,仍属远程身份体系)
6103
- "capability:list",
6104
- "capability:delete",
6105
6086
  // 联系人列表(device:connect 落同一 store;list 读 / remove 删)
6106
6087
  "contact:list",
6107
6088
  "contact:remove",
@@ -7527,7 +7508,7 @@ var require_atomic_sleep = __commonJS({
7527
7508
  var require_sonic_boom = __commonJS({
7528
7509
  "../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
7529
7510
  "use strict";
7530
- var fs66 = require("fs");
7511
+ var fs65 = require("fs");
7531
7512
  var EventEmitter3 = require("events");
7532
7513
  var inherits = require("util").inherits;
7533
7514
  var path74 = require("path");
@@ -7584,20 +7565,20 @@ var require_sonic_boom = __commonJS({
7584
7565
  const mode = sonic.mode;
7585
7566
  if (sonic.sync) {
7586
7567
  try {
7587
- if (sonic.mkdir) fs66.mkdirSync(path74.dirname(file), { recursive: true });
7588
- const fd = fs66.openSync(file, flags, mode);
7568
+ if (sonic.mkdir) fs65.mkdirSync(path74.dirname(file), { recursive: true });
7569
+ const fd = fs65.openSync(file, flags, mode);
7589
7570
  fileOpened(null, fd);
7590
7571
  } catch (err) {
7591
7572
  fileOpened(err);
7592
7573
  throw err;
7593
7574
  }
7594
7575
  } else if (sonic.mkdir) {
7595
- fs66.mkdir(path74.dirname(file), { recursive: true }, (err) => {
7576
+ fs65.mkdir(path74.dirname(file), { recursive: true }, (err) => {
7596
7577
  if (err) return fileOpened(err);
7597
- fs66.open(file, flags, mode, fileOpened);
7578
+ fs65.open(file, flags, mode, fileOpened);
7598
7579
  });
7599
7580
  } else {
7600
- fs66.open(file, flags, mode, fileOpened);
7581
+ fs65.open(file, flags, mode, fileOpened);
7601
7582
  }
7602
7583
  }
7603
7584
  function SonicBoom(opts) {
@@ -7638,8 +7619,8 @@ var require_sonic_boom = __commonJS({
7638
7619
  this.flush = flushBuffer;
7639
7620
  this.flushSync = flushBufferSync;
7640
7621
  this._actualWrite = actualWriteBuffer;
7641
- fsWriteSync = () => fs66.writeSync(this.fd, this._writingBuf);
7642
- fsWrite = () => fs66.write(this.fd, this._writingBuf, this.release);
7622
+ fsWriteSync = () => fs65.writeSync(this.fd, this._writingBuf);
7623
+ fsWrite = () => fs65.write(this.fd, this._writingBuf, this.release);
7643
7624
  } else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
7644
7625
  this._writingBuf = "";
7645
7626
  this.write = write;
@@ -7648,15 +7629,15 @@ var require_sonic_boom = __commonJS({
7648
7629
  this._actualWrite = actualWrite;
7649
7630
  fsWriteSync = () => {
7650
7631
  if (Buffer.isBuffer(this._writingBuf)) {
7651
- return fs66.writeSync(this.fd, this._writingBuf);
7632
+ return fs65.writeSync(this.fd, this._writingBuf);
7652
7633
  }
7653
- return fs66.writeSync(this.fd, this._writingBuf, "utf8");
7634
+ return fs65.writeSync(this.fd, this._writingBuf, "utf8");
7654
7635
  };
7655
7636
  fsWrite = () => {
7656
7637
  if (Buffer.isBuffer(this._writingBuf)) {
7657
- return fs66.write(this.fd, this._writingBuf, this.release);
7638
+ return fs65.write(this.fd, this._writingBuf, this.release);
7658
7639
  }
7659
- return fs66.write(this.fd, this._writingBuf, "utf8", this.release);
7640
+ return fs65.write(this.fd, this._writingBuf, "utf8", this.release);
7660
7641
  };
7661
7642
  } else {
7662
7643
  throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
@@ -7713,7 +7694,7 @@ var require_sonic_boom = __commonJS({
7713
7694
  }
7714
7695
  }
7715
7696
  if (this._fsync) {
7716
- fs66.fsyncSync(this.fd);
7697
+ fs65.fsyncSync(this.fd);
7717
7698
  }
7718
7699
  const len = this._len;
7719
7700
  if (this._reopening) {
@@ -7827,7 +7808,7 @@ var require_sonic_boom = __commonJS({
7827
7808
  const onDrain = () => {
7828
7809
  if (!this._fsync) {
7829
7810
  try {
7830
- fs66.fsync(this.fd, (err) => {
7811
+ fs65.fsync(this.fd, (err) => {
7831
7812
  this._flushPending = false;
7832
7813
  cb(err);
7833
7814
  });
@@ -7929,7 +7910,7 @@ var require_sonic_boom = __commonJS({
7929
7910
  const fd = this.fd;
7930
7911
  this.once("ready", () => {
7931
7912
  if (fd !== this.fd) {
7932
- fs66.close(fd, (err) => {
7913
+ fs65.close(fd, (err) => {
7933
7914
  if (err) {
7934
7915
  return this.emit("error", err);
7935
7916
  }
@@ -7978,7 +7959,7 @@ var require_sonic_boom = __commonJS({
7978
7959
  buf = this._bufs[0];
7979
7960
  }
7980
7961
  try {
7981
- const n = Buffer.isBuffer(buf) ? fs66.writeSync(this.fd, buf) : fs66.writeSync(this.fd, buf, "utf8");
7962
+ const n = Buffer.isBuffer(buf) ? fs65.writeSync(this.fd, buf) : fs65.writeSync(this.fd, buf, "utf8");
7982
7963
  const releasedBufObj = releaseWritingBuf(buf, this._len, n);
7983
7964
  buf = releasedBufObj.writingBuf;
7984
7965
  this._len = releasedBufObj.len;
@@ -7994,7 +7975,7 @@ var require_sonic_boom = __commonJS({
7994
7975
  }
7995
7976
  }
7996
7977
  try {
7997
- fs66.fsyncSync(this.fd);
7978
+ fs65.fsyncSync(this.fd);
7998
7979
  } catch {
7999
7980
  }
8000
7981
  }
@@ -8015,7 +7996,7 @@ var require_sonic_boom = __commonJS({
8015
7996
  buf = mergeBuf(this._bufs[0], this._lens[0]);
8016
7997
  }
8017
7998
  try {
8018
- const n = fs66.writeSync(this.fd, buf);
7999
+ const n = fs65.writeSync(this.fd, buf);
8019
8000
  buf = buf.subarray(n);
8020
8001
  this._len = Math.max(this._len - n, 0);
8021
8002
  if (buf.length <= 0) {
@@ -8043,13 +8024,13 @@ var require_sonic_boom = __commonJS({
8043
8024
  this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
8044
8025
  if (this.sync) {
8045
8026
  try {
8046
- const written = Buffer.isBuffer(this._writingBuf) ? fs66.writeSync(this.fd, this._writingBuf) : fs66.writeSync(this.fd, this._writingBuf, "utf8");
8027
+ const written = Buffer.isBuffer(this._writingBuf) ? fs65.writeSync(this.fd, this._writingBuf) : fs65.writeSync(this.fd, this._writingBuf, "utf8");
8047
8028
  release(null, written);
8048
8029
  } catch (err) {
8049
8030
  release(err);
8050
8031
  }
8051
8032
  } else {
8052
- fs66.write(this.fd, this._writingBuf, release);
8033
+ fs65.write(this.fd, this._writingBuf, release);
8053
8034
  }
8054
8035
  }
8055
8036
  function actualWriteBuffer() {
@@ -8058,7 +8039,7 @@ var require_sonic_boom = __commonJS({
8058
8039
  this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
8059
8040
  if (this.sync) {
8060
8041
  try {
8061
- const written = fs66.writeSync(this.fd, this._writingBuf);
8042
+ const written = fs65.writeSync(this.fd, this._writingBuf);
8062
8043
  release(null, written);
8063
8044
  } catch (err) {
8064
8045
  release(err);
@@ -8067,7 +8048,7 @@ var require_sonic_boom = __commonJS({
8067
8048
  if (kCopyBuffer) {
8068
8049
  this._writingBuf = Buffer.from(this._writingBuf);
8069
8050
  }
8070
- fs66.write(this.fd, this._writingBuf, release);
8051
+ fs65.write(this.fd, this._writingBuf, release);
8071
8052
  }
8072
8053
  }
8073
8054
  function actualClose(sonic) {
@@ -8083,12 +8064,12 @@ var require_sonic_boom = __commonJS({
8083
8064
  sonic._lens = [];
8084
8065
  assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
8085
8066
  try {
8086
- fs66.fsync(sonic.fd, closeWrapped);
8067
+ fs65.fsync(sonic.fd, closeWrapped);
8087
8068
  } catch {
8088
8069
  }
8089
8070
  function closeWrapped() {
8090
8071
  if (sonic.fd !== 1 && sonic.fd !== 2) {
8091
- fs66.close(sonic.fd, done);
8072
+ fs65.close(sonic.fd, done);
8092
8073
  } else {
8093
8074
  done();
8094
8075
  }
@@ -26932,8 +26913,8 @@ var require_CronFileParser = __commonJS({
26932
26913
  * @throws If file cannot be read
26933
26914
  */
26934
26915
  static parseFileSync(filePath) {
26935
- const { readFileSync: readFileSync7 } = require("fs");
26936
- const data = readFileSync7(filePath, "utf8");
26916
+ const { readFileSync: readFileSync6 } = require("fs");
26917
+ const data = readFileSync6(filePath, "utf8");
26937
26918
  return _CronFileParser.#parseContent(data);
26938
26919
  }
26939
26920
  /**
@@ -29234,7 +29215,7 @@ var require_websocket = __commonJS({
29234
29215
  var http3 = require("http");
29235
29216
  var net3 = require("net");
29236
29217
  var tls = require("tls");
29237
- var { randomBytes, createHash: createHash3 } = require("crypto");
29218
+ var { randomBytes, createHash: createHash2 } = require("crypto");
29238
29219
  var { Duplex, Readable: Readable3 } = require("stream");
29239
29220
  var { URL: URL2 } = require("url");
29240
29221
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -29894,7 +29875,7 @@ var require_websocket = __commonJS({
29894
29875
  abortHandshake(websocket, socket, "Invalid Upgrade header");
29895
29876
  return;
29896
29877
  }
29897
- const digest = createHash3("sha1").update(key + GUID).digest("base64");
29878
+ const digest = createHash2("sha1").update(key + GUID).digest("base64");
29898
29879
  if (res.headers["sec-websocket-accept"] !== digest) {
29899
29880
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
29900
29881
  return;
@@ -30261,7 +30242,7 @@ var require_websocket_server = __commonJS({
30261
30242
  var EventEmitter3 = require("events");
30262
30243
  var http3 = require("http");
30263
30244
  var { Duplex } = require("stream");
30264
- var { createHash: createHash3 } = require("crypto");
30245
+ var { createHash: createHash2 } = require("crypto");
30265
30246
  var extension2 = require_extension();
30266
30247
  var PerMessageDeflate2 = require_permessage_deflate();
30267
30248
  var subprotocol2 = require_subprotocol();
@@ -30562,7 +30543,7 @@ var require_websocket_server = __commonJS({
30562
30543
  );
30563
30544
  }
30564
30545
  if (this._state > RUNNING) return abortHandshake(socket, 503);
30565
- const digest = createHash3("sha1").update(key + GUID).digest("base64");
30546
+ const digest = createHash2("sha1").update(key + GUID).digest("base64");
30566
30547
  const headers = [
30567
30548
  "HTTP/1.1 101 Switching Protocols",
30568
30549
  "Upgrade: websocket",
@@ -45880,17 +45861,9 @@ var PersonaManager = class {
45880
45861
  }
45881
45862
  /**
45882
45863
  * 删除 persona。
45883
- * PersonaStore.remove(personaId) 已级联删除整个 <personaRoot>/<personaId>/ 目录,
45884
- * Phase 2 capability platform 新布局下含 .clawd/sessions/owner/ +
45885
- * .clawd/sessions/guests/<capId>/, rmSync recursive force 一锅清.
45886
- *
45887
- * ⚠️ TODO (Phase 3+ design gap, reviewer P1 flagged):
45888
- * ~/.clawd/capabilities.json 里若有 cap.grants 含本 personaId, grant 会变成
45889
- * 悬空引用 (guest 仍能持 token 接入, 但调依赖该 persona 的 RPC 会 fail).
45890
- * 选项: (A) 这里扫所有 cap 过滤掉该 personaId 的 grant; (B) revoke 所有 grant
45891
- * 含该 personaId 的 cap. 当前因 cleanupGuestSessionsForCapability 用 rmSync
45892
- * force=true 吞 ENOENT 不会 crash, 但 UI CapabilityManagerDrawer 会展示
45893
- * "无效的 persona" grant. Phase 3 加 cross-reference 矩阵后正式实现.
45864
+ * PersonaStore.remove(personaId) 级联删整个 <personaRoot>/<personaId>/ 目录
45865
+ * (含 .clawd/sessions/owner/ 和 .clawd/sessions/guests/<capId>/),rmSync
45866
+ * recursive force 一锅清。
45894
45867
  */
45895
45868
  delete(personaId) {
45896
45869
  this.deps.store.remove(personaId);
@@ -50678,9 +50651,9 @@ var LocalWsServer = class {
50678
50651
  this.safeSend(c.ws, frame);
50679
50652
  }
50680
50653
  }
50681
- // Task 1.9 capability platform:仅广播给 owner 连接(跳过 guest ws)。
50682
- // 用于 capability:tokenIssued / capability:tokenDeleted owner-only push frame——
50683
- // guest 没必要也无法消费这类管理帧。noAuth localhost ws 没有 ctx, 视作 owner.
50654
+ // 仅广播给 owner 连接(跳过 guest ws)。用于 contact:added / contact:removed /
50655
+ // contact:pinned / friend:reverseTokenOffered / appBuilder:project-updated
50656
+ // owner-only push frame——guest 无需消费。noAuth localhost ws ctx,视作 owner
50684
50657
  broadcastToOwners(frame) {
50685
50658
  const gate = this.opts.authGate;
50686
50659
  for (const c of this.clients.values()) {
@@ -50754,10 +50727,8 @@ var LocalWsServer = class {
50754
50727
  }
50755
50728
  }
50756
50729
  }
50757
- // Task 1.10 撤销级联:关闭某 guest principal 的所有活跃 ws 连接。close code 4401
50758
- // 让客户端识别 'capability revoked' 而非普通断连。
50759
- // capability:delete 级联用 cap.id 调本方法——noAuth 旧 per-peer cap guest 的
50760
- // principal.id ≡ cap.id,级联语义不变(Phase 3 决策 #14 正名)。
50730
+ // 关闭某 guest principal 的所有活跃 ws 连接。close code 4401 让客户端识别
50731
+ // 'TOKEN_REVOKED' 而非普通断连;调用方按需触发(如 contact:remove 后要断对方现有连接)。
50761
50732
  closeConnectionsByGuestId(guestPrincipalId, code = 4401, reason = "TOKEN_REVOKED") {
50762
50733
  const set = this.guestIdToClients.get(guestPrincipalId);
50763
50734
  if (!set) return;
@@ -51231,191 +51202,38 @@ async function authenticate(token, deps) {
51231
51202
  };
51232
51203
  }
51233
51204
 
51234
- // src/permission/capability-store.ts
51235
- var fs33 = __toESM(require("fs"), 1);
51205
+ // src/legacy-cleanup.ts
51206
+ var fs33 = __toESM(require("fs/promises"), 1);
51236
51207
  var path34 = __toESM(require("path"), 1);
51237
- var CAPABILITIES_FILE_NAME = "capabilities.json";
51238
- var FILE_VERSION = 1;
51239
- var CapabilityStore = class {
51240
- constructor(dataDir) {
51241
- this.dataDir = dataDir;
51242
- fs33.mkdirSync(dataDir, { recursive: true });
51243
- this.cache = this.readFromDisk();
51244
- }
51245
- dataDir;
51246
- cache;
51247
- list() {
51248
- return [...this.cache];
51249
- }
51250
- upsert(cap) {
51251
- const idx = this.cache.findIndex((c) => c.id === cap.id);
51252
- if (idx >= 0) {
51253
- this.cache[idx] = cap;
51254
- } else {
51255
- this.cache.push(cap);
51256
- }
51257
- this.flush();
51258
- }
51259
- remove(id) {
51260
- const next = this.cache.filter((c) => c.id !== id);
51261
- if (next.length === this.cache.length) return;
51262
- this.cache = next;
51263
- this.flush();
51264
- }
51265
- filePath() {
51266
- return path34.join(this.dataDir, CAPABILITIES_FILE_NAME);
51267
- }
51268
- readFromDisk() {
51269
- const file = this.filePath();
51270
- let raw;
51271
- try {
51272
- raw = fs33.readFileSync(file, "utf8");
51273
- } catch (err) {
51274
- if (err?.code === "ENOENT") return [];
51275
- return [];
51276
- }
51277
- if (!raw.trim()) return [];
51278
- let parsed;
51279
- try {
51280
- parsed = JSON.parse(raw);
51281
- } catch {
51282
- return [];
51283
- }
51284
- if (!parsed || typeof parsed !== "object") return [];
51285
- const arr = parsed.capabilities;
51286
- if (!Array.isArray(arr)) return [];
51287
- const out = [];
51288
- for (const item of arr) {
51289
- const r = CapabilitySchema.safeParse(item);
51290
- if (r.success) out.push(r.data);
51291
- }
51292
- return out;
51293
- }
51294
- flush() {
51295
- const content = { version: FILE_VERSION, capabilities: this.cache };
51296
- this.atomicWrite(this.filePath(), JSON.stringify(content, null, 2));
51297
- }
51298
- atomicWrite(file, content) {
51299
- const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
51300
- fs33.writeFileSync(tmp, content, { mode: 384 });
51301
- fs33.renameSync(tmp, file);
51302
- try {
51303
- fs33.chmodSync(file, 384);
51304
- } catch {
51305
- }
51306
- }
51307
- };
51308
-
51309
- // src/permission/capability-registry.ts
51310
- var crypto5 = __toESM(require("crypto"), 1);
51311
- var CapabilityRegistry = class {
51312
- constructor(store, now = () => Date.now()) {
51313
- this.store = store;
51314
- this.now = now;
51315
- for (const cap of store.list()) {
51316
- this.bySecretHash.set(cap.secretHash, cap);
51317
- }
51318
- }
51319
- store;
51320
- now;
51321
- // sha256(token) → Capability
51322
- bySecretHash = /* @__PURE__ */ new Map();
51323
- list() {
51324
- return this.store.list();
51325
- }
51326
- verifyToken(token) {
51327
- if (!token) return { ok: false, code: "TOKEN_INVALID" };
51328
- const hash = sha256Hex(token);
51329
- const cap = this.bySecretHash.get(hash);
51330
- if (!cap) return { ok: false, code: "TOKEN_INVALID" };
51331
- if (cap.expiresAt && this.now() >= cap.expiresAt) {
51332
- return { ok: false, code: "TOKEN_EXPIRED" };
51333
- }
51334
- if (cap.maxUses && cap.usedCount >= cap.maxUses) {
51335
- return { ok: false, code: "TOKEN_EXHAUSTED" };
51336
- }
51337
- const updated = { ...cap, usedCount: cap.usedCount + 1 };
51338
- this.bySecretHash.set(hash, updated);
51339
- this.store.upsert(updated);
51340
- return { ok: true, capability: updated };
51341
- }
51342
- upsertCapability(cap) {
51343
- this.bySecretHash.set(cap.secretHash, cap);
51344
- this.store.upsert(cap);
51345
- }
51346
- /**
51347
- * Hard delete:从 store 物理移除 + 从 Map 清掉 secretHash 索引。
51348
- * 后续 verifyToken 该 token 走 'TOKEN_INVALID' 分支(secretHash 已不在 Map)。
51349
- * idempotent:不存在 → null。
51350
- */
51351
- delete(id) {
51352
- const current = this.store.list().find((c) => c.id === id);
51353
- if (!current) return null;
51354
- this.bySecretHash.delete(current.secretHash);
51355
- this.store.remove(id);
51356
- return current;
51357
- }
51358
- findById(id) {
51359
- return this.store.list().find((c) => c.id === id) ?? null;
51360
- }
51361
- };
51362
- function sha256Hex(s) {
51363
- return crypto5.createHash("sha256").update(s).digest("hex");
51364
- }
51365
-
51366
- // src/permission/capability-manager.ts
51367
- var CapabilityManager = class {
51368
- constructor(registry2, hooks = {}) {
51369
- this.registry = registry2;
51370
- this.hooks = hooks;
51371
- }
51372
- registry;
51373
- hooks;
51374
- list() {
51375
- return this.registry.list();
51376
- }
51377
- // 调试 / 旧 per-peer cap 反查用(Phase 3 决策 #14 后无生产调用方,保留属三件套范围)。
51378
- // 返回 Capability 持久化形态;调用方需 stripSecretHash 后再上 wire。
51379
- findById(id) {
51380
- return this.registry.findById(id);
51381
- }
51382
- /**
51383
- * Hard delete:从 registry / store 物理移除 capability。
51384
- * idempotent:不存在 → null(handler 翻译成 VALIDATION_ERROR)
51385
- * onDeleted hook 在 daemon/index.ts wire 负责 close 连接 + cleanup 文件 + 广播
51386
- */
51387
- delete(id) {
51388
- const removed = this.registry.delete(id);
51389
- if (!removed) return null;
51390
- this.hooks.onDeleted?.(removed);
51391
- return { capability: removed };
51392
- }
51393
- };
51394
-
51395
- // src/permission/cleanup.ts
51396
- var fs34 = __toESM(require("fs"), 1);
51397
- function cleanupGuestSessionsForCapability(cap, factory) {
51208
+ var LEGACY_FILES = [
51209
+ "personal-capability.json",
51210
+ "received-capabilities.json",
51211
+ "capabilities.json"
51212
+ ];
51213
+ async function cleanupLegacyCapabilityFiles(dataDir, logger) {
51398
51214
  const removed = [];
51399
- for (const g2 of cap.grants) {
51400
- if (g2.resource.type !== "persona") continue;
51401
- const dir = factory.vmGuestRoot(g2.resource.id, cap.id);
51215
+ for (const name of LEGACY_FILES) {
51216
+ const p2 = path34.join(dataDir, name);
51402
51217
  try {
51403
- fs34.rmSync(dir, { recursive: true, force: true });
51404
- removed.push(dir);
51405
- } catch {
51218
+ await fs33.unlink(p2);
51219
+ removed.push(name);
51220
+ logger.debug("legacy capability file removed", { file: name });
51221
+ } catch (err) {
51222
+ if (err?.code === "ENOENT") continue;
51223
+ throw err;
51406
51224
  }
51407
51225
  }
51408
51226
  return { removed };
51409
51227
  }
51410
51228
 
51411
51229
  // src/inbox/inbox-store.ts
51412
- var fs35 = __toESM(require("fs"), 1);
51230
+ var fs34 = __toESM(require("fs"), 1);
51413
51231
  var path35 = __toESM(require("path"), 1);
51414
51232
  var INBOX_SUBDIR = "inbox";
51415
51233
  var InboxStore = class {
51416
51234
  constructor(dataDir) {
51417
51235
  this.dataDir = dataDir;
51418
- fs35.mkdirSync(this.dirPath(), { recursive: true });
51236
+ fs34.mkdirSync(this.dirPath(), { recursive: true });
51419
51237
  }
51420
51238
  dataDir;
51421
51239
  /**
@@ -51427,7 +51245,7 @@ var InboxStore = class {
51427
51245
  const file = this.filePath(peerDeviceId);
51428
51246
  let raw;
51429
51247
  try {
51430
- raw = fs35.readFileSync(file, "utf8");
51248
+ raw = fs34.readFileSync(file, "utf8");
51431
51249
  } catch (err) {
51432
51250
  if (err?.code === "ENOENT") return [];
51433
51251
  return [];
@@ -51443,7 +51261,7 @@ var InboxStore = class {
51443
51261
  const dir = this.dirPath();
51444
51262
  let entries;
51445
51263
  try {
51446
- entries = fs35.readdirSync(dir);
51264
+ entries = fs34.readdirSync(dir);
51447
51265
  } catch (err) {
51448
51266
  if (err?.code === "ENOENT") return [];
51449
51267
  return [];
@@ -51459,9 +51277,9 @@ var InboxStore = class {
51459
51277
  if (existing.some((m2) => m2.id === message.id)) return;
51460
51278
  const file = this.filePath(message.peerDeviceId);
51461
51279
  const line = JSON.stringify(message) + "\n";
51462
- fs35.appendFileSync(file, line, { mode: 384 });
51280
+ fs34.appendFileSync(file, line, { mode: 384 });
51463
51281
  try {
51464
- fs35.chmodSync(file, 384);
51282
+ fs34.chmodSync(file, 384);
51465
51283
  } catch {
51466
51284
  }
51467
51285
  }
@@ -51491,7 +51309,7 @@ var InboxStore = class {
51491
51309
  removeByPeerDeviceId(peerDeviceId) {
51492
51310
  const file = this.filePath(peerDeviceId);
51493
51311
  try {
51494
- fs35.unlinkSync(file);
51312
+ fs34.unlinkSync(file);
51495
51313
  } catch (err) {
51496
51314
  if (err?.code === "ENOENT") return;
51497
51315
  }
@@ -51500,10 +51318,10 @@ var InboxStore = class {
51500
51318
  const file = this.filePath(peerDeviceId);
51501
51319
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
51502
51320
  const content = messages.map((m2) => JSON.stringify(m2)).join("\n") + (messages.length > 0 ? "\n" : "");
51503
- fs35.writeFileSync(tmp, content, { mode: 384 });
51504
- fs35.renameSync(tmp, file);
51321
+ fs34.writeFileSync(tmp, content, { mode: 384 });
51322
+ fs34.renameSync(tmp, file);
51505
51323
  try {
51506
- fs35.chmodSync(file, 384);
51324
+ fs34.chmodSync(file, 384);
51507
51325
  } catch {
51508
51326
  }
51509
51327
  }
@@ -51599,7 +51417,7 @@ var InboxManager = class {
51599
51417
  };
51600
51418
 
51601
51419
  // src/state/contact-store.ts
51602
- var fs36 = __toESM(require("fs"), 1);
51420
+ var fs35 = __toESM(require("fs"), 1);
51603
51421
  var path36 = __toESM(require("path"), 1);
51604
51422
  var FILE_NAME = "contacts.json";
51605
51423
  var ContactStore = class {
@@ -51613,7 +51431,7 @@ var ContactStore = class {
51613
51431
  const file = path36.join(this.dataDir, FILE_NAME);
51614
51432
  let raw;
51615
51433
  try {
51616
- raw = fs36.readFileSync(file, "utf8");
51434
+ raw = fs35.readFileSync(file, "utf8");
51617
51435
  } catch (err) {
51618
51436
  if (err?.code !== "ENOENT") this.renameBak(file);
51619
51437
  return;
@@ -51689,20 +51507,20 @@ var ContactStore = class {
51689
51507
  null,
51690
51508
  2
51691
51509
  );
51692
- fs36.mkdirSync(this.dataDir, { recursive: true });
51693
- fs36.writeFileSync(tmp, content, { mode: 384 });
51694
- fs36.renameSync(tmp, file);
51510
+ fs35.mkdirSync(this.dataDir, { recursive: true });
51511
+ fs35.writeFileSync(tmp, content, { mode: 384 });
51512
+ fs35.renameSync(tmp, file);
51695
51513
  }
51696
51514
  renameBak(file) {
51697
51515
  try {
51698
- fs36.renameSync(file, `${file}.bak`);
51516
+ fs35.renameSync(file, `${file}.bak`);
51699
51517
  } catch {
51700
51518
  }
51701
51519
  }
51702
51520
  };
51703
51521
 
51704
51522
  // src/contact/connect-remote.ts
51705
- var crypto6 = __toESM(require("crypto"), 1);
51523
+ var crypto5 = __toESM(require("crypto"), 1);
51706
51524
  var HANDSHAKE_TIMEOUT_MS = 5e3;
51707
51525
  var RPC_TIMEOUT_MS = 5e3;
51708
51526
  async function connectRemote(args) {
@@ -51766,7 +51584,7 @@ async function connectRemote(args) {
51766
51584
  ws.once("close", onClose);
51767
51585
  });
51768
51586
  function call(method, payload) {
51769
- const requestId = crypto6.randomUUID();
51587
+ const requestId = crypto5.randomUUID();
51770
51588
  return new Promise((resolve6, reject) => {
51771
51589
  const onMessage = (raw) => {
51772
51590
  let f;
@@ -51848,7 +51666,7 @@ async function autoReverseContact(args) {
51848
51666
  }
51849
51667
 
51850
51668
  // src/migrations/2026-05-20-flatten-sessions.ts
51851
- var fs37 = __toESM(require("fs"), 1);
51669
+ var fs36 = __toESM(require("fs"), 1);
51852
51670
  var path37 = __toESM(require("path"), 1);
51853
51671
  var MIGRATION_FLAG_NAME = ".migration.v1.done";
51854
51672
  function migrateFlattenSessions(opts) {
@@ -51868,7 +51686,7 @@ function migrateFlattenSessions(opts) {
51868
51686
  if (!entry.endsWith(".json")) continue;
51869
51687
  const src = path37.join(defaultDir, entry);
51870
51688
  const dst = path37.join(sessionsDir, entry);
51871
- fs37.renameSync(src, dst);
51689
+ fs36.renameSync(src, dst);
51872
51690
  movedBare += 1;
51873
51691
  }
51874
51692
  rmdirIfEmpty(defaultDir);
@@ -51880,10 +51698,10 @@ function migrateFlattenSessions(opts) {
51880
51698
  const ownerSrc = path37.join(personaDir, "owner");
51881
51699
  if (existsSync3(ownerSrc) && isDir(ownerSrc)) {
51882
51700
  const ownerDst = path37.join(dataDir, "personas", pid, ".clawd", "sessions", "owner");
51883
- fs37.mkdirSync(ownerDst, { recursive: true });
51701
+ fs36.mkdirSync(ownerDst, { recursive: true });
51884
51702
  for (const file of readdirSafe(ownerSrc)) {
51885
51703
  if (!file.endsWith(".json")) continue;
51886
- fs37.renameSync(path37.join(ownerSrc, file), path37.join(ownerDst, file));
51704
+ fs36.renameSync(path37.join(ownerSrc, file), path37.join(ownerDst, file));
51887
51705
  movedVmOwner += 1;
51888
51706
  }
51889
51707
  rmdirIfEmpty(ownerSrc);
@@ -51891,18 +51709,18 @@ function migrateFlattenSessions(opts) {
51891
51709
  const listenerSrc = path37.join(personaDir, "listener");
51892
51710
  if (existsSync3(listenerSrc) && isDir(listenerSrc)) {
51893
51711
  const archiveDst = path37.join(dataDir, ".legacy", `listener-${pid}`);
51894
- fs37.mkdirSync(archiveDst, { recursive: true });
51712
+ fs36.mkdirSync(archiveDst, { recursive: true });
51895
51713
  for (const file of readdirSafe(listenerSrc)) {
51896
51714
  if (!file.endsWith(".json")) continue;
51897
- fs37.renameSync(path37.join(listenerSrc, file), path37.join(archiveDst, file));
51715
+ fs36.renameSync(path37.join(listenerSrc, file), path37.join(archiveDst, file));
51898
51716
  archivedListener += 1;
51899
51717
  }
51900
51718
  rmdirIfEmpty(listenerSrc);
51901
51719
  }
51902
51720
  rmdirIfEmpty(personaDir);
51903
51721
  }
51904
- fs37.mkdirSync(sessionsDir, { recursive: true });
51905
- fs37.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
51722
+ fs36.mkdirSync(sessionsDir, { recursive: true });
51723
+ fs36.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
51906
51724
  return {
51907
51725
  skipped: false,
51908
51726
  flagWritten: true,
@@ -51913,7 +51731,7 @@ function migrateFlattenSessions(opts) {
51913
51731
  }
51914
51732
  function existsSync3(p2) {
51915
51733
  try {
51916
- fs37.statSync(p2);
51734
+ fs36.statSync(p2);
51917
51735
  return true;
51918
51736
  } catch {
51919
51737
  return false;
@@ -51921,21 +51739,21 @@ function existsSync3(p2) {
51921
51739
  }
51922
51740
  function isDir(p2) {
51923
51741
  try {
51924
- return fs37.statSync(p2).isDirectory();
51742
+ return fs36.statSync(p2).isDirectory();
51925
51743
  } catch {
51926
51744
  return false;
51927
51745
  }
51928
51746
  }
51929
51747
  function readdirSafe(p2) {
51930
51748
  try {
51931
- return fs37.readdirSync(p2);
51749
+ return fs36.readdirSync(p2);
51932
51750
  } catch {
51933
51751
  return [];
51934
51752
  }
51935
51753
  }
51936
51754
  function rmdirIfEmpty(p2) {
51937
51755
  try {
51938
- fs37.rmdirSync(p2);
51756
+ fs36.rmdirSync(p2);
51939
51757
  } catch {
51940
51758
  }
51941
51759
  }
@@ -54282,7 +54100,7 @@ async function upsertDeviceBinding(opts) {
54282
54100
  }
54283
54101
 
54284
54102
  // src/feishu-auth/verify-token.ts
54285
- var crypto14 = __toESM(require("crypto"), 1);
54103
+ var crypto13 = __toESM(require("crypto"), 1);
54286
54104
  var CONNECT_TOKEN_PREFIX = "clawdtk1";
54287
54105
  function verifyConnectToken(args) {
54288
54106
  const now = args.nowSeconds ?? Math.floor(Date.now() / 1e3);
@@ -54294,7 +54112,7 @@ function verifyConnectToken(args) {
54294
54112
  const data = `${prefix}.${payloadB64}`;
54295
54113
  let signatureValid = false;
54296
54114
  try {
54297
- signatureValid = crypto14.verify(
54115
+ signatureValid = crypto13.verify(
54298
54116
  null,
54299
54117
  Buffer.from(data, "utf8"),
54300
54118
  args.publicKeyPem,
@@ -54331,7 +54149,7 @@ function verifyConnectToken(args) {
54331
54149
  }
54332
54150
 
54333
54151
  // src/feishu-auth/server-key.ts
54334
- var fs51 = __toESM(require("fs"), 1);
54152
+ var fs50 = __toESM(require("fs"), 1);
54335
54153
  var path53 = __toESM(require("path"), 1);
54336
54154
  var FILE_NAME2 = "server-signing-key.json";
54337
54155
  var ServerKeyStore = class {
@@ -54345,7 +54163,7 @@ var ServerKeyStore = class {
54345
54163
  /** 读缓存的公钥;无缓存 / 损坏 → null(调用方决定是否触发拉取) */
54346
54164
  read() {
54347
54165
  try {
54348
- const raw = fs51.readFileSync(this.filePath(), "utf8");
54166
+ const raw = fs50.readFileSync(this.filePath(), "utf8");
54349
54167
  const parsed = JSON.parse(raw);
54350
54168
  if (typeof parsed.publicKeyPem === "string" && parsed.publicKeyPem.includes("PUBLIC KEY")) {
54351
54169
  return parsed.publicKeyPem;
@@ -54360,12 +54178,12 @@ var ServerKeyStore = class {
54360
54178
  publicKeyPem,
54361
54179
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
54362
54180
  };
54363
- fs51.mkdirSync(this.dataDir, { recursive: true });
54364
- fs51.writeFileSync(this.filePath(), JSON.stringify(content, null, 2), { mode: 384 });
54181
+ fs50.mkdirSync(this.dataDir, { recursive: true });
54182
+ fs50.writeFileSync(this.filePath(), JSON.stringify(content, null, 2), { mode: 384 });
54365
54183
  }
54366
54184
  clear() {
54367
54185
  try {
54368
- fs51.unlinkSync(this.filePath());
54186
+ fs50.unlinkSync(this.filePath());
54369
54187
  } catch {
54370
54188
  }
54371
54189
  }
@@ -55272,45 +55090,6 @@ function buildCapabilitiesHandlers(deps) {
55272
55090
  };
55273
55091
  }
55274
55092
 
55275
- // src/handlers/capability.ts
55276
- init_zod();
55277
- init_protocol();
55278
- var DeleteArgsSchema = external_exports.object({
55279
- capabilityId: external_exports.string().min(1)
55280
- }).strict();
55281
- function buildCapabilityHandlers(deps) {
55282
- const { manager } = deps;
55283
- const list = async () => {
55284
- return {
55285
- response: {
55286
- type: "capability:list",
55287
- capabilities: manager.list().map(stripSecretHash)
55288
- }
55289
- };
55290
- };
55291
- const del = async (frame) => {
55292
- const { type: _type, requestId: _requestId, ...rest } = frame;
55293
- const args = DeleteArgsSchema.parse(rest);
55294
- const result = manager.delete(args.capabilityId);
55295
- if (!result) {
55296
- throw new ClawdError(
55297
- ERROR_CODES.VALIDATION_ERROR,
55298
- `capability not found: ${args.capabilityId}`
55299
- );
55300
- }
55301
- return {
55302
- response: {
55303
- type: "capability:deleted",
55304
- capabilityId: args.capabilityId
55305
- }
55306
- };
55307
- };
55308
- return {
55309
- "capability:list": list,
55310
- "capability:delete": del
55311
- };
55312
- }
55313
-
55314
55093
  // src/handlers/inbox.ts
55315
55094
  init_protocol();
55316
55095
  function resolvePeerDeviceId(ctx, argsPeerDeviceId) {
@@ -58178,9 +57957,6 @@ function buildMethodHandlers(deps) {
58178
57957
  personaManager: deps.personaManager,
58179
57958
  personaRegistry: deps.personaRegistry
58180
57959
  }),
58181
- ...buildCapabilityHandlers({
58182
- manager: deps.capabilityManager
58183
- }),
58184
57960
  ...buildInboxHandlers({
58185
57961
  manager: deps.inboxManager,
58186
57962
  sendDmDeps: {
@@ -58216,8 +57992,7 @@ function buildMethodHandlers(deps) {
58216
57992
  ownerPrincipalId: deps.ownerPrincipalId,
58217
57993
  ownerId: deps.ownerId,
58218
57994
  ownerProvider: deps.ownerProvider,
58219
- personaStore: deps.personaStore,
58220
- capabilityManager: deps.capabilityManager
57995
+ personaStore: deps.personaStore
58221
57996
  }),
58222
57997
  ...buildFeishuAuthHandlers(deps.feishuAuth),
58223
57998
  ...buildDeviceHandlers(deps.feishuDevice),
@@ -58531,12 +58306,6 @@ var METHOD_GRANT_MAP = {
58531
58306
  // handler 内部按 ctx.principal.kind 决定返回 "合成 owner self capability" 还是
58532
58307
  // "查 capabilityManager 拿 caller 的 guest capability"。
58533
58308
  "whoami": { kind: "public" },
58534
- // ---- capability platform(admin-only) ----
58535
- // 飞书统一身份 Phase 2 (2026-06-01, spec 决策 #12): personal-cap:get / capability:issue /
58536
- // bindPeer 已下线(personal token / 邀请码全链路删除)。capability:list / delete 保留供调试 +
58537
- // 撤销级联 + 清旧 per-peer cap。
58538
- "capability:list": ADMIN_ANY,
58539
- "capability:delete": ADMIN_ANY,
58540
58309
  // ---- inbox: 双投 P2P IM ----
58541
58310
  // 三条都 'public' — 能连上 = 已通过本地验票. handler 内额外校验 guest 的
58542
58311
  // args.peerDeviceId 与 ctx.principal.id(=deviceId) 一致, 防 guest 越权操作别的 DM 桶.
@@ -59196,32 +58965,13 @@ async function startDaemon(config) {
59196
58965
  }
59197
58966
  } : {}
59198
58967
  });
59199
- const capabilityStore = new CapabilityStore(config.dataDir);
59200
- const capabilityRegistry = new CapabilityRegistry(capabilityStore);
59201
- const capabilityManager = new CapabilityManager(capabilityRegistry, {
59202
- onDeleted: (cap) => {
59203
- const deletedAt = Date.now();
59204
- wsServer?.broadcastToOwners({
59205
- type: "capability:tokenDeleted",
59206
- capabilityId: cap.id,
59207
- deletedAt
59208
- });
59209
- wsServer?.closeConnectionsByGuestId(cap.id);
59210
- const cleanup = cleanupGuestSessionsForCapability(cap, sessionStoreFactory);
59211
- if (cleanup.removed.length > 0) {
59212
- logger.info("capability delete cascade: guest sessions removed", {
59213
- capabilityId: cap.id,
59214
- removedDirs: cleanup.removed
59215
- });
59216
- }
59217
- }
59218
- });
58968
+ await cleanupLegacyCapabilityFiles(config.dataDir, logger);
59219
58969
  migrateDropPersonsDir(config.dataDir);
59220
58970
  let wsServer = null;
59221
58971
  const authGate = authMode === "first-message" ? new AuthGate({
59222
58972
  shouldEnforce: buildShouldEnforce({ tunnel: config.tunnel }),
59223
58973
  // Task 1.7:authenticate 注入路径替代 expectedToken 单 token 比对。
59224
- // owner 路径 constantTimeEqual 防侧信道;guest 路径走 capabilityRegistry.
58974
+ // owner 路径 constantTimeEqual 防侧信道;guest 路径走 verifyConnectToken (Ed25519).
59225
58975
  expectedToken: resolvedAuthToken,
59226
58976
  authenticate: async (t, selfUrl) => {
59227
58977
  const result = await authenticate(t, buildConnectAuthDeps());
@@ -59745,8 +59495,6 @@ async function startDaemon(config) {
59745
59495
  personaRoot: import_node_path61.default.join(config.dataDir, "personas"),
59746
59496
  // v2 多人 persona 隔离:handler 派生 guest user-dir 放行
59747
59497
  usersRoot,
59748
- // capability:list / delete handler 依赖
59749
- capabilityManager,
59750
59498
  // v2 Phase 6: whoami handler 装在 owner principal.displayName + persona 解析
59751
59499
  ownerDisplayName,
59752
59500
  // 决策 #16: whoami owner.id = 本机 deviceId(路由主键)+ ownerId/provider 归属人
@@ -59756,8 +59504,6 @@ async function startDaemon(config) {
59756
59504
  ownerId,
59757
59505
  ownerProvider,
59758
59506
  personaStore,
59759
- // capability handler 也用 (capability:issue 走 registry / capability:list)
59760
- capabilityRegistry,
59761
59507
  // capability platform v3 inbox: handler 接 manager (post/list/markRead),
59762
59508
  // cascade 接 store (list 统计 deletedInboxEvents).
59763
59509
  inboxManager,
@@ -60092,18 +59838,11 @@ async function startDaemon(config) {
60092
59838
  ),
60093
59839
  protocolVersion: PROTOCOL_VERSION,
60094
59840
  authGate: authGate ?? void 0,
60095
- // noAuth 模式下仍验 capability token: capability platform 漏洞 远端 client
60096
- // cap token noAuth daemon 时若无此 hook fallback owner ctx, whoami 错返
60097
- // capability.id = ownerPrincipalId, 导致 myCapabilityId 写错 inbox channel 写错.
60098
- // 命中 cap attach guest ctx; 空 token / 未命中 → 保持 noAuth 行为 (handler fallback owner).
59841
+ // T-35: capability path retired;本 hook 只剩 persona web 分享 visitor token 分支:
59842
+ // noAuth localhost daemon 上访客 token 若命中 attach guest ctx,否则 fallback owner
59843
+ // (非 persona-web 场景静默 return null;tunnel 模式走 AuthGate 不经此路)。参数名
59844
+ // tryVerifyCapabilityToken 不再准确,跨 local-ws-server 改名属 followup 清理。
60099
59845
  tryVerifyCapabilityToken: (token) => {
60100
- const v2 = capabilityRegistry.verifyToken(token);
60101
- if (v2.ok) {
60102
- return {
60103
- principal: { id: v2.capability.id, kind: "guest", displayName: v2.capability.displayName },
60104
- grants: v2.capability.grants
60105
- };
60106
- }
60107
59846
  const vis = verifyVisitorToken(authFile.visitorTokenSecret, token, Math.floor(Date.now() / 1e3));
60108
59847
  if (vis.ok) return visitorGuestContext(vis.payload);
60109
59848
  return null;