@clawos-dev/clawd 0.2.107-beta.204.07ae519 → 0.2.108-beta.205.1e8d4c5

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 +564 -300
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -180,7 +180,13 @@ var init_methods = __esm({
180
180
  // guest-self: guest 在自己 daemon 上做安装与更新(ADMIN_ANY,guest 是 owner of self daemon).
181
181
  // 无 subscription 持久化:guest 端身份完全来自文件系统 + reconciled publishedExtensions.
182
182
  "extension.install-from-channel",
183
- "extension.update-from-channel"
183
+ "extension.update-from-channel",
184
+ // ---- auth:* 飞书统一身份 Phase 1 (spec 2026-06-01-feishu-identity-design) ----
185
+ // owner-only:daemon loopback 登录流程(auth:login:start 起 state → HTTP /auth/callback
186
+ // 回调落盘 → auth:login:done 事件)。中央字面量见 protocol/feishu-auth.ts FEISHU_AUTH_METHODS。
187
+ "auth:login:start",
188
+ "auth:getIdentity",
189
+ "auth:logout"
184
190
  ];
185
191
  }
186
192
  });
@@ -242,7 +248,11 @@ var init_errors = __esm({
242
248
  INTERNAL: "INTERNAL",
243
249
  UNAUTHORIZED: "UNAUTHORIZED",
244
250
  FORBIDDEN: "FORBIDDEN",
245
- INVALID_PARAM: "INVALID_PARAM"
251
+ INVALID_PARAM: "INVALID_PARAM",
252
+ // 飞书统一身份(spec 2026-06-01 决策 #9):People/远程体系强制飞书登录。
253
+ // daemon 进程身份未激活为飞书 unionId 时,FEISHU_GATED_METHODS 内的 RPC 一律返回此错误;
254
+ // UI 据此显示登录引导(区别于 UNAUTHORIZED 的越权语义)。
255
+ FEISHU_LOGIN_REQUIRED: "FEISHU_LOGIN_REQUIRED"
246
256
  };
247
257
  ClawdError = class extends Error {
248
258
  constructor(code, message) {
@@ -672,8 +682,8 @@ var init_parseUtil = __esm({
672
682
  init_errors2();
673
683
  init_en();
674
684
  makeIssue = (params) => {
675
- const { data, path: path59, errorMaps, issueData } = params;
676
- const fullPath = [...path59, ...issueData.path || []];
685
+ const { data, path: path60, errorMaps, issueData } = params;
686
+ const fullPath = [...path60, ...issueData.path || []];
677
687
  const fullIssue = {
678
688
  ...issueData,
679
689
  path: fullPath
@@ -984,11 +994,11 @@ var init_types = __esm({
984
994
  init_parseUtil();
985
995
  init_util();
986
996
  ParseInputLazyPath = class {
987
- constructor(parent, value, path59, key) {
997
+ constructor(parent, value, path60, key) {
988
998
  this._cachedPath = [];
989
999
  this.parent = parent;
990
1000
  this.data = value;
991
- this._path = path59;
1001
+ this._path = path60;
992
1002
  this._key = key;
993
1003
  }
994
1004
  get path() {
@@ -4522,7 +4532,7 @@ var init_persona_schemas = __esm({
4522
4532
  });
4523
4533
 
4524
4534
  // ../protocol/src/schemas.ts
4525
- var SessionStatusSchema, UsageSchema, ContextUsageSchema, sessionMetaShape, SessionMetaSchema, ModelInfoSchema, ModeInfoSchema, ConfigFieldSchemaSchema, CapabilitiesGetArgs, CapabilitiesResponseSchema, AllowRuleSchema, SessionFileSchema, ParsedEventBase, HistoryUserMetaSchema, SubagentToolStatsSchema, StructuredPatchHunkSchema, ToolResultExtraSchema, MemoryEntrySchema, AskQuestionOptionSchema, AskQuestionItemSchema, ParsedEventSchema, SessionCreateArgs, SessionIdArgs, SessionUpdateArgs, SessionSendArgs, SessionRewindArgs, SessionRewindResponseSchema, SessionRewindDiffArgs, RewindDiffHunkSchema, RewindDiffFileSchema, SessionRewindDiffResponseSchema, SessionRewindableMessageIdsArgs, SessionRewindableMessageIdsResponseSchema, SessionResumeArgs, SessionForkArgs, SessionForkResponseSchema, SessionObserveArgs, SessionEventsArgs, PermissionRespondArgs, HistoryListArgs, HistoryReadArgs, HistorySubagentsArgs, HistorySubagentReadArgs, WorkspaceListArgs, WorkspaceReadArgs, SkillsListArgs, SkillEntrySchema, AgentEntrySchema, AgentsListArgs, AgentsListResponseSchema, SessionSubscribeArgs, SessionPinArgs, PeerSessionUpsertArgs, PeerSessionUpsertResponseSchema, PeerSessionRemoveArgs, PeerSessionRemoveResponseSchema, SessionReorderPinsArgs, GitRootArgs, GitRootResponseSchema, GitBranchArgs, GitBranchResponseSchema, GitBranchesArgs, GitBranchesResponseSchema, HistoryRecentDirsArgs, RecentDirEntrySchema, HistoryRecentDirsResponseSchema, SessionQuestionFrameSchema, SessionQuestionClearedFrameSchema, AnswerQuestionArgs, AnswerQuestionResponseSchema, CancelQuestionArgs, CancelQuestionResponseSchema, AuthRequestFrameSchema, AuthOkFrameSchema, TunnelReadyEventSchema, TunnelExitedEventSchema, TunnelUnavailableEventSchema, InfoRunningSessionSchema, InfoResponseSchema;
4535
+ var SessionStatusSchema, UsageSchema, ContextUsageSchema, sessionMetaShape, SessionMetaSchema, ModelInfoSchema, ModeInfoSchema, ConfigFieldSchemaSchema, CapabilitiesGetArgs, CapabilitiesResponseSchema, AllowRuleSchema, SessionFileSchema, ParsedEventBase, HistoryUserMetaSchema, SubagentToolStatsSchema, StructuredPatchHunkSchema, ToolResultExtraSchema, MemoryEntrySchema, AskQuestionOptionSchema, AskQuestionItemSchema, ParsedEventSchema, SessionCreateArgs, SessionIdArgs, SessionUpdateArgs, SessionSendArgs, SessionRewindArgs, SessionRewindResponseSchema, SessionRewindDiffArgs, RewindDiffHunkSchema, RewindDiffFileSchema, SessionRewindDiffResponseSchema, SessionRewindableMessageIdsArgs, SessionRewindableMessageIdsResponseSchema, SessionResumeArgs, SessionForkArgs, SessionForkResponseSchema, SessionObserveArgs, SessionEventsArgs, PermissionRespondArgs, HistoryListArgs, HistoryReadArgs, HistorySubagentsArgs, HistorySubagentReadArgs, WorkspaceListArgs, WorkspaceReadArgs, SkillsListArgs, SkillEntrySchema, AgentEntrySchema, AgentsListArgs, AgentsListResponseSchema, SessionSubscribeArgs, SessionPinArgs, PeerSessionUpsertArgs, PeerSessionUpsertResponseSchema, PeerSessionRemoveArgs, PeerSessionRemoveResponseSchema, SessionReorderPinsArgs, GitRootArgs, GitRootResponseSchema, GitBranchArgs, GitBranchResponseSchema, GitBranchesArgs, GitBranchesResponseSchema, HistoryRecentDirsArgs, RecentDirEntrySchema, HistoryRecentDirsResponseSchema, SessionQuestionFrameSchema, SessionQuestionClearedFrameSchema, AnswerQuestionArgs, AnswerQuestionResponseSchema, CancelQuestionArgs, CancelQuestionResponseSchema, AuthRequestFrameSchema, AuthOkFrameSchema, TunnelExitedEventSchema, TunnelUnavailableEventSchema, InfoRunningSessionSchema, InfoResponseSchema;
4526
4536
  var init_schemas = __esm({
4527
4537
  "../protocol/src/schemas.ts"() {
4528
4538
  "use strict";
@@ -5102,6 +5112,12 @@ var init_schemas = __esm({
5102
5112
  * 后调 capabilityManager.recordPeerHello(capId, ownerPrincipalId, displayName)
5103
5113
  * 把 cap.peerOwnerId / firstUsedByPeerAt 字段回写,让 owner 端 UI 在 People
5104
5114
  * tab 顶层 PeerOwner 视图 + Personas tab 二级分组拿到对端身份。
5115
+ *
5116
+ * 飞书统一身份 Phase 1 (2026-06-01):取值升级为飞书 union_id(owner 已飞书登录时;
5117
+ * 来自 whoami.owner.id → daemon ownerPrincipalId → owner-identity.json unionId)。
5118
+ * 未登录 daemon 仍为 auth.json 的 owner-<uuid>。daemon 端不验证该身份真实性
5119
+ * (Phase 1 信任模型,见 spec §9)。Phase 2 将以 connect token 替代(introspect
5120
+ * 返回身份,删除自报字段,见 spec §10)。
5105
5121
  */
5106
5122
  selfPrincipalId: external_exports.string().min(1).optional(),
5107
5123
  /**
@@ -5116,11 +5132,6 @@ var init_schemas = __esm({
5116
5132
  version: external_exports.string().min(1).optional(),
5117
5133
  protocolVersion: external_exports.number().int().nonnegative().optional()
5118
5134
  });
5119
- TunnelReadyEventSchema = external_exports.object({
5120
- type: external_exports.literal("tunnel:ready"),
5121
- url: external_exports.string().min(1),
5122
- subdomain: external_exports.string().min(1)
5123
- });
5124
5135
  TunnelExitedEventSchema = external_exports.object({
5125
5136
  type: external_exports.literal("tunnel:exited"),
5126
5137
  code: external_exports.number().int().nullable(),
@@ -5570,6 +5581,64 @@ var init_extension = __esm({
5570
5581
  }
5571
5582
  });
5572
5583
 
5584
+ // ../protocol/src/feishu-auth.ts
5585
+ var FEISHU_GATED_METHODS, FeishuIdentitySchema, AuthLoginStartResponseSchema, AuthGetIdentityResponseSchema, AuthLogoutResponseSchema, AuthLoginDoneEventSchema, AuthLoginFailedEventSchema;
5586
+ var init_feishu_auth = __esm({
5587
+ "../protocol/src/feishu-auth.ts"() {
5588
+ "use strict";
5589
+ init_zod();
5590
+ FEISHU_GATED_METHODS = [
5591
+ // capability / 邀请码(生成添加码 = 把自己的身份分享出去)
5592
+ "capability:list",
5593
+ "capability:delete",
5594
+ "personal-cap:get",
5595
+ // 联系人(添加朋友 / 联系人列表 / 自动重连)
5596
+ "received-capability:list",
5597
+ "received-capability:add",
5598
+ "received-capability:remove",
5599
+ "received-capability:autoAttach",
5600
+ // DM / 跨用户通知
5601
+ "inbox:list",
5602
+ "inbox:markRead",
5603
+ "inbox:postMessage",
5604
+ // mirror peer sessions(远端会话镜像)
5605
+ "peerSession:upsert",
5606
+ "peerSession:remove"
5607
+ ];
5608
+ FeishuIdentitySchema = external_exports.object({
5609
+ /** 飞书 union_id(租户维度,跨应用稳定)。TTC user_info 接口 data.union_id */
5610
+ unionId: external_exports.string().min(1),
5611
+ /** 飞书姓名。TTC user_info 接口 data.name */
5612
+ displayName: external_exports.string().min(1),
5613
+ /** TTC user_info 接口 data.avatar_url,可缺省 */
5614
+ avatarUrl: external_exports.string().optional()
5615
+ });
5616
+ AuthLoginStartResponseSchema = external_exports.object({
5617
+ type: external_exports.literal("auth:login:start:ok"),
5618
+ /** 完整 TTC 授权页 URL(含 callback_url + state + auto=1),UI 直接 window.open */
5619
+ authUrl: external_exports.string().min(1),
5620
+ /** 防 CSRF 的一次性 nonce;回调时 daemon 校验 */
5621
+ state: external_exports.string().min(1)
5622
+ });
5623
+ AuthGetIdentityResponseSchema = external_exports.object({
5624
+ type: external_exports.literal("auth:getIdentity:ok"),
5625
+ /** null = 未登录 */
5626
+ identity: FeishuIdentitySchema.nullable()
5627
+ });
5628
+ AuthLogoutResponseSchema = external_exports.object({
5629
+ type: external_exports.literal("auth:logout:ok")
5630
+ });
5631
+ AuthLoginDoneEventSchema = external_exports.object({
5632
+ type: external_exports.literal("auth:login:done"),
5633
+ identity: FeishuIdentitySchema
5634
+ });
5635
+ AuthLoginFailedEventSchema = external_exports.object({
5636
+ type: external_exports.literal("auth:login:failed"),
5637
+ reason: external_exports.string()
5638
+ });
5639
+ }
5640
+ });
5641
+
5573
5642
  // ../protocol/src/runtime.ts
5574
5643
  var init_runtime = __esm({
5575
5644
  "../protocol/src/runtime.ts"() {
@@ -5587,6 +5656,7 @@ var init_runtime = __esm({
5587
5656
  init_inbox();
5588
5657
  init_received_capability();
5589
5658
  init_extension();
5659
+ init_feishu_auth();
5590
5660
  }
5591
5661
  });
5592
5662
 
@@ -5861,8 +5931,8 @@ var require_req = __commonJS({
5861
5931
  if (req.originalUrl) {
5862
5932
  _req.url = req.originalUrl;
5863
5933
  } else {
5864
- const path59 = req.path;
5865
- _req.url = typeof path59 === "string" ? path59 : req.url ? req.url.path || req.url : void 0;
5934
+ const path60 = req.path;
5935
+ _req.url = typeof path60 === "string" ? path60 : req.url ? req.url.path || req.url : void 0;
5866
5936
  }
5867
5937
  if (req.query) {
5868
5938
  _req.query = req.query;
@@ -6027,14 +6097,14 @@ var require_redact = __commonJS({
6027
6097
  }
6028
6098
  return obj;
6029
6099
  }
6030
- function parsePath(path59) {
6100
+ function parsePath(path60) {
6031
6101
  const parts = [];
6032
6102
  let current = "";
6033
6103
  let inBrackets = false;
6034
6104
  let inQuotes = false;
6035
6105
  let quoteChar = "";
6036
- for (let i = 0; i < path59.length; i++) {
6037
- const char = path59[i];
6106
+ for (let i = 0; i < path60.length; i++) {
6107
+ const char = path60[i];
6038
6108
  if (!inBrackets && char === ".") {
6039
6109
  if (current) {
6040
6110
  parts.push(current);
@@ -6165,10 +6235,10 @@ var require_redact = __commonJS({
6165
6235
  return current;
6166
6236
  }
6167
6237
  function redactPaths(obj, paths, censor, remove = false) {
6168
- for (const path59 of paths) {
6169
- const parts = parsePath(path59);
6238
+ for (const path60 of paths) {
6239
+ const parts = parsePath(path60);
6170
6240
  if (parts.includes("*")) {
6171
- redactWildcardPath(obj, parts, censor, path59, remove);
6241
+ redactWildcardPath(obj, parts, censor, path60, remove);
6172
6242
  } else {
6173
6243
  if (remove) {
6174
6244
  removeKey(obj, parts);
@@ -6253,8 +6323,8 @@ var require_redact = __commonJS({
6253
6323
  }
6254
6324
  } else {
6255
6325
  if (afterWildcard.includes("*")) {
6256
- const wrappedCensor = typeof censor === "function" ? (value, path59) => {
6257
- const fullPath = [...pathArray.slice(0, pathLength), ...path59];
6326
+ const wrappedCensor = typeof censor === "function" ? (value, path60) => {
6327
+ const fullPath = [...pathArray.slice(0, pathLength), ...path60];
6258
6328
  return censor(value, fullPath);
6259
6329
  } : censor;
6260
6330
  redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
@@ -6289,8 +6359,8 @@ var require_redact = __commonJS({
6289
6359
  return null;
6290
6360
  }
6291
6361
  const pathStructure = /* @__PURE__ */ new Map();
6292
- for (const path59 of pathsToClone) {
6293
- const parts = parsePath(path59);
6362
+ for (const path60 of pathsToClone) {
6363
+ const parts = parsePath(path60);
6294
6364
  let current = pathStructure;
6295
6365
  for (let i = 0; i < parts.length; i++) {
6296
6366
  const part = parts[i];
@@ -6342,24 +6412,24 @@ var require_redact = __commonJS({
6342
6412
  }
6343
6413
  return cloneSelectively(obj, pathStructure);
6344
6414
  }
6345
- function validatePath(path59) {
6346
- if (typeof path59 !== "string") {
6415
+ function validatePath(path60) {
6416
+ if (typeof path60 !== "string") {
6347
6417
  throw new Error("Paths must be (non-empty) strings");
6348
6418
  }
6349
- if (path59 === "") {
6419
+ if (path60 === "") {
6350
6420
  throw new Error("Invalid redaction path ()");
6351
6421
  }
6352
- if (path59.includes("..")) {
6353
- throw new Error(`Invalid redaction path (${path59})`);
6422
+ if (path60.includes("..")) {
6423
+ throw new Error(`Invalid redaction path (${path60})`);
6354
6424
  }
6355
- if (path59.includes(",")) {
6356
- throw new Error(`Invalid redaction path (${path59})`);
6425
+ if (path60.includes(",")) {
6426
+ throw new Error(`Invalid redaction path (${path60})`);
6357
6427
  }
6358
6428
  let bracketCount = 0;
6359
6429
  let inQuotes = false;
6360
6430
  let quoteChar = "";
6361
- for (let i = 0; i < path59.length; i++) {
6362
- const char = path59[i];
6431
+ for (let i = 0; i < path60.length; i++) {
6432
+ const char = path60[i];
6363
6433
  if ((char === '"' || char === "'") && bracketCount > 0) {
6364
6434
  if (!inQuotes) {
6365
6435
  inQuotes = true;
@@ -6373,20 +6443,20 @@ var require_redact = __commonJS({
6373
6443
  } else if (char === "]" && !inQuotes) {
6374
6444
  bracketCount--;
6375
6445
  if (bracketCount < 0) {
6376
- throw new Error(`Invalid redaction path (${path59})`);
6446
+ throw new Error(`Invalid redaction path (${path60})`);
6377
6447
  }
6378
6448
  }
6379
6449
  }
6380
6450
  if (bracketCount !== 0) {
6381
- throw new Error(`Invalid redaction path (${path59})`);
6451
+ throw new Error(`Invalid redaction path (${path60})`);
6382
6452
  }
6383
6453
  }
6384
6454
  function validatePaths(paths) {
6385
6455
  if (!Array.isArray(paths)) {
6386
6456
  throw new TypeError("paths must be an array");
6387
6457
  }
6388
- for (const path59 of paths) {
6389
- validatePath(path59);
6458
+ for (const path60 of paths) {
6459
+ validatePath(path60);
6390
6460
  }
6391
6461
  }
6392
6462
  function slowRedact(options = {}) {
@@ -6554,8 +6624,8 @@ var require_redaction = __commonJS({
6554
6624
  if (shape[k2] === null) {
6555
6625
  o[k2] = (value) => topCensor(value, [k2]);
6556
6626
  } else {
6557
- const wrappedCensor = typeof censor === "function" ? (value, path59) => {
6558
- return censor(value, [k2, ...path59]);
6627
+ const wrappedCensor = typeof censor === "function" ? (value, path60) => {
6628
+ return censor(value, [k2, ...path60]);
6559
6629
  } : censor;
6560
6630
  o[k2] = Redact({
6561
6631
  paths: shape[k2],
@@ -6773,10 +6843,10 @@ var require_atomic_sleep = __commonJS({
6773
6843
  var require_sonic_boom = __commonJS({
6774
6844
  "../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
6775
6845
  "use strict";
6776
- var fs47 = require("fs");
6846
+ var fs48 = require("fs");
6777
6847
  var EventEmitter2 = require("events");
6778
6848
  var inherits = require("util").inherits;
6779
- var path59 = require("path");
6849
+ var path60 = require("path");
6780
6850
  var sleep = require_atomic_sleep();
6781
6851
  var assert = require("assert");
6782
6852
  var BUSY_WRITE_TIMEOUT = 100;
@@ -6830,20 +6900,20 @@ var require_sonic_boom = __commonJS({
6830
6900
  const mode = sonic.mode;
6831
6901
  if (sonic.sync) {
6832
6902
  try {
6833
- if (sonic.mkdir) fs47.mkdirSync(path59.dirname(file), { recursive: true });
6834
- const fd = fs47.openSync(file, flags, mode);
6903
+ if (sonic.mkdir) fs48.mkdirSync(path60.dirname(file), { recursive: true });
6904
+ const fd = fs48.openSync(file, flags, mode);
6835
6905
  fileOpened(null, fd);
6836
6906
  } catch (err) {
6837
6907
  fileOpened(err);
6838
6908
  throw err;
6839
6909
  }
6840
6910
  } else if (sonic.mkdir) {
6841
- fs47.mkdir(path59.dirname(file), { recursive: true }, (err) => {
6911
+ fs48.mkdir(path60.dirname(file), { recursive: true }, (err) => {
6842
6912
  if (err) return fileOpened(err);
6843
- fs47.open(file, flags, mode, fileOpened);
6913
+ fs48.open(file, flags, mode, fileOpened);
6844
6914
  });
6845
6915
  } else {
6846
- fs47.open(file, flags, mode, fileOpened);
6916
+ fs48.open(file, flags, mode, fileOpened);
6847
6917
  }
6848
6918
  }
6849
6919
  function SonicBoom(opts) {
@@ -6884,8 +6954,8 @@ var require_sonic_boom = __commonJS({
6884
6954
  this.flush = flushBuffer;
6885
6955
  this.flushSync = flushBufferSync;
6886
6956
  this._actualWrite = actualWriteBuffer;
6887
- fsWriteSync = () => fs47.writeSync(this.fd, this._writingBuf);
6888
- fsWrite = () => fs47.write(this.fd, this._writingBuf, this.release);
6957
+ fsWriteSync = () => fs48.writeSync(this.fd, this._writingBuf);
6958
+ fsWrite = () => fs48.write(this.fd, this._writingBuf, this.release);
6889
6959
  } else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
6890
6960
  this._writingBuf = "";
6891
6961
  this.write = write;
@@ -6894,15 +6964,15 @@ var require_sonic_boom = __commonJS({
6894
6964
  this._actualWrite = actualWrite;
6895
6965
  fsWriteSync = () => {
6896
6966
  if (Buffer.isBuffer(this._writingBuf)) {
6897
- return fs47.writeSync(this.fd, this._writingBuf);
6967
+ return fs48.writeSync(this.fd, this._writingBuf);
6898
6968
  }
6899
- return fs47.writeSync(this.fd, this._writingBuf, "utf8");
6969
+ return fs48.writeSync(this.fd, this._writingBuf, "utf8");
6900
6970
  };
6901
6971
  fsWrite = () => {
6902
6972
  if (Buffer.isBuffer(this._writingBuf)) {
6903
- return fs47.write(this.fd, this._writingBuf, this.release);
6973
+ return fs48.write(this.fd, this._writingBuf, this.release);
6904
6974
  }
6905
- return fs47.write(this.fd, this._writingBuf, "utf8", this.release);
6975
+ return fs48.write(this.fd, this._writingBuf, "utf8", this.release);
6906
6976
  };
6907
6977
  } else {
6908
6978
  throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
@@ -6959,7 +7029,7 @@ var require_sonic_boom = __commonJS({
6959
7029
  }
6960
7030
  }
6961
7031
  if (this._fsync) {
6962
- fs47.fsyncSync(this.fd);
7032
+ fs48.fsyncSync(this.fd);
6963
7033
  }
6964
7034
  const len = this._len;
6965
7035
  if (this._reopening) {
@@ -7073,7 +7143,7 @@ var require_sonic_boom = __commonJS({
7073
7143
  const onDrain = () => {
7074
7144
  if (!this._fsync) {
7075
7145
  try {
7076
- fs47.fsync(this.fd, (err) => {
7146
+ fs48.fsync(this.fd, (err) => {
7077
7147
  this._flushPending = false;
7078
7148
  cb(err);
7079
7149
  });
@@ -7175,7 +7245,7 @@ var require_sonic_boom = __commonJS({
7175
7245
  const fd = this.fd;
7176
7246
  this.once("ready", () => {
7177
7247
  if (fd !== this.fd) {
7178
- fs47.close(fd, (err) => {
7248
+ fs48.close(fd, (err) => {
7179
7249
  if (err) {
7180
7250
  return this.emit("error", err);
7181
7251
  }
@@ -7224,7 +7294,7 @@ var require_sonic_boom = __commonJS({
7224
7294
  buf = this._bufs[0];
7225
7295
  }
7226
7296
  try {
7227
- const n = Buffer.isBuffer(buf) ? fs47.writeSync(this.fd, buf) : fs47.writeSync(this.fd, buf, "utf8");
7297
+ const n = Buffer.isBuffer(buf) ? fs48.writeSync(this.fd, buf) : fs48.writeSync(this.fd, buf, "utf8");
7228
7298
  const releasedBufObj = releaseWritingBuf(buf, this._len, n);
7229
7299
  buf = releasedBufObj.writingBuf;
7230
7300
  this._len = releasedBufObj.len;
@@ -7240,7 +7310,7 @@ var require_sonic_boom = __commonJS({
7240
7310
  }
7241
7311
  }
7242
7312
  try {
7243
- fs47.fsyncSync(this.fd);
7313
+ fs48.fsyncSync(this.fd);
7244
7314
  } catch {
7245
7315
  }
7246
7316
  }
@@ -7261,7 +7331,7 @@ var require_sonic_boom = __commonJS({
7261
7331
  buf = mergeBuf(this._bufs[0], this._lens[0]);
7262
7332
  }
7263
7333
  try {
7264
- const n = fs47.writeSync(this.fd, buf);
7334
+ const n = fs48.writeSync(this.fd, buf);
7265
7335
  buf = buf.subarray(n);
7266
7336
  this._len = Math.max(this._len - n, 0);
7267
7337
  if (buf.length <= 0) {
@@ -7289,13 +7359,13 @@ var require_sonic_boom = __commonJS({
7289
7359
  this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
7290
7360
  if (this.sync) {
7291
7361
  try {
7292
- const written = Buffer.isBuffer(this._writingBuf) ? fs47.writeSync(this.fd, this._writingBuf) : fs47.writeSync(this.fd, this._writingBuf, "utf8");
7362
+ const written = Buffer.isBuffer(this._writingBuf) ? fs48.writeSync(this.fd, this._writingBuf) : fs48.writeSync(this.fd, this._writingBuf, "utf8");
7293
7363
  release(null, written);
7294
7364
  } catch (err) {
7295
7365
  release(err);
7296
7366
  }
7297
7367
  } else {
7298
- fs47.write(this.fd, this._writingBuf, release);
7368
+ fs48.write(this.fd, this._writingBuf, release);
7299
7369
  }
7300
7370
  }
7301
7371
  function actualWriteBuffer() {
@@ -7304,7 +7374,7 @@ var require_sonic_boom = __commonJS({
7304
7374
  this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
7305
7375
  if (this.sync) {
7306
7376
  try {
7307
- const written = fs47.writeSync(this.fd, this._writingBuf);
7377
+ const written = fs48.writeSync(this.fd, this._writingBuf);
7308
7378
  release(null, written);
7309
7379
  } catch (err) {
7310
7380
  release(err);
@@ -7313,7 +7383,7 @@ var require_sonic_boom = __commonJS({
7313
7383
  if (kCopyBuffer) {
7314
7384
  this._writingBuf = Buffer.from(this._writingBuf);
7315
7385
  }
7316
- fs47.write(this.fd, this._writingBuf, release);
7386
+ fs48.write(this.fd, this._writingBuf, release);
7317
7387
  }
7318
7388
  }
7319
7389
  function actualClose(sonic) {
@@ -7329,12 +7399,12 @@ var require_sonic_boom = __commonJS({
7329
7399
  sonic._lens = [];
7330
7400
  assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
7331
7401
  try {
7332
- fs47.fsync(sonic.fd, closeWrapped);
7402
+ fs48.fsync(sonic.fd, closeWrapped);
7333
7403
  } catch {
7334
7404
  }
7335
7405
  function closeWrapped() {
7336
7406
  if (sonic.fd !== 1 && sonic.fd !== 2) {
7337
- fs47.close(sonic.fd, done);
7407
+ fs48.close(sonic.fd, done);
7338
7408
  } else {
7339
7409
  done();
7340
7410
  }
@@ -10469,11 +10539,11 @@ var init_lib = __esm({
10469
10539
  }
10470
10540
  }
10471
10541
  },
10472
- addToPath: function addToPath(path59, added, removed, oldPosInc, options) {
10473
- var last = path59.lastComponent;
10542
+ addToPath: function addToPath(path60, added, removed, oldPosInc, options) {
10543
+ var last = path60.lastComponent;
10474
10544
  if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
10475
10545
  return {
10476
- oldPos: path59.oldPos + oldPosInc,
10546
+ oldPos: path60.oldPos + oldPosInc,
10477
10547
  lastComponent: {
10478
10548
  count: last.count + 1,
10479
10549
  added,
@@ -10483,7 +10553,7 @@ var init_lib = __esm({
10483
10553
  };
10484
10554
  } else {
10485
10555
  return {
10486
- oldPos: path59.oldPos + oldPosInc,
10556
+ oldPos: path60.oldPos + oldPosInc,
10487
10557
  lastComponent: {
10488
10558
  count: 1,
10489
10559
  added,
@@ -10929,10 +10999,10 @@ function attachmentToHistoryMessage(o, ts) {
10929
10999
  const memories = raw.map((m2) => {
10930
11000
  if (!m2 || typeof m2 !== "object") return null;
10931
11001
  const rec = m2;
10932
- const path59 = typeof rec.path === "string" ? rec.path : null;
11002
+ const path60 = typeof rec.path === "string" ? rec.path : null;
10933
11003
  const content = typeof rec.content === "string" ? rec.content : null;
10934
- if (!path59 || content == null) return null;
10935
- const entry = { path: path59, content };
11004
+ if (!path60 || content == null) return null;
11005
+ const entry = { path: path60, content };
10936
11006
  if (typeof rec.mtimeMs === "number") entry.mtimeMs = rec.mtimeMs;
10937
11007
  return entry;
10938
11008
  }).filter((m2) => m2 !== null);
@@ -11763,10 +11833,10 @@ function parseAttachment(obj) {
11763
11833
  const memories = raw.map((m2) => {
11764
11834
  if (!m2 || typeof m2 !== "object") return null;
11765
11835
  const rec = m2;
11766
- const path59 = typeof rec.path === "string" ? rec.path : null;
11836
+ const path60 = typeof rec.path === "string" ? rec.path : null;
11767
11837
  const content = typeof rec.content === "string" ? rec.content : null;
11768
- if (!path59 || content == null) return null;
11769
- const out = { path: path59, content };
11838
+ if (!path60 || content == null) return null;
11839
+ const out = { path: path60, content };
11770
11840
  if (typeof rec.mtimeMs === "number") out.mtimeMs = rec.mtimeMs;
11771
11841
  return out;
11772
11842
  }).filter((m2) => m2 !== null);
@@ -19262,8 +19332,8 @@ var require_websocket = __commonJS({
19262
19332
  "use strict";
19263
19333
  var EventEmitter2 = require("events");
19264
19334
  var https = require("https");
19265
- var http3 = require("http");
19266
- var net3 = require("net");
19335
+ var http2 = require("http");
19336
+ var net2 = require("net");
19267
19337
  var tls = require("tls");
19268
19338
  var { randomBytes, createHash: createHash2 } = require("crypto");
19269
19339
  var { Duplex, Readable: Readable3 } = require("stream");
@@ -19796,7 +19866,7 @@ var require_websocket = __commonJS({
19796
19866
  }
19797
19867
  const defaultPort = isSecure ? 443 : 80;
19798
19868
  const key = randomBytes(16).toString("base64");
19799
- const request = isSecure ? https.request : http3.request;
19869
+ const request = isSecure ? https.request : http2.request;
19800
19870
  const protocolSet = /* @__PURE__ */ new Set();
19801
19871
  let perMessageDeflate;
19802
19872
  opts.createConnection = opts.createConnection || (isSecure ? tlsConnect : netConnect);
@@ -19997,12 +20067,12 @@ var require_websocket = __commonJS({
19997
20067
  }
19998
20068
  function netConnect(options) {
19999
20069
  options.path = options.socketPath;
20000
- return net3.connect(options);
20070
+ return net2.connect(options);
20001
20071
  }
20002
20072
  function tlsConnect(options) {
20003
20073
  options.path = void 0;
20004
20074
  if (!options.servername && options.servername !== "") {
20005
- options.servername = net3.isIP(options.host) ? "" : options.host;
20075
+ options.servername = net2.isIP(options.host) ? "" : options.host;
20006
20076
  }
20007
20077
  return tls.connect(options);
20008
20078
  }
@@ -20290,7 +20360,7 @@ var require_websocket_server = __commonJS({
20290
20360
  "../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/websocket-server.js"(exports2, module2) {
20291
20361
  "use strict";
20292
20362
  var EventEmitter2 = require("events");
20293
- var http3 = require("http");
20363
+ var http2 = require("http");
20294
20364
  var { Duplex } = require("stream");
20295
20365
  var { createHash: createHash2 } = require("crypto");
20296
20366
  var extension2 = require_extension();
@@ -20365,8 +20435,8 @@ var require_websocket_server = __commonJS({
20365
20435
  );
20366
20436
  }
20367
20437
  if (options.port != null) {
20368
- this._server = http3.createServer((req, res) => {
20369
- const body = http3.STATUS_CODES[426];
20438
+ this._server = http2.createServer((req, res) => {
20439
+ const body = http2.STATUS_CODES[426];
20370
20440
  res.writeHead(426, {
20371
20441
  "Content-Length": body.length,
20372
20442
  "Content-Type": "text/plain"
@@ -20653,7 +20723,7 @@ var require_websocket_server = __commonJS({
20653
20723
  this.destroy();
20654
20724
  }
20655
20725
  function abortHandshake(socket, code, message, headers) {
20656
- message = message || http3.STATUS_CODES[code];
20726
+ message = message || http2.STATUS_CODES[code];
20657
20727
  headers = {
20658
20728
  Connection: "close",
20659
20729
  "Content-Type": "text/html",
@@ -20662,7 +20732,7 @@ var require_websocket_server = __commonJS({
20662
20732
  };
20663
20733
  socket.once("finish", socket.destroy);
20664
20734
  socket.end(
20665
- `HTTP/1.1 ${code} ${http3.STATUS_CODES[code]}\r
20735
+ `HTTP/1.1 ${code} ${http2.STATUS_CODES[code]}\r
20666
20736
  ` + Object.keys(headers).map((h) => `${h}: ${headers[h]}`).join("\r\n") + "\r\n\r\n" + message
20667
20737
  );
20668
20738
  }
@@ -23555,8 +23625,8 @@ var require_utils = __commonJS({
23555
23625
  var result = transform[inputType][outputType](input);
23556
23626
  return result;
23557
23627
  };
23558
- exports2.resolve = function(path59) {
23559
- var parts = path59.split("/");
23628
+ exports2.resolve = function(path60) {
23629
+ var parts = path60.split("/");
23560
23630
  var result = [];
23561
23631
  for (var index = 0; index < parts.length; index++) {
23562
23632
  var part = parts[index];
@@ -29409,18 +29479,18 @@ var require_object = __commonJS({
29409
29479
  var object = new ZipObject(name, zipObjectContent, o);
29410
29480
  this.files[name] = object;
29411
29481
  };
29412
- var parentFolder = function(path59) {
29413
- if (path59.slice(-1) === "/") {
29414
- path59 = path59.substring(0, path59.length - 1);
29482
+ var parentFolder = function(path60) {
29483
+ if (path60.slice(-1) === "/") {
29484
+ path60 = path60.substring(0, path60.length - 1);
29415
29485
  }
29416
- var lastSlash = path59.lastIndexOf("/");
29417
- return lastSlash > 0 ? path59.substring(0, lastSlash) : "";
29486
+ var lastSlash = path60.lastIndexOf("/");
29487
+ return lastSlash > 0 ? path60.substring(0, lastSlash) : "";
29418
29488
  };
29419
- var forceTrailingSlash = function(path59) {
29420
- if (path59.slice(-1) !== "/") {
29421
- path59 += "/";
29489
+ var forceTrailingSlash = function(path60) {
29490
+ if (path60.slice(-1) !== "/") {
29491
+ path60 += "/";
29422
29492
  }
29423
- return path59;
29493
+ return path60;
29424
29494
  };
29425
29495
  var folderAdd = function(name, createFolders) {
29426
29496
  createFolders = typeof createFolders !== "undefined" ? createFolders : defaults.createFolders;
@@ -30422,7 +30492,7 @@ var require_lib3 = __commonJS({
30422
30492
  // src/run-case/recorder.ts
30423
30493
  function startRunCaseRecorder(opts) {
30424
30494
  const now = opts.now ?? Date.now;
30425
- const dir = import_node_path45.default.dirname(opts.recordPath);
30495
+ const dir = import_node_path46.default.dirname(opts.recordPath);
30426
30496
  let stream = null;
30427
30497
  let closing = false;
30428
30498
  let closedSettled = false;
@@ -30436,8 +30506,8 @@ function startRunCaseRecorder(opts) {
30436
30506
  });
30437
30507
  const ensureStream = () => {
30438
30508
  if (stream) return stream;
30439
- import_node_fs30.default.mkdirSync(dir, { recursive: true });
30440
- stream = import_node_fs30.default.createWriteStream(opts.recordPath, { flags: "a" });
30509
+ import_node_fs31.default.mkdirSync(dir, { recursive: true });
30510
+ stream = import_node_fs31.default.createWriteStream(opts.recordPath, { flags: "a" });
30441
30511
  stream.on("close", () => closedResolve());
30442
30512
  return stream;
30443
30513
  };
@@ -30462,12 +30532,12 @@ function startRunCaseRecorder(opts) {
30462
30532
  };
30463
30533
  return { tap, close, closed };
30464
30534
  }
30465
- var import_node_fs30, import_node_path45;
30535
+ var import_node_fs31, import_node_path46;
30466
30536
  var init_recorder = __esm({
30467
30537
  "src/run-case/recorder.ts"() {
30468
30538
  "use strict";
30469
- import_node_fs30 = __toESM(require("fs"), 1);
30470
- import_node_path45 = __toESM(require("path"), 1);
30539
+ import_node_fs31 = __toESM(require("fs"), 1);
30540
+ import_node_path46 = __toESM(require("path"), 1);
30471
30541
  }
30472
30542
  });
30473
30543
 
@@ -30510,7 +30580,7 @@ var init_wire = __esm({
30510
30580
  // src/run-case/controller.ts
30511
30581
  async function runController(opts) {
30512
30582
  const now = opts.now ?? Date.now;
30513
- const cwd = opts.cwd ?? (0, import_node_fs31.mkdtempSync)(import_node_path46.default.join(import_node_os19.default.tmpdir(), "clawd-runcase-"));
30583
+ const cwd = opts.cwd ?? (0, import_node_fs32.mkdtempSync)(import_node_path47.default.join(import_node_os19.default.tmpdir(), "clawd-runcase-"));
30514
30584
  const ownsCwd = opts.cwd === void 0;
30515
30585
  const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
30516
30586
  const spawnCtx = { cwd };
@@ -30671,19 +30741,19 @@ async function runController(opts) {
30671
30741
  if (sigintHandler) process.off("SIGINT", sigintHandler);
30672
30742
  if (ownsCwd) {
30673
30743
  try {
30674
- (0, import_node_fs31.rmSync)(cwd, { recursive: true, force: true });
30744
+ (0, import_node_fs32.rmSync)(cwd, { recursive: true, force: true });
30675
30745
  } catch {
30676
30746
  }
30677
30747
  }
30678
30748
  return exitCode ?? 0;
30679
30749
  }
30680
- var import_node_fs31, import_node_os19, import_node_path46;
30750
+ var import_node_fs32, import_node_os19, import_node_path47;
30681
30751
  var init_controller = __esm({
30682
30752
  "src/run-case/controller.ts"() {
30683
30753
  "use strict";
30684
- import_node_fs31 = require("fs");
30754
+ import_node_fs32 = require("fs");
30685
30755
  import_node_os19 = __toESM(require("os"), 1);
30686
- import_node_path46 = __toESM(require("path"), 1);
30756
+ import_node_path47 = __toESM(require("path"), 1);
30687
30757
  init_claude();
30688
30758
  init_stdout_splitter();
30689
30759
  init_permission_stdio();
@@ -30875,8 +30945,6 @@ function resolveConfig(opts) {
30875
30945
  const frpcBinary = env.CLAWD_FRPC_BIN ?? null;
30876
30946
  const rawMode = env.CLAWD_CC_MODE;
30877
30947
  const mode = DAEMON_MODE_VALUES.includes(rawMode ?? "") ? rawMode : "sdk";
30878
- const filePreviewPorts = Array.isArray(fileCfg.previewPorts) ? fileCfg.previewPorts.map((n) => Number(n)).filter((n) => Number.isInteger(n) && n > 0) : null;
30879
- const previewPorts = env.CLAWD_PREVIEW_PORTS ? env.CLAWD_PREVIEW_PORTS.split(",").map((s) => Number(s.trim())).filter((n) => Number.isInteger(n) && n > 0) : filePreviewPorts && filePreviewPorts.length > 0 ? filePreviewPorts : null;
30880
30948
  return {
30881
30949
  port,
30882
30950
  host,
@@ -30888,8 +30956,7 @@ function resolveConfig(opts) {
30888
30956
  authToken,
30889
30957
  noTunnelPersist,
30890
30958
  frpcBinary,
30891
- mode,
30892
- previewPorts
30959
+ mode
30893
30960
  };
30894
30961
  }
30895
30962
  var HELP_TEXT = `clawd [options]
@@ -30918,8 +30985,8 @@ Env (advanced):
30918
30985
  `;
30919
30986
 
30920
30987
  // src/index.ts
30921
- var import_node_path44 = __toESM(require("path"), 1);
30922
- var import_node_fs29 = __toESM(require("fs"), 1);
30988
+ var import_node_path45 = __toESM(require("path"), 1);
30989
+ var import_node_fs30 = __toESM(require("fs"), 1);
30923
30990
 
30924
30991
  // src/logger.ts
30925
30992
  var import_node_fs2 = __toESM(require("fs"), 1);
@@ -36260,63 +36327,13 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
36260
36327
  var wrapper_default = import_websocket.default;
36261
36328
 
36262
36329
  // src/transport/local-ws-server.ts
36263
- var import_node_http2 = __toESM(require("http"), 1);
36264
-
36265
- // src/transport/preview-proxy.ts
36266
36330
  var import_node_http = __toESM(require("http"), 1);
36267
- var import_node_net = __toESM(require("net"), 1);
36268
- var PREVIEW_RE = /^\/preview\/(\d+)(?:\/.*)?$/;
36269
- function matchPreviewPort(pathname) {
36270
- const m2 = pathname.match(PREVIEW_RE);
36271
- return m2 ? Number(m2[1]) : null;
36272
- }
36273
- function isPreviewPortAllowed(port, cfg) {
36274
- return cfg.allowedPorts.includes(port);
36275
- }
36276
- function proxyPreviewHttp(req, res, port) {
36277
- return new Promise((resolve6) => {
36278
- const upstream = import_node_http.default.request(
36279
- { host: "localhost", port, method: req.method, path: req.url, headers: req.headers },
36280
- (upRes) => {
36281
- res.writeHead(upRes.statusCode || 502, upRes.headers);
36282
- upRes.pipe(res);
36283
- upRes.on("end", () => resolve6());
36284
- }
36285
- );
36286
- upstream.on("error", (e) => {
36287
- if (!res.headersSent) {
36288
- res.writeHead(502, { "content-type": "text/plain; charset=utf-8" });
36289
- }
36290
- res.end(`preview upstream error: ${e.message}`);
36291
- resolve6();
36292
- });
36293
- req.pipe(upstream);
36294
- });
36295
- }
36296
- function proxyPreviewUpgrade(req, socket, head, port) {
36297
- const upstream = import_node_net.default.connect(port, "localhost", () => {
36298
- let raw = `${req.method} ${req.url} HTTP/1.1\r
36299
- `;
36300
- for (let i = 0; i < req.rawHeaders.length; i += 2) {
36301
- raw += `${req.rawHeaders[i]}: ${req.rawHeaders[i + 1]}\r
36302
- `;
36303
- }
36304
- raw += "\r\n";
36305
- upstream.write(raw);
36306
- if (head && head.length) upstream.write(head);
36307
- upstream.pipe(socket);
36308
- socket.pipe(upstream);
36309
- });
36310
- upstream.on("error", () => socket.destroy());
36311
- socket.on("error", () => upstream.destroy());
36312
- }
36313
-
36314
- // src/transport/local-ws-server.ts
36315
36331
  var LocalWsServer = class {
36316
36332
  constructor(opts) {
36317
36333
  this.opts = opts;
36318
36334
  this.logger = opts.logger;
36319
36335
  this.pingIntervalMs = opts.pingIntervalMs ?? 3e4;
36336
+ this.currentOwnerPrincipalId = opts.ownerPrincipalId;
36320
36337
  }
36321
36338
  opts;
36322
36339
  wss = null;
@@ -36328,28 +36345,19 @@ var LocalWsServer = class {
36328
36345
  capabilityIdToClients = /* @__PURE__ */ new Map();
36329
36346
  logger;
36330
36347
  pingIntervalMs;
36348
+ // 飞书热切换(spec 决策 #9,2026-06-01 拍板):登录后实时更新,broadcastToPrincipal
36349
+ // 路由用最新身份;初始值来自 opts.ownerPrincipalId
36350
+ currentOwnerPrincipalId;
36351
+ /** 飞书热切换:登录回调成功后由 daemon wiring 调用,更新 DM 路由身份 */
36352
+ setOwnerPrincipalId(id) {
36353
+ this.currentOwnerPrincipalId = id;
36354
+ }
36331
36355
  async start() {
36332
36356
  const host = this.opts.host ?? "127.0.0.1";
36333
36357
  await new Promise((resolve6, reject) => {
36334
- const httpServer = import_node_http2.default.createServer((req, res) => this.handleHttpRequest(req, res));
36358
+ const httpServer = import_node_http.default.createServer((req, res) => this.handleHttpRequest(req, res));
36335
36359
  const wss = new import_websocket_server.default({ noServer: true, clientTracking: true });
36336
36360
  httpServer.on("upgrade", (req, socket, head) => {
36337
- if (req.url?.startsWith("/preview/")) {
36338
- const pathname = (() => {
36339
- try {
36340
- return new URL(req.url, "http://localhost").pathname;
36341
- } catch {
36342
- return "/";
36343
- }
36344
- })();
36345
- const port = matchPreviewPort(pathname);
36346
- if (port != null && (this.opts.previewPorts ?? []).includes(port)) {
36347
- proxyPreviewUpgrade(req, socket, head, port);
36348
- } else {
36349
- socket.destroy();
36350
- }
36351
- return;
36352
- }
36353
36361
  wss.handleUpgrade(req, socket, head, (ws) => {
36354
36362
  this.routeConnection(ws, req);
36355
36363
  });
@@ -36463,7 +36471,7 @@ var LocalWsServer = class {
36463
36471
  // principalId === 'cap_xxx' → 该 capability 的所有 guest ws (多端 / 多 tab)
36464
36472
  // 用于 DM inbox:event 路由: from + to 两侧各播一次, 让双方 UI 实时看到 thread 更新.
36465
36473
  broadcastToPrincipal(principalId, frame) {
36466
- if (principalId === "owner" || principalId === this.opts.ownerPrincipalId) {
36474
+ if (principalId === "owner" || principalId === this.currentOwnerPrincipalId) {
36467
36475
  this.broadcastToOwners(frame);
36468
36476
  return;
36469
36477
  }
@@ -36515,6 +36523,21 @@ var LocalWsServer = class {
36515
36523
  getClientContext(clientId) {
36516
36524
  return this.clients.get(clientId)?.ctx ?? null;
36517
36525
  }
36526
+ // 飞书热切换(spec 决策 #9):身份切换后踢掉所有连接强制重连——在线连接的 ctx
36527
+ // (attachClientContext 时绑定)持有旧身份,重连后用新身份重建。
36528
+ // close code 4408(非 4401!4401 = token 撤销,客户端会停止重连):
36529
+ // 客户端按普通断连处理 → 自动重连 → 新身份 ctx。
36530
+ closeAllClients(code = 4408, reason = "IDENTITY_SWITCHED") {
36531
+ const ids = [...this.clients.keys()];
36532
+ for (const id of ids) {
36533
+ const c = this.clients.get(id);
36534
+ if (!c) continue;
36535
+ try {
36536
+ c.ws.close(code, reason);
36537
+ } catch {
36538
+ }
36539
+ }
36540
+ }
36518
36541
  // Task 1.10 撤销级联:关闭某 capability 的所有活跃 ws 连接。close code 4401
36519
36542
  // 让客户端识别 'capability revoked' 而非普通断连。
36520
36543
  closeConnectionsByCapability(capabilityId, code = 4401, reason = "TOKEN_REVOKED") {
@@ -37997,13 +38020,23 @@ function createHttpRouter(deps) {
37997
38020
  });
37998
38021
  return true;
37999
38022
  }
38000
- if (url.pathname.startsWith("/preview/")) {
38001
- const port = matchPreviewPort(url.pathname);
38002
- if (port != null && isPreviewPortAllowed(port, { allowedPorts: deps.previewPorts ?? [] })) {
38003
- await proxyPreviewHttp(req, res, port);
38023
+ if (url.pathname === "/auth/callback") {
38024
+ if (req.method !== "GET") {
38025
+ sendJson(res, 404, { code: "NOT_FOUND", message: `no route for ${req.method} ${url.pathname}` });
38026
+ return true;
38027
+ }
38028
+ if (!deps.feishuLogin) {
38029
+ sendJson(res, 501, { code: "NOT_IMPLEMENTED", message: "feishu login not wired" });
38004
38030
  return true;
38005
38031
  }
38006
- sendJson(res, 403, { code: "PREVIEW_PORT_FORBIDDEN", message: "preview port not allowed" });
38032
+ const result = await deps.feishuLogin.handleLoginCallback({
38033
+ token: url.searchParams.get("token") ?? "",
38034
+ state: url.searchParams.get("state") ?? "",
38035
+ expiresAt: url.searchParams.get("expires_at")
38036
+ });
38037
+ const html = result.ok ? `<!doctype html><meta charset="utf-8"><title>clawd</title><style>body{font-family:-apple-system,sans-serif;padding:48px;color:#222}</style><h2>\u767B\u5F55\u6210\u529F\uFF0C\u53EF\u5173\u95ED\u6B64\u9875\u3002</h2><p>\u8EAB\u4EFD\uFF1A${escapeHtml(result.identity.displayName)}</p><script>setTimeout(()=>window.close(),1500)</script>` : `<!doctype html><meta charset="utf-8"><title>clawd</title><style>body{font-family:-apple-system,sans-serif;padding:48px;color:#922}</style><h2>\u767B\u5F55\u5931\u8D25</h2><p>${escapeHtml(result.reason)}</p>`;
38038
+ res.writeHead(result.ok ? 200 : 400, { "Content-Type": "text/html; charset=utf-8" });
38039
+ res.end(html);
38007
38040
  return true;
38008
38041
  }
38009
38042
  if (!url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/") && url.pathname !== "/attachments/upload" && url.pathname !== "/extensions/import") {
@@ -38241,6 +38274,9 @@ function parseUrl(rawUrl) {
38241
38274
  return null;
38242
38275
  }
38243
38276
  }
38277
+ function escapeHtml(s) {
38278
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
38279
+ }
38244
38280
  function sendJson(res, status, body, extraHeaders) {
38245
38281
  res.writeHead(status, {
38246
38282
  "Content-Type": "application/json; charset=utf-8",
@@ -39263,6 +39299,160 @@ function loadOwnerDisplayName(dataDir) {
39263
39299
  return displayName;
39264
39300
  }
39265
39301
 
39302
+ // src/feishu-auth/owner-identity-store.ts
39303
+ var import_node_fs27 = __toESM(require("fs"), 1);
39304
+ var import_node_path31 = __toESM(require("path"), 1);
39305
+ var OWNER_IDENTITY_FILE_NAME = "owner-identity.json";
39306
+ var OwnerIdentityStore = class {
39307
+ file;
39308
+ constructor(dataDir) {
39309
+ this.file = import_node_path31.default.join(dataDir, OWNER_IDENTITY_FILE_NAME);
39310
+ }
39311
+ read() {
39312
+ let raw;
39313
+ try {
39314
+ raw = import_node_fs27.default.readFileSync(this.file, "utf8");
39315
+ } catch {
39316
+ return null;
39317
+ }
39318
+ let parsed;
39319
+ try {
39320
+ parsed = JSON.parse(raw);
39321
+ } catch {
39322
+ return null;
39323
+ }
39324
+ const r = parsed;
39325
+ if (!r.identity || typeof r.identity.unionId !== "string" || r.identity.unionId.length === 0 || typeof r.identity.displayName !== "string" || r.identity.displayName.length === 0 || typeof r.ttcToken !== "string" || r.ttcToken.length === 0) {
39326
+ return null;
39327
+ }
39328
+ return {
39329
+ identity: {
39330
+ unionId: r.identity.unionId,
39331
+ displayName: r.identity.displayName,
39332
+ ...typeof r.identity.avatarUrl === "string" ? { avatarUrl: r.identity.avatarUrl } : {}
39333
+ },
39334
+ ttcToken: r.ttcToken,
39335
+ ttcTokenExpiresAt: typeof r.ttcTokenExpiresAt === "number" ? r.ttcTokenExpiresAt : null,
39336
+ loginAt: typeof r.loginAt === "string" ? r.loginAt : (/* @__PURE__ */ new Date(0)).toISOString()
39337
+ };
39338
+ }
39339
+ write(record) {
39340
+ import_node_fs27.default.mkdirSync(import_node_path31.default.dirname(this.file), { recursive: true });
39341
+ import_node_fs27.default.writeFileSync(this.file, JSON.stringify(record, null, 2), { mode: 384 });
39342
+ try {
39343
+ import_node_fs27.default.chmodSync(this.file, 384);
39344
+ } catch {
39345
+ }
39346
+ }
39347
+ clear() {
39348
+ try {
39349
+ import_node_fs27.default.unlinkSync(this.file);
39350
+ } catch (err) {
39351
+ const code = err?.code;
39352
+ if (code !== "ENOENT") throw err;
39353
+ }
39354
+ }
39355
+ };
39356
+
39357
+ // src/feishu-auth/login-flow.ts
39358
+ var import_node_crypto11 = __toESM(require("crypto"), 1);
39359
+ var STATE_TTL_MS = 5 * 60 * 1e3;
39360
+ var LoginFlow = class {
39361
+ constructor(deps) {
39362
+ this.deps = deps;
39363
+ }
39364
+ deps;
39365
+ pendingStates = /* @__PURE__ */ new Map();
39366
+ start() {
39367
+ const state = import_node_crypto11.default.randomBytes(16).toString("base64url");
39368
+ const now = (this.deps.now ?? Date.now)();
39369
+ this.pendingStates.set(state, now);
39370
+ this.gcExpired(now);
39371
+ const url = new URL("/auth/authorize", this.deps.ttcAuthBase);
39372
+ url.searchParams.set("callback_url", this.deps.getCallbackUrl());
39373
+ url.searchParams.set("state", state);
39374
+ url.searchParams.set("auto", "1");
39375
+ return { authUrl: url.toString(), state };
39376
+ }
39377
+ async handleCallback(params) {
39378
+ const now = (this.deps.now ?? Date.now)();
39379
+ const issuedAt = this.pendingStates.get(params.state);
39380
+ if (issuedAt === void 0) {
39381
+ return { ok: false, reason: "state mismatch" };
39382
+ }
39383
+ this.pendingStates.delete(params.state);
39384
+ if (now - issuedAt > STATE_TTL_MS) {
39385
+ return { ok: false, reason: "state expired" };
39386
+ }
39387
+ if (!params.token) {
39388
+ return { ok: false, reason: "missing token" };
39389
+ }
39390
+ let identity;
39391
+ try {
39392
+ identity = await this.deps.fetchUserInfo(params.token);
39393
+ } catch (err) {
39394
+ return { ok: false, reason: `user_info failed: ${err.message}` };
39395
+ }
39396
+ const expiresAtNum = params.expiresAt ? Number.parseInt(params.expiresAt, 10) : NaN;
39397
+ this.deps.store.write({
39398
+ identity,
39399
+ ttcToken: params.token,
39400
+ ttcTokenExpiresAt: Number.isFinite(expiresAtNum) ? expiresAtNum : null,
39401
+ loginAt: new Date(now).toISOString()
39402
+ });
39403
+ return { ok: true, identity };
39404
+ }
39405
+ gcExpired(now) {
39406
+ for (const [state, issuedAt] of this.pendingStates) {
39407
+ if (now - issuedAt > STATE_TTL_MS) this.pendingStates.delete(state);
39408
+ }
39409
+ }
39410
+ };
39411
+
39412
+ // src/feishu-auth/ttc-client.ts
39413
+ var TTC_HOSTS = {
39414
+ auth: "https://app.ttcadvisory.com",
39415
+ api: "https://api.ttcadvisory.com"
39416
+ };
39417
+ var TtcUserInfoError = class extends Error {
39418
+ constructor(code, message) {
39419
+ super(message);
39420
+ this.code = code;
39421
+ this.name = "TtcUserInfoError";
39422
+ }
39423
+ code;
39424
+ };
39425
+ async function fetchTtcUserInfo(opts) {
39426
+ const doFetch = opts.fetchImpl ?? fetch;
39427
+ let res;
39428
+ try {
39429
+ res = await doFetch(`${opts.apiBase}/api/user_service/v1/login/user`, {
39430
+ headers: { Authorization: `Bearer ${opts.token}` }
39431
+ });
39432
+ } catch (err) {
39433
+ throw new TtcUserInfoError("NETWORK", `TTC user_info request failed: ${err.message}`);
39434
+ }
39435
+ if (!res.ok) {
39436
+ if (res.status === 401) {
39437
+ throw new TtcUserInfoError("UNAUTHORIZED", "TTC token invalid or expired");
39438
+ }
39439
+ throw new TtcUserInfoError("API_ERROR", `TTC user_info HTTP ${res.status}`);
39440
+ }
39441
+ const body = await res.json();
39442
+ if (body.code !== 0) {
39443
+ throw new TtcUserInfoError("API_ERROR", `TTC user_info code=${body.code}: ${body.message ?? ""}`);
39444
+ }
39445
+ const d = body.data;
39446
+ if (!d || typeof d.union_id !== "string" || d.union_id.length === 0 || typeof d.name !== "string" || d.name.length === 0) {
39447
+ throw new TtcUserInfoError("BAD_RESPONSE", "TTC user_info missing union_id or name");
39448
+ }
39449
+ return {
39450
+ unionId: d.union_id,
39451
+ displayName: d.name,
39452
+ ...typeof d.avatar_url === "string" && d.avatar_url.length > 0 ? { avatarUrl: d.avatar_url } : {}
39453
+ };
39454
+ }
39455
+
39266
39456
  // src/index.ts
39267
39457
  init_protocol();
39268
39458
 
@@ -39270,12 +39460,12 @@ init_protocol();
39270
39460
  init_protocol();
39271
39461
 
39272
39462
  // src/session/fork.ts
39273
- var import_node_fs27 = __toESM(require("fs"), 1);
39463
+ var import_node_fs28 = __toESM(require("fs"), 1);
39274
39464
  var import_node_os14 = __toESM(require("os"), 1);
39275
- var import_node_path31 = __toESM(require("path"), 1);
39465
+ var import_node_path32 = __toESM(require("path"), 1);
39276
39466
  init_claude_history();
39277
39467
  function readJsonlEntries(file) {
39278
- const raw = import_node_fs27.default.readFileSync(file, "utf8");
39468
+ const raw = import_node_fs28.default.readFileSync(file, "utf8");
39279
39469
  const out = [];
39280
39470
  for (const line of raw.split("\n")) {
39281
39471
  const t = line.trim();
@@ -39288,10 +39478,10 @@ function readJsonlEntries(file) {
39288
39478
  return out;
39289
39479
  }
39290
39480
  function forkSession(input) {
39291
- const baseDir = input.baseDir ?? import_node_path31.default.join(import_node_os14.default.homedir(), ".claude");
39292
- const projectDir = import_node_path31.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
39293
- const sourceFile = import_node_path31.default.join(projectDir, `${input.toolSessionId}.jsonl`);
39294
- if (!import_node_fs27.default.existsSync(sourceFile)) {
39481
+ const baseDir = input.baseDir ?? import_node_path32.default.join(import_node_os14.default.homedir(), ".claude");
39482
+ const projectDir = import_node_path32.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
39483
+ const sourceFile = import_node_path32.default.join(projectDir, `${input.toolSessionId}.jsonl`);
39484
+ if (!import_node_fs28.default.existsSync(sourceFile)) {
39295
39485
  throw new Error(`fork: source transcript not found: ${sourceFile}`);
39296
39486
  }
39297
39487
  const entries = readJsonlEntries(sourceFile);
@@ -39321,9 +39511,9 @@ function forkSession(input) {
39321
39511
  }
39322
39512
  forkedLines.push(JSON.stringify(forked));
39323
39513
  }
39324
- const forkedFilePath = import_node_path31.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
39325
- import_node_fs27.default.mkdirSync(projectDir, { recursive: true });
39326
- import_node_fs27.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
39514
+ const forkedFilePath = import_node_path32.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
39515
+ import_node_fs28.default.mkdirSync(projectDir, { recursive: true });
39516
+ import_node_fs28.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
39327
39517
  return { forkedToolSessionId, forkedFilePath };
39328
39518
  }
39329
39519
 
@@ -39675,7 +39865,7 @@ function buildPermissionHandlers(deps) {
39675
39865
  }
39676
39866
 
39677
39867
  // src/handlers/history.ts
39678
- var path41 = __toESM(require("path"), 1);
39868
+ var path42 = __toESM(require("path"), 1);
39679
39869
  init_protocol();
39680
39870
 
39681
39871
  // src/session/recent-dirs.ts
@@ -39693,7 +39883,7 @@ function listRecentDirs(store, limit = 50) {
39693
39883
  }
39694
39884
 
39695
39885
  // src/permission/persona-paths.ts
39696
- var path40 = __toESM(require("path"), 1);
39886
+ var path41 = __toESM(require("path"), 1);
39697
39887
  function getAllowedPersonaIds(grants, action) {
39698
39888
  const ids = /* @__PURE__ */ new Set();
39699
39889
  for (const g2 of grants) {
@@ -39706,26 +39896,26 @@ function getAllowedPersonaIds(grants, action) {
39706
39896
  return ids;
39707
39897
  }
39708
39898
  function isGuestPathAllowed(grants, absPath, personaRoot, action = "read") {
39709
- const root = path40.resolve(personaRoot);
39710
- const target = path40.resolve(absPath);
39711
- const sep3 = root.endsWith(path40.sep) ? "" : path40.sep;
39899
+ const root = path41.resolve(personaRoot);
39900
+ const target = path41.resolve(absPath);
39901
+ const sep3 = root.endsWith(path41.sep) ? "" : path41.sep;
39712
39902
  if (!target.startsWith(root + sep3)) return false;
39713
- const rel = path40.relative(root, target);
39903
+ const rel = path41.relative(root, target);
39714
39904
  if (!rel || rel.startsWith("..")) return false;
39715
- const personaId = rel.split(path40.sep)[0];
39905
+ const personaId = rel.split(path41.sep)[0];
39716
39906
  if (!personaId) return false;
39717
39907
  const allowed = getAllowedPersonaIds(grants, action);
39718
39908
  if (allowed === "*") return true;
39719
39909
  return allowed.has(personaId);
39720
39910
  }
39721
39911
  function personaIdFromPath(absPath, personaRoot) {
39722
- const root = path40.resolve(personaRoot);
39723
- const target = path40.resolve(absPath);
39724
- const sep3 = root.endsWith(path40.sep) ? "" : path40.sep;
39912
+ const root = path41.resolve(personaRoot);
39913
+ const target = path41.resolve(absPath);
39914
+ const sep3 = root.endsWith(path41.sep) ? "" : path41.sep;
39725
39915
  if (!target.startsWith(root + sep3)) return null;
39726
- const rel = path40.relative(root, target);
39916
+ const rel = path41.relative(root, target);
39727
39917
  if (!rel || rel.startsWith("..")) return null;
39728
- const id = rel.split(path40.sep)[0];
39918
+ const id = rel.split(path41.sep)[0];
39729
39919
  return id || null;
39730
39920
  }
39731
39921
 
@@ -39750,7 +39940,7 @@ function buildHistoryHandlers(deps) {
39750
39940
  if (!pid) return false;
39751
39941
  return isGuestPathAllowed(
39752
39942
  ctx.grants,
39753
- path41.join(personaRoot, pid),
39943
+ path42.join(personaRoot, pid),
39754
39944
  personaRoot,
39755
39945
  "read"
39756
39946
  );
@@ -39761,7 +39951,7 @@ function buildHistoryHandlers(deps) {
39761
39951
  };
39762
39952
  const list = async (frame, _client, ctx) => {
39763
39953
  const args = HistoryListArgs.parse(frame);
39764
- assertGuestPath(ctx, path41.resolve(args.projectPath), personaRoot, "history:list");
39954
+ assertGuestPath(ctx, path42.resolve(args.projectPath), personaRoot, "history:list");
39765
39955
  const sessions = await history.listSessions(args);
39766
39956
  return { response: { type: "history:list", sessions } };
39767
39957
  };
@@ -39793,13 +39983,13 @@ function buildHistoryHandlers(deps) {
39793
39983
  };
39794
39984
  const subagents = async (frame, _client, ctx) => {
39795
39985
  const args = HistorySubagentsArgs.parse(frame);
39796
- assertGuestPath(ctx, path41.resolve(args.cwd), personaRoot, "history:subagents");
39986
+ assertGuestPath(ctx, path42.resolve(args.cwd), personaRoot, "history:subagents");
39797
39987
  const subs = await history.listSubagents(args);
39798
39988
  return { response: { type: "history:subagents", subagents: subs } };
39799
39989
  };
39800
39990
  const subagentRead = async (frame, _client, ctx) => {
39801
39991
  const args = HistorySubagentReadArgs.parse(frame);
39802
- assertGuestPath(ctx, path41.resolve(args.cwd), personaRoot, "history:subagent-read");
39992
+ assertGuestPath(ctx, path42.resolve(args.cwd), personaRoot, "history:subagent-read");
39803
39993
  const res = await history.readSubagent(args);
39804
39994
  return { response: { type: "history:subagent-read", ...res } };
39805
39995
  };
@@ -39807,7 +39997,7 @@ function buildHistoryHandlers(deps) {
39807
39997
  const dirs = listRecentDirs(store);
39808
39998
  if (ctx?.principal.kind === "guest" && personaRoot) {
39809
39999
  const filtered = dirs.filter(
39810
- (d) => isGuestPathAllowed(ctx.grants, path41.resolve(d.cwd), personaRoot, "read")
40000
+ (d) => isGuestPathAllowed(ctx.grants, path42.resolve(d.cwd), personaRoot, "read")
39811
40001
  );
39812
40002
  return { response: { type: "history:recentDirs", dirs: filtered } };
39813
40003
  }
@@ -39824,7 +40014,7 @@ function buildHistoryHandlers(deps) {
39824
40014
  }
39825
40015
 
39826
40016
  // src/handlers/workspace.ts
39827
- var path42 = __toESM(require("path"), 1);
40017
+ var path43 = __toESM(require("path"), 1);
39828
40018
  var os15 = __toESM(require("os"), 1);
39829
40019
  init_protocol();
39830
40020
  init_protocol();
@@ -39865,22 +40055,22 @@ function buildWorkspaceHandlers(deps) {
39865
40055
  const args = WorkspaceListArgs.parse(frame);
39866
40056
  const isGuest = ctx?.principal.kind === "guest";
39867
40057
  const fallbackCwd = isGuest && personaRoot ? personaRoot : os15.homedir();
39868
- const resolvedCwd = path42.resolve(args.cwd ?? fallbackCwd);
39869
- const target = args.path ? path42.resolve(resolvedCwd, args.path) : resolvedCwd;
40058
+ const resolvedCwd = path43.resolve(args.cwd ?? fallbackCwd);
40059
+ const target = args.path ? path43.resolve(resolvedCwd, args.path) : resolvedCwd;
39870
40060
  assertGuestPath2(ctx, target, personaRoot, "workspace:list");
39871
40061
  const res = workspace.list({ ...args, cwd: resolvedCwd });
39872
40062
  return { response: { type: "workspace:list", ...res } };
39873
40063
  };
39874
40064
  const read = async (frame, _client, ctx) => {
39875
40065
  const args = WorkspaceReadArgs.parse(frame);
39876
- const target = path42.isAbsolute(args.path) ? path42.resolve(args.path) : path42.resolve(args.cwd, args.path);
40066
+ const target = path43.isAbsolute(args.path) ? path43.resolve(args.path) : path43.resolve(args.cwd, args.path);
39877
40067
  assertGuestPath2(ctx, target, personaRoot, "workspace:read");
39878
40068
  const res = workspace.read(args);
39879
40069
  return { response: { type: "workspace:read", ...res } };
39880
40070
  };
39881
40071
  const skillsList = async (frame, _client, ctx) => {
39882
40072
  const args = SkillsListArgs.parse(frame);
39883
- const cwdAbs = path42.resolve(args.cwd);
40073
+ const cwdAbs = path43.resolve(args.cwd);
39884
40074
  assertGuestPath2(ctx, cwdAbs, personaRoot, "skills:list");
39885
40075
  const list2 = skills.list(args);
39886
40076
  if (ctx?.principal.kind === "guest" && personaRoot) {
@@ -39892,7 +40082,7 @@ function buildWorkspaceHandlers(deps) {
39892
40082
  };
39893
40083
  const agentsList = async (frame, _client, ctx) => {
39894
40084
  const args = AgentsListArgs.parse(frame);
39895
- const cwdAbs = path42.resolve(args.cwd);
40085
+ const cwdAbs = path43.resolve(args.cwd);
39896
40086
  assertGuestPath2(ctx, cwdAbs, personaRoot, "agents:list");
39897
40087
  const list2 = agents.list(args);
39898
40088
  if (ctx?.principal.kind === "guest" && personaRoot) {
@@ -39911,20 +40101,20 @@ function buildWorkspaceHandlers(deps) {
39911
40101
  }
39912
40102
 
39913
40103
  // src/handlers/git.ts
39914
- var path44 = __toESM(require("path"), 1);
40104
+ var path45 = __toESM(require("path"), 1);
39915
40105
  init_protocol();
39916
40106
  init_protocol();
39917
40107
 
39918
40108
  // src/workspace/git.ts
39919
40109
  var import_node_child_process6 = require("child_process");
39920
- var import_node_fs28 = __toESM(require("fs"), 1);
39921
- var import_node_path32 = __toESM(require("path"), 1);
40110
+ var import_node_fs29 = __toESM(require("fs"), 1);
40111
+ var import_node_path33 = __toESM(require("path"), 1);
39922
40112
  var import_node_util = require("util");
39923
40113
  var pexec = (0, import_node_util.promisify)(import_node_child_process6.execFile);
39924
40114
  function normalizePath(p2) {
39925
- const resolved = import_node_path32.default.resolve(p2);
40115
+ const resolved = import_node_path33.default.resolve(p2);
39926
40116
  try {
39927
- return import_node_fs28.default.realpathSync(resolved);
40117
+ return import_node_fs29.default.realpathSync(resolved);
39928
40118
  } catch {
39929
40119
  return resolved;
39930
40120
  }
@@ -39997,7 +40187,7 @@ async function listGitBranches(cwd) {
39997
40187
  // src/handlers/git.ts
39998
40188
  function assertGuestCwd(ctx, cwd, personaRoot, method) {
39999
40189
  if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
40000
- if (!isGuestPathAllowed(ctx.grants, path44.resolve(cwd), personaRoot, "read")) {
40190
+ if (!isGuestPathAllowed(ctx.grants, path45.resolve(cwd), personaRoot, "read")) {
40001
40191
  throw new ClawdError(
40002
40192
  ERROR_CODES.UNAUTHORIZED,
40003
40193
  `guest ${ctx.principal.id} cannot ${method} cwd ${cwd}`
@@ -40359,6 +40549,36 @@ function buildWhoamiHandler(deps) {
40359
40549
  };
40360
40550
  }
40361
40551
 
40552
+ // src/handlers/feishu-auth.ts
40553
+ function buildFeishuAuthHandlers(deps) {
40554
+ const loginStart = async () => {
40555
+ const { authUrl, state } = deps.loginFlow.start();
40556
+ return {
40557
+ response: { type: "auth:login:start:ok", authUrl, state }
40558
+ };
40559
+ };
40560
+ const getIdentity = async () => {
40561
+ const record = deps.ownerIdentityStore.read();
40562
+ return {
40563
+ response: {
40564
+ type: "auth:getIdentity:ok",
40565
+ identity: record ? record.identity : null
40566
+ }
40567
+ };
40568
+ };
40569
+ const logout = async () => {
40570
+ deps.ownerIdentityStore.clear();
40571
+ return {
40572
+ response: { type: "auth:logout:ok" }
40573
+ };
40574
+ };
40575
+ return {
40576
+ "auth:login:start": loginStart,
40577
+ "auth:getIdentity": getIdentity,
40578
+ "auth:logout": logout
40579
+ };
40580
+ }
40581
+
40362
40582
  // src/handlers/meta.ts
40363
40583
  var import_node_os15 = __toESM(require("os"), 1);
40364
40584
  init_protocol();
@@ -40486,7 +40706,7 @@ function buildPersonaHandlers(deps) {
40486
40706
  }
40487
40707
 
40488
40708
  // src/handlers/attachment.ts
40489
- var import_node_path33 = __toESM(require("path"), 1);
40709
+ var import_node_path34 = __toESM(require("path"), 1);
40490
40710
  init_protocol();
40491
40711
  init_protocol();
40492
40712
  var DEFAULT_TTL_SECONDS = 24 * 3600;
@@ -40540,12 +40760,12 @@ function buildAttachmentHandlers(deps) {
40540
40760
  `session ${args.sessionId} scope unresolved`
40541
40761
  );
40542
40762
  }
40543
- const cwdAbs = import_node_path33.default.resolve(sessionFile.cwd);
40544
- const candidateAbs = import_node_path33.default.isAbsolute(args.relPath) ? import_node_path33.default.resolve(args.relPath) : import_node_path33.default.resolve(cwdAbs, args.relPath);
40763
+ const cwdAbs = import_node_path34.default.resolve(sessionFile.cwd);
40764
+ const candidateAbs = import_node_path34.default.isAbsolute(args.relPath) ? import_node_path34.default.resolve(args.relPath) : import_node_path34.default.resolve(cwdAbs, args.relPath);
40545
40765
  assertGuestAttachmentPath(ctx, candidateAbs, deps.personaRoot, "attachment.signUrl");
40546
40766
  const entries = deps.groupFileStore.list(scope, args.sessionId);
40547
40767
  const entry = entries.find((e) => {
40548
- const storedAbs = import_node_path33.default.isAbsolute(e.relPath) ? import_node_path33.default.resolve(e.relPath) : import_node_path33.default.resolve(cwdAbs, e.relPath);
40768
+ const storedAbs = import_node_path34.default.isAbsolute(e.relPath) ? import_node_path34.default.resolve(e.relPath) : import_node_path34.default.resolve(cwdAbs, e.relPath);
40549
40769
  return storedAbs === candidateAbs && !e.stale;
40550
40770
  });
40551
40771
  if (!entry) {
@@ -40569,7 +40789,7 @@ function buildAttachmentHandlers(deps) {
40569
40789
  if (!ctx || ctx.principal.kind !== "guest" || !deps.personaRoot || !deps.sessionStore) return;
40570
40790
  const f = deps.sessionStore.read(sessionId);
40571
40791
  if (!f) return;
40572
- assertGuestAttachmentPath(ctx, import_node_path33.default.resolve(f.cwd), deps.personaRoot, method);
40792
+ assertGuestAttachmentPath(ctx, import_node_path34.default.resolve(f.cwd), deps.personaRoot, method);
40573
40793
  }
40574
40794
  const groupAdd = async (frame, _client, ctx) => {
40575
40795
  if (!deps.groupFileStore || !deps.getSessionScope) {
@@ -40641,19 +40861,19 @@ function buildAttachmentHandlers(deps) {
40641
40861
 
40642
40862
  // src/handlers/extension.ts
40643
40863
  var import_promises7 = __toESM(require("fs/promises"), 1);
40644
- var import_node_path38 = __toESM(require("path"), 1);
40864
+ var import_node_path39 = __toESM(require("path"), 1);
40645
40865
  init_protocol();
40646
40866
 
40647
40867
  // src/extension/bundle-zip.ts
40648
40868
  var import_promises4 = __toESM(require("fs/promises"), 1);
40649
- var import_node_path34 = __toESM(require("path"), 1);
40650
- var import_node_crypto11 = __toESM(require("crypto"), 1);
40869
+ var import_node_path35 = __toESM(require("path"), 1);
40870
+ var import_node_crypto12 = __toESM(require("crypto"), 1);
40651
40871
  var import_jszip2 = __toESM(require_lib3(), 1);
40652
40872
  async function bundleExtensionDir(dir) {
40653
40873
  const entries = await listFilesSorted(dir);
40654
40874
  const zip = new import_jszip2.default();
40655
40875
  for (const rel of entries) {
40656
- const abs = import_node_path34.default.join(dir, rel);
40876
+ const abs = import_node_path35.default.join(dir, rel);
40657
40877
  const content = await import_promises4.default.readFile(abs);
40658
40878
  zip.file(rel, content, { date: FIXED_DATE });
40659
40879
  }
@@ -40662,7 +40882,7 @@ async function bundleExtensionDir(dir) {
40662
40882
  compression: "DEFLATE",
40663
40883
  compressionOptions: { level: 6 }
40664
40884
  });
40665
- const sha256 = import_node_crypto11.default.createHash("sha256").update(buffer).digest("hex");
40885
+ const sha256 = import_node_crypto12.default.createHash("sha256").update(buffer).digest("hex");
40666
40886
  return { buffer, sha256 };
40667
40887
  }
40668
40888
  var FIXED_DATE = /* @__PURE__ */ new Date("2020-01-01T00:00:00.000Z");
@@ -40674,7 +40894,7 @@ async function listFilesSorted(rootDir) {
40674
40894
  return out;
40675
40895
  }
40676
40896
  async function walk(absRoot, relPrefix, out) {
40677
- const dirAbs = import_node_path34.default.join(absRoot, relPrefix);
40897
+ const dirAbs = import_node_path35.default.join(absRoot, relPrefix);
40678
40898
  const entries = await import_promises4.default.readdir(dirAbs, { withFileTypes: true });
40679
40899
  for (const e of entries) {
40680
40900
  if (IGNORE_BASENAMES.has(e.name)) continue;
@@ -40728,25 +40948,25 @@ function computePublishCheck(args) {
40728
40948
 
40729
40949
  // src/extension/install-flow.ts
40730
40950
  var import_promises5 = __toESM(require("fs/promises"), 1);
40731
- var import_node_path36 = __toESM(require("path"), 1);
40951
+ var import_node_path37 = __toESM(require("path"), 1);
40732
40952
  var import_node_os17 = __toESM(require("os"), 1);
40733
- var import_node_crypto12 = __toESM(require("crypto"), 1);
40953
+ var import_node_crypto13 = __toESM(require("crypto"), 1);
40734
40954
  var import_jszip3 = __toESM(require_lib3(), 1);
40735
40955
 
40736
40956
  // src/extension/paths.ts
40737
40957
  var import_node_os16 = __toESM(require("os"), 1);
40738
- var import_node_path35 = __toESM(require("path"), 1);
40958
+ var import_node_path36 = __toESM(require("path"), 1);
40739
40959
  function clawdHomeRoot(override) {
40740
- return override ?? process.env.CLAWD_HOME ?? import_node_path35.default.join(import_node_os16.default.homedir(), ".clawd");
40960
+ return override ?? process.env.CLAWD_HOME ?? import_node_path36.default.join(import_node_os16.default.homedir(), ".clawd");
40741
40961
  }
40742
40962
  function extensionsRoot(override) {
40743
- return import_node_path35.default.join(clawdHomeRoot(override), "extensions");
40963
+ return import_node_path36.default.join(clawdHomeRoot(override), "extensions");
40744
40964
  }
40745
40965
  function publishedChannelsFile(override) {
40746
- return import_node_path35.default.join(clawdHomeRoot(override), "extensions-published.json");
40966
+ return import_node_path36.default.join(clawdHomeRoot(override), "extensions-published.json");
40747
40967
  }
40748
40968
  function bundleCacheRoot(override) {
40749
- return import_node_path35.default.join(clawdHomeRoot(override), "extension-bundles");
40969
+ return import_node_path36.default.join(clawdHomeRoot(override), "extension-bundles");
40750
40970
  }
40751
40971
 
40752
40972
  // src/extension/install-flow.ts
@@ -40759,7 +40979,7 @@ var InstallError = class extends Error {
40759
40979
  };
40760
40980
  async function installFromChannel(args, deps) {
40761
40981
  const { channelRef, snapshotHash, bundleZip } = args;
40762
- const computed = import_node_crypto12.default.createHash("sha256").update(bundleZip).digest("hex");
40982
+ const computed = import_node_crypto13.default.createHash("sha256").update(bundleZip).digest("hex");
40763
40983
  if (computed !== snapshotHash) {
40764
40984
  throw new InstallError(
40765
40985
  "HASH_MISMATCH",
@@ -40773,7 +40993,7 @@ async function installFromChannel(args, deps) {
40773
40993
  throw new InstallError("ZIP_INVALID", `failed to load zip: ${e.message}`);
40774
40994
  }
40775
40995
  for (const name of Object.keys(zip.files)) {
40776
- if (name.includes("..") || name.startsWith("/") || import_node_path36.default.isAbsolute(name)) {
40996
+ if (name.includes("..") || name.startsWith("/") || import_node_path37.default.isAbsolute(name)) {
40777
40997
  throw new InstallError("ZIP_INVALID", `unsafe zip entry: ${name}`);
40778
40998
  }
40779
40999
  }
@@ -40805,7 +41025,7 @@ async function installFromChannel(args, deps) {
40805
41025
  );
40806
41026
  }
40807
41027
  const localExtId = namespacedExtId(ownerSlug, channelRef.ownerPrincipalId);
40808
- const destDir = import_node_path36.default.join(deps.extensionsRoot, localExtId);
41028
+ const destDir = import_node_path37.default.join(deps.extensionsRoot, localExtId);
40809
41029
  let destExists = false;
40810
41030
  try {
40811
41031
  await import_promises5.default.access(destDir);
@@ -40819,16 +41039,16 @@ async function installFromChannel(args, deps) {
40819
41039
  );
40820
41040
  }
40821
41041
  const stage = await import_promises5.default.mkdtemp(
40822
- import_node_path36.default.join(import_node_os17.default.tmpdir(), `clawd-ext-install-${localExtId}-`)
41042
+ import_node_path37.default.join(import_node_os17.default.tmpdir(), `clawd-ext-install-${localExtId}-`)
40823
41043
  );
40824
41044
  try {
40825
41045
  for (const [name, entry] of Object.entries(zip.files)) {
40826
- const dest = import_node_path36.default.join(stage, name);
41046
+ const dest = import_node_path37.default.join(stage, name);
40827
41047
  if (entry.dir) {
40828
41048
  await import_promises5.default.mkdir(dest, { recursive: true });
40829
41049
  continue;
40830
41050
  }
40831
- await import_promises5.default.mkdir(import_node_path36.default.dirname(dest), { recursive: true });
41051
+ await import_promises5.default.mkdir(import_node_path37.default.dirname(dest), { recursive: true });
40832
41052
  if (name === "manifest.json") {
40833
41053
  const rewritten = { ...parsed.data, id: localExtId };
40834
41054
  await import_promises5.default.writeFile(dest, JSON.stringify(rewritten, null, 2));
@@ -40849,9 +41069,9 @@ async function installFromChannel(args, deps) {
40849
41069
 
40850
41070
  // src/extension/update-flow.ts
40851
41071
  var import_promises6 = __toESM(require("fs/promises"), 1);
40852
- var import_node_path37 = __toESM(require("path"), 1);
41072
+ var import_node_path38 = __toESM(require("path"), 1);
40853
41073
  var import_node_os18 = __toESM(require("os"), 1);
40854
- var import_node_crypto13 = __toESM(require("crypto"), 1);
41074
+ var import_node_crypto14 = __toESM(require("crypto"), 1);
40855
41075
  var import_jszip4 = __toESM(require_lib3(), 1);
40856
41076
  var UpdateError = class extends Error {
40857
41077
  constructor(code, message) {
@@ -40866,11 +41086,11 @@ async function updateFromChannel(args, deps) {
40866
41086
  channelRef.extId,
40867
41087
  channelRef.ownerPrincipalId
40868
41088
  );
40869
- const liveDir = import_node_path37.default.join(deps.extensionsRoot, localExtId);
41089
+ const liveDir = import_node_path38.default.join(deps.extensionsRoot, localExtId);
40870
41090
  const prevDir = `${liveDir}.prev`;
40871
41091
  let existingVersion;
40872
41092
  try {
40873
- const raw = await import_promises6.default.readFile(import_node_path37.default.join(liveDir, "manifest.json"), "utf8");
41093
+ const raw = await import_promises6.default.readFile(import_node_path38.default.join(liveDir, "manifest.json"), "utf8");
40874
41094
  const parsed2 = ExtensionManifestSchema.safeParse(JSON.parse(raw));
40875
41095
  if (!parsed2.success) {
40876
41096
  throw new UpdateError(
@@ -40889,7 +41109,7 @@ async function updateFromChannel(args, deps) {
40889
41109
  if (e instanceof UpdateError) throw e;
40890
41110
  throw e;
40891
41111
  }
40892
- const computed = import_node_crypto13.default.createHash("sha256").update(bundleZip).digest("hex");
41112
+ const computed = import_node_crypto14.default.createHash("sha256").update(bundleZip).digest("hex");
40893
41113
  if (computed !== snapshotHash) {
40894
41114
  throw new UpdateError(
40895
41115
  "HASH_MISMATCH",
@@ -40903,7 +41123,7 @@ async function updateFromChannel(args, deps) {
40903
41123
  throw new UpdateError("ZIP_INVALID", `failed to load zip: ${e.message}`);
40904
41124
  }
40905
41125
  for (const name of Object.keys(zip.files)) {
40906
- if (name.includes("..") || name.startsWith("/") || import_node_path37.default.isAbsolute(name)) {
41126
+ if (name.includes("..") || name.startsWith("/") || import_node_path38.default.isAbsolute(name)) {
40907
41127
  throw new UpdateError("ZIP_INVALID", `unsafe zip entry: ${name}`);
40908
41128
  }
40909
41129
  }
@@ -40938,16 +41158,16 @@ async function updateFromChannel(args, deps) {
40938
41158
  await import_promises6.default.rm(prevDir, { recursive: true, force: true });
40939
41159
  await import_promises6.default.rename(liveDir, prevDir);
40940
41160
  const stage = await import_promises6.default.mkdtemp(
40941
- import_node_path37.default.join(import_node_os18.default.tmpdir(), `clawd-ext-update-${localExtId}-`)
41161
+ import_node_path38.default.join(import_node_os18.default.tmpdir(), `clawd-ext-update-${localExtId}-`)
40942
41162
  );
40943
41163
  try {
40944
41164
  for (const [name, entry] of Object.entries(zip.files)) {
40945
- const dest = import_node_path37.default.join(stage, name);
41165
+ const dest = import_node_path38.default.join(stage, name);
40946
41166
  if (entry.dir) {
40947
41167
  await import_promises6.default.mkdir(dest, { recursive: true });
40948
41168
  continue;
40949
41169
  }
40950
- await import_promises6.default.mkdir(import_node_path37.default.dirname(dest), { recursive: true });
41170
+ await import_promises6.default.mkdir(import_node_path38.default.dirname(dest), { recursive: true });
40951
41171
  if (name === "manifest.json") {
40952
41172
  const rewritten = { ...parsed.data, id: localExtId };
40953
41173
  await import_promises6.default.writeFile(dest, JSON.stringify(rewritten, null, 2));
@@ -41040,7 +41260,7 @@ async function rewriteManifestVersion(root, extId, newVersion, previousPublished
41040
41260
  );
41041
41261
  }
41042
41262
  }
41043
- const manifestPath = import_node_path38.default.join(root, extId, "manifest.json");
41263
+ const manifestPath = import_node_path39.default.join(root, extId, "manifest.json");
41044
41264
  const manifest = await readManifest(root, extId);
41045
41265
  const next = { ...manifest, version: newVersion };
41046
41266
  const tmp = `${manifestPath}.tmp`;
@@ -41048,7 +41268,7 @@ async function rewriteManifestVersion(root, extId, newVersion, previousPublished
41048
41268
  await import_promises7.default.rename(tmp, manifestPath);
41049
41269
  }
41050
41270
  async function readManifest(root, extId) {
41051
- const file = import_node_path38.default.join(root, extId, "manifest.json");
41271
+ const file = import_node_path39.default.join(root, extId, "manifest.json");
41052
41272
  let raw;
41053
41273
  try {
41054
41274
  raw = await import_promises7.default.readFile(file, "utf8");
@@ -41139,7 +41359,7 @@ function buildExtensionHandlers(deps) {
41139
41359
  };
41140
41360
  async function buildSnapshotMeta(extId) {
41141
41361
  const manifest = await readManifest(deps.root, extId);
41142
- const { sha256, buffer } = await bundleExtensionDir(import_node_path38.default.join(deps.root, extId));
41362
+ const { sha256, buffer } = await bundleExtensionDir(import_node_path39.default.join(deps.root, extId));
41143
41363
  return { manifest, contentHash: sha256, buffer };
41144
41364
  }
41145
41365
  const publish = async (frame, _client, ctx) => {
@@ -41321,7 +41541,7 @@ function buildExtensionHandlers(deps) {
41321
41541
 
41322
41542
  // src/extension/registry.ts
41323
41543
  var import_promises8 = __toESM(require("fs/promises"), 1);
41324
- var import_node_path39 = __toESM(require("path"), 1);
41544
+ var import_node_path40 = __toESM(require("path"), 1);
41325
41545
  async function loadAll(root) {
41326
41546
  let entries;
41327
41547
  try {
@@ -41334,13 +41554,13 @@ async function loadAll(root) {
41334
41554
  for (const ent of entries) {
41335
41555
  if (!ent.isDirectory()) continue;
41336
41556
  if (ent.name.startsWith(".")) continue;
41337
- records.push(await loadOne(import_node_path39.default.join(root, ent.name), ent.name));
41557
+ records.push(await loadOne(import_node_path40.default.join(root, ent.name), ent.name));
41338
41558
  }
41339
41559
  records.sort((a, b2) => a.extId < b2.extId ? -1 : a.extId > b2.extId ? 1 : 0);
41340
41560
  return records;
41341
41561
  }
41342
41562
  async function loadOne(dir, dirName) {
41343
- const manifestPath = import_node_path39.default.join(dir, "manifest.json");
41563
+ const manifestPath = import_node_path40.default.join(dir, "manifest.json");
41344
41564
  let raw;
41345
41565
  try {
41346
41566
  raw = await import_promises8.default.readFile(manifestPath, "utf8");
@@ -41385,7 +41605,7 @@ async function loadOne(dir, dirName) {
41385
41605
 
41386
41606
  // src/extension/uninstall.ts
41387
41607
  var import_promises9 = __toESM(require("fs/promises"), 1);
41388
- var import_node_path40 = __toESM(require("path"), 1);
41608
+ var import_node_path41 = __toESM(require("path"), 1);
41389
41609
  var UninstallError = class extends Error {
41390
41610
  constructor(code, message) {
41391
41611
  super(message);
@@ -41394,7 +41614,7 @@ var UninstallError = class extends Error {
41394
41614
  code;
41395
41615
  };
41396
41616
  async function uninstall(deps) {
41397
- const dir = import_node_path40.default.join(deps.root, deps.extId);
41617
+ const dir = import_node_path41.default.join(deps.root, deps.extId);
41398
41618
  try {
41399
41619
  await import_promises9.default.access(dir);
41400
41620
  } catch {
@@ -41440,6 +41660,7 @@ function buildMethodHandlers(deps) {
41440
41660
  personaStore: deps.personaStore,
41441
41661
  capabilityManager: deps.capabilityManager
41442
41662
  }),
41663
+ ...buildFeishuAuthHandlers(deps.feishuAuth),
41443
41664
  ...deps.attachment ? buildAttachmentHandlers(deps.attachment) : {},
41444
41665
  ...buildExtensionHandlers({
41445
41666
  loadAll,
@@ -41581,7 +41802,12 @@ var METHOD_GRANT_MAP = {
41581
41802
  "extension.fetchBundle": CAPABILITY_SCOPED,
41582
41803
  // guest-self:guest 在自己 daemon 上触发安装与更新(无持久化 subscription 概念)
41583
41804
  "extension.install-from-channel": ADMIN_ANY,
41584
- "extension.update-from-channel": ADMIN_ANY
41805
+ "extension.update-from-channel": ADMIN_ANY,
41806
+ // ---- auth:* 飞书统一身份 Phase 1(owner-only) ----
41807
+ // 登录/登出/查身份都是 owner 本机操作,guest 不可调
41808
+ "auth:login:start": ADMIN_ANY,
41809
+ "auth:getIdentity": ADMIN_ANY,
41810
+ "auth:logout": ADMIN_ANY
41585
41811
  };
41586
41812
  function computeGrantForFrame(method, frame) {
41587
41813
  const rule = METHOD_GRANT_MAP[method];
@@ -41598,11 +41824,11 @@ function computeGrantForFrame(method, frame) {
41598
41824
 
41599
41825
  // src/extension/runtime.ts
41600
41826
  var import_node_child_process7 = require("child_process");
41601
- var import_node_path41 = __toESM(require("path"), 1);
41827
+ var import_node_path42 = __toESM(require("path"), 1);
41602
41828
  var import_promises10 = require("timers/promises");
41603
41829
 
41604
41830
  // src/extension/port-allocator.ts
41605
- var import_node_net2 = __toESM(require("net"), 1);
41831
+ var import_node_net = __toESM(require("net"), 1);
41606
41832
  var PortExhaustedError = class extends Error {
41607
41833
  constructor(min, max) {
41608
41834
  super(`no free port in [${min},${max}]`);
@@ -41615,7 +41841,7 @@ var PortExhaustedError = class extends Error {
41615
41841
  };
41616
41842
  function probe(port) {
41617
41843
  return new Promise((resolve6) => {
41618
- const srv = import_node_net2.default.createServer();
41844
+ const srv = import_node_net.default.createServer();
41619
41845
  srv.once("error", () => resolve6(false));
41620
41846
  srv.once("listening", () => {
41621
41847
  srv.close(() => resolve6(true));
@@ -41699,7 +41925,7 @@ var Runtime = class {
41699
41925
  /\$CLAWOS_EXT_PORT/g,
41700
41926
  String(port)
41701
41927
  );
41702
- const dir = import_node_path41.default.join(this.root, extId);
41928
+ const dir = import_node_path42.default.join(this.root, extId);
41703
41929
  const env = {
41704
41930
  ...process.env,
41705
41931
  CLAWOS_EXT_PORT: String(port),
@@ -41811,7 +42037,7 @@ ${handle.stderrTail}`
41811
42037
 
41812
42038
  // src/extension/published-channels.ts
41813
42039
  var import_promises11 = __toESM(require("fs/promises"), 1);
41814
- var import_node_path42 = __toESM(require("path"), 1);
42040
+ var import_node_path43 = __toESM(require("path"), 1);
41815
42041
  init_zod();
41816
42042
  var PublishedChannelsError = class extends Error {
41817
42043
  constructor(code, message) {
@@ -41910,7 +42136,7 @@ var PublishedChannelStore = class {
41910
42136
  )
41911
42137
  };
41912
42138
  const tmp = `${this.filePath}.tmp`;
41913
- await import_promises11.default.mkdir(import_node_path42.default.dirname(this.filePath), { recursive: true });
42139
+ await import_promises11.default.mkdir(import_node_path43.default.dirname(this.filePath), { recursive: true });
41914
42140
  await import_promises11.default.writeFile(tmp, JSON.stringify(data, null, 2), { mode: 384 });
41915
42141
  await import_promises11.default.rename(tmp, this.filePath);
41916
42142
  }
@@ -41918,7 +42144,7 @@ var PublishedChannelStore = class {
41918
42144
 
41919
42145
  // src/extension/bundle-cache.ts
41920
42146
  var import_promises12 = __toESM(require("fs/promises"), 1);
41921
- var import_node_path43 = __toESM(require("path"), 1);
42147
+ var import_node_path44 = __toESM(require("path"), 1);
41922
42148
  var BundleCache = class {
41923
42149
  constructor(rootDir) {
41924
42150
  this.rootDir = rootDir;
@@ -41927,14 +42153,14 @@ var BundleCache = class {
41927
42153
  /** Atomic write: stage tmp → rename. Caller passes the hex sha256. */
41928
42154
  async write(snapshotHash, buffer) {
41929
42155
  await import_promises12.default.mkdir(this.rootDir, { recursive: true });
41930
- const file = import_node_path43.default.join(this.rootDir, `${snapshotHash}.zip`);
42156
+ const file = import_node_path44.default.join(this.rootDir, `${snapshotHash}.zip`);
41931
42157
  const tmp = `${file}.tmp`;
41932
42158
  await import_promises12.default.writeFile(tmp, buffer, { mode: 384 });
41933
42159
  await import_promises12.default.rename(tmp, file);
41934
42160
  }
41935
42161
  /** Returns the bundle bytes, or null when the file doesn't exist. */
41936
42162
  async read(snapshotHash) {
41937
- const file = import_node_path43.default.join(this.rootDir, `${snapshotHash}.zip`);
42163
+ const file = import_node_path44.default.join(this.rootDir, `${snapshotHash}.zip`);
41938
42164
  try {
41939
42165
  return await import_promises12.default.readFile(file);
41940
42166
  } catch (e) {
@@ -41944,7 +42170,7 @@ var BundleCache = class {
41944
42170
  }
41945
42171
  /** Idempotent — missing file is not an error. */
41946
42172
  async delete(snapshotHash) {
41947
- const file = import_node_path43.default.join(this.rootDir, `${snapshotHash}.zip`);
42173
+ const file = import_node_path44.default.join(this.rootDir, `${snapshotHash}.zip`);
41948
42174
  await import_promises12.default.rm(file, { force: true });
41949
42175
  }
41950
42176
  };
@@ -41953,7 +42179,7 @@ var BundleCache = class {
41953
42179
  async function startDaemon(config) {
41954
42180
  const logger = createLogger({
41955
42181
  level: config.logLevel,
41956
- file: import_node_path44.default.join(config.dataDir, "clawd.log")
42182
+ file: import_node_path45.default.join(config.dataDir, "clawd.log")
41957
42183
  });
41958
42184
  logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
41959
42185
  const stateMgr = new StateFileManager({ dataDir: config.dataDir });
@@ -41971,8 +42197,11 @@ async function startDaemon(config) {
41971
42197
  } else if (config.tunnel) {
41972
42198
  resolvedAuthToken = authFile.token;
41973
42199
  }
41974
- const ownerPrincipalId = authFile.ownerPrincipalId;
41975
- const ownerDisplayName = loadOwnerDisplayName(config.dataDir);
42200
+ const ownerIdentityStore = new OwnerIdentityStore(config.dataDir);
42201
+ const feishuIdentity = ownerIdentityStore.read();
42202
+ let feishuActive = feishuIdentity !== null;
42203
+ let ownerPrincipalId = feishuIdentity?.identity.unionId ?? authFile.ownerPrincipalId;
42204
+ let ownerDisplayName = feishuIdentity?.identity.displayName ?? loadOwnerDisplayName(config.dataDir);
41976
42205
  const authMode = resolvedAuthToken == null ? "none" : "first-message";
41977
42206
  const inboxStore = new InboxStore(config.dataDir);
41978
42207
  const inboxManager = new InboxManager(inboxStore, (_peerOwnerId, frame) => {
@@ -42014,7 +42243,9 @@ async function startDaemon(config) {
42014
42243
  isOwnerToken: (x) => resolvedAuthToken != null && constantTimeEqual(x, resolvedAuthToken),
42015
42244
  ownerPrincipalId,
42016
42245
  ownerDisplayName,
42017
- capabilityRegistry,
42246
+ // 飞书 gate(spec 决策 #9):未激活飞书身份时不接受 guest(capability token)入站——
42247
+ // 不传 registry 让 guest token 一律 BAD_TOKEN。owner 自己连接不受影响。
42248
+ ...feishuActive ? { capabilityRegistry } : {},
42018
42249
  selfPrincipalId,
42019
42250
  selfDisplayName
42020
42251
  });
@@ -42040,7 +42271,7 @@ async function startDaemon(config) {
42040
42271
  const agents = new AgentsScanner();
42041
42272
  const history = new ClaudeHistoryReader();
42042
42273
  let transport = null;
42043
- const personaStore = new PersonaStore(import_node_path44.default.join(config.dataDir, "personas"));
42274
+ const personaStore = new PersonaStore(import_node_path45.default.join(config.dataDir, "personas"));
42044
42275
  const defaultsRoot = findDefaultsRoot();
42045
42276
  if (defaultsRoot) {
42046
42277
  seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
@@ -42057,7 +42288,7 @@ async function startDaemon(config) {
42057
42288
  getAdapter,
42058
42289
  historyReader: history,
42059
42290
  dataDir: config.dataDir,
42060
- personaRoot: import_node_path44.default.join(config.dataDir, "personas"),
42291
+ personaRoot: import_node_path45.default.join(config.dataDir, "personas"),
42061
42292
  personaStore,
42062
42293
  ownerDisplayName,
42063
42294
  ownerPrincipalId,
@@ -42081,10 +42312,10 @@ async function startDaemon(config) {
42081
42312
  // 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
42082
42313
  attachmentGroup: {
42083
42314
  onFileEdit: (input) => {
42084
- const absPath = import_node_path44.default.isAbsolute(input.relPath) ? input.relPath : import_node_path44.default.join(input.cwd, input.relPath);
42315
+ const absPath = import_node_path45.default.isAbsolute(input.relPath) ? input.relPath : import_node_path45.default.join(input.cwd, input.relPath);
42085
42316
  let size = 0;
42086
42317
  try {
42087
- size = import_node_fs29.default.statSync(absPath).size;
42318
+ size = import_node_fs30.default.statSync(absPath).size;
42088
42319
  } catch (err) {
42089
42320
  logger.warn("attachment.onFileEdit stat failed", {
42090
42321
  sessionId: input.sessionId,
@@ -42171,7 +42402,13 @@ async function startDaemon(config) {
42171
42402
  const publishedChannelStore = new PublishedChannelStore(publishedChannelsFile());
42172
42403
  await publishedChannelStore.load();
42173
42404
  const bundleCache = new BundleCache(bundleCacheRoot());
42174
- const handlers = buildMethodHandlers({
42405
+ const loginFlow = new LoginFlow({
42406
+ store: ownerIdentityStore,
42407
+ fetchUserInfo: (ttcToken) => fetchTtcUserInfo({ apiBase: TTC_HOSTS.api, token: ttcToken }),
42408
+ ttcAuthBase: TTC_HOSTS.auth,
42409
+ getCallbackUrl: () => `http://127.0.0.1:${config.port}/auth/callback`
42410
+ });
42411
+ const makeHandlers = () => buildMethodHandlers({
42175
42412
  manager,
42176
42413
  workspace,
42177
42414
  skills,
@@ -42210,10 +42447,10 @@ async function startDaemon(config) {
42210
42447
  // 'persona/<pid>/owner',default 走 'default'。
42211
42448
  getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
42212
42449
  // guest path guard:candidate 必须在 personaRoot 子树下
42213
- personaRoot: import_node_path44.default.join(config.dataDir, "personas")
42450
+ personaRoot: import_node_path45.default.join(config.dataDir, "personas")
42214
42451
  },
42215
42452
  // workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
42216
- personaRoot: import_node_path44.default.join(config.dataDir, "personas"),
42453
+ personaRoot: import_node_path45.default.join(config.dataDir, "personas"),
42217
42454
  // capability:list / delete handler 依赖
42218
42455
  capabilityManager,
42219
42456
  // personal-cap:get 返回的 shareBaseUrl 用此 base URL:
@@ -42243,8 +42480,11 @@ async function startDaemon(config) {
42243
42480
  extensionRuntime,
42244
42481
  // extension sharing v1 — owner-side published-channels store.
42245
42482
  publishedChannelStore,
42246
- bundleCache
42483
+ bundleCache,
42484
+ // 飞书统一身份 Phase 1:登录 RPC handlers(auth:login:start / getIdentity / logout)
42485
+ feishuAuth: { loginFlow, ownerIdentityStore }
42247
42486
  });
42487
+ let handlers = makeHandlers();
42248
42488
  const authResolver = new AuthContextResolver({
42249
42489
  ownerToken: resolvedAuthToken
42250
42490
  });
@@ -42270,8 +42510,29 @@ async function startDaemon(config) {
42270
42510
  },
42271
42511
  // POST /extensions/import lands in ~/.clawd/extensions/<id>/
42272
42512
  extensionsRoot: () => extensionsRoot(),
42273
- // app-builder build 模式:/preview/<port>/ 反代 dev server 的端口白名单(见 spec §三.2)
42274
- previewPorts: config.previewPorts ?? void 0
42513
+ // 飞书登录 loopback 回调(热切换,2026-06-01 拍板):成功落盘后——
42514
+ // 1. 进程内实时切换身份(let binding + 值快照持有者重建)
42515
+ // 2. 广播 auth:login:done 给在线 owner 连接(UI 即时反馈)
42516
+ // 3. 踢掉所有 ws 连接(在线连接的 ctx 是旧身份)→ UI 自动重连 → 新身份 ctx →
42517
+ // People 立即解锁,不需要重启 daemon
42518
+ // 失败则广播 auth:login:failed。
42519
+ feishuLogin: {
42520
+ handleLoginCallback: async (params) => {
42521
+ const result = await loginFlow.handleCallback(params);
42522
+ if (result.ok) {
42523
+ feishuActive = true;
42524
+ ownerPrincipalId = result.identity.unionId;
42525
+ ownerDisplayName = result.identity.displayName;
42526
+ handlers = makeHandlers();
42527
+ wsServer?.setOwnerPrincipalId(result.identity.unionId);
42528
+ wsServer?.broadcastToOwners({ type: "auth:login:done", identity: result.identity });
42529
+ setImmediate(() => wsServer?.closeAllClients());
42530
+ } else {
42531
+ wsServer?.broadcastToOwners({ type: "auth:login:failed", reason: result.reason });
42532
+ }
42533
+ return result;
42534
+ }
42535
+ }
42275
42536
  });
42276
42537
  wsServer = new LocalWsServer({
42277
42538
  host: config.host,
@@ -42310,8 +42571,6 @@ async function startDaemon(config) {
42310
42571
  },
42311
42572
  // file-sharing HTTP 路由复用 daemon 同端口(spec §5 第 3 条);router 自己处理 auth + 404
42312
42573
  httpRequestHandler: httpRouter,
42313
- // app-builder build 模式:/preview/<port>/ HMR websocket upgrade 转发的端口白名单(同 http-router 配置)
42314
- previewPorts: config.previewPorts ?? void 0,
42315
42574
  // 订阅成功后给该 client 重放 in-flight pendingQuestions(plan: clawd-question-server-truth)。
42316
42575
  // daemon 是 pendingQuestions 的唯一 source of truth;新 client 接入 / 刷新页面时
42317
42576
  // 把当前所有未决 question 以 session:question 帧定向回放,让 UI 不再误显示 Ended。
@@ -42356,6 +42615,12 @@ async function startDaemon(config) {
42356
42615
  const requestId = typeof frame.requestId === "string" ? frame.requestId : void 0;
42357
42616
  const handler = handlers[type];
42358
42617
  if (!handler) throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, `not implemented: ${type}`);
42618
+ if (!feishuActive && FEISHU_GATED_METHODS.includes(type)) {
42619
+ throw new ClawdError(
42620
+ ERROR_CODES.FEISHU_LOGIN_REQUIRED,
42621
+ `${type} requires feishu login (People/remote identity)`
42622
+ );
42623
+ }
42359
42624
  const ctx = wsServer.getClientContext(client.id) ?? ownerContext(ownerPrincipalId, ownerDisplayName);
42360
42625
  const verdict = computeGrantForFrame(type, frame);
42361
42626
  if (verdict.kind === "check") {
@@ -42428,13 +42693,12 @@ async function startDaemon(config) {
42428
42693
  stateSnapshot = { ...stateSnapshot, tunnelUrl: r.url, tunnelError: void 0 };
42429
42694
  stateMgr.write(stateSnapshot);
42430
42695
  currentTunnelUrl = r.url;
42431
- wss.broadcastAll({ type: "tunnel:ready", url: r.url, subdomain: r.subdomain });
42432
42696
  const connectUrl = resolvedAuthToken ? `${r.url}#token=${resolvedAuthToken}` : r.url;
42433
42697
  const lines = [
42434
42698
  `Tunnel: ${r.url}`,
42435
42699
  ...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
42436
- `Frpc config: ${import_node_path44.default.join(config.dataDir, "frpc.toml")}`,
42437
- `Frpc log: ${import_node_path44.default.join(config.dataDir, "frpc.log")}`
42700
+ `Frpc config: ${import_node_path45.default.join(config.dataDir, "frpc.toml")}`,
42701
+ `Frpc log: ${import_node_path45.default.join(config.dataDir, "frpc.log")}`
42438
42702
  ];
42439
42703
  const width = Math.max(...lines.map((l) => l.length));
42440
42704
  const bar = "\u2550".repeat(width + 4);
@@ -42447,8 +42711,8 @@ ${bar}
42447
42711
 
42448
42712
  `);
42449
42713
  try {
42450
- const connectPath = import_node_path44.default.join(config.dataDir, "connect.txt");
42451
- import_node_fs29.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
42714
+ const connectPath = import_node_path45.default.join(config.dataDir, "connect.txt");
42715
+ import_node_fs30.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
42452
42716
  } catch {
42453
42717
  }
42454
42718
  } catch (err) {
@@ -42514,9 +42778,9 @@ ${bar}
42514
42778
  };
42515
42779
  }
42516
42780
  function migrateDropPersonsDir(dataDir) {
42517
- const dir = import_node_path44.default.join(dataDir, "persons");
42781
+ const dir = import_node_path45.default.join(dataDir, "persons");
42518
42782
  try {
42519
- import_node_fs29.default.rmSync(dir, { recursive: true, force: true });
42783
+ import_node_fs30.default.rmSync(dir, { recursive: true, force: true });
42520
42784
  } catch {
42521
42785
  }
42522
42786
  }