@clawos-dev/clawd 0.2.251 → 0.2.253

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",
@@ -42057,6 +42038,8 @@ function buildSpawnContext(state, deps) {
42057
42038
  env.CLAWD_SESSION_ID = file.sessionId;
42058
42039
  const daemonUrl = deps.getDaemonUrl?.() ?? null;
42059
42040
  if (daemonUrl) env.CLAWD_DAEMON_URL = daemonUrl;
42041
+ const clawosApi = deps.getClawosApi?.();
42042
+ if (clawosApi) env.CLAWOS_API = clawosApi;
42060
42043
  const dispatchId = deps.lookupDispatchByBSessionId?.(file.sessionId);
42061
42044
  if (dispatchId) env.CLAWD_DISPATCH_ID = dispatchId;
42062
42045
  const personaId = file.ownerPersonaId;
@@ -42906,6 +42889,9 @@ var SessionRunner = class {
42906
42889
  // 单栏 refactor (spec 2026-06-02 §5.1): cc 子进程 env 注入 CLAWD_DAEMON_URL,让 assistant
42907
42890
  // curl daemon HTTP RPC adapter (/api/rpc/<method>) 触发管理操作。null = HTTP adapter 未启。
42908
42891
  getDaemonUrl: this.hooks.getDaemonUrl,
42892
+ // clawd 管家 (spec 2026-07-22-clawd-butler-persona §3.1): cc 子进程 env 注入 CLAWOS_API,
42893
+ // 让 clawd 管家 skill fetch `${CLAWOS_API}/docs/**` 线上文档。
42894
+ getClawosApi: this.hooks.getClawosApi,
42909
42895
  // daemon 级 MCP server 列表闭包透传;见 reducer.buildSpawnContext 派生
42910
42896
  // SpawnContext.mcpConfigs + 按 DISPATCH_MCP_ID 判断 dispatch 教学注入
42911
42897
  getMcpConfigs: this.hooks.getMcpConfigs,
@@ -43708,6 +43694,9 @@ var SessionManager = class {
43708
43694
  // 单栏 refactor (spec 2026-06-02 §5.1): 透传 daemon HTTP RPC base URL 闭包,
43709
43695
  // reducer 把它注入 cc 子进程 env CLAWD_DAEMON_URL.
43710
43696
  getDaemonUrl: this.deps.getDaemonUrl,
43697
+ // clawd 管家 (spec 2026-07-22-clawd-butler-persona §3.1): 透传 clawos 云 api base URL
43698
+ // 闭包, reducer 把它注入 cc 子进程 env CLAWOS_API, skill fetch 线上 md 文档用。
43699
+ getClawosApi: this.deps.getClawosApi,
43711
43700
  // daemon 级 MCP server 列表闭包透传给 reducer,cc spawn 逐项加 --mcp-config flag;
43712
43701
  // B session 的 CLAWD_DISPATCH_ID env 由下方 lookupDispatchByBSessionId 反查闭包负责。
43713
43702
  getMcpConfigs: () => this.deps.mcpConfigs ?? [],
@@ -45880,17 +45869,9 @@ var PersonaManager = class {
45880
45869
  }
45881
45870
  /**
45882
45871
  * 删除 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 矩阵后正式实现.
45872
+ * PersonaStore.remove(personaId) 级联删整个 <personaRoot>/<personaId>/ 目录
45873
+ * (含 .clawd/sessions/owner/ 和 .clawd/sessions/guests/<capId>/),rmSync
45874
+ * recursive force 一锅清。
45894
45875
  */
45895
45876
  delete(personaId) {
45896
45877
  this.deps.store.remove(personaId);
@@ -45940,7 +45921,7 @@ var DEFAULT_PERSONAS = [
45940
45921
  },
45941
45922
  {
45942
45923
  personaId: "persona-clawd-helper",
45943
- label: "clawd\u4F7F\u7528\u52A9\u624B",
45924
+ label: "clawd \u7BA1\u5BB6",
45944
45925
  model: "opus",
45945
45926
  iconKey: "assist",
45946
45927
  public: false,
@@ -50678,9 +50659,9 @@ var LocalWsServer = class {
50678
50659
  this.safeSend(c.ws, frame);
50679
50660
  }
50680
50661
  }
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.
50662
+ // 仅广播给 owner 连接(跳过 guest ws)。用于 contact:added / contact:removed /
50663
+ // contact:pinned / friend:reverseTokenOffered / appBuilder:project-updated
50664
+ // owner-only push frame——guest 无需消费。noAuth localhost ws ctx,视作 owner
50684
50665
  broadcastToOwners(frame) {
50685
50666
  const gate = this.opts.authGate;
50686
50667
  for (const c of this.clients.values()) {
@@ -50754,10 +50735,8 @@ var LocalWsServer = class {
50754
50735
  }
50755
50736
  }
50756
50737
  }
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 正名)。
50738
+ // 关闭某 guest principal 的所有活跃 ws 连接。close code 4401 让客户端识别
50739
+ // 'TOKEN_REVOKED' 而非普通断连;调用方按需触发(如 contact:remove 后要断对方现有连接)。
50761
50740
  closeConnectionsByGuestId(guestPrincipalId, code = 4401, reason = "TOKEN_REVOKED") {
50762
50741
  const set = this.guestIdToClients.get(guestPrincipalId);
50763
50742
  if (!set) return;
@@ -51231,191 +51210,38 @@ async function authenticate(token, deps) {
51231
51210
  };
51232
51211
  }
51233
51212
 
51234
- // src/permission/capability-store.ts
51235
- var fs33 = __toESM(require("fs"), 1);
51213
+ // src/legacy-cleanup.ts
51214
+ var fs33 = __toESM(require("fs/promises"), 1);
51236
51215
  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) {
51216
+ var LEGACY_FILES = [
51217
+ "personal-capability.json",
51218
+ "received-capabilities.json",
51219
+ "capabilities.json"
51220
+ ];
51221
+ async function cleanupLegacyCapabilityFiles(dataDir, logger) {
51398
51222
  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);
51223
+ for (const name of LEGACY_FILES) {
51224
+ const p2 = path34.join(dataDir, name);
51402
51225
  try {
51403
- fs34.rmSync(dir, { recursive: true, force: true });
51404
- removed.push(dir);
51405
- } catch {
51226
+ await fs33.unlink(p2);
51227
+ removed.push(name);
51228
+ logger.debug("legacy capability file removed", { file: name });
51229
+ } catch (err) {
51230
+ if (err?.code === "ENOENT") continue;
51231
+ throw err;
51406
51232
  }
51407
51233
  }
51408
51234
  return { removed };
51409
51235
  }
51410
51236
 
51411
51237
  // src/inbox/inbox-store.ts
51412
- var fs35 = __toESM(require("fs"), 1);
51238
+ var fs34 = __toESM(require("fs"), 1);
51413
51239
  var path35 = __toESM(require("path"), 1);
51414
51240
  var INBOX_SUBDIR = "inbox";
51415
51241
  var InboxStore = class {
51416
51242
  constructor(dataDir) {
51417
51243
  this.dataDir = dataDir;
51418
- fs35.mkdirSync(this.dirPath(), { recursive: true });
51244
+ fs34.mkdirSync(this.dirPath(), { recursive: true });
51419
51245
  }
51420
51246
  dataDir;
51421
51247
  /**
@@ -51427,7 +51253,7 @@ var InboxStore = class {
51427
51253
  const file = this.filePath(peerDeviceId);
51428
51254
  let raw;
51429
51255
  try {
51430
- raw = fs35.readFileSync(file, "utf8");
51256
+ raw = fs34.readFileSync(file, "utf8");
51431
51257
  } catch (err) {
51432
51258
  if (err?.code === "ENOENT") return [];
51433
51259
  return [];
@@ -51443,7 +51269,7 @@ var InboxStore = class {
51443
51269
  const dir = this.dirPath();
51444
51270
  let entries;
51445
51271
  try {
51446
- entries = fs35.readdirSync(dir);
51272
+ entries = fs34.readdirSync(dir);
51447
51273
  } catch (err) {
51448
51274
  if (err?.code === "ENOENT") return [];
51449
51275
  return [];
@@ -51459,9 +51285,9 @@ var InboxStore = class {
51459
51285
  if (existing.some((m2) => m2.id === message.id)) return;
51460
51286
  const file = this.filePath(message.peerDeviceId);
51461
51287
  const line = JSON.stringify(message) + "\n";
51462
- fs35.appendFileSync(file, line, { mode: 384 });
51288
+ fs34.appendFileSync(file, line, { mode: 384 });
51463
51289
  try {
51464
- fs35.chmodSync(file, 384);
51290
+ fs34.chmodSync(file, 384);
51465
51291
  } catch {
51466
51292
  }
51467
51293
  }
@@ -51491,7 +51317,7 @@ var InboxStore = class {
51491
51317
  removeByPeerDeviceId(peerDeviceId) {
51492
51318
  const file = this.filePath(peerDeviceId);
51493
51319
  try {
51494
- fs35.unlinkSync(file);
51320
+ fs34.unlinkSync(file);
51495
51321
  } catch (err) {
51496
51322
  if (err?.code === "ENOENT") return;
51497
51323
  }
@@ -51500,10 +51326,10 @@ var InboxStore = class {
51500
51326
  const file = this.filePath(peerDeviceId);
51501
51327
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
51502
51328
  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);
51329
+ fs34.writeFileSync(tmp, content, { mode: 384 });
51330
+ fs34.renameSync(tmp, file);
51505
51331
  try {
51506
- fs35.chmodSync(file, 384);
51332
+ fs34.chmodSync(file, 384);
51507
51333
  } catch {
51508
51334
  }
51509
51335
  }
@@ -51599,7 +51425,7 @@ var InboxManager = class {
51599
51425
  };
51600
51426
 
51601
51427
  // src/state/contact-store.ts
51602
- var fs36 = __toESM(require("fs"), 1);
51428
+ var fs35 = __toESM(require("fs"), 1);
51603
51429
  var path36 = __toESM(require("path"), 1);
51604
51430
  var FILE_NAME = "contacts.json";
51605
51431
  var ContactStore = class {
@@ -51613,7 +51439,7 @@ var ContactStore = class {
51613
51439
  const file = path36.join(this.dataDir, FILE_NAME);
51614
51440
  let raw;
51615
51441
  try {
51616
- raw = fs36.readFileSync(file, "utf8");
51442
+ raw = fs35.readFileSync(file, "utf8");
51617
51443
  } catch (err) {
51618
51444
  if (err?.code !== "ENOENT") this.renameBak(file);
51619
51445
  return;
@@ -51689,20 +51515,20 @@ var ContactStore = class {
51689
51515
  null,
51690
51516
  2
51691
51517
  );
51692
- fs36.mkdirSync(this.dataDir, { recursive: true });
51693
- fs36.writeFileSync(tmp, content, { mode: 384 });
51694
- fs36.renameSync(tmp, file);
51518
+ fs35.mkdirSync(this.dataDir, { recursive: true });
51519
+ fs35.writeFileSync(tmp, content, { mode: 384 });
51520
+ fs35.renameSync(tmp, file);
51695
51521
  }
51696
51522
  renameBak(file) {
51697
51523
  try {
51698
- fs36.renameSync(file, `${file}.bak`);
51524
+ fs35.renameSync(file, `${file}.bak`);
51699
51525
  } catch {
51700
51526
  }
51701
51527
  }
51702
51528
  };
51703
51529
 
51704
51530
  // src/contact/connect-remote.ts
51705
- var crypto6 = __toESM(require("crypto"), 1);
51531
+ var crypto5 = __toESM(require("crypto"), 1);
51706
51532
  var HANDSHAKE_TIMEOUT_MS = 5e3;
51707
51533
  var RPC_TIMEOUT_MS = 5e3;
51708
51534
  async function connectRemote(args) {
@@ -51766,7 +51592,7 @@ async function connectRemote(args) {
51766
51592
  ws.once("close", onClose);
51767
51593
  });
51768
51594
  function call(method, payload) {
51769
- const requestId = crypto6.randomUUID();
51595
+ const requestId = crypto5.randomUUID();
51770
51596
  return new Promise((resolve6, reject) => {
51771
51597
  const onMessage = (raw) => {
51772
51598
  let f;
@@ -51848,7 +51674,7 @@ async function autoReverseContact(args) {
51848
51674
  }
51849
51675
 
51850
51676
  // src/migrations/2026-05-20-flatten-sessions.ts
51851
- var fs37 = __toESM(require("fs"), 1);
51677
+ var fs36 = __toESM(require("fs"), 1);
51852
51678
  var path37 = __toESM(require("path"), 1);
51853
51679
  var MIGRATION_FLAG_NAME = ".migration.v1.done";
51854
51680
  function migrateFlattenSessions(opts) {
@@ -51868,7 +51694,7 @@ function migrateFlattenSessions(opts) {
51868
51694
  if (!entry.endsWith(".json")) continue;
51869
51695
  const src = path37.join(defaultDir, entry);
51870
51696
  const dst = path37.join(sessionsDir, entry);
51871
- fs37.renameSync(src, dst);
51697
+ fs36.renameSync(src, dst);
51872
51698
  movedBare += 1;
51873
51699
  }
51874
51700
  rmdirIfEmpty(defaultDir);
@@ -51880,10 +51706,10 @@ function migrateFlattenSessions(opts) {
51880
51706
  const ownerSrc = path37.join(personaDir, "owner");
51881
51707
  if (existsSync3(ownerSrc) && isDir(ownerSrc)) {
51882
51708
  const ownerDst = path37.join(dataDir, "personas", pid, ".clawd", "sessions", "owner");
51883
- fs37.mkdirSync(ownerDst, { recursive: true });
51709
+ fs36.mkdirSync(ownerDst, { recursive: true });
51884
51710
  for (const file of readdirSafe(ownerSrc)) {
51885
51711
  if (!file.endsWith(".json")) continue;
51886
- fs37.renameSync(path37.join(ownerSrc, file), path37.join(ownerDst, file));
51712
+ fs36.renameSync(path37.join(ownerSrc, file), path37.join(ownerDst, file));
51887
51713
  movedVmOwner += 1;
51888
51714
  }
51889
51715
  rmdirIfEmpty(ownerSrc);
@@ -51891,18 +51717,18 @@ function migrateFlattenSessions(opts) {
51891
51717
  const listenerSrc = path37.join(personaDir, "listener");
51892
51718
  if (existsSync3(listenerSrc) && isDir(listenerSrc)) {
51893
51719
  const archiveDst = path37.join(dataDir, ".legacy", `listener-${pid}`);
51894
- fs37.mkdirSync(archiveDst, { recursive: true });
51720
+ fs36.mkdirSync(archiveDst, { recursive: true });
51895
51721
  for (const file of readdirSafe(listenerSrc)) {
51896
51722
  if (!file.endsWith(".json")) continue;
51897
- fs37.renameSync(path37.join(listenerSrc, file), path37.join(archiveDst, file));
51723
+ fs36.renameSync(path37.join(listenerSrc, file), path37.join(archiveDst, file));
51898
51724
  archivedListener += 1;
51899
51725
  }
51900
51726
  rmdirIfEmpty(listenerSrc);
51901
51727
  }
51902
51728
  rmdirIfEmpty(personaDir);
51903
51729
  }
51904
- fs37.mkdirSync(sessionsDir, { recursive: true });
51905
- fs37.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
51730
+ fs36.mkdirSync(sessionsDir, { recursive: true });
51731
+ fs36.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
51906
51732
  return {
51907
51733
  skipped: false,
51908
51734
  flagWritten: true,
@@ -51913,7 +51739,7 @@ function migrateFlattenSessions(opts) {
51913
51739
  }
51914
51740
  function existsSync3(p2) {
51915
51741
  try {
51916
- fs37.statSync(p2);
51742
+ fs36.statSync(p2);
51917
51743
  return true;
51918
51744
  } catch {
51919
51745
  return false;
@@ -51921,21 +51747,21 @@ function existsSync3(p2) {
51921
51747
  }
51922
51748
  function isDir(p2) {
51923
51749
  try {
51924
- return fs37.statSync(p2).isDirectory();
51750
+ return fs36.statSync(p2).isDirectory();
51925
51751
  } catch {
51926
51752
  return false;
51927
51753
  }
51928
51754
  }
51929
51755
  function readdirSafe(p2) {
51930
51756
  try {
51931
- return fs37.readdirSync(p2);
51757
+ return fs36.readdirSync(p2);
51932
51758
  } catch {
51933
51759
  return [];
51934
51760
  }
51935
51761
  }
51936
51762
  function rmdirIfEmpty(p2) {
51937
51763
  try {
51938
- fs37.rmdirSync(p2);
51764
+ fs36.rmdirSync(p2);
51939
51765
  } catch {
51940
51766
  }
51941
51767
  }
@@ -54282,7 +54108,7 @@ async function upsertDeviceBinding(opts) {
54282
54108
  }
54283
54109
 
54284
54110
  // src/feishu-auth/verify-token.ts
54285
- var crypto14 = __toESM(require("crypto"), 1);
54111
+ var crypto13 = __toESM(require("crypto"), 1);
54286
54112
  var CONNECT_TOKEN_PREFIX = "clawdtk1";
54287
54113
  function verifyConnectToken(args) {
54288
54114
  const now = args.nowSeconds ?? Math.floor(Date.now() / 1e3);
@@ -54294,7 +54120,7 @@ function verifyConnectToken(args) {
54294
54120
  const data = `${prefix}.${payloadB64}`;
54295
54121
  let signatureValid = false;
54296
54122
  try {
54297
- signatureValid = crypto14.verify(
54123
+ signatureValid = crypto13.verify(
54298
54124
  null,
54299
54125
  Buffer.from(data, "utf8"),
54300
54126
  args.publicKeyPem,
@@ -54331,7 +54157,7 @@ function verifyConnectToken(args) {
54331
54157
  }
54332
54158
 
54333
54159
  // src/feishu-auth/server-key.ts
54334
- var fs51 = __toESM(require("fs"), 1);
54160
+ var fs50 = __toESM(require("fs"), 1);
54335
54161
  var path53 = __toESM(require("path"), 1);
54336
54162
  var FILE_NAME2 = "server-signing-key.json";
54337
54163
  var ServerKeyStore = class {
@@ -54345,7 +54171,7 @@ var ServerKeyStore = class {
54345
54171
  /** 读缓存的公钥;无缓存 / 损坏 → null(调用方决定是否触发拉取) */
54346
54172
  read() {
54347
54173
  try {
54348
- const raw = fs51.readFileSync(this.filePath(), "utf8");
54174
+ const raw = fs50.readFileSync(this.filePath(), "utf8");
54349
54175
  const parsed = JSON.parse(raw);
54350
54176
  if (typeof parsed.publicKeyPem === "string" && parsed.publicKeyPem.includes("PUBLIC KEY")) {
54351
54177
  return parsed.publicKeyPem;
@@ -54360,12 +54186,12 @@ var ServerKeyStore = class {
54360
54186
  publicKeyPem,
54361
54187
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
54362
54188
  };
54363
- fs51.mkdirSync(this.dataDir, { recursive: true });
54364
- fs51.writeFileSync(this.filePath(), JSON.stringify(content, null, 2), { mode: 384 });
54189
+ fs50.mkdirSync(this.dataDir, { recursive: true });
54190
+ fs50.writeFileSync(this.filePath(), JSON.stringify(content, null, 2), { mode: 384 });
54365
54191
  }
54366
54192
  clear() {
54367
54193
  try {
54368
- fs51.unlinkSync(this.filePath());
54194
+ fs50.unlinkSync(this.filePath());
54369
54195
  } catch {
54370
54196
  }
54371
54197
  }
@@ -55272,45 +55098,6 @@ function buildCapabilitiesHandlers(deps) {
55272
55098
  };
55273
55099
  }
55274
55100
 
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
55101
  // src/handlers/inbox.ts
55315
55102
  init_protocol();
55316
55103
  function resolvePeerDeviceId(ctx, argsPeerDeviceId) {
@@ -58178,9 +57965,6 @@ function buildMethodHandlers(deps) {
58178
57965
  personaManager: deps.personaManager,
58179
57966
  personaRegistry: deps.personaRegistry
58180
57967
  }),
58181
- ...buildCapabilityHandlers({
58182
- manager: deps.capabilityManager
58183
- }),
58184
57968
  ...buildInboxHandlers({
58185
57969
  manager: deps.inboxManager,
58186
57970
  sendDmDeps: {
@@ -58216,8 +58000,7 @@ function buildMethodHandlers(deps) {
58216
58000
  ownerPrincipalId: deps.ownerPrincipalId,
58217
58001
  ownerId: deps.ownerId,
58218
58002
  ownerProvider: deps.ownerProvider,
58219
- personaStore: deps.personaStore,
58220
- capabilityManager: deps.capabilityManager
58003
+ personaStore: deps.personaStore
58221
58004
  }),
58222
58005
  ...buildFeishuAuthHandlers(deps.feishuAuth),
58223
58006
  ...buildDeviceHandlers(deps.feishuDevice),
@@ -58531,12 +58314,6 @@ var METHOD_GRANT_MAP = {
58531
58314
  // handler 内部按 ctx.principal.kind 决定返回 "合成 owner self capability" 还是
58532
58315
  // "查 capabilityManager 拿 caller 的 guest capability"。
58533
58316
  "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
58317
  // ---- inbox: 双投 P2P IM ----
58541
58318
  // 三条都 'public' — 能连上 = 已通过本地验票. handler 内额外校验 guest 的
58542
58319
  // args.peerDeviceId 与 ctx.principal.id(=deviceId) 一致, 防 guest 越权操作别的 DM 桶.
@@ -59196,32 +58973,13 @@ async function startDaemon(config) {
59196
58973
  }
59197
58974
  } : {}
59198
58975
  });
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
- });
58976
+ await cleanupLegacyCapabilityFiles(config.dataDir, logger);
59219
58977
  migrateDropPersonsDir(config.dataDir);
59220
58978
  let wsServer = null;
59221
58979
  const authGate = authMode === "first-message" ? new AuthGate({
59222
58980
  shouldEnforce: buildShouldEnforce({ tunnel: config.tunnel }),
59223
58981
  // Task 1.7:authenticate 注入路径替代 expectedToken 单 token 比对。
59224
- // owner 路径 constantTimeEqual 防侧信道;guest 路径走 capabilityRegistry.
58982
+ // owner 路径 constantTimeEqual 防侧信道;guest 路径走 verifyConnectToken (Ed25519).
59225
58983
  expectedToken: resolvedAuthToken,
59226
58984
  authenticate: async (t, selfUrl) => {
59227
58985
  const result = await authenticate(t, buildConnectAuthDeps());
@@ -59477,6 +59235,11 @@ async function startDaemon(config) {
59477
59235
  // 127.0.0.1(不是 config.host)—— cc 跑在本机,loopback 最稳;外部访问限制 + http-router
59478
59236
  // 的 isLoopback 兜底已确保安全。
59479
59237
  getDaemonUrl: () => `http://127.0.0.1:${config.port}`,
59238
+ // clawd 管家 (spec 2026-07-22-clawd-butler-persona §3.1):cc 子进程 env CLAWOS_API 闭包来源。
59239
+ // clawd 管家 skill 用它 fetch 云端 md 文档(`${CLAWOS_API}/docs/**`)。非 tunnel 模式
59240
+ // config.clawosApi 为 null → fallback 到 DEFAULT_CLAWOS_API(api.clawos.chat),保证注入
59241
+ // 的 env 一定非空;skill 拿到但云端不可达时走 §3.4 明说降级。
59242
+ getClawosApi: () => config.clawosApi ?? DEFAULT_CLAWOS_API,
59480
59243
  // Persona dispatch (Task 8): manager.createDispatchedSession 用 personaDispatchManager
59481
59244
  // registerBSession;reducer 通过闭包反查 dispatchId 注 CLAWD_DISPATCH_ID env。
59482
59245
  personaDispatchManager,
@@ -59745,8 +59508,6 @@ async function startDaemon(config) {
59745
59508
  personaRoot: import_node_path61.default.join(config.dataDir, "personas"),
59746
59509
  // v2 多人 persona 隔离:handler 派生 guest user-dir 放行
59747
59510
  usersRoot,
59748
- // capability:list / delete handler 依赖
59749
- capabilityManager,
59750
59511
  // v2 Phase 6: whoami handler 装在 owner principal.displayName + persona 解析
59751
59512
  ownerDisplayName,
59752
59513
  // 决策 #16: whoami owner.id = 本机 deviceId(路由主键)+ ownerId/provider 归属人
@@ -59756,8 +59517,6 @@ async function startDaemon(config) {
59756
59517
  ownerId,
59757
59518
  ownerProvider,
59758
59519
  personaStore,
59759
- // capability handler 也用 (capability:issue 走 registry / capability:list)
59760
- capabilityRegistry,
59761
59520
  // capability platform v3 inbox: handler 接 manager (post/list/markRead),
59762
59521
  // cascade 接 store (list 统计 deletedInboxEvents).
59763
59522
  inboxManager,
@@ -60092,18 +59851,11 @@ async function startDaemon(config) {
60092
59851
  ),
60093
59852
  protocolVersion: PROTOCOL_VERSION,
60094
59853
  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).
59854
+ // T-35: capability path retired;本 hook 只剩 persona web 分享 visitor token 分支:
59855
+ // noAuth localhost daemon 上访客 token 若命中 attach guest ctx,否则 fallback owner
59856
+ // (非 persona-web 场景静默 return null;tunnel 模式走 AuthGate 不经此路)。参数名
59857
+ // tryVerifyCapabilityToken 不再准确,跨 local-ws-server 改名属 followup 清理。
60099
59858
  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
59859
  const vis = verifyVisitorToken(authFile.visitorTokenSecret, token, Math.floor(Date.now() / 1e3));
60108
59860
  if (vis.ok) return visitorGuestContext(vis.payload);
60109
59861
  return null;