@clawos-dev/clawd 0.2.112-beta.215.872b507 → 0.2.112-beta.216.545c7ca

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.
Files changed (2) hide show
  1. package/dist/cli.cjs +779 -758
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -132,15 +132,11 @@ var init_methods = __esm({
132
132
  "attachment.groupRemove",
133
133
  "attachment.groupList",
134
134
  // ---- capability:* (capability platform 鉴权底座) ----
135
- // global personal token 模型 (2026-05-25): UI 不再调 capability:issue —— daemon 启动时
136
- // loadOrCreatePersonalCapability 自动创建 1 fixed-grants personal capability,
137
- // 所有联系人共用 personal token。capability:list / capability:delete 保留 (供调试 /
138
- // 老板手动清旧 cap);capability:issue / capability:bindPeer 已下线。
135
+ // 飞书统一身份 Phase 2 (2026-06-01, spec 决策 #12): personal token / 邀请码全链路删除——
136
+ // hub 已完整替代(中心 server 签发 per-hub connect token)。capability:list / capability:delete
137
+ // 保留:调试 + whoami 反查 + 撤销级联 + 清旧 per-peer cap。personal-cap:get 已下线。
139
138
  "capability:list",
140
139
  "capability:delete",
141
- // global personal token (2026-05-25): UI invite/accept dialog 挂载即调,拿 personal
142
- // token + capability + shareBaseUrl 用于拼 inviteUrl / autoAttach 反向推。owner-only。
143
- "personal-cap:get",
144
140
  // ---- inbox:* (capability platform Phase 3 跨用户通知 + Phase 4 DM) ----
145
141
  // owner 接 guest 的 cross-principal 消息事件 + DM 双向私聊.
146
142
  // inbox:list / markRead: admin-only (Phase 3); inbox:postMessage: DM 自带能力,
@@ -148,14 +144,11 @@ var init_methods = __esm({
148
144
  "inbox:list",
149
145
  "inbox:markRead",
150
146
  "inbox:postMessage",
151
- // ---- received-capability:* (对方颁给我的 cap, 视角 B 双向授权模型, 2026-05-23) ----
152
- // owner 入口: list / add / remove
153
- // guest 端: autoAttach (B A token 后通过 cap channel ws 推 cap 给 A)
154
- // 详见 received-capability.ts JSDoc + spec 2026-05-23-bidirectional-cap-design.md
147
+ // ---- received-capability:* (联系人列表, 视角 B 双向授权模型 2026-05-23) ----
148
+ // 飞书统一身份 Phase 2 (2026-06-01, spec 决策 #12): 邀请码 add / autoAttach 已删除——
149
+ // hub:connect 落同一 store + 自动反向(决策 #11)替代。list / remove 保留作联系人列表读删。
155
150
  "received-capability:list",
156
- "received-capability:add",
157
151
  "received-capability:remove",
158
- "received-capability:autoAttach",
159
152
  // ---- whoami (v2 capability platform) ----
160
153
  // 任何 authed connection 都能调;返回 owner 显示名 + 当前 capability wire 形态 +
161
154
  // grants 对应的 persona 元数据 (id + displayName)。UI 端 AddRemotePersonaDialog
@@ -687,8 +680,8 @@ var init_parseUtil = __esm({
687
680
  init_errors2();
688
681
  init_en();
689
682
  makeIssue = (params) => {
690
- const { data, path: path60, errorMaps, issueData } = params;
691
- const fullPath = [...path60, ...issueData.path || []];
683
+ const { data, path: path59, errorMaps, issueData } = params;
684
+ const fullPath = [...path59, ...issueData.path || []];
692
685
  const fullIssue = {
693
686
  ...issueData,
694
687
  path: fullPath
@@ -999,11 +992,11 @@ var init_types = __esm({
999
992
  init_parseUtil();
1000
993
  init_util();
1001
994
  ParseInputLazyPath = class {
1002
- constructor(parent, value, path60, key) {
995
+ constructor(parent, value, path59, key) {
1003
996
  this._cachedPath = [];
1004
997
  this.parent = parent;
1005
998
  this.data = value;
1006
- this._path = path60;
999
+ this._path = path59;
1007
1000
  this._key = key;
1008
1001
  }
1009
1002
  get path() {
@@ -4496,11 +4489,6 @@ var init_persona_schemas = __esm({
4496
4489
  enabled: external_exports.boolean().optional(),
4497
4490
  autoAllowBashIfSandboxed: external_exports.boolean().optional(),
4498
4491
  allowUnsandboxedCommands: external_exports.boolean().optional(),
4499
- // 开发型 persona 的网络域名白名单:CC 沙箱代理按 hostname 放行(spec 实测 D2)。
4500
- // 缺省 = 无网络(沙箱代理对所有域名回 403 blocked-by-allowlist)
4501
- network: external_exports.object({
4502
- allowedDomains: external_exports.array(external_exports.string()).optional()
4503
- }).optional(),
4504
4492
  filesystem: external_exports.object({
4505
4493
  denyRead: external_exports.array(external_exports.string()).optional(),
4506
4494
  allowRead: external_exports.array(external_exports.string()).optional(),
@@ -4535,10 +4523,7 @@ var init_persona_schemas = __esm({
4535
4523
  personality: external_exports.string().optional(),
4536
4524
  public: external_exports.boolean().optional(),
4537
4525
  // pass `null` to clear; daemon strips the field from PersonaFile on null
4538
- iconKey: external_exports.string().nullable().optional(),
4539
- // 整体替换 sandbox-settings.json 的 sandbox.network.allowedDomains(不是 merge 数组)。
4540
- // 窄接口设计:daemon 做 read-modify-write,owner 手改的其他 sandbox 字段不被 UI 覆盖
4541
- networkAllowedDomains: external_exports.array(external_exports.string()).optional()
4526
+ iconKey: external_exports.string().nullable().optional()
4542
4527
  }).strict()
4543
4528
  }).strict();
4544
4529
  }
@@ -5138,7 +5123,17 @@ var init_schemas = __esm({
5138
5123
  * 显示名写入 cap.peerOwnerDisplayName(cap.displayName 本来就是 owner 颁时填
5139
5124
  * 的"颁给谁/用途",hello 收到的 displayName 是辅助)。
5140
5125
  */
5141
- selfDisplayName: external_exports.string().optional()
5126
+ selfDisplayName: external_exports.string().optional(),
5127
+ /**
5128
+ * 飞书统一身份 Phase 2(spec 决策 #11,自动反向建立联系人):guest 自报自己可达的
5129
+ * ws 地址(tunnel 地址优先)。hub introspect 验票成功后,若 guest 带 selfUrl,hub 自动
5130
+ * 把 guest 落入本机 ReceivedCapabilityStore(remoteUrl = selfUrl)+ 广播 received-capability:added,
5131
+ * 让 hub 的 People 立即出现该 guest——双向互为联系人,身份由 introspect 背书不可伪造。
5132
+ *
5133
+ * 缺省(A 无公网地址)→ hub 反向连不上 A,**不**自动建 cap;双向 IM 仅在 A 在线连接
5134
+ * 期间通过该连接收发(spec 决策 #11 限制)。不填假地址(不造假数据)。
5135
+ */
5136
+ selfUrl: external_exports.string().min(1).optional()
5142
5137
  });
5143
5138
  AuthOkFrameSchema = external_exports.object({
5144
5139
  type: external_exports.literal("auth:ok"),
@@ -5228,7 +5223,7 @@ function stripSecretHash(cap) {
5228
5223
  const { secretHash: _hash, ...wire } = cap;
5229
5224
  return wire;
5230
5225
  }
5231
- var ResourceSchema, ActionSchema, GrantSchema, CapabilitySchema, CapabilityWireSchema, CapabilityErrorCodeSchema, WhoamiResponseSchema, PERSONAL_CAP_GRANTS, PersonalCapGetResponseSchema;
5226
+ var ResourceSchema, ActionSchema, GrantSchema, CapabilitySchema, CapabilityWireSchema, CapabilityErrorCodeSchema, WhoamiResponseSchema, PERSONAL_CAP_GRANTS;
5232
5227
  var init_capability = __esm({
5233
5228
  "../protocol/src/capability.ts"() {
5234
5229
  "use strict";
@@ -5296,13 +5291,6 @@ var init_capability = __esm({
5296
5291
  actions: Object.freeze(["read", "send"])
5297
5292
  })
5298
5293
  ]);
5299
- PersonalCapGetResponseSchema = external_exports.object({
5300
- type: external_exports.literal("personal-cap:get:ok"),
5301
- token: external_exports.string().min(1),
5302
- capability: CapabilityWireSchema,
5303
- /** ws/wss base URL,UI deriveInviteBase 用来拼 inviteUrl(tunnel 优先 / 否则本机 ws) */
5304
- shareBaseUrl: external_exports.string().min(1)
5305
- }).strict();
5306
5294
  }
5307
5295
  });
5308
5296
 
@@ -5338,7 +5326,7 @@ var init_inbox = __esm({
5338
5326
  });
5339
5327
 
5340
5328
  // ../protocol/src/received-capability.ts
5341
- var ReceivedCapabilitySchema, ReceivedCapabilityWireSchema, ReceivedCapabilityAddArgsSchema, ReceivedCapabilityAddOkSchema, ReceivedCapabilityRemoveArgsSchema, ReceivedCapabilityRemoveOkSchema, ReceivedCapabilityListOkSchema, ReceivedCapabilityAutoAttachArgsSchema, ReceivedCapabilityAutoAttachOkSchema, ReceivedCapabilityAddedFrameSchema, ReceivedCapabilityRemovedFrameSchema;
5329
+ var ReceivedCapabilitySchema, ReceivedCapabilityWireSchema, ReceivedCapabilityRemoveArgsSchema, ReceivedCapabilityRemoveOkSchema, ReceivedCapabilityListOkSchema, ReceivedCapabilityAddedFrameSchema, ReceivedCapabilityRemovedFrameSchema;
5342
5330
  var init_received_capability = __esm({
5343
5331
  "../protocol/src/received-capability.ts"() {
5344
5332
  "use strict";
@@ -5349,19 +5337,15 @@ var init_received_capability = __esm({
5349
5337
  ownerDisplayName: external_exports.string(),
5350
5338
  remoteUrl: external_exports.string().min(1),
5351
5339
  capabilityId: external_exports.string().min(1),
5352
- capabilityToken: external_exports.string().min(1),
5340
+ // Phase 2 自动反向建立联系人(spec 决策 #11):hub introspect 验票成功后自动落 A 的
5341
+ // received cap,但 hub 不持有 A 颁发的 token(A 从未给 hub 换票)→ connectToken 为空串。
5342
+ // hub:connect 出站路径仍存中心 server 签发的真实 connect token(断线重连复用)。
5343
+ // 不用 .min(1):自动反向路径合法地存空串,禁止填假 token 占位(不造假数据)。
5344
+ connectToken: external_exports.string(),
5353
5345
  grants: external_exports.array(GrantSchema),
5354
5346
  receivedAt: external_exports.number().int().nonnegative()
5355
5347
  }).strict();
5356
5348
  ReceivedCapabilityWireSchema = ReceivedCapabilitySchema;
5357
- ReceivedCapabilityAddArgsSchema = external_exports.object({
5358
- remoteUrl: external_exports.string().min(1),
5359
- token: external_exports.string().min(1)
5360
- }).strict();
5361
- ReceivedCapabilityAddOkSchema = external_exports.object({
5362
- type: external_exports.literal("received-capability:add:ok"),
5363
- receivedCap: ReceivedCapabilityWireSchema
5364
- }).strict();
5365
5349
  ReceivedCapabilityRemoveArgsSchema = external_exports.object({
5366
5350
  ownerPrincipalId: external_exports.string().min(1)
5367
5351
  }).strict();
@@ -5373,17 +5357,6 @@ var init_received_capability = __esm({
5373
5357
  type: external_exports.literal("received-capability:list:ok"),
5374
5358
  receivedCaps: external_exports.array(ReceivedCapabilityWireSchema)
5375
5359
  }).strict();
5376
- ReceivedCapabilityAutoAttachArgsSchema = external_exports.object({
5377
- ownerPrincipalId: external_exports.string().min(1),
5378
- ownerDisplayName: external_exports.string(),
5379
- remoteUrl: external_exports.string().min(1),
5380
- capabilityId: external_exports.string().min(1),
5381
- token: external_exports.string().min(1),
5382
- grants: external_exports.array(GrantSchema)
5383
- }).strict();
5384
- ReceivedCapabilityAutoAttachOkSchema = external_exports.object({
5385
- type: external_exports.literal("received-capability:autoAttach:ok")
5386
- }).strict();
5387
5360
  ReceivedCapabilityAddedFrameSchema = external_exports.object({
5388
5361
  type: external_exports.literal("received-capability:added"),
5389
5362
  receivedCap: ReceivedCapabilityWireSchema
@@ -5601,15 +5574,12 @@ var init_feishu_auth = __esm({
5601
5574
  "use strict";
5602
5575
  init_zod();
5603
5576
  FEISHU_GATED_METHODS = [
5604
- // capability / 邀请码(生成添加码 = 把自己的身份分享出去)
5577
+ // capability(调试 / 撤销,仍属远程身份体系)
5605
5578
  "capability:list",
5606
5579
  "capability:delete",
5607
- "personal-cap:get",
5608
- // 联系人(添加朋友 / 联系人列表 / 自动重连)
5580
+ // 联系人列表(hub:connect 落同一 store;list 读 / remove 删)
5609
5581
  "received-capability:list",
5610
- "received-capability:add",
5611
5582
  "received-capability:remove",
5612
- "received-capability:autoAttach",
5613
5583
  // DM / 跨用户通知
5614
5584
  "inbox:list",
5615
5585
  "inbox:markRead",
@@ -5636,9 +5606,13 @@ var init_feishu_auth = __esm({
5636
5606
  HubConnectArgsSchema = external_exports.object({
5637
5607
  /** 目标 hub 的 hubId(= hub owner unionId) */
5638
5608
  hubId: external_exports.string().min(1),
5639
- /** 目标 hub 的 ws/tunnel 地址 */
5640
- url: external_exports.string().min(1),
5641
- /** 显示名(公共 hub 来自目录;个人 hub 链接可缺省,连上后用 whoami 回填) */
5609
+ /**
5610
+ * 目标 hub 的 ws/tunnel 地址(决策 #13 统一注册表后改为可选):
5611
+ * - 公开目录点击:带 url(hub:list 已下发,省一次 server 查询)
5612
+ * - 私有 hub 仅凭 hubId 连接:缺省 url → daemon exchange 时由中心 server 下发该 hub 上报的 url
5613
+ */
5614
+ url: external_exports.string().min(1).optional(),
5615
+ /** 显示名(公共 hub 来自目录;私有 hub 缺省,连上后用 whoami 回填) */
5642
5616
  name: external_exports.string().optional()
5643
5617
  });
5644
5618
  HubConnectResponseSchema = external_exports.object({
@@ -5973,8 +5947,8 @@ var require_req = __commonJS({
5973
5947
  if (req.originalUrl) {
5974
5948
  _req.url = req.originalUrl;
5975
5949
  } else {
5976
- const path60 = req.path;
5977
- _req.url = typeof path60 === "string" ? path60 : req.url ? req.url.path || req.url : void 0;
5950
+ const path59 = req.path;
5951
+ _req.url = typeof path59 === "string" ? path59 : req.url ? req.url.path || req.url : void 0;
5978
5952
  }
5979
5953
  if (req.query) {
5980
5954
  _req.query = req.query;
@@ -6139,14 +6113,14 @@ var require_redact = __commonJS({
6139
6113
  }
6140
6114
  return obj;
6141
6115
  }
6142
- function parsePath(path60) {
6116
+ function parsePath(path59) {
6143
6117
  const parts = [];
6144
6118
  let current = "";
6145
6119
  let inBrackets = false;
6146
6120
  let inQuotes = false;
6147
6121
  let quoteChar = "";
6148
- for (let i = 0; i < path60.length; i++) {
6149
- const char = path60[i];
6122
+ for (let i = 0; i < path59.length; i++) {
6123
+ const char = path59[i];
6150
6124
  if (!inBrackets && char === ".") {
6151
6125
  if (current) {
6152
6126
  parts.push(current);
@@ -6277,10 +6251,10 @@ var require_redact = __commonJS({
6277
6251
  return current;
6278
6252
  }
6279
6253
  function redactPaths(obj, paths, censor, remove = false) {
6280
- for (const path60 of paths) {
6281
- const parts = parsePath(path60);
6254
+ for (const path59 of paths) {
6255
+ const parts = parsePath(path59);
6282
6256
  if (parts.includes("*")) {
6283
- redactWildcardPath(obj, parts, censor, path60, remove);
6257
+ redactWildcardPath(obj, parts, censor, path59, remove);
6284
6258
  } else {
6285
6259
  if (remove) {
6286
6260
  removeKey(obj, parts);
@@ -6365,8 +6339,8 @@ var require_redact = __commonJS({
6365
6339
  }
6366
6340
  } else {
6367
6341
  if (afterWildcard.includes("*")) {
6368
- const wrappedCensor = typeof censor === "function" ? (value, path60) => {
6369
- const fullPath = [...pathArray.slice(0, pathLength), ...path60];
6342
+ const wrappedCensor = typeof censor === "function" ? (value, path59) => {
6343
+ const fullPath = [...pathArray.slice(0, pathLength), ...path59];
6370
6344
  return censor(value, fullPath);
6371
6345
  } : censor;
6372
6346
  redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
@@ -6401,8 +6375,8 @@ var require_redact = __commonJS({
6401
6375
  return null;
6402
6376
  }
6403
6377
  const pathStructure = /* @__PURE__ */ new Map();
6404
- for (const path60 of pathsToClone) {
6405
- const parts = parsePath(path60);
6378
+ for (const path59 of pathsToClone) {
6379
+ const parts = parsePath(path59);
6406
6380
  let current = pathStructure;
6407
6381
  for (let i = 0; i < parts.length; i++) {
6408
6382
  const part = parts[i];
@@ -6454,24 +6428,24 @@ var require_redact = __commonJS({
6454
6428
  }
6455
6429
  return cloneSelectively(obj, pathStructure);
6456
6430
  }
6457
- function validatePath(path60) {
6458
- if (typeof path60 !== "string") {
6431
+ function validatePath(path59) {
6432
+ if (typeof path59 !== "string") {
6459
6433
  throw new Error("Paths must be (non-empty) strings");
6460
6434
  }
6461
- if (path60 === "") {
6435
+ if (path59 === "") {
6462
6436
  throw new Error("Invalid redaction path ()");
6463
6437
  }
6464
- if (path60.includes("..")) {
6465
- throw new Error(`Invalid redaction path (${path60})`);
6438
+ if (path59.includes("..")) {
6439
+ throw new Error(`Invalid redaction path (${path59})`);
6466
6440
  }
6467
- if (path60.includes(",")) {
6468
- throw new Error(`Invalid redaction path (${path60})`);
6441
+ if (path59.includes(",")) {
6442
+ throw new Error(`Invalid redaction path (${path59})`);
6469
6443
  }
6470
6444
  let bracketCount = 0;
6471
6445
  let inQuotes = false;
6472
6446
  let quoteChar = "";
6473
- for (let i = 0; i < path60.length; i++) {
6474
- const char = path60[i];
6447
+ for (let i = 0; i < path59.length; i++) {
6448
+ const char = path59[i];
6475
6449
  if ((char === '"' || char === "'") && bracketCount > 0) {
6476
6450
  if (!inQuotes) {
6477
6451
  inQuotes = true;
@@ -6485,20 +6459,20 @@ var require_redact = __commonJS({
6485
6459
  } else if (char === "]" && !inQuotes) {
6486
6460
  bracketCount--;
6487
6461
  if (bracketCount < 0) {
6488
- throw new Error(`Invalid redaction path (${path60})`);
6462
+ throw new Error(`Invalid redaction path (${path59})`);
6489
6463
  }
6490
6464
  }
6491
6465
  }
6492
6466
  if (bracketCount !== 0) {
6493
- throw new Error(`Invalid redaction path (${path60})`);
6467
+ throw new Error(`Invalid redaction path (${path59})`);
6494
6468
  }
6495
6469
  }
6496
6470
  function validatePaths(paths) {
6497
6471
  if (!Array.isArray(paths)) {
6498
6472
  throw new TypeError("paths must be an array");
6499
6473
  }
6500
- for (const path60 of paths) {
6501
- validatePath(path60);
6474
+ for (const path59 of paths) {
6475
+ validatePath(path59);
6502
6476
  }
6503
6477
  }
6504
6478
  function slowRedact(options = {}) {
@@ -6666,8 +6640,8 @@ var require_redaction = __commonJS({
6666
6640
  if (shape[k2] === null) {
6667
6641
  o[k2] = (value) => topCensor(value, [k2]);
6668
6642
  } else {
6669
- const wrappedCensor = typeof censor === "function" ? (value, path60) => {
6670
- return censor(value, [k2, ...path60]);
6643
+ const wrappedCensor = typeof censor === "function" ? (value, path59) => {
6644
+ return censor(value, [k2, ...path59]);
6671
6645
  } : censor;
6672
6646
  o[k2] = Redact({
6673
6647
  paths: shape[k2],
@@ -6885,10 +6859,10 @@ var require_atomic_sleep = __commonJS({
6885
6859
  var require_sonic_boom = __commonJS({
6886
6860
  "../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
6887
6861
  "use strict";
6888
- var fs48 = require("fs");
6862
+ var fs47 = require("fs");
6889
6863
  var EventEmitter2 = require("events");
6890
6864
  var inherits = require("util").inherits;
6891
- var path60 = require("path");
6865
+ var path59 = require("path");
6892
6866
  var sleep = require_atomic_sleep();
6893
6867
  var assert = require("assert");
6894
6868
  var BUSY_WRITE_TIMEOUT = 100;
@@ -6942,20 +6916,20 @@ var require_sonic_boom = __commonJS({
6942
6916
  const mode = sonic.mode;
6943
6917
  if (sonic.sync) {
6944
6918
  try {
6945
- if (sonic.mkdir) fs48.mkdirSync(path60.dirname(file), { recursive: true });
6946
- const fd = fs48.openSync(file, flags, mode);
6919
+ if (sonic.mkdir) fs47.mkdirSync(path59.dirname(file), { recursive: true });
6920
+ const fd = fs47.openSync(file, flags, mode);
6947
6921
  fileOpened(null, fd);
6948
6922
  } catch (err) {
6949
6923
  fileOpened(err);
6950
6924
  throw err;
6951
6925
  }
6952
6926
  } else if (sonic.mkdir) {
6953
- fs48.mkdir(path60.dirname(file), { recursive: true }, (err) => {
6927
+ fs47.mkdir(path59.dirname(file), { recursive: true }, (err) => {
6954
6928
  if (err) return fileOpened(err);
6955
- fs48.open(file, flags, mode, fileOpened);
6929
+ fs47.open(file, flags, mode, fileOpened);
6956
6930
  });
6957
6931
  } else {
6958
- fs48.open(file, flags, mode, fileOpened);
6932
+ fs47.open(file, flags, mode, fileOpened);
6959
6933
  }
6960
6934
  }
6961
6935
  function SonicBoom(opts) {
@@ -6996,8 +6970,8 @@ var require_sonic_boom = __commonJS({
6996
6970
  this.flush = flushBuffer;
6997
6971
  this.flushSync = flushBufferSync;
6998
6972
  this._actualWrite = actualWriteBuffer;
6999
- fsWriteSync = () => fs48.writeSync(this.fd, this._writingBuf);
7000
- fsWrite = () => fs48.write(this.fd, this._writingBuf, this.release);
6973
+ fsWriteSync = () => fs47.writeSync(this.fd, this._writingBuf);
6974
+ fsWrite = () => fs47.write(this.fd, this._writingBuf, this.release);
7001
6975
  } else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
7002
6976
  this._writingBuf = "";
7003
6977
  this.write = write;
@@ -7006,15 +6980,15 @@ var require_sonic_boom = __commonJS({
7006
6980
  this._actualWrite = actualWrite;
7007
6981
  fsWriteSync = () => {
7008
6982
  if (Buffer.isBuffer(this._writingBuf)) {
7009
- return fs48.writeSync(this.fd, this._writingBuf);
6983
+ return fs47.writeSync(this.fd, this._writingBuf);
7010
6984
  }
7011
- return fs48.writeSync(this.fd, this._writingBuf, "utf8");
6985
+ return fs47.writeSync(this.fd, this._writingBuf, "utf8");
7012
6986
  };
7013
6987
  fsWrite = () => {
7014
6988
  if (Buffer.isBuffer(this._writingBuf)) {
7015
- return fs48.write(this.fd, this._writingBuf, this.release);
6989
+ return fs47.write(this.fd, this._writingBuf, this.release);
7016
6990
  }
7017
- return fs48.write(this.fd, this._writingBuf, "utf8", this.release);
6991
+ return fs47.write(this.fd, this._writingBuf, "utf8", this.release);
7018
6992
  };
7019
6993
  } else {
7020
6994
  throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
@@ -7071,7 +7045,7 @@ var require_sonic_boom = __commonJS({
7071
7045
  }
7072
7046
  }
7073
7047
  if (this._fsync) {
7074
- fs48.fsyncSync(this.fd);
7048
+ fs47.fsyncSync(this.fd);
7075
7049
  }
7076
7050
  const len = this._len;
7077
7051
  if (this._reopening) {
@@ -7185,7 +7159,7 @@ var require_sonic_boom = __commonJS({
7185
7159
  const onDrain = () => {
7186
7160
  if (!this._fsync) {
7187
7161
  try {
7188
- fs48.fsync(this.fd, (err) => {
7162
+ fs47.fsync(this.fd, (err) => {
7189
7163
  this._flushPending = false;
7190
7164
  cb(err);
7191
7165
  });
@@ -7287,7 +7261,7 @@ var require_sonic_boom = __commonJS({
7287
7261
  const fd = this.fd;
7288
7262
  this.once("ready", () => {
7289
7263
  if (fd !== this.fd) {
7290
- fs48.close(fd, (err) => {
7264
+ fs47.close(fd, (err) => {
7291
7265
  if (err) {
7292
7266
  return this.emit("error", err);
7293
7267
  }
@@ -7336,7 +7310,7 @@ var require_sonic_boom = __commonJS({
7336
7310
  buf = this._bufs[0];
7337
7311
  }
7338
7312
  try {
7339
- const n = Buffer.isBuffer(buf) ? fs48.writeSync(this.fd, buf) : fs48.writeSync(this.fd, buf, "utf8");
7313
+ const n = Buffer.isBuffer(buf) ? fs47.writeSync(this.fd, buf) : fs47.writeSync(this.fd, buf, "utf8");
7340
7314
  const releasedBufObj = releaseWritingBuf(buf, this._len, n);
7341
7315
  buf = releasedBufObj.writingBuf;
7342
7316
  this._len = releasedBufObj.len;
@@ -7352,7 +7326,7 @@ var require_sonic_boom = __commonJS({
7352
7326
  }
7353
7327
  }
7354
7328
  try {
7355
- fs48.fsyncSync(this.fd);
7329
+ fs47.fsyncSync(this.fd);
7356
7330
  } catch {
7357
7331
  }
7358
7332
  }
@@ -7373,7 +7347,7 @@ var require_sonic_boom = __commonJS({
7373
7347
  buf = mergeBuf(this._bufs[0], this._lens[0]);
7374
7348
  }
7375
7349
  try {
7376
- const n = fs48.writeSync(this.fd, buf);
7350
+ const n = fs47.writeSync(this.fd, buf);
7377
7351
  buf = buf.subarray(n);
7378
7352
  this._len = Math.max(this._len - n, 0);
7379
7353
  if (buf.length <= 0) {
@@ -7401,13 +7375,13 @@ var require_sonic_boom = __commonJS({
7401
7375
  this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
7402
7376
  if (this.sync) {
7403
7377
  try {
7404
- const written = Buffer.isBuffer(this._writingBuf) ? fs48.writeSync(this.fd, this._writingBuf) : fs48.writeSync(this.fd, this._writingBuf, "utf8");
7378
+ const written = Buffer.isBuffer(this._writingBuf) ? fs47.writeSync(this.fd, this._writingBuf) : fs47.writeSync(this.fd, this._writingBuf, "utf8");
7405
7379
  release(null, written);
7406
7380
  } catch (err) {
7407
7381
  release(err);
7408
7382
  }
7409
7383
  } else {
7410
- fs48.write(this.fd, this._writingBuf, release);
7384
+ fs47.write(this.fd, this._writingBuf, release);
7411
7385
  }
7412
7386
  }
7413
7387
  function actualWriteBuffer() {
@@ -7416,7 +7390,7 @@ var require_sonic_boom = __commonJS({
7416
7390
  this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
7417
7391
  if (this.sync) {
7418
7392
  try {
7419
- const written = fs48.writeSync(this.fd, this._writingBuf);
7393
+ const written = fs47.writeSync(this.fd, this._writingBuf);
7420
7394
  release(null, written);
7421
7395
  } catch (err) {
7422
7396
  release(err);
@@ -7425,7 +7399,7 @@ var require_sonic_boom = __commonJS({
7425
7399
  if (kCopyBuffer) {
7426
7400
  this._writingBuf = Buffer.from(this._writingBuf);
7427
7401
  }
7428
- fs48.write(this.fd, this._writingBuf, release);
7402
+ fs47.write(this.fd, this._writingBuf, release);
7429
7403
  }
7430
7404
  }
7431
7405
  function actualClose(sonic) {
@@ -7441,12 +7415,12 @@ var require_sonic_boom = __commonJS({
7441
7415
  sonic._lens = [];
7442
7416
  assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
7443
7417
  try {
7444
- fs48.fsync(sonic.fd, closeWrapped);
7418
+ fs47.fsync(sonic.fd, closeWrapped);
7445
7419
  } catch {
7446
7420
  }
7447
7421
  function closeWrapped() {
7448
7422
  if (sonic.fd !== 1 && sonic.fd !== 2) {
7449
- fs48.close(sonic.fd, done);
7423
+ fs47.close(sonic.fd, done);
7450
7424
  } else {
7451
7425
  done();
7452
7426
  }
@@ -10581,11 +10555,11 @@ var init_lib = __esm({
10581
10555
  }
10582
10556
  }
10583
10557
  },
10584
- addToPath: function addToPath(path60, added, removed, oldPosInc, options) {
10585
- var last = path60.lastComponent;
10558
+ addToPath: function addToPath(path59, added, removed, oldPosInc, options) {
10559
+ var last = path59.lastComponent;
10586
10560
  if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
10587
10561
  return {
10588
- oldPos: path60.oldPos + oldPosInc,
10562
+ oldPos: path59.oldPos + oldPosInc,
10589
10563
  lastComponent: {
10590
10564
  count: last.count + 1,
10591
10565
  added,
@@ -10595,7 +10569,7 @@ var init_lib = __esm({
10595
10569
  };
10596
10570
  } else {
10597
10571
  return {
10598
- oldPos: path60.oldPos + oldPosInc,
10572
+ oldPos: path59.oldPos + oldPosInc,
10599
10573
  lastComponent: {
10600
10574
  count: 1,
10601
10575
  added,
@@ -11041,10 +11015,10 @@ function attachmentToHistoryMessage(o, ts) {
11041
11015
  const memories = raw.map((m2) => {
11042
11016
  if (!m2 || typeof m2 !== "object") return null;
11043
11017
  const rec = m2;
11044
- const path60 = typeof rec.path === "string" ? rec.path : null;
11018
+ const path59 = typeof rec.path === "string" ? rec.path : null;
11045
11019
  const content = typeof rec.content === "string" ? rec.content : null;
11046
- if (!path60 || content == null) return null;
11047
- const entry = { path: path60, content };
11020
+ if (!path59 || content == null) return null;
11021
+ const entry = { path: path59, content };
11048
11022
  if (typeof rec.mtimeMs === "number") entry.mtimeMs = rec.mtimeMs;
11049
11023
  return entry;
11050
11024
  }).filter((m2) => m2 !== null);
@@ -11875,10 +11849,10 @@ function parseAttachment(obj) {
11875
11849
  const memories = raw.map((m2) => {
11876
11850
  if (!m2 || typeof m2 !== "object") return null;
11877
11851
  const rec = m2;
11878
- const path60 = typeof rec.path === "string" ? rec.path : null;
11852
+ const path59 = typeof rec.path === "string" ? rec.path : null;
11879
11853
  const content = typeof rec.content === "string" ? rec.content : null;
11880
- if (!path60 || content == null) return null;
11881
- const out = { path: path60, content };
11854
+ if (!path59 || content == null) return null;
11855
+ const out = { path: path59, content };
11882
11856
  if (typeof rec.mtimeMs === "number") out.mtimeMs = rec.mtimeMs;
11883
11857
  return out;
11884
11858
  }).filter((m2) => m2 !== null);
@@ -23667,8 +23641,8 @@ var require_utils = __commonJS({
23667
23641
  var result = transform[inputType][outputType](input);
23668
23642
  return result;
23669
23643
  };
23670
- exports2.resolve = function(path60) {
23671
- var parts = path60.split("/");
23644
+ exports2.resolve = function(path59) {
23645
+ var parts = path59.split("/");
23672
23646
  var result = [];
23673
23647
  for (var index = 0; index < parts.length; index++) {
23674
23648
  var part = parts[index];
@@ -29521,18 +29495,18 @@ var require_object = __commonJS({
29521
29495
  var object = new ZipObject(name, zipObjectContent, o);
29522
29496
  this.files[name] = object;
29523
29497
  };
29524
- var parentFolder = function(path60) {
29525
- if (path60.slice(-1) === "/") {
29526
- path60 = path60.substring(0, path60.length - 1);
29498
+ var parentFolder = function(path59) {
29499
+ if (path59.slice(-1) === "/") {
29500
+ path59 = path59.substring(0, path59.length - 1);
29527
29501
  }
29528
- var lastSlash = path60.lastIndexOf("/");
29529
- return lastSlash > 0 ? path60.substring(0, lastSlash) : "";
29502
+ var lastSlash = path59.lastIndexOf("/");
29503
+ return lastSlash > 0 ? path59.substring(0, lastSlash) : "";
29530
29504
  };
29531
- var forceTrailingSlash = function(path60) {
29532
- if (path60.slice(-1) !== "/") {
29533
- path60 += "/";
29505
+ var forceTrailingSlash = function(path59) {
29506
+ if (path59.slice(-1) !== "/") {
29507
+ path59 += "/";
29534
29508
  }
29535
- return path60;
29509
+ return path59;
29536
29510
  };
29537
29511
  var folderAdd = function(name, createFolders) {
29538
29512
  createFolders = typeof createFolders !== "undefined" ? createFolders : defaults.createFolders;
@@ -30534,7 +30508,7 @@ var require_lib3 = __commonJS({
30534
30508
  // src/run-case/recorder.ts
30535
30509
  function startRunCaseRecorder(opts) {
30536
30510
  const now = opts.now ?? Date.now;
30537
- const dir = import_node_path46.default.dirname(opts.recordPath);
30511
+ const dir = import_node_path45.default.dirname(opts.recordPath);
30538
30512
  let stream = null;
30539
30513
  let closing = false;
30540
30514
  let closedSettled = false;
@@ -30548,8 +30522,8 @@ function startRunCaseRecorder(opts) {
30548
30522
  });
30549
30523
  const ensureStream = () => {
30550
30524
  if (stream) return stream;
30551
- import_node_fs31.default.mkdirSync(dir, { recursive: true });
30552
- stream = import_node_fs31.default.createWriteStream(opts.recordPath, { flags: "a" });
30525
+ import_node_fs30.default.mkdirSync(dir, { recursive: true });
30526
+ stream = import_node_fs30.default.createWriteStream(opts.recordPath, { flags: "a" });
30553
30527
  stream.on("close", () => closedResolve());
30554
30528
  return stream;
30555
30529
  };
@@ -30574,12 +30548,12 @@ function startRunCaseRecorder(opts) {
30574
30548
  };
30575
30549
  return { tap, close, closed };
30576
30550
  }
30577
- var import_node_fs31, import_node_path46;
30551
+ var import_node_fs30, import_node_path45;
30578
30552
  var init_recorder = __esm({
30579
30553
  "src/run-case/recorder.ts"() {
30580
30554
  "use strict";
30581
- import_node_fs31 = __toESM(require("fs"), 1);
30582
- import_node_path46 = __toESM(require("path"), 1);
30555
+ import_node_fs30 = __toESM(require("fs"), 1);
30556
+ import_node_path45 = __toESM(require("path"), 1);
30583
30557
  }
30584
30558
  });
30585
30559
 
@@ -30622,7 +30596,7 @@ var init_wire = __esm({
30622
30596
  // src/run-case/controller.ts
30623
30597
  async function runController(opts) {
30624
30598
  const now = opts.now ?? Date.now;
30625
- const cwd = opts.cwd ?? (0, import_node_fs32.mkdtempSync)(import_node_path47.default.join(import_node_os19.default.tmpdir(), "clawd-runcase-"));
30599
+ const cwd = opts.cwd ?? (0, import_node_fs31.mkdtempSync)(import_node_path46.default.join(import_node_os19.default.tmpdir(), "clawd-runcase-"));
30626
30600
  const ownsCwd = opts.cwd === void 0;
30627
30601
  const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
30628
30602
  const spawnCtx = { cwd };
@@ -30783,19 +30757,19 @@ async function runController(opts) {
30783
30757
  if (sigintHandler) process.off("SIGINT", sigintHandler);
30784
30758
  if (ownsCwd) {
30785
30759
  try {
30786
- (0, import_node_fs32.rmSync)(cwd, { recursive: true, force: true });
30760
+ (0, import_node_fs31.rmSync)(cwd, { recursive: true, force: true });
30787
30761
  } catch {
30788
30762
  }
30789
30763
  }
30790
30764
  return exitCode ?? 0;
30791
30765
  }
30792
- var import_node_fs32, import_node_os19, import_node_path47;
30766
+ var import_node_fs31, import_node_os19, import_node_path46;
30793
30767
  var init_controller = __esm({
30794
30768
  "src/run-case/controller.ts"() {
30795
30769
  "use strict";
30796
- import_node_fs32 = require("fs");
30770
+ import_node_fs31 = require("fs");
30797
30771
  import_node_os19 = __toESM(require("os"), 1);
30798
- import_node_path47 = __toESM(require("path"), 1);
30772
+ import_node_path46 = __toESM(require("path"), 1);
30799
30773
  init_claude();
30800
30774
  init_stdout_splitter();
30801
30775
  init_permission_stdio();
@@ -31027,8 +31001,8 @@ Env (advanced):
31027
31001
  `;
31028
31002
 
31029
31003
  // src/index.ts
31030
- var import_node_path45 = __toESM(require("path"), 1);
31031
- var import_node_fs30 = __toESM(require("fs"), 1);
31004
+ var import_node_path44 = __toESM(require("path"), 1);
31005
+ var import_node_fs29 = __toESM(require("fs"), 1);
31032
31006
 
31033
31007
  // src/logger.ts
31034
31008
  var import_node_fs2 = __toESM(require("fs"), 1);
@@ -32638,14 +32612,6 @@ function derivePersonaSpawnCwd(file, personaRoot) {
32638
32612
  }
32639
32613
  return import_node_path7.default.join(personaRoot, safeFileName(personaId));
32640
32614
  }
32641
- function deriveGuestWorkspaceCwd(personaId, capId, personaRoot) {
32642
- if (!personaRoot) {
32643
- throw new Error(
32644
- `deriveGuestWorkspaceCwd: personaRoot missing (personaId=${personaId}, capId=${capId})`
32645
- );
32646
- }
32647
- return import_node_path7.default.join(personaRoot, safeFileName(personaId), "workspaces", safeFileName(capId));
32648
- }
32649
32615
  function makeInitialState(file, subSessionMeta) {
32650
32616
  return {
32651
32617
  file,
@@ -33007,38 +32973,17 @@ var SessionManager = class {
33007
32973
  create(args, creatorPrincipalId, creatorOwnerPrincipalId) {
33008
32974
  let cwd;
33009
32975
  if (args.ownerPersonaId) {
33010
- const probeScope = scopeForFile(
32976
+ cwd = derivePersonaSpawnCwd(
33011
32977
  {
33012
32978
  sessionId: "",
33013
32979
  cwd: args.cwd ?? "",
33014
32980
  tool: "claude",
33015
32981
  ownerPersonaId: args.ownerPersonaId,
33016
- creatorPrincipalId,
33017
32982
  createdAt: "",
33018
32983
  updatedAt: ""
33019
32984
  },
33020
- this.deps.ownerPrincipalId
32985
+ this.deps.personaRoot
33021
32986
  );
33022
- if (probeScope.kind === "persona" && probeScope.mode === "guest") {
33023
- cwd = deriveGuestWorkspaceCwd(
33024
- args.ownerPersonaId,
33025
- probeScope.capId,
33026
- this.deps.personaRoot
33027
- );
33028
- import_node_fs6.default.mkdirSync(cwd, { recursive: true });
33029
- } else {
33030
- cwd = derivePersonaSpawnCwd(
33031
- {
33032
- sessionId: "",
33033
- cwd: args.cwd ?? "",
33034
- tool: "claude",
33035
- ownerPersonaId: args.ownerPersonaId,
33036
- createdAt: "",
33037
- updatedAt: ""
33038
- },
33039
- this.deps.personaRoot
33040
- );
33041
- }
33042
32987
  } else if (args.cwd) {
33043
32988
  cwd = args.cwd;
33044
32989
  } else {
@@ -34185,35 +34130,6 @@ var PersonaStore = class {
34185
34130
  if (!fs7.existsSync(p2)) return null;
34186
34131
  return fs7.readFileSync(p2, "utf8");
34187
34132
  }
34188
- /**
34189
- * Read-modify-write 更新 sandbox.network.allowedDomains(开发型 persona 网络白名单)。
34190
- *
34191
- * 只动 network.allowedDomains 一个字段:sandbox-settings.json 是 owner 可手改文件
34192
- * (manager.spec.ts 锁定 "updating personality does NOT overwrite a customized
34193
- * sandbox-settings.json"),整文件覆盖会丢手改内容。
34194
- *
34195
- * 文件不存在 / JSON 损坏 → 以 DEFAULT_SETTINGS 为底重建。
34196
- * (损坏文件无法做 merge,与其让 RPC 失败不如重建——读路径 readSandboxSettings
34197
- * 对损坏返回 null 不修复,写路径必须产出合法文件,两者职责不同。)
34198
- */
34199
- updateNetworkAllowedDomains(personaId, domains) {
34200
- const p2 = this.sandboxSettingsPath(personaId);
34201
- let current;
34202
- try {
34203
- current = JSON.parse(fs7.readFileSync(p2, "utf8"));
34204
- if (!current || typeof current !== "object" || Array.isArray(current)) {
34205
- current = structuredClone(DEFAULT_SETTINGS);
34206
- }
34207
- } catch {
34208
- current = structuredClone(DEFAULT_SETTINGS);
34209
- }
34210
- const sandboxRaw = current.sandbox;
34211
- const sandbox = sandboxRaw && typeof sandboxRaw === "object" && !Array.isArray(sandboxRaw) ? sandboxRaw : current.sandbox = {};
34212
- const networkRaw = sandbox.network;
34213
- const network = networkRaw && typeof networkRaw === "object" && !Array.isArray(networkRaw) ? networkRaw : sandbox.network = {};
34214
- network.allowedDomains = domains;
34215
- this.atomicWrite(p2, JSON.stringify(current, null, 2));
34216
- }
34217
34133
  /**
34218
34134
  * 读 sandbox-settings.json 当前内容;文件不存在 / JSON 解析失败均返回 null。
34219
34135
  * owner 手改文件可能写出非法形状,daemon 不强校验,UI 拿到 null → 显示空状态。
@@ -34644,20 +34560,6 @@ var PersonaManager = class {
34644
34560
  this.deps.registry.set(updated);
34645
34561
  return updated;
34646
34562
  }
34647
- /**
34648
- * 更新开发型 persona 的网络域名白名单(sandbox.network.allowedDomains)。
34649
- * store 层做 read-modify-write(保留 owner 手改字段);这里只负责存在性校验 +
34650
- * touch updatedAt + registry 同步,与 update() 的元数据维护行为一致。
34651
- */
34652
- updateNetworkAllowedDomains(personaId, domains) {
34653
- const existing = this.deps.registry.get(personaId);
34654
- if (!existing) throw new Error(`persona not found: ${personaId}`);
34655
- this.deps.store.updateNetworkAllowedDomains(personaId, domains);
34656
- const updated = { ...existing, updatedAt: Date.now() };
34657
- this.deps.store.writeMeta(updated);
34658
- this.deps.registry.set(updated);
34659
- return updated;
34660
- }
34661
34563
  /** 读取 CLAUDE.md 内容;persona 目录或文件不存在时返回 null。透传给 handler 拼响应。 */
34662
34564
  readPersonality(personaId) {
34663
34565
  return this.deps.store.readPersonality(personaId);
@@ -36720,7 +36622,9 @@ var LocalWsServer = class {
36720
36622
  this.logger?.warn("ready frame build failed", { err: err.message });
36721
36623
  }
36722
36624
  socket.on("message", (data) => {
36723
- this.onMessage(handle, data, remoteAddress);
36625
+ void this.onMessage(handle, data, remoteAddress).catch((err) => {
36626
+ this.logger?.warn("onMessage failed", { err: err.message });
36627
+ });
36724
36628
  });
36725
36629
  socket.on("close", () => {
36726
36630
  const c = this.clients.get(id);
@@ -36739,7 +36643,7 @@ var LocalWsServer = class {
36739
36643
  this.logger?.warn("client socket error", { clientId: id, err: err.message });
36740
36644
  });
36741
36645
  }
36742
- onMessage(client, data, remoteAddress) {
36646
+ async onMessage(client, data, remoteAddress) {
36743
36647
  let frame;
36744
36648
  try {
36745
36649
  frame = JSON.parse(data.toString());
@@ -36750,7 +36654,7 @@ var LocalWsServer = class {
36750
36654
  const authGate = this.opts.authGate;
36751
36655
  if (authGate) {
36752
36656
  const wasAuthed = authGate.isAuthed(client.id);
36753
- const verdict = authGate.handleFrame({ id: client.id, remoteAddress }, frame);
36657
+ const verdict = await authGate.handleFrame({ id: client.id, remoteAddress }, frame);
36754
36658
  if (verdict !== "pass") {
36755
36659
  if (!wasAuthed && authGate.isAuthed(client.id)) {
36756
36660
  try {
@@ -36853,7 +36757,7 @@ var AuthGate = class {
36853
36757
  registerConnection(handle) {
36854
36758
  const enforce = this.opts.shouldEnforce(handle.remoteAddress);
36855
36759
  if (!enforce) {
36856
- this.states.set(handle.id, { authed: true, enforce: false, timer: null });
36760
+ this.states.set(handle.id, { authed: true, enforce: false, timer: null, pending: false });
36857
36761
  return true;
36858
36762
  }
36859
36763
  const setT = this.opts.setTimer ?? ((cb, ms) => setTimeout(cb, ms));
@@ -36862,7 +36766,7 @@ var AuthGate = class {
36862
36766
  if (!st || st.authed) return;
36863
36767
  this.opts.closeConnection(handle, 1008, "auth timeout");
36864
36768
  }, this.opts.timeoutMs);
36865
- this.states.set(handle.id, { authed: false, enforce: true, timer });
36769
+ this.states.set(handle.id, { authed: false, enforce: true, timer, pending: false });
36866
36770
  return false;
36867
36771
  }
36868
36772
  unregister(handleId) {
@@ -36878,12 +36782,15 @@ var AuthGate = class {
36878
36782
  }
36879
36783
  // 收到一帧时调;返回 'consumed' / 'reject' / 'pass'
36880
36784
  // - consumed:这一帧是 auth 帧(成功或失败),调用方不要再走业务路由
36881
- // - reject:未通过 auth 但帧不是 auth 帧,调用方应放弃此帧(gate 已关连接)
36785
+ // - reject:未通过 auth 但帧不是 auth 帧,调用方应放弃此帧(gate 已关连接 / 验证进行中)
36882
36786
  // - pass:已 authed,调用方继续走业务路由
36883
- handleFrame(handle, frame) {
36787
+ //
36788
+ // Phase 2:async(authenticate 注入路径走中心 server introspect HTTP 调用)。
36789
+ async handleFrame(handle, frame) {
36884
36790
  const st = this.states.get(handle.id);
36885
36791
  if (!st) return "reject";
36886
36792
  if (st.authed) return "pass";
36793
+ if (st.pending) return "reject";
36887
36794
  const parsed = AuthRequestFrameSchema.safeParse(frame);
36888
36795
  if (!parsed.success) {
36889
36796
  const reason = frame.type === "auth" ? "auth failed" : "auth required";
@@ -36893,11 +36800,19 @@ var AuthGate = class {
36893
36800
  }
36894
36801
  let ctx = null;
36895
36802
  if (this.opts.authenticate) {
36896
- const r = this.opts.authenticate(
36897
- parsed.data.token,
36898
- parsed.data.selfPrincipalId,
36899
- parsed.data.selfDisplayName
36900
- );
36803
+ st.pending = true;
36804
+ let r;
36805
+ try {
36806
+ r = await this.opts.authenticate(
36807
+ parsed.data.token,
36808
+ parsed.data.selfPrincipalId,
36809
+ parsed.data.selfDisplayName,
36810
+ parsed.data.selfUrl
36811
+ );
36812
+ } finally {
36813
+ st.pending = false;
36814
+ }
36815
+ if (!this.states.has(handle.id)) return "reject";
36901
36816
  if (!r.ok) {
36902
36817
  this.opts.closeConnection(handle, 4401, r.code);
36903
36818
  this.markFailed(handle.id);
@@ -37000,26 +36915,33 @@ function ownerContext(ownerPrincipalId, displayName) {
37000
36915
  grants: [{ resource: { type: "*" }, actions: ["admin"] }]
37001
36916
  };
37002
36917
  }
37003
- function guestContext(cap, peerOwnerPrincipalId) {
36918
+ function connectGuestContext(unionId, displayName) {
37004
36919
  return {
37005
- principal: { id: cap.id, kind: "guest", displayName: cap.displayName },
37006
- grants: cap.grants,
37007
- capabilityId: cap.id,
37008
- ...peerOwnerPrincipalId ? { peerOwnerPrincipalId } : {}
36920
+ principal: { id: unionId, kind: "guest", displayName },
36921
+ grants: PERSONAL_CAP_GRANTS.map((g2) => ({
36922
+ resource: { ...g2.resource },
36923
+ actions: [...g2.actions]
36924
+ })),
36925
+ capabilityId: unionId,
36926
+ peerOwnerPrincipalId: unionId
37009
36927
  };
37010
36928
  }
37011
- function authenticate(token, deps) {
36929
+ async function authenticate(token, deps) {
37012
36930
  if (!token) return { ok: false, code: "NO_TOKEN" };
37013
36931
  if (deps.isOwnerToken(token)) {
37014
36932
  return { ok: true, context: ownerContext(deps.ownerPrincipalId, deps.ownerDisplayName) };
37015
36933
  }
37016
- if (!deps.capabilityRegistry) return { ok: false, code: "BAD_TOKEN" };
37017
- const v2 = deps.capabilityRegistry.verifyToken(token);
37018
- if (!v2.ok) {
37019
- if (v2.code === "TOKEN_INVALID") return { ok: false, code: "BAD_TOKEN" };
37020
- return { ok: false, code: v2.code };
36934
+ if (!deps.introspectConnectToken) return { ok: false, code: "BAD_TOKEN" };
36935
+ let result;
36936
+ try {
36937
+ result = await deps.introspectConnectToken(token);
36938
+ } catch {
36939
+ return { ok: false, code: "BAD_TOKEN" };
36940
+ }
36941
+ if (!result.active) {
36942
+ return { ok: false, code: "TOKEN_REVOKED" };
37021
36943
  }
37022
- return { ok: true, context: guestContext(v2.capability, deps.selfPrincipalId) };
36944
+ return { ok: true, context: connectGuestContext(result.unionId, result.displayName) };
37023
36945
  }
37024
36946
 
37025
36947
  // src/permission/capability-store.ts
@@ -37199,89 +37121,14 @@ function cleanupGuestSessionsForCapability(cap, factory) {
37199
37121
  return { removed };
37200
37122
  }
37201
37123
 
37202
- // src/permission/personal-capability.ts
37203
- var import_node_fs15 = __toESM(require("fs"), 1);
37204
- var import_node_path16 = __toESM(require("path"), 1);
37205
- var import_node_crypto4 = __toESM(require("crypto"), 1);
37206
- var PERSONAL_CAP_FILE_NAME = "personal-capability.json";
37207
- var PERSONAL_CAP_DISPLAY_NAME = "personal";
37208
- function personalCapFilePath(dataDir) {
37209
- return import_node_path16.default.join(dataDir, PERSONAL_CAP_FILE_NAME);
37210
- }
37211
- function loadOrCreatePersonalCapability(opts) {
37212
- const file = personalCapFilePath(opts.dataDir);
37213
- const generateToken = opts.generateToken ?? defaultGenerateToken;
37214
- const generateId = opts.generateId ?? defaultGenerateId;
37215
- const now = opts.now ?? Date.now;
37216
- const existing = readFile(file);
37217
- if (existing) return existing;
37218
- const token = generateToken();
37219
- const id = generateId();
37220
- const capability = {
37221
- id,
37222
- secretHash: sha256Hex2(token),
37223
- displayName: PERSONAL_CAP_DISPLAY_NAME,
37224
- // CapabilitySchema 期待 mutable Grant[],protocol 常量是 readonly,落盘前拷一份
37225
- grants: PERSONAL_CAP_GRANTS.map((g2) => ({
37226
- resource: { ...g2.resource },
37227
- actions: [...g2.actions]
37228
- })),
37229
- issuedAt: now(),
37230
- usedCount: 0
37231
- };
37232
- const content = { token, capability };
37233
- writeFile(file, content);
37234
- return content;
37235
- }
37236
- function readFile(file) {
37237
- let raw;
37238
- try {
37239
- raw = import_node_fs15.default.readFileSync(file, "utf8");
37240
- } catch (err) {
37241
- const code = err?.code;
37242
- if (code === "ENOENT") return null;
37243
- return null;
37244
- }
37245
- let parsed;
37246
- try {
37247
- parsed = JSON.parse(raw);
37248
- } catch {
37249
- return null;
37250
- }
37251
- if (!parsed || typeof parsed !== "object") return null;
37252
- const obj = parsed;
37253
- if (typeof obj.token !== "string" || obj.token.length === 0) return null;
37254
- const capParsed = CapabilitySchema.safeParse(obj.capability);
37255
- if (!capParsed.success) return null;
37256
- if (sha256Hex2(obj.token) !== capParsed.data.secretHash) return null;
37257
- return { token: obj.token, capability: capParsed.data };
37258
- }
37259
- function writeFile(file, content) {
37260
- import_node_fs15.default.mkdirSync(import_node_path16.default.dirname(file), { recursive: true });
37261
- import_node_fs15.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
37262
- try {
37263
- import_node_fs15.default.chmodSync(file, 384);
37264
- } catch {
37265
- }
37266
- }
37267
- function defaultGenerateToken() {
37268
- return import_node_crypto4.default.randomBytes(24).toString("base64url");
37269
- }
37270
- function defaultGenerateId() {
37271
- return "personal_" + import_node_crypto4.default.randomBytes(6).toString("base64url");
37272
- }
37273
- function sha256Hex2(s) {
37274
- return import_node_crypto4.default.createHash("sha256").update(s).digest("hex");
37275
- }
37276
-
37277
37124
  // src/inbox/inbox-store.ts
37278
- var fs20 = __toESM(require("fs"), 1);
37279
- var path22 = __toESM(require("path"), 1);
37125
+ var fs19 = __toESM(require("fs"), 1);
37126
+ var path21 = __toESM(require("path"), 1);
37280
37127
  var INBOX_SUBDIR = "inbox";
37281
37128
  var InboxStore = class {
37282
37129
  constructor(dataDir) {
37283
37130
  this.dataDir = dataDir;
37284
- fs20.mkdirSync(this.dirPath(), { recursive: true });
37131
+ fs19.mkdirSync(this.dirPath(), { recursive: true });
37285
37132
  }
37286
37133
  dataDir;
37287
37134
  /**
@@ -37293,7 +37140,7 @@ var InboxStore = class {
37293
37140
  const file = this.filePath(peerOwnerId);
37294
37141
  let raw;
37295
37142
  try {
37296
- raw = fs20.readFileSync(file, "utf8");
37143
+ raw = fs19.readFileSync(file, "utf8");
37297
37144
  } catch (err) {
37298
37145
  if (err?.code === "ENOENT") return [];
37299
37146
  return [];
@@ -37309,7 +37156,7 @@ var InboxStore = class {
37309
37156
  const dir = this.dirPath();
37310
37157
  let entries;
37311
37158
  try {
37312
- entries = fs20.readdirSync(dir);
37159
+ entries = fs19.readdirSync(dir);
37313
37160
  } catch (err) {
37314
37161
  if (err?.code === "ENOENT") return [];
37315
37162
  return [];
@@ -37325,9 +37172,9 @@ var InboxStore = class {
37325
37172
  if (existing.some((m2) => m2.id === message.id)) return;
37326
37173
  const file = this.filePath(message.peerOwnerId);
37327
37174
  const line = JSON.stringify(message) + "\n";
37328
- fs20.appendFileSync(file, line, { mode: 384 });
37175
+ fs19.appendFileSync(file, line, { mode: 384 });
37329
37176
  try {
37330
- fs20.chmodSync(file, 384);
37177
+ fs19.chmodSync(file, 384);
37331
37178
  } catch {
37332
37179
  }
37333
37180
  }
@@ -37357,7 +37204,7 @@ var InboxStore = class {
37357
37204
  removeByPeerOwnerId(peerOwnerId) {
37358
37205
  const file = this.filePath(peerOwnerId);
37359
37206
  try {
37360
- fs20.unlinkSync(file);
37207
+ fs19.unlinkSync(file);
37361
37208
  } catch (err) {
37362
37209
  if (err?.code === "ENOENT") return;
37363
37210
  }
@@ -37366,18 +37213,18 @@ var InboxStore = class {
37366
37213
  const file = this.filePath(peerOwnerId);
37367
37214
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
37368
37215
  const content = messages.map((m2) => JSON.stringify(m2)).join("\n") + (messages.length > 0 ? "\n" : "");
37369
- fs20.writeFileSync(tmp, content, { mode: 384 });
37370
- fs20.renameSync(tmp, file);
37216
+ fs19.writeFileSync(tmp, content, { mode: 384 });
37217
+ fs19.renameSync(tmp, file);
37371
37218
  try {
37372
- fs20.chmodSync(file, 384);
37219
+ fs19.chmodSync(file, 384);
37373
37220
  } catch {
37374
37221
  }
37375
37222
  }
37376
37223
  dirPath() {
37377
- return path22.join(this.dataDir, INBOX_SUBDIR);
37224
+ return path21.join(this.dataDir, INBOX_SUBDIR);
37378
37225
  }
37379
37226
  filePath(peerOwnerId) {
37380
- return path22.join(this.dirPath(), `${peerOwnerId}.jsonl`);
37227
+ return path21.join(this.dirPath(), `${peerOwnerId}.jsonl`);
37381
37228
  }
37382
37229
  };
37383
37230
  function parseAllLines(raw) {
@@ -37464,8 +37311,8 @@ var InboxManager = class {
37464
37311
  };
37465
37312
 
37466
37313
  // src/state/received-capability-store.ts
37467
- var fs21 = __toESM(require("fs"), 1);
37468
- var path23 = __toESM(require("path"), 1);
37314
+ var fs20 = __toESM(require("fs"), 1);
37315
+ var path22 = __toESM(require("path"), 1);
37469
37316
  var FILE_NAME = "received-capabilities.json";
37470
37317
  var ReceivedCapabilityStore = class {
37471
37318
  constructor(dataDir) {
@@ -37475,10 +37322,10 @@ var ReceivedCapabilityStore = class {
37475
37322
  caps = /* @__PURE__ */ new Map();
37476
37323
  load() {
37477
37324
  this.caps.clear();
37478
- const file = path23.join(this.dataDir, FILE_NAME);
37325
+ const file = path22.join(this.dataDir, FILE_NAME);
37479
37326
  let raw;
37480
37327
  try {
37481
- raw = fs21.readFileSync(file, "utf8");
37328
+ raw = fs20.readFileSync(file, "utf8");
37482
37329
  } catch (err) {
37483
37330
  if (err?.code !== "ENOENT") this.renameBak(file);
37484
37331
  return;
@@ -37515,27 +37362,27 @@ var ReceivedCapabilityStore = class {
37515
37362
  this.flush();
37516
37363
  }
37517
37364
  flush() {
37518
- const file = path23.join(this.dataDir, FILE_NAME);
37365
+ const file = path22.join(this.dataDir, FILE_NAME);
37519
37366
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
37520
37367
  const content = JSON.stringify(
37521
37368
  { receivedCaps: Array.from(this.caps.values()) },
37522
37369
  null,
37523
37370
  2
37524
37371
  );
37525
- fs21.mkdirSync(this.dataDir, { recursive: true });
37526
- fs21.writeFileSync(tmp, content, { mode: 384 });
37527
- fs21.renameSync(tmp, file);
37372
+ fs20.mkdirSync(this.dataDir, { recursive: true });
37373
+ fs20.writeFileSync(tmp, content, { mode: 384 });
37374
+ fs20.renameSync(tmp, file);
37528
37375
  }
37529
37376
  renameBak(file) {
37530
37377
  try {
37531
- fs21.renameSync(file, `${file}.bak`);
37378
+ fs20.renameSync(file, `${file}.bak`);
37532
37379
  } catch {
37533
37380
  }
37534
37381
  }
37535
37382
  };
37536
37383
 
37537
37384
  // src/received-capability/connect-remote.ts
37538
- var crypto6 = __toESM(require("crypto"), 1);
37385
+ var crypto5 = __toESM(require("crypto"), 1);
37539
37386
  var HANDSHAKE_TIMEOUT_MS = 5e3;
37540
37387
  var RPC_TIMEOUT_MS = 5e3;
37541
37388
  async function connectRemote(args) {
@@ -37553,7 +37400,10 @@ async function connectRemote(args) {
37553
37400
  type: "auth",
37554
37401
  token: args.token,
37555
37402
  selfPrincipalId: args.selfPrincipalId,
37556
- selfDisplayName: args.selfDisplayName
37403
+ selfDisplayName: args.selfDisplayName,
37404
+ // Phase 2 自动反向(spec 决策 #11):带本机可达地址时 hub 反向建联系人;
37405
+ // 缺省时省略字段(不发空串),hub 端 schema selfUrl 是 .min(1).optional()
37406
+ ...args.selfUrl ? { selfUrl: args.selfUrl } : {}
37557
37407
  })
37558
37408
  );
37559
37409
  await new Promise((resolve6, reject) => {
@@ -37581,7 +37431,7 @@ async function connectRemote(args) {
37581
37431
  }, HANDSHAKE_TIMEOUT_MS);
37582
37432
  });
37583
37433
  function call(method, payload) {
37584
- const requestId = crypto6.randomUUID();
37434
+ const requestId = crypto5.randomUUID();
37585
37435
  return new Promise((resolve6, reject) => {
37586
37436
  const onMessage = (raw) => {
37587
37437
  let f;
@@ -37625,62 +37475,85 @@ async function connectRemote(args) {
37625
37475
  };
37626
37476
  }
37627
37477
 
37478
+ // src/received-capability/auto-reverse.ts
37479
+ init_protocol();
37480
+ function autoReverseContact(args) {
37481
+ if (!args.selfUrl) return;
37482
+ const now = args.now ?? Date.now;
37483
+ const rc = {
37484
+ ownerPrincipalId: args.unionId,
37485
+ ownerDisplayName: args.displayName,
37486
+ remoteUrl: args.selfUrl,
37487
+ // capabilityId 复用 unionId(与 connectGuestContext 的 guest channel 索引键一致)
37488
+ capabilityId: args.unionId,
37489
+ // B 不持有 A 颁的 token:空串。schema 已放宽允许空(不造假数据)。
37490
+ connectToken: "",
37491
+ grants: PERSONAL_CAP_GRANTS.map((g2) => ({
37492
+ resource: { ...g2.resource },
37493
+ actions: [...g2.actions]
37494
+ })),
37495
+ receivedAt: now()
37496
+ };
37497
+ args.store.upsert(rc);
37498
+ args.broadcast({ type: "received-capability:added", receivedCap: rc });
37499
+ }
37500
+
37628
37501
  // src/migrations/2026-05-20-flatten-sessions.ts
37629
- var fs22 = __toESM(require("fs"), 1);
37630
- var path24 = __toESM(require("path"), 1);
37502
+ var fs21 = __toESM(require("fs"), 1);
37503
+ var path23 = __toESM(require("path"), 1);
37631
37504
  var MIGRATION_FLAG_NAME = ".migration.v1.done";
37632
37505
  function migrateFlattenSessions(opts) {
37633
37506
  const dataDir = opts.dataDir;
37634
37507
  const now = opts.now ?? Date.now;
37635
- const sessionsDir = path24.join(dataDir, "sessions");
37636
- const flagPath = path24.join(sessionsDir, MIGRATION_FLAG_NAME);
37508
+ const sessionsDir = path23.join(dataDir, "sessions");
37509
+ const flagPath = path23.join(sessionsDir, MIGRATION_FLAG_NAME);
37637
37510
  if (existsSync3(flagPath)) {
37638
37511
  return { skipped: true, flagWritten: false, movedBare: 0, movedVmOwner: 0, archivedListener: 0 };
37639
37512
  }
37640
37513
  let movedBare = 0;
37641
37514
  let movedVmOwner = 0;
37642
37515
  let archivedListener = 0;
37643
- const defaultDir = path24.join(sessionsDir, "default");
37516
+ const defaultDir = path23.join(sessionsDir, "default");
37644
37517
  if (existsSync3(defaultDir)) {
37645
37518
  for (const entry of readdirSafe(defaultDir)) {
37646
37519
  if (!entry.endsWith(".json")) continue;
37647
- const src = path24.join(defaultDir, entry);
37648
- const dst = path24.join(sessionsDir, entry);
37649
- fs22.renameSync(src, dst);
37520
+ const src = path23.join(defaultDir, entry);
37521
+ const dst = path23.join(sessionsDir, entry);
37522
+ fs21.renameSync(src, dst);
37650
37523
  movedBare += 1;
37651
37524
  }
37652
37525
  rmdirIfEmpty(defaultDir);
37653
37526
  }
37654
37527
  for (const pid of readdirSafe(sessionsDir)) {
37655
- const personaDir = path24.join(sessionsDir, pid);
37528
+ const personaDir = path23.join(sessionsDir, pid);
37656
37529
  if (!isDir(personaDir)) continue;
37657
37530
  if (pid === "default") continue;
37658
- const ownerSrc = path24.join(personaDir, "owner");
37531
+ const ownerSrc = path23.join(personaDir, "owner");
37659
37532
  if (existsSync3(ownerSrc) && isDir(ownerSrc)) {
37660
- const ownerDst = path24.join(dataDir, "personas", pid, ".clawd", "sessions", "owner");
37661
- fs22.mkdirSync(ownerDst, { recursive: true });
37533
+ const ownerDst = path23.join(dataDir, "personas", pid, ".clawd", "sessions", "owner");
37534
+ fs21.mkdirSync(ownerDst, { recursive: true });
37662
37535
  for (const file of readdirSafe(ownerSrc)) {
37663
37536
  if (!file.endsWith(".json")) continue;
37664
- fs22.renameSync(path24.join(ownerSrc, file), path24.join(ownerDst, file));
37537
+ fs21.renameSync(path23.join(ownerSrc, file), path23.join(ownerDst, file));
37665
37538
  movedVmOwner += 1;
37666
37539
  }
37667
37540
  rmdirIfEmpty(ownerSrc);
37668
37541
  }
37669
- const listenerSrc = path24.join(personaDir, "listener");
37542
+ const listenerSrc = path23.join(personaDir, "listener");
37670
37543
  if (existsSync3(listenerSrc) && isDir(listenerSrc)) {
37671
- const archiveDst = path24.join(dataDir, ".legacy", `listener-${pid}`);
37672
- fs22.mkdirSync(archiveDst, { recursive: true });
37544
+ const archiveDst = path23.join(dataDir, ".legacy", `listener-${pid}`);
37545
+ fs21.mkdirSync(archiveDst, { recursive: true });
37673
37546
  for (const file of readdirSafe(listenerSrc)) {
37674
37547
  if (!file.endsWith(".json")) continue;
37675
- fs22.renameSync(path24.join(listenerSrc, file), path24.join(archiveDst, file));
37548
+ fs21.renameSync(path23.join(listenerSrc, file), path23.join(archiveDst, file));
37676
37549
  archivedListener += 1;
37677
37550
  }
37678
37551
  rmdirIfEmpty(listenerSrc);
37679
37552
  }
37680
37553
  rmdirIfEmpty(personaDir);
37681
37554
  }
37682
- fs22.mkdirSync(sessionsDir, { recursive: true });
37683
- fs22.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
37555
+ fs21.mkdirSync(sessionsDir, { recursive: true });
37556
+ fs21.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
37684
37557
  return {
37685
37558
  skipped: false,
37686
37559
  flagWritten: true,
@@ -37691,7 +37564,7 @@ function migrateFlattenSessions(opts) {
37691
37564
  }
37692
37565
  function existsSync3(p2) {
37693
37566
  try {
37694
- fs22.statSync(p2);
37567
+ fs21.statSync(p2);
37695
37568
  return true;
37696
37569
  } catch {
37697
37570
  return false;
@@ -37699,31 +37572,31 @@ function existsSync3(p2) {
37699
37572
  }
37700
37573
  function isDir(p2) {
37701
37574
  try {
37702
- return fs22.statSync(p2).isDirectory();
37575
+ return fs21.statSync(p2).isDirectory();
37703
37576
  } catch {
37704
37577
  return false;
37705
37578
  }
37706
37579
  }
37707
37580
  function readdirSafe(p2) {
37708
37581
  try {
37709
- return fs22.readdirSync(p2);
37582
+ return fs21.readdirSync(p2);
37710
37583
  } catch {
37711
37584
  return [];
37712
37585
  }
37713
37586
  }
37714
37587
  function rmdirIfEmpty(p2) {
37715
37588
  try {
37716
- fs22.rmdirSync(p2);
37589
+ fs21.rmdirSync(p2);
37717
37590
  } catch {
37718
37591
  }
37719
37592
  }
37720
37593
 
37721
37594
  // src/transport/http-router.ts
37722
- var import_node_fs17 = __toESM(require("fs"), 1);
37723
- var import_node_path20 = __toESM(require("path"), 1);
37595
+ var import_node_fs16 = __toESM(require("fs"), 1);
37596
+ var import_node_path19 = __toESM(require("path"), 1);
37724
37597
 
37725
37598
  // src/attachment/mime.ts
37726
- var import_node_path17 = __toESM(require("path"), 1);
37599
+ var import_node_path16 = __toESM(require("path"), 1);
37727
37600
  var TEXT_PLAIN = "text/plain; charset=utf-8";
37728
37601
  var EXT_TO_NATIVE_MIME = {
37729
37602
  // 图片
@@ -37830,14 +37703,14 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
37830
37703
  ".mk"
37831
37704
  ]);
37832
37705
  function lookupMime(filePathOrName) {
37833
- const ext = import_node_path17.default.extname(filePathOrName).toLowerCase();
37706
+ const ext = import_node_path16.default.extname(filePathOrName).toLowerCase();
37834
37707
  if (EXT_TO_NATIVE_MIME[ext]) return EXT_TO_NATIVE_MIME[ext];
37835
37708
  if (TEXT_EXTENSIONS.has(ext)) return TEXT_PLAIN;
37836
37709
  return "application/octet-stream";
37837
37710
  }
37838
37711
 
37839
37712
  // src/attachment/sign-url.ts
37840
- var import_node_crypto5 = __toESM(require("crypto"), 1);
37713
+ var import_node_crypto4 = __toESM(require("crypto"), 1);
37841
37714
  var HMAC_ALGO = "sha256";
37842
37715
  function base64urlEncode(buf) {
37843
37716
  const b2 = typeof buf === "string" ? Buffer.from(buf, "utf8") : buf;
@@ -37854,7 +37727,7 @@ function decodeAbsPathFromUrl(encoded) {
37854
37727
  }
37855
37728
  function computeSig(secret, absPath, e) {
37856
37729
  const msg = e === null ? absPath : `${absPath}|${e}`;
37857
- return import_node_crypto5.default.createHmac(HMAC_ALGO, secret).update(msg).digest();
37730
+ return import_node_crypto4.default.createHmac(HMAC_ALGO, secret).update(msg).digest();
37858
37731
  }
37859
37732
  function signUrlParts(secret, absPath, ttlSeconds, now = Date.now) {
37860
37733
  const e = ttlSeconds === null ? null : Math.floor(now() / 1e3) + ttlSeconds;
@@ -37889,7 +37762,7 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
37889
37762
  if (provided.length !== expected.length) {
37890
37763
  return { ok: false, code: "BAD_SIG" };
37891
37764
  }
37892
- if (!import_node_crypto5.default.timingSafeEqual(provided, expected)) {
37765
+ if (!import_node_crypto4.default.timingSafeEqual(provided, expected)) {
37893
37766
  return { ok: false, code: "BAD_SIG" };
37894
37767
  }
37895
37768
  if (e !== null && now() / 1e3 > e) {
@@ -37899,9 +37772,9 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
37899
37772
  }
37900
37773
 
37901
37774
  // src/attachment/upload.ts
37902
- var import_node_fs16 = __toESM(require("fs"), 1);
37903
- var import_node_path18 = __toESM(require("path"), 1);
37904
- var import_node_crypto6 = __toESM(require("crypto"), 1);
37775
+ var import_node_fs15 = __toESM(require("fs"), 1);
37776
+ var import_node_path17 = __toESM(require("path"), 1);
37777
+ var import_node_crypto5 = __toESM(require("crypto"), 1);
37905
37778
  var import_promises = require("stream/promises");
37906
37779
  var UploadError = class extends Error {
37907
37780
  constructor(code, message) {
@@ -37912,24 +37785,24 @@ var UploadError = class extends Error {
37912
37785
  code;
37913
37786
  };
37914
37787
  function assertValidFileName(fileName) {
37915
- if (fileName.length === 0 || fileName === "." || fileName === ".." || fileName.startsWith(".") || fileName.includes("/") || fileName.includes("\\") || fileName !== import_node_path18.default.basename(fileName)) {
37788
+ if (fileName.length === 0 || fileName === "." || fileName === ".." || fileName.startsWith(".") || fileName.includes("/") || fileName.includes("\\") || fileName !== import_node_path17.default.basename(fileName)) {
37916
37789
  throw new UploadError("INVALID_FILENAME", `fileName must be a plain basename, got: ${fileName}`);
37917
37790
  }
37918
37791
  }
37919
37792
  var HASH_PREFIX_LEN = 16;
37920
37793
  async function writeUploadedAttachment(args) {
37921
37794
  assertValidFileName(args.fileName);
37922
- const attachmentsRoot = import_node_path18.default.join(args.sessionDir, ".attachments");
37795
+ const attachmentsRoot = import_node_path17.default.join(args.sessionDir, ".attachments");
37923
37796
  try {
37924
- import_node_fs16.default.mkdirSync(attachmentsRoot, { recursive: true });
37797
+ import_node_fs15.default.mkdirSync(attachmentsRoot, { recursive: true });
37925
37798
  } catch (err) {
37926
37799
  throw new UploadError("STORAGE_ERROR", `mkdir failed: ${err.message}`);
37927
37800
  }
37928
- const hasher = import_node_crypto6.default.createHash("sha256");
37801
+ const hasher = import_node_crypto5.default.createHash("sha256");
37929
37802
  let actualSize = 0;
37930
- const tmpPath = import_node_path18.default.join(
37803
+ const tmpPath = import_node_path17.default.join(
37931
37804
  attachmentsRoot,
37932
- `.upload-${process.pid}-${Date.now()}-${import_node_crypto6.default.randomBytes(4).toString("hex")}`
37805
+ `.upload-${process.pid}-${Date.now()}-${import_node_crypto5.default.randomBytes(4).toString("hex")}`
37933
37806
  );
37934
37807
  try {
37935
37808
  await (0, import_promises.pipeline)(
@@ -37942,18 +37815,18 @@ async function writeUploadedAttachment(args) {
37942
37815
  yield buf;
37943
37816
  }
37944
37817
  },
37945
- import_node_fs16.default.createWriteStream(tmpPath, { mode: 384 })
37818
+ import_node_fs15.default.createWriteStream(tmpPath, { mode: 384 })
37946
37819
  );
37947
37820
  } catch (err) {
37948
37821
  try {
37949
- import_node_fs16.default.unlinkSync(tmpPath);
37822
+ import_node_fs15.default.unlinkSync(tmpPath);
37950
37823
  } catch {
37951
37824
  }
37952
37825
  throw new UploadError("STORAGE_ERROR", `write failed: ${err.message}`);
37953
37826
  }
37954
37827
  if (actualSize !== args.contentLength) {
37955
37828
  try {
37956
- import_node_fs16.default.unlinkSync(tmpPath);
37829
+ import_node_fs15.default.unlinkSync(tmpPath);
37957
37830
  } catch {
37958
37831
  }
37959
37832
  throw new UploadError(
@@ -37962,35 +37835,35 @@ async function writeUploadedAttachment(args) {
37962
37835
  );
37963
37836
  }
37964
37837
  const attachmentId = hasher.digest("hex").slice(0, HASH_PREFIX_LEN);
37965
- const hashDir = import_node_path18.default.join(attachmentsRoot, attachmentId);
37838
+ const hashDir = import_node_path17.default.join(attachmentsRoot, attachmentId);
37966
37839
  let finalFileName;
37967
37840
  let hashDirExists = false;
37968
37841
  try {
37969
- hashDirExists = import_node_fs16.default.statSync(hashDir).isDirectory();
37842
+ hashDirExists = import_node_fs15.default.statSync(hashDir).isDirectory();
37970
37843
  } catch {
37971
37844
  }
37972
37845
  if (hashDirExists) {
37973
- const existing = import_node_fs16.default.readdirSync(hashDir).filter((n) => !n.startsWith("."));
37846
+ const existing = import_node_fs15.default.readdirSync(hashDir).filter((n) => !n.startsWith("."));
37974
37847
  finalFileName = existing[0] ?? args.fileName;
37975
37848
  try {
37976
- import_node_fs16.default.unlinkSync(tmpPath);
37849
+ import_node_fs15.default.unlinkSync(tmpPath);
37977
37850
  } catch {
37978
37851
  }
37979
37852
  } else {
37980
37853
  try {
37981
- import_node_fs16.default.mkdirSync(hashDir, { recursive: true });
37854
+ import_node_fs15.default.mkdirSync(hashDir, { recursive: true });
37982
37855
  finalFileName = args.fileName;
37983
- import_node_fs16.default.renameSync(tmpPath, import_node_path18.default.join(hashDir, finalFileName));
37856
+ import_node_fs15.default.renameSync(tmpPath, import_node_path17.default.join(hashDir, finalFileName));
37984
37857
  } catch (err) {
37985
37858
  try {
37986
- import_node_fs16.default.unlinkSync(tmpPath);
37859
+ import_node_fs15.default.unlinkSync(tmpPath);
37987
37860
  } catch {
37988
37861
  }
37989
37862
  throw new UploadError("STORAGE_ERROR", `rename failed: ${err.message}`);
37990
37863
  }
37991
37864
  }
37992
- const absPath = import_node_path18.default.join(hashDir, finalFileName);
37993
- const relPath = import_node_path18.default.relative(args.sessionDir, absPath);
37865
+ const absPath = import_node_path17.default.join(hashDir, finalFileName);
37866
+ const relPath = import_node_path17.default.relative(args.sessionDir, absPath);
37994
37867
  args.groupFileStore.upsert(args.scope, args.sessionId, {
37995
37868
  relPath: absPath,
37996
37869
  // 存绝对路径,与现有 agent 入清单的形态一致(attachment.ts:144 双形态兼容)
@@ -38004,7 +37877,7 @@ async function writeUploadedAttachment(args) {
38004
37877
 
38005
37878
  // src/extension/import.ts
38006
37879
  var import_promises2 = __toESM(require("fs/promises"), 1);
38007
- var import_node_path19 = __toESM(require("path"), 1);
37880
+ var import_node_path18 = __toESM(require("path"), 1);
38008
37881
  var import_node_os10 = __toESM(require("os"), 1);
38009
37882
  var import_jszip = __toESM(require_lib3(), 1);
38010
37883
  var ImportError = class extends Error {
@@ -38022,7 +37895,7 @@ async function importZip(buf, root) {
38022
37895
  throw new ImportError("ZIP_INVALID", `failed to load zip: ${e.message}`);
38023
37896
  }
38024
37897
  for (const name of Object.keys(zip.files)) {
38025
- if (name.includes("..") || name.startsWith("/") || import_node_path19.default.isAbsolute(name)) {
37898
+ if (name.includes("..") || name.startsWith("/") || import_node_path18.default.isAbsolute(name)) {
38026
37899
  throw new ImportError("ZIP_INVALID", `unsafe zip entry path: ${name}`);
38027
37900
  }
38028
37901
  }
@@ -38055,7 +37928,7 @@ async function importZip(buf, root) {
38055
37928
  );
38056
37929
  }
38057
37930
  }
38058
- const destDir = import_node_path19.default.join(root, manifest.id);
37931
+ const destDir = import_node_path18.default.join(root, manifest.id);
38059
37932
  let destExists = false;
38060
37933
  try {
38061
37934
  await import_promises2.default.access(destDir);
@@ -38065,15 +37938,15 @@ async function importZip(buf, root) {
38065
37938
  if (destExists) {
38066
37939
  throw new ImportError("ALREADY_EXISTS", `extension ${manifest.id} already installed`);
38067
37940
  }
38068
- const stage = await import_promises2.default.mkdtemp(import_node_path19.default.join(import_node_os10.default.tmpdir(), `clawd-ext-stage-${manifest.id}-`));
37941
+ const stage = await import_promises2.default.mkdtemp(import_node_path18.default.join(import_node_os10.default.tmpdir(), `clawd-ext-stage-${manifest.id}-`));
38069
37942
  try {
38070
37943
  for (const [name, entry] of Object.entries(zip.files)) {
38071
- const dest = import_node_path19.default.join(stage, name);
37944
+ const dest = import_node_path18.default.join(stage, name);
38072
37945
  if (entry.dir) {
38073
37946
  await import_promises2.default.mkdir(dest, { recursive: true });
38074
37947
  continue;
38075
37948
  }
38076
- await import_promises2.default.mkdir(import_node_path19.default.dirname(dest), { recursive: true });
37949
+ await import_promises2.default.mkdir(import_node_path18.default.dirname(dest), { recursive: true });
38077
37950
  const content = await entry.async("nodebuffer");
38078
37951
  await import_promises2.default.writeFile(dest, content);
38079
37952
  }
@@ -38119,7 +37992,7 @@ function isValidUploadFileName(fileName) {
38119
37992
  if (fileName === "." || fileName === "..") return false;
38120
37993
  if (fileName.startsWith(".")) return false;
38121
37994
  if (fileName.includes("/") || fileName.includes("\\")) return false;
38122
- return fileName === import_node_path20.default.basename(fileName);
37995
+ return fileName === import_node_path19.default.basename(fileName);
38123
37996
  }
38124
37997
  function createHttpRouter(deps) {
38125
37998
  return async (req, res) => {
@@ -38313,7 +38186,7 @@ function createHttpRouter(deps) {
38313
38186
  return true;
38314
38187
  }
38315
38188
  let absPath;
38316
- if (import_node_path20.default.isAbsolute(pathParam)) {
38189
+ if (import_node_path19.default.isAbsolute(pathParam)) {
38317
38190
  absPath = pathParam;
38318
38191
  } else if (deps.sessionStore) {
38319
38192
  const file = deps.sessionStore.read(sid);
@@ -38321,7 +38194,7 @@ function createHttpRouter(deps) {
38321
38194
  sendJson(res, 404, { code: "NOT_FOUND", message: `session ${sid} not found` });
38322
38195
  return true;
38323
38196
  }
38324
- absPath = import_node_path20.default.join(file.cwd, pathParam);
38197
+ absPath = import_node_path19.default.join(file.cwd, pathParam);
38325
38198
  } else {
38326
38199
  sendJson(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "sessionStore not wired" }));
38327
38200
  return true;
@@ -38404,7 +38277,7 @@ function withCtx(ctx, body) {
38404
38277
  function streamFile(res, absPath, logger) {
38405
38278
  let stat;
38406
38279
  try {
38407
- stat = import_node_fs17.default.statSync(absPath);
38280
+ stat = import_node_fs16.default.statSync(absPath);
38408
38281
  } catch (err) {
38409
38282
  const code = err?.code;
38410
38283
  if (code === "ENOENT") {
@@ -38419,7 +38292,7 @@ function streamFile(res, absPath, logger) {
38419
38292
  return;
38420
38293
  }
38421
38294
  const mime = lookupMime(absPath);
38422
- const basename = import_node_path20.default.basename(absPath);
38295
+ const basename = import_node_path19.default.basename(absPath);
38423
38296
  res.writeHead(200, {
38424
38297
  "Content-Type": mime,
38425
38298
  "Content-Length": String(stat.size),
@@ -38427,7 +38300,7 @@ function streamFile(res, absPath, logger) {
38427
38300
  // 防止浏览器把任意 mime 当 html 渲染
38428
38301
  "X-Content-Type-Options": "nosniff"
38429
38302
  });
38430
- const stream = import_node_fs17.default.createReadStream(absPath);
38303
+ const stream = import_node_fs16.default.createReadStream(absPath);
38431
38304
  stream.on("error", (err) => {
38432
38305
  logger?.warn("streamFile read error", { absPath, err: err.message });
38433
38306
  res.destroy();
@@ -38436,8 +38309,8 @@ function streamFile(res, absPath, logger) {
38436
38309
  }
38437
38310
 
38438
38311
  // src/attachment/gc.ts
38439
- var import_node_fs18 = __toESM(require("fs"), 1);
38440
- var import_node_path21 = __toESM(require("path"), 1);
38312
+ var import_node_fs17 = __toESM(require("fs"), 1);
38313
+ var import_node_path20 = __toESM(require("path"), 1);
38441
38314
  var DEFAULT_TTL_MS = 30 * 24 * 3600 * 1e3;
38442
38315
  function runAttachmentGc(args) {
38443
38316
  const now = (args.now ?? Date.now)();
@@ -38446,38 +38319,38 @@ function runAttachmentGc(args) {
38446
38319
  for (const { scope, sessionId } of args.sessionScopes) {
38447
38320
  for (const entry of args.groupFileStore.list(scope, sessionId)) {
38448
38321
  if (entry.stale) continue;
38449
- if (import_node_path21.default.isAbsolute(entry.relPath)) liveAbs.add(entry.relPath);
38322
+ if (import_node_path20.default.isAbsolute(entry.relPath)) liveAbs.add(entry.relPath);
38450
38323
  }
38451
38324
  }
38452
38325
  for (const { scope, sessionId } of args.sessionScopes) {
38453
- const sessionDir = args.getSessionCwd?.(sessionId) ?? import_node_path21.default.join(
38326
+ const sessionDir = args.getSessionCwd?.(sessionId) ?? import_node_path20.default.join(
38454
38327
  args.dataDir,
38455
38328
  "sessions",
38456
38329
  ...scopeSubPath(scope).map(safeFileName),
38457
38330
  safeFileName(sessionId)
38458
38331
  );
38459
- const attRoot = import_node_path21.default.join(sessionDir, ".attachments");
38332
+ const attRoot = import_node_path20.default.join(sessionDir, ".attachments");
38460
38333
  let hashDirs;
38461
38334
  try {
38462
- hashDirs = import_node_fs18.default.readdirSync(attRoot);
38335
+ hashDirs = import_node_fs17.default.readdirSync(attRoot);
38463
38336
  } catch (err) {
38464
38337
  if (err.code === "ENOENT") continue;
38465
38338
  args.logger?.warn("attachment gc: readdir failed", { attRoot, err: err.message });
38466
38339
  continue;
38467
38340
  }
38468
38341
  for (const hashDir of hashDirs) {
38469
- const hashDirAbs = import_node_path21.default.join(attRoot, hashDir);
38342
+ const hashDirAbs = import_node_path20.default.join(attRoot, hashDir);
38470
38343
  let files;
38471
38344
  try {
38472
- files = import_node_fs18.default.readdirSync(hashDirAbs);
38345
+ files = import_node_fs17.default.readdirSync(hashDirAbs);
38473
38346
  } catch {
38474
38347
  continue;
38475
38348
  }
38476
38349
  for (const name of files) {
38477
- const file = import_node_path21.default.join(hashDirAbs, name);
38350
+ const file = import_node_path20.default.join(hashDirAbs, name);
38478
38351
  let stat;
38479
38352
  try {
38480
- stat = import_node_fs18.default.statSync(file);
38353
+ stat = import_node_fs17.default.statSync(file);
38481
38354
  } catch {
38482
38355
  continue;
38483
38356
  }
@@ -38486,27 +38359,27 @@ function runAttachmentGc(args) {
38486
38359
  if (age < ttlMs) continue;
38487
38360
  if (liveAbs.has(file)) continue;
38488
38361
  try {
38489
- import_node_fs18.default.unlinkSync(file);
38362
+ import_node_fs17.default.unlinkSync(file);
38490
38363
  } catch (err) {
38491
38364
  args.logger?.warn("attachment gc: unlink failed", { file, err: err.message });
38492
38365
  }
38493
38366
  }
38494
38367
  try {
38495
- if (import_node_fs18.default.readdirSync(hashDirAbs).length === 0) import_node_fs18.default.rmdirSync(hashDirAbs);
38368
+ if (import_node_fs17.default.readdirSync(hashDirAbs).length === 0) import_node_fs17.default.rmdirSync(hashDirAbs);
38496
38369
  } catch {
38497
38370
  }
38498
38371
  }
38499
38372
  try {
38500
- if (import_node_fs18.default.readdirSync(attRoot).length === 0) import_node_fs18.default.rmdirSync(attRoot);
38373
+ if (import_node_fs17.default.readdirSync(attRoot).length === 0) import_node_fs17.default.rmdirSync(attRoot);
38501
38374
  } catch {
38502
38375
  }
38503
38376
  }
38504
38377
  }
38505
38378
 
38506
38379
  // src/attachment/group.ts
38507
- var import_node_fs19 = __toESM(require("fs"), 1);
38508
- var import_node_path22 = __toESM(require("path"), 1);
38509
- var import_node_crypto7 = __toESM(require("crypto"), 1);
38380
+ var import_node_fs18 = __toESM(require("fs"), 1);
38381
+ var import_node_path21 = __toESM(require("path"), 1);
38382
+ var import_node_crypto6 = __toESM(require("crypto"), 1);
38510
38383
  init_protocol();
38511
38384
  var GroupFileStore = class {
38512
38385
  dataDir;
@@ -38517,11 +38390,11 @@ var GroupFileStore = class {
38517
38390
  this.logger = opts.logger;
38518
38391
  }
38519
38392
  rootForScope(scope) {
38520
- return import_node_path22.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
38393
+ return import_node_path21.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
38521
38394
  }
38522
38395
  /** 与 SessionStore.filePath 平级,扩展名 .group-files.json */
38523
38396
  filePath(scope, sessionId) {
38524
- return import_node_path22.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
38397
+ return import_node_path21.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
38525
38398
  }
38526
38399
  cacheKey(scope, sessionId) {
38527
38400
  return scope.kind === "default" ? `default::${sessionId}` : `persona:${scope.personaId}:${scope.mode}::${sessionId}`;
@@ -38530,7 +38403,7 @@ var GroupFileStore = class {
38530
38403
  readFile(scope, sessionId) {
38531
38404
  const file = this.filePath(scope, sessionId);
38532
38405
  try {
38533
- const raw = import_node_fs19.default.readFileSync(file, "utf8");
38406
+ const raw = import_node_fs18.default.readFileSync(file, "utf8");
38534
38407
  const parsed = JSON.parse(raw);
38535
38408
  if (!Array.isArray(parsed)) {
38536
38409
  this.logger?.warn("GroupFileStore.readFile: not an array; resetting session entries", {
@@ -38556,10 +38429,10 @@ var GroupFileStore = class {
38556
38429
  }
38557
38430
  writeFile(scope, sessionId, entries) {
38558
38431
  const file = this.filePath(scope, sessionId);
38559
- import_node_fs19.default.mkdirSync(import_node_path22.default.dirname(file), { recursive: true });
38432
+ import_node_fs18.default.mkdirSync(import_node_path21.default.dirname(file), { recursive: true });
38560
38433
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
38561
- import_node_fs19.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
38562
- import_node_fs19.default.renameSync(tmp, file);
38434
+ import_node_fs18.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
38435
+ import_node_fs18.default.renameSync(tmp, file);
38563
38436
  }
38564
38437
  /** 拉一份当前 session 的清单。读盘 → cache;之后调用复用 cache */
38565
38438
  list(scope, sessionId) {
@@ -38595,7 +38468,7 @@ var GroupFileStore = class {
38595
38468
  entries[idx] = next;
38596
38469
  } else {
38597
38470
  next = {
38598
- id: `gf-${import_node_crypto7.default.randomBytes(6).toString("base64url")}`,
38471
+ id: `gf-${import_node_crypto6.default.randomBytes(6).toString("base64url")}`,
38599
38472
  relPath: input.relPath,
38600
38473
  from: input.from,
38601
38474
  label: input.label,
@@ -38645,10 +38518,10 @@ var GroupFileStore = class {
38645
38518
  };
38646
38519
 
38647
38520
  // src/discovery/state-file.ts
38648
- var import_node_fs20 = __toESM(require("fs"), 1);
38649
- var import_node_path23 = __toESM(require("path"), 1);
38521
+ var import_node_fs19 = __toESM(require("fs"), 1);
38522
+ var import_node_path22 = __toESM(require("path"), 1);
38650
38523
  function defaultStateFilePath(dataDir) {
38651
- return import_node_path23.default.join(dataDir, "state.json");
38524
+ return import_node_path22.default.join(dataDir, "state.json");
38652
38525
  }
38653
38526
  function isPidAlive(pid) {
38654
38527
  if (!Number.isFinite(pid) || pid <= 0) return false;
@@ -38670,7 +38543,7 @@ var StateFileManager = class {
38670
38543
  }
38671
38544
  read() {
38672
38545
  try {
38673
- const raw = import_node_fs20.default.readFileSync(this.file, "utf8");
38546
+ const raw = import_node_fs19.default.readFileSync(this.file, "utf8");
38674
38547
  const parsed = JSON.parse(raw);
38675
38548
  return parsed;
38676
38549
  } catch {
@@ -38684,34 +38557,34 @@ var StateFileManager = class {
38684
38557
  return { status: "stale", existing };
38685
38558
  }
38686
38559
  write(state) {
38687
- import_node_fs20.default.mkdirSync(import_node_path23.default.dirname(this.file), { recursive: true });
38560
+ import_node_fs19.default.mkdirSync(import_node_path22.default.dirname(this.file), { recursive: true });
38688
38561
  const tmp = `${this.file}.tmp.${process.pid}.${Date.now()}`;
38689
- import_node_fs20.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
38690
- import_node_fs20.default.renameSync(tmp, this.file);
38562
+ import_node_fs19.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
38563
+ import_node_fs19.default.renameSync(tmp, this.file);
38691
38564
  if (process.platform !== "win32") {
38692
38565
  try {
38693
- import_node_fs20.default.chmodSync(this.file, 384);
38566
+ import_node_fs19.default.chmodSync(this.file, 384);
38694
38567
  } catch {
38695
38568
  }
38696
38569
  }
38697
38570
  }
38698
38571
  delete() {
38699
38572
  try {
38700
- import_node_fs20.default.unlinkSync(this.file);
38573
+ import_node_fs19.default.unlinkSync(this.file);
38701
38574
  } catch {
38702
38575
  }
38703
38576
  }
38704
38577
  };
38705
38578
 
38706
38579
  // src/tunnel/tunnel-manager.ts
38707
- var import_node_fs24 = __toESM(require("fs"), 1);
38708
- var import_node_path27 = __toESM(require("path"), 1);
38709
- var import_node_crypto8 = __toESM(require("crypto"), 1);
38580
+ var import_node_fs23 = __toESM(require("fs"), 1);
38581
+ var import_node_path26 = __toESM(require("path"), 1);
38582
+ var import_node_crypto7 = __toESM(require("crypto"), 1);
38710
38583
  var import_node_child_process5 = require("child_process");
38711
38584
 
38712
38585
  // src/tunnel/tunnel-store.ts
38713
- var import_node_fs21 = __toESM(require("fs"), 1);
38714
- var import_node_path24 = __toESM(require("path"), 1);
38586
+ var import_node_fs20 = __toESM(require("fs"), 1);
38587
+ var import_node_path23 = __toESM(require("path"), 1);
38715
38588
  var TunnelStore = class {
38716
38589
  constructor(filePath) {
38717
38590
  this.filePath = filePath;
@@ -38719,7 +38592,7 @@ var TunnelStore = class {
38719
38592
  filePath;
38720
38593
  async get() {
38721
38594
  try {
38722
- const raw = await import_node_fs21.default.promises.readFile(this.filePath, "utf8");
38595
+ const raw = await import_node_fs20.default.promises.readFile(this.filePath, "utf8");
38723
38596
  const obj = JSON.parse(raw);
38724
38597
  if (!isPersistedTunnel(obj)) return null;
38725
38598
  return obj;
@@ -38730,22 +38603,22 @@ var TunnelStore = class {
38730
38603
  }
38731
38604
  }
38732
38605
  async set(v2) {
38733
- const dir = import_node_path24.default.dirname(this.filePath);
38734
- await import_node_fs21.default.promises.mkdir(dir, { recursive: true });
38606
+ const dir = import_node_path23.default.dirname(this.filePath);
38607
+ await import_node_fs20.default.promises.mkdir(dir, { recursive: true });
38735
38608
  const data = JSON.stringify(v2, null, 2);
38736
38609
  const tmp = `${this.filePath}.tmp.${process.pid}.${Date.now()}`;
38737
- await import_node_fs21.default.promises.writeFile(tmp, data, { mode: 384 });
38610
+ await import_node_fs20.default.promises.writeFile(tmp, data, { mode: 384 });
38738
38611
  if (process.platform !== "win32") {
38739
38612
  try {
38740
- await import_node_fs21.default.promises.chmod(tmp, 384);
38613
+ await import_node_fs20.default.promises.chmod(tmp, 384);
38741
38614
  } catch {
38742
38615
  }
38743
38616
  }
38744
- await import_node_fs21.default.promises.rename(tmp, this.filePath);
38617
+ await import_node_fs20.default.promises.rename(tmp, this.filePath);
38745
38618
  }
38746
38619
  async clear() {
38747
38620
  try {
38748
- await import_node_fs21.default.promises.unlink(this.filePath);
38621
+ await import_node_fs20.default.promises.unlink(this.filePath);
38749
38622
  } catch (err) {
38750
38623
  const code = err?.code;
38751
38624
  if (code !== "ENOENT") throw err;
@@ -38840,9 +38713,9 @@ function escape(v2) {
38840
38713
  }
38841
38714
 
38842
38715
  // src/tunnel/frpc-binary.ts
38843
- var import_node_fs22 = __toESM(require("fs"), 1);
38716
+ var import_node_fs21 = __toESM(require("fs"), 1);
38844
38717
  var import_node_os11 = __toESM(require("os"), 1);
38845
- var import_node_path25 = __toESM(require("path"), 1);
38718
+ var import_node_path24 = __toESM(require("path"), 1);
38846
38719
  var import_node_child_process3 = require("child_process");
38847
38720
  var import_node_stream2 = require("stream");
38848
38721
  var import_promises3 = require("stream/promises");
@@ -38874,20 +38747,20 @@ function frpcDownloadUrl(version2, p2) {
38874
38747
  }
38875
38748
  async function ensureFrpcBinary(opts) {
38876
38749
  if (opts.override) {
38877
- if (!import_node_fs22.default.existsSync(opts.override)) {
38750
+ if (!import_node_fs21.default.existsSync(opts.override)) {
38878
38751
  throw new Error(`frpc binary not found at override path: ${opts.override}`);
38879
38752
  }
38880
38753
  return opts.override;
38881
38754
  }
38882
38755
  const version2 = opts.version ?? FRPC_VERSION;
38883
38756
  const platform = opts.platform ?? detectPlatform();
38884
- const binDir = import_node_path25.default.join(opts.dataDir, "bin");
38885
- import_node_fs22.default.mkdirSync(binDir, { recursive: true });
38757
+ const binDir = import_node_path24.default.join(opts.dataDir, "bin");
38758
+ import_node_fs21.default.mkdirSync(binDir, { recursive: true });
38886
38759
  cleanupStaleArtifacts(binDir);
38887
- const stableBin = import_node_path25.default.join(binDir, "frpc");
38888
- if (import_node_fs22.default.existsSync(stableBin)) return stableBin;
38760
+ const stableBin = import_node_path24.default.join(binDir, "frpc");
38761
+ if (import_node_fs21.default.existsSync(stableBin)) return stableBin;
38889
38762
  const partialBin = `${stableBin}.partial`;
38890
- const tarballPath = import_node_path25.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
38763
+ const tarballPath = import_node_path24.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
38891
38764
  try {
38892
38765
  const url = frpcDownloadUrl(version2, platform);
38893
38766
  await downloadToFile(url, tarballPath, opts.fetchImpl);
@@ -38896,8 +38769,8 @@ async function ensureFrpcBinary(opts) {
38896
38769
  } else {
38897
38770
  await extractFrpcFromTarball(tarballPath, binDir, version2, platform, partialBin);
38898
38771
  }
38899
- import_node_fs22.default.chmodSync(partialBin, 493);
38900
- import_node_fs22.default.renameSync(partialBin, stableBin);
38772
+ import_node_fs21.default.chmodSync(partialBin, 493);
38773
+ import_node_fs21.default.renameSync(partialBin, stableBin);
38901
38774
  } finally {
38902
38775
  safeUnlink(tarballPath);
38903
38776
  safeUnlink(partialBin);
@@ -38907,15 +38780,15 @@ async function ensureFrpcBinary(opts) {
38907
38780
  function cleanupStaleArtifacts(binDir) {
38908
38781
  let entries;
38909
38782
  try {
38910
- entries = import_node_fs22.default.readdirSync(binDir);
38783
+ entries = import_node_fs21.default.readdirSync(binDir);
38911
38784
  } catch {
38912
38785
  return;
38913
38786
  }
38914
38787
  for (const name of entries) {
38915
38788
  if (name.endsWith(".partial") || name.startsWith("extract-")) {
38916
- const full = import_node_path25.default.join(binDir, name);
38789
+ const full = import_node_path24.default.join(binDir, name);
38917
38790
  try {
38918
- import_node_fs22.default.rmSync(full, { recursive: true, force: true });
38791
+ import_node_fs21.default.rmSync(full, { recursive: true, force: true });
38919
38792
  } catch {
38920
38793
  }
38921
38794
  }
@@ -38923,7 +38796,7 @@ function cleanupStaleArtifacts(binDir) {
38923
38796
  }
38924
38797
  function safeUnlink(p2) {
38925
38798
  try {
38926
- import_node_fs22.default.unlinkSync(p2);
38799
+ import_node_fs21.default.unlinkSync(p2);
38927
38800
  } catch {
38928
38801
  }
38929
38802
  }
@@ -38934,13 +38807,13 @@ async function downloadToFile(url, dest, fetchImpl) {
38934
38807
  if (!res.ok || !res.body) {
38935
38808
  throw new Error(`download failed: ${res.status} ${res.statusText}`);
38936
38809
  }
38937
- const out = import_node_fs22.default.createWriteStream(dest);
38810
+ const out = import_node_fs21.default.createWriteStream(dest);
38938
38811
  const nodeStream = import_node_stream2.Readable.fromWeb(res.body);
38939
38812
  await (0, import_promises3.pipeline)(nodeStream, out);
38940
38813
  }
38941
38814
  async function extractFrpcFromTarball(tarball, binDir, version2, platform, destBin) {
38942
- const work = import_node_path25.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
38943
- import_node_fs22.default.mkdirSync(work, { recursive: true });
38815
+ const work = import_node_path24.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
38816
+ import_node_fs21.default.mkdirSync(work, { recursive: true });
38944
38817
  try {
38945
38818
  await new Promise((resolve6, reject) => {
38946
38819
  const proc = (0, import_node_child_process3.spawn)("tar", ["xzf", tarball, "-C", work], { stdio: "pipe" });
@@ -38948,32 +38821,32 @@ async function extractFrpcFromTarball(tarball, binDir, version2, platform, destB
38948
38821
  proc.on("exit", (code) => code === 0 ? resolve6() : reject(new Error(`tar exited ${code}`)));
38949
38822
  });
38950
38823
  const dirName = `frp_${version2}_${platform.os}_${platform.arch}`;
38951
- const src = import_node_path25.default.join(work, dirName, "frpc");
38952
- if (!import_node_fs22.default.existsSync(src)) {
38824
+ const src = import_node_path24.default.join(work, dirName, "frpc");
38825
+ if (!import_node_fs21.default.existsSync(src)) {
38953
38826
  throw new Error(`frpc not found inside tarball at ${src}`);
38954
38827
  }
38955
- import_node_fs22.default.copyFileSync(src, destBin);
38828
+ import_node_fs21.default.copyFileSync(src, destBin);
38956
38829
  } finally {
38957
- import_node_fs22.default.rmSync(work, { recursive: true, force: true });
38830
+ import_node_fs21.default.rmSync(work, { recursive: true, force: true });
38958
38831
  }
38959
38832
  }
38960
38833
 
38961
38834
  // src/tunnel/frpc-process.ts
38962
- var import_node_fs23 = __toESM(require("fs"), 1);
38963
- var import_node_path26 = __toESM(require("path"), 1);
38835
+ var import_node_fs22 = __toESM(require("fs"), 1);
38836
+ var import_node_path25 = __toESM(require("path"), 1);
38964
38837
  var import_node_child_process4 = require("child_process");
38965
38838
  function frpcPidFilePath(dataDir) {
38966
- return import_node_path26.default.join(dataDir, "frpc.pid");
38839
+ return import_node_path25.default.join(dataDir, "frpc.pid");
38967
38840
  }
38968
38841
  function writeFrpcPid(dataDir, pid) {
38969
38842
  try {
38970
- import_node_fs23.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
38843
+ import_node_fs22.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
38971
38844
  } catch {
38972
38845
  }
38973
38846
  }
38974
38847
  function clearFrpcPid(dataDir) {
38975
38848
  try {
38976
- import_node_fs23.default.unlinkSync(frpcPidFilePath(dataDir));
38849
+ import_node_fs22.default.unlinkSync(frpcPidFilePath(dataDir));
38977
38850
  } catch {
38978
38851
  }
38979
38852
  }
@@ -38989,7 +38862,7 @@ function defaultIsPidAlive(pid) {
38989
38862
  }
38990
38863
  function defaultReadPidFile(file) {
38991
38864
  try {
38992
- return import_node_fs23.default.readFileSync(file, "utf8");
38865
+ return import_node_fs22.default.readFileSync(file, "utf8");
38993
38866
  } catch {
38994
38867
  return null;
38995
38868
  }
@@ -39005,7 +38878,7 @@ function defaultSleep(ms) {
39005
38878
  }
39006
38879
  async function killStaleFrpc(deps) {
39007
38880
  const pidFile = frpcPidFilePath(deps.dataDir);
39008
- const tomlPath = import_node_path26.default.join(deps.dataDir, "frpc.toml");
38881
+ const tomlPath = import_node_path25.default.join(deps.dataDir, "frpc.toml");
39009
38882
  const readPidFile = deps.readPidFileImpl ?? defaultReadPidFile;
39010
38883
  const isAlive = deps.isPidAliveImpl ?? defaultIsPidAlive;
39011
38884
  const killPid = deps.killPidImpl ?? defaultKillPid;
@@ -39029,7 +38902,7 @@ async function killStaleFrpc(deps) {
39029
38902
  }
39030
38903
  if (victims.size === 0) {
39031
38904
  try {
39032
- import_node_fs23.default.unlinkSync(pidFile);
38905
+ import_node_fs22.default.unlinkSync(pidFile);
39033
38906
  } catch {
39034
38907
  }
39035
38908
  return;
@@ -39040,7 +38913,7 @@ async function killStaleFrpc(deps) {
39040
38913
  }
39041
38914
  await sleep(deps.reapWaitMs ?? 300);
39042
38915
  try {
39043
- import_node_fs23.default.unlinkSync(pidFile);
38916
+ import_node_fs22.default.unlinkSync(pidFile);
39044
38917
  } catch {
39045
38918
  }
39046
38919
  }
@@ -39077,7 +38950,7 @@ var DEFAULT_TUNNEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
39077
38950
  var TunnelManager = class {
39078
38951
  constructor(deps) {
39079
38952
  this.deps = deps;
39080
- this.store = deps.store ?? new TunnelStore(import_node_path27.default.join(deps.dataDir, "tunnel.json"));
38953
+ this.store = deps.store ?? new TunnelStore(import_node_path26.default.join(deps.dataDir, "tunnel.json"));
39081
38954
  this.ttlMs = deps.ttlMs ?? DEFAULT_TUNNEL_TTL_MS;
39082
38955
  this.startupTimeoutMs = deps.startupTimeoutMs ?? 15e3;
39083
38956
  }
@@ -39204,8 +39077,8 @@ var TunnelManager = class {
39204
39077
  dataDir: this.deps.dataDir,
39205
39078
  override: this.deps.frpcBinaryOverride ?? void 0
39206
39079
  });
39207
- const tomlPath = import_node_path27.default.join(this.deps.dataDir, "frpc.toml");
39208
- const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto8.default.randomBytes(3).toString("hex")}`;
39080
+ const tomlPath = import_node_path26.default.join(this.deps.dataDir, "frpc.toml");
39081
+ const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto7.default.randomBytes(3).toString("hex")}`;
39209
39082
  const toml = buildFrpcToml({
39210
39083
  serverAddr: t.frpsHost,
39211
39084
  serverPort: t.frpsPort,
@@ -39215,12 +39088,12 @@ var TunnelManager = class {
39215
39088
  localPort,
39216
39089
  logLevel: "info"
39217
39090
  });
39218
- await import_node_fs24.default.promises.writeFile(tomlPath, toml, { mode: 384 });
39091
+ await import_node_fs23.default.promises.writeFile(tomlPath, toml, { mode: 384 });
39219
39092
  const proc = (this.deps.spawnImpl ?? import_node_child_process5.spawn)(frpcBin, ["-c", tomlPath], {
39220
39093
  stdio: ["ignore", "pipe", "pipe"]
39221
39094
  });
39222
- const logFilePath = import_node_path27.default.join(this.deps.dataDir, "frpc.log");
39223
- const logStream = import_node_fs24.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
39095
+ const logFilePath = import_node_path26.default.join(this.deps.dataDir, "frpc.log");
39096
+ const logStream = import_node_fs23.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
39224
39097
  logStream.on("error", () => {
39225
39098
  });
39226
39099
  const tee = (chunk) => {
@@ -39303,31 +39176,31 @@ async function waitForFrpcReady(proc, timeoutMs) {
39303
39176
 
39304
39177
  // src/tunnel/device-key.ts
39305
39178
  var import_node_os12 = __toESM(require("os"), 1);
39306
- var import_node_path28 = __toESM(require("path"), 1);
39307
- var import_node_crypto9 = __toESM(require("crypto"), 1);
39179
+ var import_node_path27 = __toESM(require("path"), 1);
39180
+ var import_node_crypto8 = __toESM(require("crypto"), 1);
39308
39181
  var DERIVE_SALT = "clawd-tunnel-device-v1";
39309
39182
  function deriveStableDeviceKey(opts = {}) {
39310
39183
  const hostname = opts.hostname ?? import_node_os12.default.hostname();
39311
39184
  const uid = opts.uid ?? (typeof import_node_os12.default.userInfo === "function" ? import_node_os12.default.userInfo().uid : 0);
39312
39185
  const home = opts.home ?? import_node_os12.default.homedir();
39313
- const defaultDataDir = import_node_path28.default.resolve(import_node_path28.default.join(home, ".clawd"));
39314
- const normalizedDataDir = opts.dataDir ? import_node_path28.default.resolve(opts.dataDir) : null;
39186
+ const defaultDataDir = import_node_path27.default.resolve(import_node_path27.default.join(home, ".clawd"));
39187
+ const normalizedDataDir = opts.dataDir ? import_node_path27.default.resolve(opts.dataDir) : null;
39315
39188
  const isDefaultDir = normalizedDataDir == null || normalizedDataDir === defaultDataDir;
39316
39189
  const input = isDefaultDir ? `${hostname}::${uid}` : `${hostname}::${uid}::${normalizedDataDir}`;
39317
- return import_node_crypto9.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
39190
+ return import_node_crypto8.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
39318
39191
  }
39319
39192
 
39320
39193
  // src/auth-store.ts
39321
- var import_node_fs25 = __toESM(require("fs"), 1);
39322
- var import_node_path29 = __toESM(require("path"), 1);
39323
- var import_node_crypto10 = __toESM(require("crypto"), 1);
39194
+ var import_node_fs24 = __toESM(require("fs"), 1);
39195
+ var import_node_path28 = __toESM(require("path"), 1);
39196
+ var import_node_crypto9 = __toESM(require("crypto"), 1);
39324
39197
  var AUTH_FILE_NAME = "auth.json";
39325
39198
  function authFilePath(dataDir) {
39326
- return import_node_path29.default.join(dataDir, AUTH_FILE_NAME);
39199
+ return import_node_path28.default.join(dataDir, AUTH_FILE_NAME);
39327
39200
  }
39328
39201
  function loadOrCreateAuthFile(opts) {
39329
39202
  const file = authFilePath(opts.dataDir);
39330
- const generate = opts.generate ?? defaultGenerateToken2;
39203
+ const generate = opts.generate ?? defaultGenerateToken;
39331
39204
  const genId = opts.genOwnerPrincipalId ?? defaultGenerateOwnerPrincipalId;
39332
39205
  const now = opts.now ?? (() => /* @__PURE__ */ new Date());
39333
39206
  const existing = readAuthFile(file);
@@ -39347,15 +39220,15 @@ function loadOrCreateAuthFile(opts) {
39347
39220
  writeAuthFile(file, next);
39348
39221
  return next;
39349
39222
  }
39350
- function defaultGenerateToken2() {
39351
- return import_node_crypto10.default.randomBytes(32).toString("base64url");
39223
+ function defaultGenerateToken() {
39224
+ return import_node_crypto9.default.randomBytes(32).toString("base64url");
39352
39225
  }
39353
39226
  function defaultGenerateOwnerPrincipalId() {
39354
- return `owner-${import_node_crypto10.default.randomUUID()}`;
39227
+ return `owner-${import_node_crypto9.default.randomUUID()}`;
39355
39228
  }
39356
39229
  function readAuthFile(file) {
39357
39230
  try {
39358
- const raw = import_node_fs25.default.readFileSync(file, "utf8");
39231
+ const raw = import_node_fs24.default.readFileSync(file, "utf8");
39359
39232
  const parsed = JSON.parse(raw);
39360
39233
  if (typeof parsed?.token !== "string" || parsed.token.length === 0) {
39361
39234
  return null;
@@ -39373,25 +39246,25 @@ function readAuthFile(file) {
39373
39246
  }
39374
39247
  }
39375
39248
  function writeAuthFile(file, content) {
39376
- import_node_fs25.default.mkdirSync(import_node_path29.default.dirname(file), { recursive: true });
39377
- import_node_fs25.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
39249
+ import_node_fs24.default.mkdirSync(import_node_path28.default.dirname(file), { recursive: true });
39250
+ import_node_fs24.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
39378
39251
  try {
39379
- import_node_fs25.default.chmodSync(file, 384);
39252
+ import_node_fs24.default.chmodSync(file, 384);
39380
39253
  } catch {
39381
39254
  }
39382
39255
  }
39383
39256
 
39384
39257
  // src/owner-profile.ts
39385
- var import_node_fs26 = __toESM(require("fs"), 1);
39258
+ var import_node_fs25 = __toESM(require("fs"), 1);
39386
39259
  var import_node_os13 = __toESM(require("os"), 1);
39387
- var import_node_path30 = __toESM(require("path"), 1);
39260
+ var import_node_path29 = __toESM(require("path"), 1);
39388
39261
  var PROFILE_FILENAME = "profile.json";
39389
39262
  function loadOwnerDisplayName(dataDir) {
39390
39263
  const fallback = import_node_os13.default.userInfo().username;
39391
- const profilePath = import_node_path30.default.join(dataDir, PROFILE_FILENAME);
39264
+ const profilePath = import_node_path29.default.join(dataDir, PROFILE_FILENAME);
39392
39265
  let raw;
39393
39266
  try {
39394
- raw = import_node_fs26.default.readFileSync(profilePath, "utf8");
39267
+ raw = import_node_fs25.default.readFileSync(profilePath, "utf8");
39395
39268
  } catch {
39396
39269
  return fallback;
39397
39270
  }
@@ -39414,18 +39287,18 @@ function loadOwnerDisplayName(dataDir) {
39414
39287
  }
39415
39288
 
39416
39289
  // src/feishu-auth/owner-identity-store.ts
39417
- var import_node_fs27 = __toESM(require("fs"), 1);
39418
- var import_node_path31 = __toESM(require("path"), 1);
39290
+ var import_node_fs26 = __toESM(require("fs"), 1);
39291
+ var import_node_path30 = __toESM(require("path"), 1);
39419
39292
  var OWNER_IDENTITY_FILE_NAME = "owner-identity.json";
39420
39293
  var OwnerIdentityStore = class {
39421
39294
  file;
39422
39295
  constructor(dataDir) {
39423
- this.file = import_node_path31.default.join(dataDir, OWNER_IDENTITY_FILE_NAME);
39296
+ this.file = import_node_path30.default.join(dataDir, OWNER_IDENTITY_FILE_NAME);
39424
39297
  }
39425
39298
  read() {
39426
39299
  let raw;
39427
39300
  try {
39428
- raw = import_node_fs27.default.readFileSync(this.file, "utf8");
39301
+ raw = import_node_fs26.default.readFileSync(this.file, "utf8");
39429
39302
  } catch {
39430
39303
  return null;
39431
39304
  }
@@ -39451,16 +39324,16 @@ var OwnerIdentityStore = class {
39451
39324
  };
39452
39325
  }
39453
39326
  write(record) {
39454
- import_node_fs27.default.mkdirSync(import_node_path31.default.dirname(this.file), { recursive: true });
39455
- import_node_fs27.default.writeFileSync(this.file, JSON.stringify(record, null, 2), { mode: 384 });
39327
+ import_node_fs26.default.mkdirSync(import_node_path30.default.dirname(this.file), { recursive: true });
39328
+ import_node_fs26.default.writeFileSync(this.file, JSON.stringify(record, null, 2), { mode: 384 });
39456
39329
  try {
39457
- import_node_fs27.default.chmodSync(this.file, 384);
39330
+ import_node_fs26.default.chmodSync(this.file, 384);
39458
39331
  } catch {
39459
39332
  }
39460
39333
  }
39461
39334
  clear() {
39462
39335
  try {
39463
- import_node_fs27.default.unlinkSync(this.file);
39336
+ import_node_fs26.default.unlinkSync(this.file);
39464
39337
  } catch (err) {
39465
39338
  const code = err?.code;
39466
39339
  if (code !== "ENOENT") throw err;
@@ -39469,7 +39342,7 @@ var OwnerIdentityStore = class {
39469
39342
  };
39470
39343
 
39471
39344
  // src/feishu-auth/login-flow.ts
39472
- var import_node_crypto11 = __toESM(require("crypto"), 1);
39345
+ var import_node_crypto10 = __toESM(require("crypto"), 1);
39473
39346
  var STATE_TTL_MS = 5 * 60 * 1e3;
39474
39347
  var LoginFlow = class {
39475
39348
  constructor(deps) {
@@ -39478,7 +39351,7 @@ var LoginFlow = class {
39478
39351
  deps;
39479
39352
  pendingStates = /* @__PURE__ */ new Map();
39480
39353
  start() {
39481
- const state = import_node_crypto11.default.randomBytes(16).toString("base64url");
39354
+ const state = import_node_crypto10.default.randomBytes(16).toString("base64url");
39482
39355
  const now = (this.deps.now ?? Date.now)();
39483
39356
  this.pendingStates.set(state, now);
39484
39357
  this.gcExpired(now);
@@ -39567,6 +39440,105 @@ async function fetchTtcUserInfo(opts) {
39567
39440
  };
39568
39441
  }
39569
39442
 
39443
+ // src/feishu-auth/central-client.ts
39444
+ var CentralClientError = class extends Error {
39445
+ constructor(code, message, cause) {
39446
+ super(message);
39447
+ this.code = code;
39448
+ this.cause = cause;
39449
+ this.name = "CentralClientError";
39450
+ }
39451
+ code;
39452
+ cause;
39453
+ };
39454
+ async function centralRequest(opts, path59, init) {
39455
+ const f = opts.fetchImpl ?? globalThis.fetch;
39456
+ const url = `${opts.api.replace(/\/+$/, "")}${path59}`;
39457
+ const ctrl = new AbortController();
39458
+ const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 15e3);
39459
+ let res;
39460
+ try {
39461
+ res = await f(url, {
39462
+ method: init.method,
39463
+ headers: {
39464
+ "content-type": "application/json",
39465
+ ...init.bearer ? { authorization: `Bearer ${init.bearer}` } : {}
39466
+ },
39467
+ ...init.body !== void 0 ? { body: JSON.stringify(init.body) } : {},
39468
+ signal: ctrl.signal
39469
+ });
39470
+ } catch (err) {
39471
+ clearTimeout(timer);
39472
+ throw new CentralClientError("NETWORK", `central server request failed: ${err.message}`, err);
39473
+ }
39474
+ clearTimeout(timer);
39475
+ if (res.status === 401) {
39476
+ throw new CentralClientError("UNAUTHORIZED", "TTC JWT invalid or expired");
39477
+ }
39478
+ if (!res.ok) {
39479
+ throw new CentralClientError("API_ERROR", `central server HTTP ${res.status}`);
39480
+ }
39481
+ try {
39482
+ return await res.json();
39483
+ } catch (err) {
39484
+ throw new CentralClientError("BAD_RESPONSE", "central server: invalid json response", err);
39485
+ }
39486
+ }
39487
+ async function centralExchange(opts) {
39488
+ const body = await centralRequest(opts, "/api/clawd-identity/exchange", {
39489
+ method: "POST",
39490
+ body: { ttcJwt: opts.ttcJwt, hubId: opts.hubId }
39491
+ });
39492
+ if (typeof body.token !== "string" || body.token.length === 0 || typeof body.unionId !== "string" || typeof body.displayName !== "string") {
39493
+ throw new CentralClientError("BAD_RESPONSE", "exchange: missing token/unionId/displayName");
39494
+ }
39495
+ const hubUrl = typeof body.hubUrl === "string" && body.hubUrl.length > 0 ? body.hubUrl : null;
39496
+ return { token: body.token, unionId: body.unionId, displayName: body.displayName, hubUrl };
39497
+ }
39498
+ async function centralUpsertHub(opts) {
39499
+ const body = await centralRequest(opts, "/api/clawd-identity/hubs/self", {
39500
+ method: "PUT",
39501
+ bearer: opts.ttcJwt,
39502
+ body: { url: opts.url, ...opts.name ? { name: opts.name } : {} }
39503
+ });
39504
+ if (body.ok !== true) {
39505
+ throw new CentralClientError("BAD_RESPONSE", "upsertHub: server did not ack ok");
39506
+ }
39507
+ return { ok: true };
39508
+ }
39509
+ async function centralIntrospect(opts) {
39510
+ const body = await centralRequest(opts, "/api/clawd-identity/introspect", {
39511
+ method: "POST",
39512
+ bearer: opts.hubTtcJwt,
39513
+ body: { token: opts.token }
39514
+ });
39515
+ if (body.active !== true) {
39516
+ return { active: false };
39517
+ }
39518
+ if (typeof body.unionId !== "string" || typeof body.displayName !== "string") {
39519
+ throw new CentralClientError("BAD_RESPONSE", "introspect: active but missing identity");
39520
+ }
39521
+ return { active: true, unionId: body.unionId, displayName: body.displayName };
39522
+ }
39523
+ async function centralListHubs(opts) {
39524
+ const body = await centralRequest(opts, "/api/clawd-identity/hubs", {
39525
+ method: "GET",
39526
+ bearer: opts.ttcJwt
39527
+ });
39528
+ if (!Array.isArray(body.hubs)) {
39529
+ throw new CentralClientError("BAD_RESPONSE", "hubs: missing hubs array");
39530
+ }
39531
+ const out = [];
39532
+ for (const raw of body.hubs) {
39533
+ const h = raw;
39534
+ if (typeof h.hubId !== "string" || typeof h.name !== "string" || typeof h.url !== "string") {
39535
+ throw new CentralClientError("BAD_RESPONSE", "hubs: malformed hub entry");
39536
+ }
39537
+ out.push({ hubId: h.hubId, name: h.name, url: h.url });
39538
+ }
39539
+ return out;
39540
+ }
39541
+
39570
39542
  // src/index.ts
39571
39543
  init_protocol();
39572
39544
 
@@ -39574,12 +39546,12 @@ init_protocol();
39574
39546
  init_protocol();
39575
39547
 
39576
39548
  // src/session/fork.ts
39577
- var import_node_fs28 = __toESM(require("fs"), 1);
39549
+ var import_node_fs27 = __toESM(require("fs"), 1);
39578
39550
  var import_node_os14 = __toESM(require("os"), 1);
39579
- var import_node_path32 = __toESM(require("path"), 1);
39551
+ var import_node_path31 = __toESM(require("path"), 1);
39580
39552
  init_claude_history();
39581
39553
  function readJsonlEntries(file) {
39582
- const raw = import_node_fs28.default.readFileSync(file, "utf8");
39554
+ const raw = import_node_fs27.default.readFileSync(file, "utf8");
39583
39555
  const out = [];
39584
39556
  for (const line of raw.split("\n")) {
39585
39557
  const t = line.trim();
@@ -39592,10 +39564,10 @@ function readJsonlEntries(file) {
39592
39564
  return out;
39593
39565
  }
39594
39566
  function forkSession(input) {
39595
- const baseDir = input.baseDir ?? import_node_path32.default.join(import_node_os14.default.homedir(), ".claude");
39596
- const projectDir = import_node_path32.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
39597
- const sourceFile = import_node_path32.default.join(projectDir, `${input.toolSessionId}.jsonl`);
39598
- if (!import_node_fs28.default.existsSync(sourceFile)) {
39567
+ const baseDir = input.baseDir ?? import_node_path31.default.join(import_node_os14.default.homedir(), ".claude");
39568
+ const projectDir = import_node_path31.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
39569
+ const sourceFile = import_node_path31.default.join(projectDir, `${input.toolSessionId}.jsonl`);
39570
+ if (!import_node_fs27.default.existsSync(sourceFile)) {
39599
39571
  throw new Error(`fork: source transcript not found: ${sourceFile}`);
39600
39572
  }
39601
39573
  const entries = readJsonlEntries(sourceFile);
@@ -39625,9 +39597,9 @@ function forkSession(input) {
39625
39597
  }
39626
39598
  forkedLines.push(JSON.stringify(forked));
39627
39599
  }
39628
- const forkedFilePath = import_node_path32.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
39629
- import_node_fs28.default.mkdirSync(projectDir, { recursive: true });
39630
- import_node_fs28.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
39600
+ const forkedFilePath = import_node_path31.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
39601
+ import_node_fs27.default.mkdirSync(projectDir, { recursive: true });
39602
+ import_node_fs27.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
39631
39603
  return { forkedToolSessionId, forkedFilePath };
39632
39604
  }
39633
39605
 
@@ -39979,7 +39951,7 @@ function buildPermissionHandlers(deps) {
39979
39951
  }
39980
39952
 
39981
39953
  // src/handlers/history.ts
39982
- var path42 = __toESM(require("path"), 1);
39954
+ var path41 = __toESM(require("path"), 1);
39983
39955
  init_protocol();
39984
39956
 
39985
39957
  // src/session/recent-dirs.ts
@@ -39997,7 +39969,7 @@ function listRecentDirs(store, limit = 50) {
39997
39969
  }
39998
39970
 
39999
39971
  // src/permission/persona-paths.ts
40000
- var path41 = __toESM(require("path"), 1);
39972
+ var path40 = __toESM(require("path"), 1);
40001
39973
  function getAllowedPersonaIds(grants, action) {
40002
39974
  const ids = /* @__PURE__ */ new Set();
40003
39975
  for (const g2 of grants) {
@@ -40010,26 +39982,26 @@ function getAllowedPersonaIds(grants, action) {
40010
39982
  return ids;
40011
39983
  }
40012
39984
  function isGuestPathAllowed(grants, absPath, personaRoot, action = "read") {
40013
- const root = path41.resolve(personaRoot);
40014
- const target = path41.resolve(absPath);
40015
- const sep3 = root.endsWith(path41.sep) ? "" : path41.sep;
39985
+ const root = path40.resolve(personaRoot);
39986
+ const target = path40.resolve(absPath);
39987
+ const sep3 = root.endsWith(path40.sep) ? "" : path40.sep;
40016
39988
  if (!target.startsWith(root + sep3)) return false;
40017
- const rel = path41.relative(root, target);
39989
+ const rel = path40.relative(root, target);
40018
39990
  if (!rel || rel.startsWith("..")) return false;
40019
- const personaId = rel.split(path41.sep)[0];
39991
+ const personaId = rel.split(path40.sep)[0];
40020
39992
  if (!personaId) return false;
40021
39993
  const allowed = getAllowedPersonaIds(grants, action);
40022
39994
  if (allowed === "*") return true;
40023
39995
  return allowed.has(personaId);
40024
39996
  }
40025
39997
  function personaIdFromPath(absPath, personaRoot) {
40026
- const root = path41.resolve(personaRoot);
40027
- const target = path41.resolve(absPath);
40028
- const sep3 = root.endsWith(path41.sep) ? "" : path41.sep;
39998
+ const root = path40.resolve(personaRoot);
39999
+ const target = path40.resolve(absPath);
40000
+ const sep3 = root.endsWith(path40.sep) ? "" : path40.sep;
40029
40001
  if (!target.startsWith(root + sep3)) return null;
40030
- const rel = path41.relative(root, target);
40002
+ const rel = path40.relative(root, target);
40031
40003
  if (!rel || rel.startsWith("..")) return null;
40032
- const id = rel.split(path41.sep)[0];
40004
+ const id = rel.split(path40.sep)[0];
40033
40005
  return id || null;
40034
40006
  }
40035
40007
 
@@ -40054,7 +40026,7 @@ function buildHistoryHandlers(deps) {
40054
40026
  if (!pid) return false;
40055
40027
  return isGuestPathAllowed(
40056
40028
  ctx.grants,
40057
- path42.join(personaRoot, pid),
40029
+ path41.join(personaRoot, pid),
40058
40030
  personaRoot,
40059
40031
  "read"
40060
40032
  );
@@ -40065,7 +40037,7 @@ function buildHistoryHandlers(deps) {
40065
40037
  };
40066
40038
  const list = async (frame, _client, ctx) => {
40067
40039
  const args = HistoryListArgs.parse(frame);
40068
- assertGuestPath(ctx, path42.resolve(args.projectPath), personaRoot, "history:list");
40040
+ assertGuestPath(ctx, path41.resolve(args.projectPath), personaRoot, "history:list");
40069
40041
  const sessions = await history.listSessions(args);
40070
40042
  return { response: { type: "history:list", sessions } };
40071
40043
  };
@@ -40097,13 +40069,13 @@ function buildHistoryHandlers(deps) {
40097
40069
  };
40098
40070
  const subagents = async (frame, _client, ctx) => {
40099
40071
  const args = HistorySubagentsArgs.parse(frame);
40100
- assertGuestPath(ctx, path42.resolve(args.cwd), personaRoot, "history:subagents");
40072
+ assertGuestPath(ctx, path41.resolve(args.cwd), personaRoot, "history:subagents");
40101
40073
  const subs = await history.listSubagents(args);
40102
40074
  return { response: { type: "history:subagents", subagents: subs } };
40103
40075
  };
40104
40076
  const subagentRead = async (frame, _client, ctx) => {
40105
40077
  const args = HistorySubagentReadArgs.parse(frame);
40106
- assertGuestPath(ctx, path42.resolve(args.cwd), personaRoot, "history:subagent-read");
40078
+ assertGuestPath(ctx, path41.resolve(args.cwd), personaRoot, "history:subagent-read");
40107
40079
  const res = await history.readSubagent(args);
40108
40080
  return { response: { type: "history:subagent-read", ...res } };
40109
40081
  };
@@ -40111,7 +40083,7 @@ function buildHistoryHandlers(deps) {
40111
40083
  const dirs = listRecentDirs(store);
40112
40084
  if (ctx?.principal.kind === "guest" && personaRoot) {
40113
40085
  const filtered = dirs.filter(
40114
- (d) => isGuestPathAllowed(ctx.grants, path42.resolve(d.cwd), personaRoot, "read")
40086
+ (d) => isGuestPathAllowed(ctx.grants, path41.resolve(d.cwd), personaRoot, "read")
40115
40087
  );
40116
40088
  return { response: { type: "history:recentDirs", dirs: filtered } };
40117
40089
  }
@@ -40128,7 +40100,7 @@ function buildHistoryHandlers(deps) {
40128
40100
  }
40129
40101
 
40130
40102
  // src/handlers/workspace.ts
40131
- var path43 = __toESM(require("path"), 1);
40103
+ var path42 = __toESM(require("path"), 1);
40132
40104
  var os15 = __toESM(require("os"), 1);
40133
40105
  init_protocol();
40134
40106
  init_protocol();
@@ -40169,22 +40141,22 @@ function buildWorkspaceHandlers(deps) {
40169
40141
  const args = WorkspaceListArgs.parse(frame);
40170
40142
  const isGuest = ctx?.principal.kind === "guest";
40171
40143
  const fallbackCwd = isGuest && personaRoot ? personaRoot : os15.homedir();
40172
- const resolvedCwd = path43.resolve(args.cwd ?? fallbackCwd);
40173
- const target = args.path ? path43.resolve(resolvedCwd, args.path) : resolvedCwd;
40144
+ const resolvedCwd = path42.resolve(args.cwd ?? fallbackCwd);
40145
+ const target = args.path ? path42.resolve(resolvedCwd, args.path) : resolvedCwd;
40174
40146
  assertGuestPath2(ctx, target, personaRoot, "workspace:list");
40175
40147
  const res = workspace.list({ ...args, cwd: resolvedCwd });
40176
40148
  return { response: { type: "workspace:list", ...res } };
40177
40149
  };
40178
40150
  const read = async (frame, _client, ctx) => {
40179
40151
  const args = WorkspaceReadArgs.parse(frame);
40180
- const target = path43.isAbsolute(args.path) ? path43.resolve(args.path) : path43.resolve(args.cwd, args.path);
40152
+ const target = path42.isAbsolute(args.path) ? path42.resolve(args.path) : path42.resolve(args.cwd, args.path);
40181
40153
  assertGuestPath2(ctx, target, personaRoot, "workspace:read");
40182
40154
  const res = workspace.read(args);
40183
40155
  return { response: { type: "workspace:read", ...res } };
40184
40156
  };
40185
40157
  const skillsList = async (frame, _client, ctx) => {
40186
40158
  const args = SkillsListArgs.parse(frame);
40187
- const cwdAbs = path43.resolve(args.cwd);
40159
+ const cwdAbs = path42.resolve(args.cwd);
40188
40160
  assertGuestPath2(ctx, cwdAbs, personaRoot, "skills:list");
40189
40161
  const list2 = skills.list(args);
40190
40162
  if (ctx?.principal.kind === "guest" && personaRoot) {
@@ -40196,7 +40168,7 @@ function buildWorkspaceHandlers(deps) {
40196
40168
  };
40197
40169
  const agentsList = async (frame, _client, ctx) => {
40198
40170
  const args = AgentsListArgs.parse(frame);
40199
- const cwdAbs = path43.resolve(args.cwd);
40171
+ const cwdAbs = path42.resolve(args.cwd);
40200
40172
  assertGuestPath2(ctx, cwdAbs, personaRoot, "agents:list");
40201
40173
  const list2 = agents.list(args);
40202
40174
  if (ctx?.principal.kind === "guest" && personaRoot) {
@@ -40215,20 +40187,20 @@ function buildWorkspaceHandlers(deps) {
40215
40187
  }
40216
40188
 
40217
40189
  // src/handlers/git.ts
40218
- var path45 = __toESM(require("path"), 1);
40190
+ var path44 = __toESM(require("path"), 1);
40219
40191
  init_protocol();
40220
40192
  init_protocol();
40221
40193
 
40222
40194
  // src/workspace/git.ts
40223
40195
  var import_node_child_process6 = require("child_process");
40224
- var import_node_fs29 = __toESM(require("fs"), 1);
40225
- var import_node_path33 = __toESM(require("path"), 1);
40196
+ var import_node_fs28 = __toESM(require("fs"), 1);
40197
+ var import_node_path32 = __toESM(require("path"), 1);
40226
40198
  var import_node_util = require("util");
40227
40199
  var pexec = (0, import_node_util.promisify)(import_node_child_process6.execFile);
40228
40200
  function normalizePath(p2) {
40229
- const resolved = import_node_path33.default.resolve(p2);
40201
+ const resolved = import_node_path32.default.resolve(p2);
40230
40202
  try {
40231
- return import_node_fs29.default.realpathSync(resolved);
40203
+ return import_node_fs28.default.realpathSync(resolved);
40232
40204
  } catch {
40233
40205
  return resolved;
40234
40206
  }
@@ -40301,7 +40273,7 @@ async function listGitBranches(cwd) {
40301
40273
  // src/handlers/git.ts
40302
40274
  function assertGuestCwd(ctx, cwd, personaRoot, method) {
40303
40275
  if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
40304
- if (!isGuestPathAllowed(ctx.grants, path45.resolve(cwd), personaRoot, "read")) {
40276
+ if (!isGuestPathAllowed(ctx.grants, path44.resolve(cwd), personaRoot, "read")) {
40305
40277
  throw new ClawdError(
40306
40278
  ERROR_CODES.UNAUTHORIZED,
40307
40279
  `guest ${ctx.principal.id} cannot ${method} cwd ${cwd}`
@@ -40356,7 +40328,7 @@ var DeleteArgsSchema = external_exports.object({
40356
40328
  capabilityId: external_exports.string().min(1)
40357
40329
  }).strict();
40358
40330
  function buildCapabilityHandlers(deps) {
40359
- const { manager, getShareBaseUrl, getPersonalCapability } = deps;
40331
+ const { manager } = deps;
40360
40332
  const list = async () => {
40361
40333
  return {
40362
40334
  response: {
@@ -40382,21 +40354,9 @@ function buildCapabilityHandlers(deps) {
40382
40354
  }
40383
40355
  };
40384
40356
  };
40385
- const personalCapGet = async () => {
40386
- const { token, capability } = getPersonalCapability();
40387
- return {
40388
- response: {
40389
- type: "personal-cap:get:ok",
40390
- token,
40391
- capability: stripSecretHash(capability),
40392
- shareBaseUrl: getShareBaseUrl()
40393
- }
40394
- };
40395
- };
40396
40357
  return {
40397
40358
  "capability:list": list,
40398
- "capability:delete": del,
40399
- "personal-cap:get": personalCapGet
40359
+ "capability:delete": del
40400
40360
  };
40401
40361
  }
40402
40362
 
@@ -40504,16 +40464,7 @@ function ensureOwner(ctx) {
40504
40464
  );
40505
40465
  }
40506
40466
  }
40507
- function ensureGuestCtx(ctx) {
40508
- if (!ctx || ctx.principal.kind !== "guest" || !ctx.capabilityId) {
40509
- throw new ClawdError(
40510
- ERROR_CODES.UNAUTHORIZED,
40511
- "UNAUTHORIZED: received-capability:autoAttach requires guest cap ctx"
40512
- );
40513
- }
40514
- }
40515
40467
  function buildReceivedCapabilityHandlers(deps) {
40516
- const now = deps.now ?? Date.now;
40517
40468
  const list = async (_frame, _client, ctx) => {
40518
40469
  ensureOwner(ctx);
40519
40470
  return {
@@ -40523,47 +40474,6 @@ function buildReceivedCapabilityHandlers(deps) {
40523
40474
  }
40524
40475
  };
40525
40476
  };
40526
- const add = async (frame, _client, ctx) => {
40527
- ensureOwner(ctx);
40528
- const { type: _t, requestId: _r, ...rest } = frame;
40529
- const args = ReceivedCapabilityAddArgsSchema.parse(rest);
40530
- let conn;
40531
- try {
40532
- conn = await deps.connectRemote({
40533
- url: args.remoteUrl,
40534
- token: args.token,
40535
- selfPrincipalId: deps.selfPrincipalId(),
40536
- selfDisplayName: deps.selfDisplayName()
40537
- });
40538
- } catch (e) {
40539
- throw new ClawdError(
40540
- ERROR_CODES.VALIDATION_ERROR,
40541
- `INVITE_REMOTE_UNREACHABLE: ${e.message}`
40542
- );
40543
- }
40544
- try {
40545
- const wh = await conn.whoami();
40546
- const rc = {
40547
- ownerPrincipalId: wh.ownerId,
40548
- ownerDisplayName: wh.displayName,
40549
- remoteUrl: args.remoteUrl,
40550
- capabilityId: wh.capabilityId,
40551
- capabilityToken: args.token,
40552
- grants: wh.grants,
40553
- receivedAt: now()
40554
- };
40555
- deps.store.upsert(rc);
40556
- deps.broadcast({ type: "received-capability:added", receivedCap: rc });
40557
- return {
40558
- response: { type: "received-capability:add:ok", receivedCap: rc }
40559
- };
40560
- } finally {
40561
- try {
40562
- conn.close();
40563
- } catch {
40564
- }
40565
- }
40566
- };
40567
40477
  const remove = async (frame, _client, ctx) => {
40568
40478
  ensureOwner(ctx);
40569
40479
  const { type: _t, requestId: _r, ...rest } = frame;
@@ -40580,30 +40490,9 @@ function buildReceivedCapabilityHandlers(deps) {
40580
40490
  }
40581
40491
  };
40582
40492
  };
40583
- const autoAttach = async (frame, _client, ctx) => {
40584
- ensureGuestCtx(ctx);
40585
- const { type: _t, requestId: _r, ...rest } = frame;
40586
- const args = ReceivedCapabilityAutoAttachArgsSchema.parse(rest);
40587
- const rc = {
40588
- ownerPrincipalId: args.ownerPrincipalId,
40589
- ownerDisplayName: args.ownerDisplayName,
40590
- remoteUrl: args.remoteUrl,
40591
- capabilityId: args.capabilityId,
40592
- capabilityToken: args.token,
40593
- grants: args.grants,
40594
- receivedAt: now()
40595
- };
40596
- deps.store.upsert(rc);
40597
- deps.broadcast({ type: "received-capability:added", receivedCap: rc });
40598
- return {
40599
- response: { type: "received-capability:autoAttach:ok" }
40600
- };
40601
- };
40602
40493
  return {
40603
40494
  "received-capability:list": list,
40604
- "received-capability:add": add,
40605
- "received-capability:remove": remove,
40606
- "received-capability:autoAttach": autoAttach
40495
+ "received-capability:remove": remove
40607
40496
  };
40608
40497
  }
40609
40498
 
@@ -40625,14 +40514,13 @@ function buildWhoamiHandler(deps) {
40625
40514
  usedCount: 0
40626
40515
  };
40627
40516
  } else {
40628
- if (!ctx.capabilityId) {
40629
- throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "whoami: guest ctx without capabilityId");
40630
- }
40631
- const cap = deps.capabilityManager.findById(ctx.capabilityId);
40632
- if (!cap) {
40633
- throw new ClawdError(ERROR_CODES.UNAUTHORIZED, `whoami: capability not found: ${ctx.capabilityId}`);
40634
- }
40635
- capability = stripSecretHash(cap);
40517
+ capability = {
40518
+ id: ctx.principal.id,
40519
+ displayName: ctx.principal.displayName ?? "guest",
40520
+ grants: ctx.grants,
40521
+ issuedAt: 0,
40522
+ usedCount: 0
40523
+ };
40636
40524
  }
40637
40525
  const grantedPersonas = [];
40638
40526
  const isGuest = ctx.principal.kind === "guest";
@@ -40693,6 +40581,87 @@ function buildFeishuAuthHandlers(deps) {
40693
40581
  };
40694
40582
  }
40695
40583
 
40584
+ // src/handlers/hub.ts
40585
+ init_protocol();
40586
+ function ensureOwner2(ctx) {
40587
+ if (!ctx || ctx.principal.kind !== "owner") {
40588
+ throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "UNAUTHORIZED: hub:* requires owner ctx");
40589
+ }
40590
+ }
40591
+ function buildHubHandlers(deps) {
40592
+ const now = deps.now ?? Date.now;
40593
+ const list = async (_frame, _client, ctx) => {
40594
+ ensureOwner2(ctx);
40595
+ const hubs = await deps.listHubs();
40596
+ return {
40597
+ response: { type: "hub:list:ok", hubs }
40598
+ };
40599
+ };
40600
+ const connect = async (frame, _client, ctx) => {
40601
+ ensureOwner2(ctx);
40602
+ const { type: _t, requestId: _r, ...rest } = frame;
40603
+ const args = HubConnectArgsSchema.parse(rest);
40604
+ const exchanged = await deps.exchange(args.hubId);
40605
+ const url = args.url ?? exchanged.hubUrl;
40606
+ if (!url) {
40607
+ throw new ClawdError(
40608
+ ERROR_CODES.VALIDATION_ERROR,
40609
+ "HUB_URL_UNKNOWN: hub \u672A\u4E0A\u62A5\u5730\u5740\uFF08\u5BF9\u65B9 daemon \u672A\u767B\u5F55\u6216\u672A\u5F00 tunnel\uFF09\uFF0C\u65E0\u6CD5\u8FDE\u63A5"
40610
+ );
40611
+ }
40612
+ let conn;
40613
+ try {
40614
+ conn = await deps.connectRemote({
40615
+ url,
40616
+ token: exchanged.token,
40617
+ selfPrincipalId: deps.selfPrincipalId(),
40618
+ selfDisplayName: deps.selfDisplayName(),
40619
+ // Phase 2 自动反向(spec 决策 #11):自报本机可达地址,让 hub 反向加我为联系人;
40620
+ // 本机无 tunnel → null → connectRemote 省略 selfUrl 字段(不造假数据)
40621
+ selfUrl: deps.selfUrl() ?? void 0
40622
+ });
40623
+ } catch (e) {
40624
+ throw new ClawdError(
40625
+ ERROR_CODES.VALIDATION_ERROR,
40626
+ `HUB_UNREACHABLE: ${e.message}`
40627
+ );
40628
+ }
40629
+ try {
40630
+ const wh = await conn.whoami();
40631
+ const rc = {
40632
+ ownerPrincipalId: wh.ownerId,
40633
+ ownerDisplayName: wh.displayName || args.name || args.hubId,
40634
+ remoteUrl: url,
40635
+ capabilityId: wh.capabilityId,
40636
+ // Phase 2:connectToken 存中心 server 签发的 connect token(断线重连复用,
40637
+ // 不需要重新换票——token 长期有效,撤销由中心 server introspect 兜底)
40638
+ connectToken: exchanged.token,
40639
+ grants: wh.grants,
40640
+ receivedAt: now()
40641
+ };
40642
+ deps.store.upsert(rc);
40643
+ deps.broadcast({ type: "received-capability:added", receivedCap: rc });
40644
+ return {
40645
+ response: {
40646
+ type: "hub:connect:ok",
40647
+ hubId: wh.ownerId,
40648
+ name: rc.ownerDisplayName,
40649
+ url
40650
+ }
40651
+ };
40652
+ } finally {
40653
+ try {
40654
+ conn.close();
40655
+ } catch {
40656
+ }
40657
+ }
40658
+ };
40659
+ return {
40660
+ "hub:list": list,
40661
+ "hub:connect": connect
40662
+ };
40663
+ }
40664
+
40696
40665
  // src/handlers/meta.ts
40697
40666
  var import_node_os15 = __toESM(require("os"), 1);
40698
40667
  init_protocol();
@@ -40799,14 +40768,8 @@ function buildPersonaHandlers(deps) {
40799
40768
  const update = async (frame) => {
40800
40769
  const { type: _type, requestId: _requestId, ...rest } = frame;
40801
40770
  const args = PersonaUpdateArgsSchema.parse(rest);
40802
- const { personality, networkAllowedDomains, ...metaPatch } = args.patch;
40803
- let persona = personaManager.update(args.personaId, metaPatch, personality);
40804
- if (networkAllowedDomains !== void 0) {
40805
- persona = personaManager.updateNetworkAllowedDomains(
40806
- args.personaId,
40807
- networkAllowedDomains
40808
- );
40809
- }
40771
+ const { personality, ...metaPatch } = args.patch;
40772
+ const persona = personaManager.update(args.personaId, metaPatch, personality);
40810
40773
  return { response: { type: "persona:info", ...persona } };
40811
40774
  };
40812
40775
  const del = async (frame) => {
@@ -40826,7 +40789,7 @@ function buildPersonaHandlers(deps) {
40826
40789
  }
40827
40790
 
40828
40791
  // src/handlers/attachment.ts
40829
- var import_node_path34 = __toESM(require("path"), 1);
40792
+ var import_node_path33 = __toESM(require("path"), 1);
40830
40793
  init_protocol();
40831
40794
  init_protocol();
40832
40795
  var DEFAULT_TTL_SECONDS = 24 * 3600;
@@ -40880,12 +40843,12 @@ function buildAttachmentHandlers(deps) {
40880
40843
  `session ${args.sessionId} scope unresolved`
40881
40844
  );
40882
40845
  }
40883
- const cwdAbs = import_node_path34.default.resolve(sessionFile.cwd);
40884
- const candidateAbs = import_node_path34.default.isAbsolute(args.relPath) ? import_node_path34.default.resolve(args.relPath) : import_node_path34.default.resolve(cwdAbs, args.relPath);
40846
+ const cwdAbs = import_node_path33.default.resolve(sessionFile.cwd);
40847
+ const candidateAbs = import_node_path33.default.isAbsolute(args.relPath) ? import_node_path33.default.resolve(args.relPath) : import_node_path33.default.resolve(cwdAbs, args.relPath);
40885
40848
  assertGuestAttachmentPath(ctx, candidateAbs, deps.personaRoot, "attachment.signUrl");
40886
40849
  const entries = deps.groupFileStore.list(scope, args.sessionId);
40887
40850
  const entry = entries.find((e) => {
40888
- const storedAbs = import_node_path34.default.isAbsolute(e.relPath) ? import_node_path34.default.resolve(e.relPath) : import_node_path34.default.resolve(cwdAbs, e.relPath);
40851
+ const storedAbs = import_node_path33.default.isAbsolute(e.relPath) ? import_node_path33.default.resolve(e.relPath) : import_node_path33.default.resolve(cwdAbs, e.relPath);
40889
40852
  return storedAbs === candidateAbs && !e.stale;
40890
40853
  });
40891
40854
  if (!entry) {
@@ -40909,7 +40872,7 @@ function buildAttachmentHandlers(deps) {
40909
40872
  if (!ctx || ctx.principal.kind !== "guest" || !deps.personaRoot || !deps.sessionStore) return;
40910
40873
  const f = deps.sessionStore.read(sessionId);
40911
40874
  if (!f) return;
40912
- assertGuestAttachmentPath(ctx, import_node_path34.default.resolve(f.cwd), deps.personaRoot, method);
40875
+ assertGuestAttachmentPath(ctx, import_node_path33.default.resolve(f.cwd), deps.personaRoot, method);
40913
40876
  }
40914
40877
  const groupAdd = async (frame, _client, ctx) => {
40915
40878
  if (!deps.groupFileStore || !deps.getSessionScope) {
@@ -40981,19 +40944,19 @@ function buildAttachmentHandlers(deps) {
40981
40944
 
40982
40945
  // src/handlers/extension.ts
40983
40946
  var import_promises7 = __toESM(require("fs/promises"), 1);
40984
- var import_node_path39 = __toESM(require("path"), 1);
40947
+ var import_node_path38 = __toESM(require("path"), 1);
40985
40948
  init_protocol();
40986
40949
 
40987
40950
  // src/extension/bundle-zip.ts
40988
40951
  var import_promises4 = __toESM(require("fs/promises"), 1);
40989
- var import_node_path35 = __toESM(require("path"), 1);
40990
- var import_node_crypto12 = __toESM(require("crypto"), 1);
40952
+ var import_node_path34 = __toESM(require("path"), 1);
40953
+ var import_node_crypto11 = __toESM(require("crypto"), 1);
40991
40954
  var import_jszip2 = __toESM(require_lib3(), 1);
40992
40955
  async function bundleExtensionDir(dir) {
40993
40956
  const entries = await listFilesSorted(dir);
40994
40957
  const zip = new import_jszip2.default();
40995
40958
  for (const rel of entries) {
40996
- const abs = import_node_path35.default.join(dir, rel);
40959
+ const abs = import_node_path34.default.join(dir, rel);
40997
40960
  const content = await import_promises4.default.readFile(abs);
40998
40961
  zip.file(rel, content, { date: FIXED_DATE });
40999
40962
  }
@@ -41002,7 +40965,7 @@ async function bundleExtensionDir(dir) {
41002
40965
  compression: "DEFLATE",
41003
40966
  compressionOptions: { level: 6 }
41004
40967
  });
41005
- const sha256 = import_node_crypto12.default.createHash("sha256").update(buffer).digest("hex");
40968
+ const sha256 = import_node_crypto11.default.createHash("sha256").update(buffer).digest("hex");
41006
40969
  return { buffer, sha256 };
41007
40970
  }
41008
40971
  var FIXED_DATE = /* @__PURE__ */ new Date("2020-01-01T00:00:00.000Z");
@@ -41014,7 +40977,7 @@ async function listFilesSorted(rootDir) {
41014
40977
  return out;
41015
40978
  }
41016
40979
  async function walk(absRoot, relPrefix, out) {
41017
- const dirAbs = import_node_path35.default.join(absRoot, relPrefix);
40980
+ const dirAbs = import_node_path34.default.join(absRoot, relPrefix);
41018
40981
  const entries = await import_promises4.default.readdir(dirAbs, { withFileTypes: true });
41019
40982
  for (const e of entries) {
41020
40983
  if (IGNORE_BASENAMES.has(e.name)) continue;
@@ -41068,25 +41031,25 @@ function computePublishCheck(args) {
41068
41031
 
41069
41032
  // src/extension/install-flow.ts
41070
41033
  var import_promises5 = __toESM(require("fs/promises"), 1);
41071
- var import_node_path37 = __toESM(require("path"), 1);
41034
+ var import_node_path36 = __toESM(require("path"), 1);
41072
41035
  var import_node_os17 = __toESM(require("os"), 1);
41073
- var import_node_crypto13 = __toESM(require("crypto"), 1);
41036
+ var import_node_crypto12 = __toESM(require("crypto"), 1);
41074
41037
  var import_jszip3 = __toESM(require_lib3(), 1);
41075
41038
 
41076
41039
  // src/extension/paths.ts
41077
41040
  var import_node_os16 = __toESM(require("os"), 1);
41078
- var import_node_path36 = __toESM(require("path"), 1);
41041
+ var import_node_path35 = __toESM(require("path"), 1);
41079
41042
  function clawdHomeRoot(override) {
41080
- return override ?? process.env.CLAWD_HOME ?? import_node_path36.default.join(import_node_os16.default.homedir(), ".clawd");
41043
+ return override ?? process.env.CLAWD_HOME ?? import_node_path35.default.join(import_node_os16.default.homedir(), ".clawd");
41081
41044
  }
41082
41045
  function extensionsRoot(override) {
41083
- return import_node_path36.default.join(clawdHomeRoot(override), "extensions");
41046
+ return import_node_path35.default.join(clawdHomeRoot(override), "extensions");
41084
41047
  }
41085
41048
  function publishedChannelsFile(override) {
41086
- return import_node_path36.default.join(clawdHomeRoot(override), "extensions-published.json");
41049
+ return import_node_path35.default.join(clawdHomeRoot(override), "extensions-published.json");
41087
41050
  }
41088
41051
  function bundleCacheRoot(override) {
41089
- return import_node_path36.default.join(clawdHomeRoot(override), "extension-bundles");
41052
+ return import_node_path35.default.join(clawdHomeRoot(override), "extension-bundles");
41090
41053
  }
41091
41054
 
41092
41055
  // src/extension/install-flow.ts
@@ -41099,7 +41062,7 @@ var InstallError = class extends Error {
41099
41062
  };
41100
41063
  async function installFromChannel(args, deps) {
41101
41064
  const { channelRef, snapshotHash, bundleZip } = args;
41102
- const computed = import_node_crypto13.default.createHash("sha256").update(bundleZip).digest("hex");
41065
+ const computed = import_node_crypto12.default.createHash("sha256").update(bundleZip).digest("hex");
41103
41066
  if (computed !== snapshotHash) {
41104
41067
  throw new InstallError(
41105
41068
  "HASH_MISMATCH",
@@ -41113,7 +41076,7 @@ async function installFromChannel(args, deps) {
41113
41076
  throw new InstallError("ZIP_INVALID", `failed to load zip: ${e.message}`);
41114
41077
  }
41115
41078
  for (const name of Object.keys(zip.files)) {
41116
- if (name.includes("..") || name.startsWith("/") || import_node_path37.default.isAbsolute(name)) {
41079
+ if (name.includes("..") || name.startsWith("/") || import_node_path36.default.isAbsolute(name)) {
41117
41080
  throw new InstallError("ZIP_INVALID", `unsafe zip entry: ${name}`);
41118
41081
  }
41119
41082
  }
@@ -41145,7 +41108,7 @@ async function installFromChannel(args, deps) {
41145
41108
  );
41146
41109
  }
41147
41110
  const localExtId = namespacedExtId(ownerSlug, channelRef.ownerPrincipalId);
41148
- const destDir = import_node_path37.default.join(deps.extensionsRoot, localExtId);
41111
+ const destDir = import_node_path36.default.join(deps.extensionsRoot, localExtId);
41149
41112
  let destExists = false;
41150
41113
  try {
41151
41114
  await import_promises5.default.access(destDir);
@@ -41159,16 +41122,16 @@ async function installFromChannel(args, deps) {
41159
41122
  );
41160
41123
  }
41161
41124
  const stage = await import_promises5.default.mkdtemp(
41162
- import_node_path37.default.join(import_node_os17.default.tmpdir(), `clawd-ext-install-${localExtId}-`)
41125
+ import_node_path36.default.join(import_node_os17.default.tmpdir(), `clawd-ext-install-${localExtId}-`)
41163
41126
  );
41164
41127
  try {
41165
41128
  for (const [name, entry] of Object.entries(zip.files)) {
41166
- const dest = import_node_path37.default.join(stage, name);
41129
+ const dest = import_node_path36.default.join(stage, name);
41167
41130
  if (entry.dir) {
41168
41131
  await import_promises5.default.mkdir(dest, { recursive: true });
41169
41132
  continue;
41170
41133
  }
41171
- await import_promises5.default.mkdir(import_node_path37.default.dirname(dest), { recursive: true });
41134
+ await import_promises5.default.mkdir(import_node_path36.default.dirname(dest), { recursive: true });
41172
41135
  if (name === "manifest.json") {
41173
41136
  const rewritten = { ...parsed.data, id: localExtId };
41174
41137
  await import_promises5.default.writeFile(dest, JSON.stringify(rewritten, null, 2));
@@ -41189,9 +41152,9 @@ async function installFromChannel(args, deps) {
41189
41152
 
41190
41153
  // src/extension/update-flow.ts
41191
41154
  var import_promises6 = __toESM(require("fs/promises"), 1);
41192
- var import_node_path38 = __toESM(require("path"), 1);
41155
+ var import_node_path37 = __toESM(require("path"), 1);
41193
41156
  var import_node_os18 = __toESM(require("os"), 1);
41194
- var import_node_crypto14 = __toESM(require("crypto"), 1);
41157
+ var import_node_crypto13 = __toESM(require("crypto"), 1);
41195
41158
  var import_jszip4 = __toESM(require_lib3(), 1);
41196
41159
  var UpdateError = class extends Error {
41197
41160
  constructor(code, message) {
@@ -41206,11 +41169,11 @@ async function updateFromChannel(args, deps) {
41206
41169
  channelRef.extId,
41207
41170
  channelRef.ownerPrincipalId
41208
41171
  );
41209
- const liveDir = import_node_path38.default.join(deps.extensionsRoot, localExtId);
41172
+ const liveDir = import_node_path37.default.join(deps.extensionsRoot, localExtId);
41210
41173
  const prevDir = `${liveDir}.prev`;
41211
41174
  let existingVersion;
41212
41175
  try {
41213
- const raw = await import_promises6.default.readFile(import_node_path38.default.join(liveDir, "manifest.json"), "utf8");
41176
+ const raw = await import_promises6.default.readFile(import_node_path37.default.join(liveDir, "manifest.json"), "utf8");
41214
41177
  const parsed2 = ExtensionManifestSchema.safeParse(JSON.parse(raw));
41215
41178
  if (!parsed2.success) {
41216
41179
  throw new UpdateError(
@@ -41229,7 +41192,7 @@ async function updateFromChannel(args, deps) {
41229
41192
  if (e instanceof UpdateError) throw e;
41230
41193
  throw e;
41231
41194
  }
41232
- const computed = import_node_crypto14.default.createHash("sha256").update(bundleZip).digest("hex");
41195
+ const computed = import_node_crypto13.default.createHash("sha256").update(bundleZip).digest("hex");
41233
41196
  if (computed !== snapshotHash) {
41234
41197
  throw new UpdateError(
41235
41198
  "HASH_MISMATCH",
@@ -41243,7 +41206,7 @@ async function updateFromChannel(args, deps) {
41243
41206
  throw new UpdateError("ZIP_INVALID", `failed to load zip: ${e.message}`);
41244
41207
  }
41245
41208
  for (const name of Object.keys(zip.files)) {
41246
- if (name.includes("..") || name.startsWith("/") || import_node_path38.default.isAbsolute(name)) {
41209
+ if (name.includes("..") || name.startsWith("/") || import_node_path37.default.isAbsolute(name)) {
41247
41210
  throw new UpdateError("ZIP_INVALID", `unsafe zip entry: ${name}`);
41248
41211
  }
41249
41212
  }
@@ -41278,16 +41241,16 @@ async function updateFromChannel(args, deps) {
41278
41241
  await import_promises6.default.rm(prevDir, { recursive: true, force: true });
41279
41242
  await import_promises6.default.rename(liveDir, prevDir);
41280
41243
  const stage = await import_promises6.default.mkdtemp(
41281
- import_node_path38.default.join(import_node_os18.default.tmpdir(), `clawd-ext-update-${localExtId}-`)
41244
+ import_node_path37.default.join(import_node_os18.default.tmpdir(), `clawd-ext-update-${localExtId}-`)
41282
41245
  );
41283
41246
  try {
41284
41247
  for (const [name, entry] of Object.entries(zip.files)) {
41285
- const dest = import_node_path38.default.join(stage, name);
41248
+ const dest = import_node_path37.default.join(stage, name);
41286
41249
  if (entry.dir) {
41287
41250
  await import_promises6.default.mkdir(dest, { recursive: true });
41288
41251
  continue;
41289
41252
  }
41290
- await import_promises6.default.mkdir(import_node_path38.default.dirname(dest), { recursive: true });
41253
+ await import_promises6.default.mkdir(import_node_path37.default.dirname(dest), { recursive: true });
41291
41254
  if (name === "manifest.json") {
41292
41255
  const rewritten = { ...parsed.data, id: localExtId };
41293
41256
  await import_promises6.default.writeFile(dest, JSON.stringify(rewritten, null, 2));
@@ -41380,7 +41343,7 @@ async function rewriteManifestVersion(root, extId, newVersion, previousPublished
41380
41343
  );
41381
41344
  }
41382
41345
  }
41383
- const manifestPath = import_node_path39.default.join(root, extId, "manifest.json");
41346
+ const manifestPath = import_node_path38.default.join(root, extId, "manifest.json");
41384
41347
  const manifest = await readManifest(root, extId);
41385
41348
  const next = { ...manifest, version: newVersion };
41386
41349
  const tmp = `${manifestPath}.tmp`;
@@ -41388,7 +41351,7 @@ async function rewriteManifestVersion(root, extId, newVersion, previousPublished
41388
41351
  await import_promises7.default.rename(tmp, manifestPath);
41389
41352
  }
41390
41353
  async function readManifest(root, extId) {
41391
- const file = import_node_path39.default.join(root, extId, "manifest.json");
41354
+ const file = import_node_path38.default.join(root, extId, "manifest.json");
41392
41355
  let raw;
41393
41356
  try {
41394
41357
  raw = await import_promises7.default.readFile(file, "utf8");
@@ -41479,7 +41442,7 @@ function buildExtensionHandlers(deps) {
41479
41442
  };
41480
41443
  async function buildSnapshotMeta(extId) {
41481
41444
  const manifest = await readManifest(deps.root, extId);
41482
- const { sha256, buffer } = await bundleExtensionDir(import_node_path39.default.join(deps.root, extId));
41445
+ const { sha256, buffer } = await bundleExtensionDir(import_node_path38.default.join(deps.root, extId));
41483
41446
  return { manifest, contentHash: sha256, buffer };
41484
41447
  }
41485
41448
  const publish = async (frame, _client, ctx) => {
@@ -41661,7 +41624,7 @@ function buildExtensionHandlers(deps) {
41661
41624
 
41662
41625
  // src/extension/registry.ts
41663
41626
  var import_promises8 = __toESM(require("fs/promises"), 1);
41664
- var import_node_path40 = __toESM(require("path"), 1);
41627
+ var import_node_path39 = __toESM(require("path"), 1);
41665
41628
  async function loadAll(root) {
41666
41629
  let entries;
41667
41630
  try {
@@ -41674,13 +41637,13 @@ async function loadAll(root) {
41674
41637
  for (const ent of entries) {
41675
41638
  if (!ent.isDirectory()) continue;
41676
41639
  if (ent.name.startsWith(".")) continue;
41677
- records.push(await loadOne(import_node_path40.default.join(root, ent.name), ent.name));
41640
+ records.push(await loadOne(import_node_path39.default.join(root, ent.name), ent.name));
41678
41641
  }
41679
41642
  records.sort((a, b2) => a.extId < b2.extId ? -1 : a.extId > b2.extId ? 1 : 0);
41680
41643
  return records;
41681
41644
  }
41682
41645
  async function loadOne(dir, dirName) {
41683
- const manifestPath = import_node_path40.default.join(dir, "manifest.json");
41646
+ const manifestPath = import_node_path39.default.join(dir, "manifest.json");
41684
41647
  let raw;
41685
41648
  try {
41686
41649
  raw = await import_promises8.default.readFile(manifestPath, "utf8");
@@ -41725,7 +41688,7 @@ async function loadOne(dir, dirName) {
41725
41688
 
41726
41689
  // src/extension/uninstall.ts
41727
41690
  var import_promises9 = __toESM(require("fs/promises"), 1);
41728
- var import_node_path41 = __toESM(require("path"), 1);
41691
+ var import_node_path40 = __toESM(require("path"), 1);
41729
41692
  var UninstallError = class extends Error {
41730
41693
  constructor(code, message) {
41731
41694
  super(message);
@@ -41734,7 +41697,7 @@ var UninstallError = class extends Error {
41734
41697
  code;
41735
41698
  };
41736
41699
  async function uninstall(deps) {
41737
- const dir = import_node_path41.default.join(deps.root, deps.extId);
41700
+ const dir = import_node_path40.default.join(deps.root, deps.extId);
41738
41701
  try {
41739
41702
  await import_promises9.default.access(dir);
41740
41703
  } catch {
@@ -41759,9 +41722,7 @@ function buildMethodHandlers(deps) {
41759
41722
  personaRegistry: deps.personaRegistry
41760
41723
  }),
41761
41724
  ...buildCapabilityHandlers({
41762
- manager: deps.capabilityManager,
41763
- getShareBaseUrl: deps.getShareBaseUrl,
41764
- getPersonalCapability: deps.getPersonalCapability
41725
+ manager: deps.capabilityManager
41765
41726
  }),
41766
41727
  ...buildInboxHandlers({
41767
41728
  manager: deps.inboxManager,
@@ -41769,10 +41730,7 @@ function buildMethodHandlers(deps) {
41769
41730
  }),
41770
41731
  ...buildReceivedCapabilityHandlers({
41771
41732
  store: deps.receivedCapabilityStore,
41772
- connectRemote: deps.connectRemote,
41773
- broadcast: deps.broadcastToOwners,
41774
- selfPrincipalId: () => deps.ownerPrincipalId,
41775
- selfDisplayName: () => deps.ownerDisplayName
41733
+ broadcast: deps.broadcastToOwners
41776
41734
  }),
41777
41735
  whoami: buildWhoamiHandler({
41778
41736
  ownerDisplayName: deps.ownerDisplayName,
@@ -41781,6 +41739,7 @@ function buildMethodHandlers(deps) {
41781
41739
  capabilityManager: deps.capabilityManager
41782
41740
  }),
41783
41741
  ...buildFeishuAuthHandlers(deps.feishuAuth),
41742
+ ...buildHubHandlers(deps.feishuHub),
41784
41743
  ...deps.attachment ? buildAttachmentHandlers(deps.attachment) : {},
41785
41744
  ...buildExtensionHandlers({
41786
41745
  loadAll,
@@ -41809,23 +41768,22 @@ var METHOD_GRANT_MAP = {
41809
41768
  // "查 capabilityManager 拿 caller 的 guest capability"。
41810
41769
  "whoami": { kind: "public" },
41811
41770
  // ---- capability platform(admin-only) ----
41812
- // global personal token (2026-05-25): capability:issue / bindPeer 已下线;
41813
- // personal capability daemon 启动时自动创建,personal-cap:get owner-only token + shareBaseUrl
41771
+ // 飞书统一身份 Phase 2 (2026-06-01, spec 决策 #12): personal-cap:get / capability:issue /
41772
+ // bindPeer 已下线(personal token / 邀请码全链路删除)。capability:list / delete 保留供调试 +
41773
+ // 撤销级联 + 清旧 per-peer cap。
41814
41774
  "capability:list": ADMIN_ANY,
41815
41775
  "capability:delete": ADMIN_ANY,
41816
- "personal-cap:get": ADMIN_ANY,
41817
41776
  // ---- inbox: channel-based IM (capability platform v3) ----
41818
41777
  // 三条都 'public' — capability 本身就是授权凭证. handler 内额外校验
41819
41778
  // guest ctx.capabilityId === args.capabilityId, 防 guest 越权操作别的 channel.
41820
41779
  "inbox:list": { kind: "public" },
41821
41780
  "inbox:markRead": { kind: "public" },
41822
41781
  "inbox:postMessage": { kind: "public" },
41823
- // ---- received-capability:* (bidirectional cap 视角 B 2026-05-23) ----
41824
- // owner-only: list / add / remove;guest-only: autoAttach (capability 本身是凭证)
41782
+ // ---- received-capability:* (联系人列表,视角 B 双向授权模型) ----
41783
+ // 飞书统一身份 Phase 2 (spec 决策 #12): owner-only add / guest-only autoAttach 已删除
41784
+ // (hub:connect 落同一 store + 自动反向替代)。list / remove 保留作联系人读 / 删。
41825
41785
  "received-capability:list": ADMIN_ANY,
41826
- "received-capability:add": ADMIN_ANY,
41827
41786
  "received-capability:remove": ADMIN_ANY,
41828
- "received-capability:autoAttach": { kind: "public" },
41829
41787
  // ---- session:* / chat:* 业务方法(v2 Phase 8 两层模型)----
41830
41788
  // dispatcher 不验资源,handler 内按 ctx + frame.args 反查 ownerPersonaId 调 assertGrant。
41831
41789
  // owner 自动通过(ctx 自带 '*':'admin' grant 一切 match);guest 在被授权 persona 内可调。
@@ -41948,7 +41906,7 @@ function computeGrantForFrame(method, frame) {
41948
41906
 
41949
41907
  // src/extension/runtime.ts
41950
41908
  var import_node_child_process7 = require("child_process");
41951
- var import_node_path42 = __toESM(require("path"), 1);
41909
+ var import_node_path41 = __toESM(require("path"), 1);
41952
41910
  var import_promises10 = require("timers/promises");
41953
41911
 
41954
41912
  // src/extension/port-allocator.ts
@@ -42049,7 +42007,7 @@ var Runtime = class {
42049
42007
  /\$CLAWOS_EXT_PORT/g,
42050
42008
  String(port)
42051
42009
  );
42052
- const dir = import_node_path42.default.join(this.root, extId);
42010
+ const dir = import_node_path41.default.join(this.root, extId);
42053
42011
  const env = {
42054
42012
  ...process.env,
42055
42013
  CLAWOS_EXT_PORT: String(port),
@@ -42161,7 +42119,7 @@ ${handle.stderrTail}`
42161
42119
 
42162
42120
  // src/extension/published-channels.ts
42163
42121
  var import_promises11 = __toESM(require("fs/promises"), 1);
42164
- var import_node_path43 = __toESM(require("path"), 1);
42122
+ var import_node_path42 = __toESM(require("path"), 1);
42165
42123
  init_zod();
42166
42124
  var PublishedChannelsError = class extends Error {
42167
42125
  constructor(code, message) {
@@ -42260,7 +42218,7 @@ var PublishedChannelStore = class {
42260
42218
  )
42261
42219
  };
42262
42220
  const tmp = `${this.filePath}.tmp`;
42263
- await import_promises11.default.mkdir(import_node_path43.default.dirname(this.filePath), { recursive: true });
42221
+ await import_promises11.default.mkdir(import_node_path42.default.dirname(this.filePath), { recursive: true });
42264
42222
  await import_promises11.default.writeFile(tmp, JSON.stringify(data, null, 2), { mode: 384 });
42265
42223
  await import_promises11.default.rename(tmp, this.filePath);
42266
42224
  }
@@ -42268,7 +42226,7 @@ var PublishedChannelStore = class {
42268
42226
 
42269
42227
  // src/extension/bundle-cache.ts
42270
42228
  var import_promises12 = __toESM(require("fs/promises"), 1);
42271
- var import_node_path44 = __toESM(require("path"), 1);
42229
+ var import_node_path43 = __toESM(require("path"), 1);
42272
42230
  var BundleCache = class {
42273
42231
  constructor(rootDir) {
42274
42232
  this.rootDir = rootDir;
@@ -42277,14 +42235,14 @@ var BundleCache = class {
42277
42235
  /** Atomic write: stage tmp → rename. Caller passes the hex sha256. */
42278
42236
  async write(snapshotHash, buffer) {
42279
42237
  await import_promises12.default.mkdir(this.rootDir, { recursive: true });
42280
- const file = import_node_path44.default.join(this.rootDir, `${snapshotHash}.zip`);
42238
+ const file = import_node_path43.default.join(this.rootDir, `${snapshotHash}.zip`);
42281
42239
  const tmp = `${file}.tmp`;
42282
42240
  await import_promises12.default.writeFile(tmp, buffer, { mode: 384 });
42283
42241
  await import_promises12.default.rename(tmp, file);
42284
42242
  }
42285
42243
  /** Returns the bundle bytes, or null when the file doesn't exist. */
42286
42244
  async read(snapshotHash) {
42287
- const file = import_node_path44.default.join(this.rootDir, `${snapshotHash}.zip`);
42245
+ const file = import_node_path43.default.join(this.rootDir, `${snapshotHash}.zip`);
42288
42246
  try {
42289
42247
  return await import_promises12.default.readFile(file);
42290
42248
  } catch (e) {
@@ -42294,7 +42252,7 @@ var BundleCache = class {
42294
42252
  }
42295
42253
  /** Idempotent — missing file is not an error. */
42296
42254
  async delete(snapshotHash) {
42297
- const file = import_node_path44.default.join(this.rootDir, `${snapshotHash}.zip`);
42255
+ const file = import_node_path43.default.join(this.rootDir, `${snapshotHash}.zip`);
42298
42256
  await import_promises12.default.rm(file, { force: true });
42299
42257
  }
42300
42258
  };
@@ -42303,7 +42261,7 @@ var BundleCache = class {
42303
42261
  async function startDaemon(config) {
42304
42262
  const logger = createLogger({
42305
42263
  level: config.logLevel,
42306
- file: import_node_path45.default.join(config.dataDir, "clawd.log")
42264
+ file: import_node_path44.default.join(config.dataDir, "clawd.log")
42307
42265
  });
42308
42266
  logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
42309
42267
  const stateMgr = new StateFileManager({ dataDir: config.dataDir });
@@ -42335,8 +42293,6 @@ async function startDaemon(config) {
42335
42293
  receivedCapabilityStore.load();
42336
42294
  const capabilityStore = new CapabilityStore(config.dataDir);
42337
42295
  const capabilityRegistry = new CapabilityRegistry(capabilityStore);
42338
- const personalCapability = loadOrCreatePersonalCapability({ dataDir: config.dataDir });
42339
- capabilityRegistry.upsertCapability(personalCapability.capability);
42340
42296
  const capabilityManager = new CapabilityManager(capabilityRegistry, {
42341
42297
  onDeleted: (cap) => {
42342
42298
  const deletedAt = Date.now();
@@ -42362,17 +42318,36 @@ async function startDaemon(config) {
42362
42318
  // Task 1.7:authenticate 注入路径替代 expectedToken 单 token 比对。
42363
42319
  // owner 路径 constantTimeEqual 防侧信道;guest 路径走 capabilityRegistry.
42364
42320
  expectedToken: resolvedAuthToken,
42365
- authenticate: (t, selfPrincipalId, selfDisplayName) => {
42366
- return authenticate(t, {
42321
+ authenticate: async (t, _selfPrincipalId, _selfDisplayName, selfUrl) => {
42322
+ const result = await authenticate(t, {
42367
42323
  isOwnerToken: (x) => resolvedAuthToken != null && constantTimeEqual(x, resolvedAuthToken),
42368
42324
  ownerPrincipalId,
42369
42325
  ownerDisplayName,
42370
- // 飞书 gate(spec 决策 #9):未激活飞书身份时不接受 guest(capability token)入站——
42371
- // 不传 registry guest token 一律 BAD_TOKEN。owner 自己连接不受影响。
42372
- ...feishuActive ? { capabilityRegistry } : {},
42373
- selfPrincipalId,
42374
- selfDisplayName
42326
+ // 飞书 gate(spec 决策 #9):未激活飞书身份时不接受 guest 入站(不注入 introspect
42327
+ // 一律 BAD_TOKEN)。已激活时 hub 用自己的 ttcJwt 做验票方认证;
42328
+ // ttcJwt 每次验票时从 store 现读(热切换后立即用新身份)。
42329
+ ...feishuActive ? {
42330
+ introspectConnectToken: async (token) => {
42331
+ const record = ownerIdentityStore.read();
42332
+ if (!record) return { active: false };
42333
+ return centralIntrospect({
42334
+ api: config.clawosApi ?? DEFAULT_CLAWOS_API,
42335
+ hubTtcJwt: record.ttcToken,
42336
+ token
42337
+ });
42338
+ }
42339
+ } : {}
42375
42340
  });
42341
+ if (result.ok && result.context.principal.kind === "guest") {
42342
+ autoReverseContact({
42343
+ store: receivedCapabilityStore,
42344
+ broadcast: (frame) => wsServer?.broadcastToOwners(frame),
42345
+ unionId: result.context.principal.id,
42346
+ displayName: result.context.principal.displayName ?? result.context.principal.id,
42347
+ selfUrl
42348
+ });
42349
+ }
42350
+ return result;
42376
42351
  },
42377
42352
  onAuthed: (h, ctx) => wsServer?.attachClientContext(h.id, ctx),
42378
42353
  buildOwnerContext: () => ownerContext(ownerPrincipalId, ownerDisplayName),
@@ -42395,7 +42370,7 @@ async function startDaemon(config) {
42395
42370
  const agents = new AgentsScanner();
42396
42371
  const history = new ClaudeHistoryReader();
42397
42372
  let transport = null;
42398
- const personaStore = new PersonaStore(import_node_path45.default.join(config.dataDir, "personas"));
42373
+ const personaStore = new PersonaStore(import_node_path44.default.join(config.dataDir, "personas"));
42399
42374
  const defaultsRoot = findDefaultsRoot();
42400
42375
  if (defaultsRoot) {
42401
42376
  seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
@@ -42412,7 +42387,7 @@ async function startDaemon(config) {
42412
42387
  getAdapter,
42413
42388
  historyReader: history,
42414
42389
  dataDir: config.dataDir,
42415
- personaRoot: import_node_path45.default.join(config.dataDir, "personas"),
42390
+ personaRoot: import_node_path44.default.join(config.dataDir, "personas"),
42416
42391
  personaStore,
42417
42392
  ownerDisplayName,
42418
42393
  ownerPrincipalId,
@@ -42436,10 +42411,10 @@ async function startDaemon(config) {
42436
42411
  // 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
42437
42412
  attachmentGroup: {
42438
42413
  onFileEdit: (input) => {
42439
- const absPath = import_node_path45.default.isAbsolute(input.relPath) ? input.relPath : import_node_path45.default.join(input.cwd, input.relPath);
42414
+ const absPath = import_node_path44.default.isAbsolute(input.relPath) ? input.relPath : import_node_path44.default.join(input.cwd, input.relPath);
42440
42415
  let size = 0;
42441
42416
  try {
42442
- size = import_node_fs30.default.statSync(absPath).size;
42417
+ size = import_node_fs29.default.statSync(absPath).size;
42443
42418
  } catch (err) {
42444
42419
  logger.warn("attachment.onFileEdit stat failed", {
42445
42420
  sessionId: input.sessionId,
@@ -42522,6 +42497,22 @@ async function startDaemon(config) {
42522
42497
  }
42523
42498
  return `http://${config.host}:${config.port}`;
42524
42499
  };
42500
+ const reportHubUrl = async () => {
42501
+ if (!feishuActive || !currentTunnelUrl) return;
42502
+ const record = ownerIdentityStore.read();
42503
+ if (!record) return;
42504
+ try {
42505
+ await centralUpsertHub({
42506
+ api: config.clawosApi ?? DEFAULT_CLAWOS_API,
42507
+ ttcJwt: record.ttcToken,
42508
+ url: currentTunnelUrl,
42509
+ name: ownerDisplayName
42510
+ });
42511
+ logger.info("hub url reported to central server", { url: currentTunnelUrl });
42512
+ } catch (err) {
42513
+ logger.warn("hub url report failed (best-effort)", { err: err.message });
42514
+ }
42515
+ };
42525
42516
  const extensionRuntime = new Runtime({ root: extensionsRoot() });
42526
42517
  const publishedChannelStore = new PublishedChannelStore(publishedChannelsFile());
42527
42518
  await publishedChannelStore.load();
@@ -42571,17 +42562,12 @@ async function startDaemon(config) {
42571
42562
  // 'persona/<pid>/owner',default 走 'default'。
42572
42563
  getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
42573
42564
  // guest path guard:candidate 必须在 personaRoot 子树下
42574
- personaRoot: import_node_path45.default.join(config.dataDir, "personas")
42565
+ personaRoot: import_node_path44.default.join(config.dataDir, "personas")
42575
42566
  },
42576
42567
  // workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
42577
- personaRoot: import_node_path45.default.join(config.dataDir, "personas"),
42568
+ personaRoot: import_node_path44.default.join(config.dataDir, "personas"),
42578
42569
  // capability:list / delete handler 依赖
42579
42570
  capabilityManager,
42580
- // personal-cap:get 返回的 shareBaseUrl 用此 base URL:
42581
- // tunnel 拉起后切到反代 wss://... 地址;否则本机 ws://host:port。
42582
- getShareBaseUrl: () => currentTunnelUrl ?? `ws://${config.host}:${config.port}`,
42583
- // global personal token (2026-05-25): personal-cap:get handler 直接返回此 token + cap
42584
- getPersonalCapability: () => personalCapability,
42585
42571
  // v2 Phase 6: whoami handler 装在 owner principal.displayName + persona 解析
42586
42572
  ownerDisplayName,
42587
42573
  // owner-id stabilization: whoami 用稳定 ownerPrincipalId 替代 'owner' 字面量
@@ -42593,12 +42579,10 @@ async function startDaemon(config) {
42593
42579
  // cascade 接 store (list 统计 deletedInboxEvents).
42594
42580
  inboxManager,
42595
42581
  inboxStore,
42596
- // bidirectional cap 视角 B: 对方颁给我的 cap 走这个 store
42582
+ // 联系人列表 store(hub:connect 落同一 store;视角 B 双向授权模型)
42597
42583
  receivedCapabilityStore,
42598
- // received-capability:added / removed broadcast;复用 capability:tokenIssued 同款通路
42584
+ // received-capability:removed broadcast;复用 capability:tokenIssued 同款通路
42599
42585
  broadcastToOwners: (frame) => wsServer?.broadcastToOwners(frame),
42600
- // received-capability:add 临时 ws 连远端跑 whoami — 直连 ws (不走 transport listener)
42601
- connectRemote,
42602
42586
  // extension runtime (v1: local-mode only). Instance owned by daemon top
42603
42587
  // level so stopAll() runs in the shutdown hook below.
42604
42588
  extensionRuntime,
@@ -42606,7 +42590,42 @@ async function startDaemon(config) {
42606
42590
  publishedChannelStore,
42607
42591
  bundleCache,
42608
42592
  // 飞书统一身份 Phase 1:登录 RPC handlers(auth:login:start / getIdentity / logout)
42609
- feishuAuth: { loginFlow, ownerIdentityStore }
42593
+ feishuAuth: { loginFlow, ownerIdentityStore },
42594
+ // 飞书统一身份 Phase 2:hub 目录 + 连接(中心 server 代理)。
42595
+ // exchange/listHubs 包掉 owner ttcJwt(每次现读 store——热切换后立即用新身份);
42596
+ // dispatcher 的 FEISHU_GATED_METHODS gate 已保证未登录时这些 RPC 到不了 handler,
42597
+ // 这里的 FEISHU_LOGIN_REQUIRED 抛错只是防御纵深。
42598
+ feishuHub: {
42599
+ store: receivedCapabilityStore,
42600
+ exchange: async (hubId) => {
42601
+ const record = ownerIdentityStore.read();
42602
+ if (!record) {
42603
+ throw new ClawdError(ERROR_CODES.FEISHU_LOGIN_REQUIRED, "hub:connect requires feishu login");
42604
+ }
42605
+ return centralExchange({
42606
+ api: config.clawosApi ?? DEFAULT_CLAWOS_API,
42607
+ ttcJwt: record.ttcToken,
42608
+ hubId
42609
+ });
42610
+ },
42611
+ listHubs: async () => {
42612
+ const record = ownerIdentityStore.read();
42613
+ if (!record) {
42614
+ throw new ClawdError(ERROR_CODES.FEISHU_LOGIN_REQUIRED, "hub:list requires feishu login");
42615
+ }
42616
+ return centralListHubs({
42617
+ api: config.clawosApi ?? DEFAULT_CLAWOS_API,
42618
+ ttcJwt: record.ttcToken
42619
+ });
42620
+ },
42621
+ connectRemote,
42622
+ broadcast: (frame) => wsServer?.broadcastToOwners(frame),
42623
+ selfPrincipalId: () => ownerPrincipalId,
42624
+ selfDisplayName: () => ownerDisplayName,
42625
+ // Phase 2 自动反向(spec 决策 #11):本机可达地址 = tunnel URL(公网),出站连 hub 时
42626
+ // 透传让 hub 反向加我为联系人。无 tunnel(仅本机 ws)→ null,hub 不自动反向(不造假数据)。
42627
+ selfUrl: () => currentTunnelUrl
42628
+ }
42610
42629
  });
42611
42630
  let handlers = makeHandlers();
42612
42631
  const authResolver = new AuthContextResolver({
@@ -42651,6 +42670,7 @@ async function startDaemon(config) {
42651
42670
  wsServer?.setOwnerPrincipalId(result.identity.unionId);
42652
42671
  wsServer?.broadcastToOwners({ type: "auth:login:done", identity: result.identity });
42653
42672
  setImmediate(() => wsServer?.closeAllClients());
42673
+ void reportHubUrl();
42654
42674
  } else {
42655
42675
  wsServer?.broadcastToOwners({ type: "auth:login:failed", reason: result.reason });
42656
42676
  }
@@ -42821,8 +42841,8 @@ async function startDaemon(config) {
42821
42841
  const lines = [
42822
42842
  `Tunnel: ${r.url}`,
42823
42843
  ...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
42824
- `Frpc config: ${import_node_path45.default.join(config.dataDir, "frpc.toml")}`,
42825
- `Frpc log: ${import_node_path45.default.join(config.dataDir, "frpc.log")}`
42844
+ `Frpc config: ${import_node_path44.default.join(config.dataDir, "frpc.toml")}`,
42845
+ `Frpc log: ${import_node_path44.default.join(config.dataDir, "frpc.log")}`
42826
42846
  ];
42827
42847
  const width = Math.max(...lines.map((l) => l.length));
42828
42848
  const bar = "\u2550".repeat(width + 4);
@@ -42835,10 +42855,11 @@ ${bar}
42835
42855
 
42836
42856
  `);
42837
42857
  try {
42838
- const connectPath = import_node_path45.default.join(config.dataDir, "connect.txt");
42839
- import_node_fs30.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
42858
+ const connectPath = import_node_path44.default.join(config.dataDir, "connect.txt");
42859
+ import_node_fs29.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
42840
42860
  } catch {
42841
42861
  }
42862
+ void reportHubUrl();
42842
42863
  } catch (err) {
42843
42864
  const tunnelError = err?.message ?? String(err);
42844
42865
  try {
@@ -42902,9 +42923,9 @@ ${bar}
42902
42923
  };
42903
42924
  }
42904
42925
  function migrateDropPersonsDir(dataDir) {
42905
- const dir = import_node_path45.default.join(dataDir, "persons");
42926
+ const dir = import_node_path44.default.join(dataDir, "persons");
42906
42927
  try {
42907
- import_node_fs30.default.rmSync(dir, { recursive: true, force: true });
42928
+ import_node_fs29.default.rmSync(dir, { recursive: true, force: true });
42908
42929
  } catch {
42909
42930
  }
42910
42931
  }