@clawos-dev/clawd 0.2.112-beta.213.6cb11a8 → 0.2.112-beta.215.872b507

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 (29) hide show
  1. package/dist/cli.cjs +677 -730
  2. package/package.json +1 -1
  3. package/dist/persona-defaults/persona-app-builder/CLAUDE.md +0 -145
  4. package/dist/persona-defaults/persona-app-builder/extension-kit/README.md +0 -96
  5. package/dist/persona-defaults/persona-app-builder/extension-kit/config.env +0 -20
  6. package/dist/persona-defaults/persona-app-builder/extension-kit/contract/bootstrap +0 -22
  7. package/dist/persona-defaults/persona-app-builder/extension-kit/contract/s.yaml.tmpl +0 -54
  8. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/.env.example +0 -3
  9. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/.fcignore +0 -7
  10. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/nest-cli.json +0 -8
  11. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/package-lock.json +0 -4531
  12. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/package.json +0 -26
  13. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/app.module.ts +0 -14
  14. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/main.ts +0 -14
  15. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/messages/messages.controller.ts +0 -27
  16. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/messages/messages.module.ts +0 -9
  17. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/messages/messages.service.ts +0 -38
  18. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/polyfill.ts +0 -8
  19. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/tsconfig.json +0 -14
  20. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/index.html +0 -12
  21. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/package-lock.json +0 -1680
  22. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/package.json +0 -18
  23. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/src/App.jsx +0 -161
  24. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/src/main.jsx +0 -5
  25. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/vite.config.js +0 -31
  26. package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/new-extension.sh +0 -49
  27. package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/publish.sh +0 -78
  28. package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/remove-extension.sh +0 -57
  29. package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/verify.sh +0 -20
package/dist/cli.cjs CHANGED
@@ -181,16 +181,17 @@ var init_methods = __esm({
181
181
  // 无 subscription 持久化:guest 端身份完全来自文件系统 + reconciled publishedExtensions.
182
182
  "extension.install-from-channel",
183
183
  "extension.update-from-channel",
184
- // ---- app-builder Project picker (spec 2026-06-01-app-builder-project-picker-design) ----
185
- // persona-app-builder project 管理;schemas: ProjectMetadataSchema + 4 args/result 对。
186
- // listProjects: readdir projects/,按目录读 .clawd-project.json
187
- // createProject: 校验名 + 从 6173-6182 段分配端口 + 写元数据 + 创建空目录
188
- // deleteProject: 停 dev server + rm -rf 目录 + 连带删该 project 名下所有 session
189
- // updateProjectPort: 校验新端口段内 + 未被别的 project 占 + 重启 dev server
190
- "appBuilder:listProjects",
191
- "appBuilder:createProject",
192
- "appBuilder:deleteProject",
193
- "appBuilder:updateProjectPort"
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",
190
+ // ---- hub:* 飞书统一身份 Phase 2 (spec §10.2) ----
191
+ // owner-only:公共 hub 目录 + per-hub connect token 连接(daemon 代理中心 server)。
192
+ // 中央字面量见 protocol/feishu-auth.ts HUB_METHODS。
193
+ "hub:list",
194
+ "hub:connect"
194
195
  ];
195
196
  }
196
197
  });
@@ -252,7 +253,11 @@ var init_errors = __esm({
252
253
  INTERNAL: "INTERNAL",
253
254
  UNAUTHORIZED: "UNAUTHORIZED",
254
255
  FORBIDDEN: "FORBIDDEN",
255
- INVALID_PARAM: "INVALID_PARAM"
256
+ INVALID_PARAM: "INVALID_PARAM",
257
+ // 飞书统一身份(spec 2026-06-01 决策 #9):People/远程体系强制飞书登录。
258
+ // daemon 进程身份未激活为飞书 unionId 时,FEISHU_GATED_METHODS 内的 RPC 一律返回此错误;
259
+ // UI 据此显示登录引导(区别于 UNAUTHORIZED 的越权语义)。
260
+ FEISHU_LOGIN_REQUIRED: "FEISHU_LOGIN_REQUIRED"
256
261
  };
257
262
  ClawdError = class extends Error {
258
263
  constructor(code, message) {
@@ -682,8 +687,8 @@ var init_parseUtil = __esm({
682
687
  init_errors2();
683
688
  init_en();
684
689
  makeIssue = (params) => {
685
- const { data, path: path59, errorMaps, issueData } = params;
686
- const fullPath = [...path59, ...issueData.path || []];
690
+ const { data, path: path60, errorMaps, issueData } = params;
691
+ const fullPath = [...path60, ...issueData.path || []];
687
692
  const fullIssue = {
688
693
  ...issueData,
689
694
  path: fullPath
@@ -994,11 +999,11 @@ var init_types = __esm({
994
999
  init_parseUtil();
995
1000
  init_util();
996
1001
  ParseInputLazyPath = class {
997
- constructor(parent, value, path59, key) {
1002
+ constructor(parent, value, path60, key) {
998
1003
  this._cachedPath = [];
999
1004
  this.parent = parent;
1000
1005
  this.data = value;
1001
- this._path = path59;
1006
+ this._path = path60;
1002
1007
  this._key = key;
1003
1008
  }
1004
1009
  get path() {
@@ -4491,6 +4496,11 @@ var init_persona_schemas = __esm({
4491
4496
  enabled: external_exports.boolean().optional(),
4492
4497
  autoAllowBashIfSandboxed: external_exports.boolean().optional(),
4493
4498
  allowUnsandboxedCommands: external_exports.boolean().optional(),
4499
+ // 开发型 persona 的网络域名白名单:CC 沙箱代理按 hostname 放行(spec 实测 D2)。
4500
+ // 缺省 = 无网络(沙箱代理对所有域名回 403 blocked-by-allowlist)
4501
+ network: external_exports.object({
4502
+ allowedDomains: external_exports.array(external_exports.string()).optional()
4503
+ }).optional(),
4494
4504
  filesystem: external_exports.object({
4495
4505
  denyRead: external_exports.array(external_exports.string()).optional(),
4496
4506
  allowRead: external_exports.array(external_exports.string()).optional(),
@@ -4525,14 +4535,17 @@ var init_persona_schemas = __esm({
4525
4535
  personality: external_exports.string().optional(),
4526
4536
  public: external_exports.boolean().optional(),
4527
4537
  // pass `null` to clear; daemon strips the field from PersonaFile on null
4528
- iconKey: external_exports.string().nullable().optional()
4538
+ iconKey: external_exports.string().nullable().optional(),
4539
+ // 整体替换 sandbox-settings.json 的 sandbox.network.allowedDomains(不是 merge 数组)。
4540
+ // 窄接口设计:daemon 做 read-modify-write,owner 手改的其他 sandbox 字段不被 UI 覆盖
4541
+ networkAllowedDomains: external_exports.array(external_exports.string()).optional()
4529
4542
  }).strict()
4530
4543
  }).strict();
4531
4544
  }
4532
4545
  });
4533
4546
 
4534
4547
  // ../protocol/src/schemas.ts
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, TunnelReadyEventSchema, TunnelExitedEventSchema, TunnelUnavailableEventSchema, InfoRunningSessionSchema, InfoResponseSchema, PROJECT_PORT_MIN, PROJECT_PORT_MAX, projectNameRegex, ProjectMetadataSchema, ProjectWithStatusSchema, ListProjectsArgsSchema, ListProjectsResultSchema, CreateProjectArgsSchema, CreateProjectResultSchema, DeleteProjectArgsSchema, DeleteProjectResultSchema, UpdateProjectPortArgsSchema, UpdateProjectPortResultSchema;
4548
+ 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;
4536
4549
  var init_schemas = __esm({
4537
4550
  "../protocol/src/schemas.ts"() {
4538
4551
  "use strict";
@@ -4629,10 +4642,6 @@ var init_schemas = __esm({
4629
4642
  // owner-mode persona session 身份标识;UI 用它在 SessionList 过滤 + jump,
4630
4643
  // daemon 用它做 idempotent dedupe + spawn 时派生 ctx.personaMode='owner'
4631
4644
  ownerPersonaId: external_exports.string().min(1).optional(),
4632
- // app-builder Project 反向索引(spec: superpowers/specs/2026-06-01-app-builder-project-picker-design.md)。
4633
- // 当此 session 绑定到某个 build 中的 project 时填写;name 与 projects/<name>/ 子目录同名,
4634
- // 受 projectNameSchema regex 约束。daemon session:resume 时校验该目录存在,不存在则拒。
4635
- appBuilderProject: external_exports.string().regex(/^[a-z][a-z0-9-]{0,39}$/).optional(),
4636
4645
  // listener-mode sub-session 的原始 chatId(owner-mode 不写;listener-scope 强制写入)。
4637
4646
  // 派生 sessionId 是单向 hash `${personaId}-${tokenHash12}-${chatHash8}`,必须保留原始 chatId
4638
4647
  // 才能让 alice 重连同一 sub-session(持久化在 alice localStorage 之外,由 daemon push 同步)。
@@ -4905,12 +4914,7 @@ var init_schemas = __esm({
4905
4914
  forkedFromSessionId: external_exports.string().min(1).optional(),
4906
4915
  // owner-mode persona session:传此字段时 daemon 自行派生 cwd 和启动参数,
4907
4916
  // 且按 ownerPersonaId 做 idempotent dedupe(每 persona 永远只有一个 owner session)
4908
- ownerPersonaId: external_exports.string().min(1).optional(),
4909
- // app-builder Project 绑定(spec 2026-06-01-app-builder-project-picker-design)。
4910
- // UI picker 上点 idle project / 新建 project 时传入;daemon 透传写入 SessionFile.appBuilderProject。
4911
- // 跟 ownerPersonaId 同层但不互斥(典型场景两者都传:ownerPersonaId='persona-app-builder' +
4912
- // appBuilderProject='<project-name>')。
4913
- appBuilderProject: external_exports.string().regex(/^[a-z][a-z0-9-]{0,39}$/).optional()
4917
+ ownerPersonaId: external_exports.string().min(1).optional()
4914
4918
  }).refine((args) => args.cwd != null || args.ownerPersonaId != null, {
4915
4919
  message: "cwd \u4E0E ownerPersonaId \u81F3\u5C11\u4F20\u4E00\u4E2A"
4916
4920
  });
@@ -4924,11 +4928,7 @@ var init_schemas = __esm({
4924
4928
  effort: external_exports.string().optional(),
4925
4929
  cwd: external_exports.string().optional(),
4926
4930
  // 用户在 Edit modal 选择的 icon 标识;UI 端做 enum 兜底
4927
- iconKey: external_exports.string().optional(),
4928
- // app-builder picker 在当前 session 内切 project 时写入(spec 2026-06-01-app-builder-project-picker-design)。
4929
- // daemon session:update handler 检测此字段变化时停旧 project dev server + 起新 project dev server。
4930
- // 设为空字符串 '' 表示解绑(picker 不暴露解绑入口,但 schema 允许)。
4931
- appBuilderProject: external_exports.string().regex(/^[a-z][a-z0-9-]{0,39}$/).or(external_exports.literal("")).optional()
4931
+ iconKey: external_exports.string().optional()
4932
4932
  })
4933
4933
  });
4934
4934
  SessionSendArgs = external_exports.object({
@@ -5125,6 +5125,12 @@ var init_schemas = __esm({
5125
5125
  * 后调 capabilityManager.recordPeerHello(capId, ownerPrincipalId, displayName)
5126
5126
  * 把 cap.peerOwnerId / firstUsedByPeerAt 字段回写,让 owner 端 UI 在 People
5127
5127
  * tab 顶层 PeerOwner 视图 + Personas tab 二级分组拿到对端身份。
5128
+ *
5129
+ * 飞书统一身份 Phase 1 (2026-06-01):取值升级为飞书 union_id(owner 已飞书登录时;
5130
+ * 来自 whoami.owner.id → daemon ownerPrincipalId → owner-identity.json unionId)。
5131
+ * 未登录 daemon 仍为 auth.json 的 owner-<uuid>。daemon 端不验证该身份真实性
5132
+ * (Phase 1 信任模型,见 spec §9)。Phase 2 将以 connect token 替代(introspect
5133
+ * 返回身份,删除自报字段,见 spec §10)。
5128
5134
  */
5129
5135
  selfPrincipalId: external_exports.string().min(1).optional(),
5130
5136
  /**
@@ -5139,11 +5145,6 @@ var init_schemas = __esm({
5139
5145
  version: external_exports.string().min(1).optional(),
5140
5146
  protocolVersion: external_exports.number().int().nonnegative().optional()
5141
5147
  });
5142
- TunnelReadyEventSchema = external_exports.object({
5143
- type: external_exports.literal("tunnel:ready"),
5144
- url: external_exports.string().min(1),
5145
- subdomain: external_exports.string().min(1)
5146
- });
5147
5148
  TunnelExitedEventSchema = external_exports.object({
5148
5149
  type: external_exports.literal("tunnel:exited"),
5149
5150
  code: external_exports.number().int().nullable(),
@@ -5185,41 +5186,6 @@ var init_schemas = __esm({
5185
5186
  /** file-sharing HTTP 路由的 Authorization: Bearer token;与 WS token 解耦,HTTP 401 仅触发 ReAuth 不强踢 WS(spec §11 第 4 条) */
5186
5187
  httpToken: external_exports.string().optional()
5187
5188
  });
5188
- PROJECT_PORT_MIN = 6173;
5189
- PROJECT_PORT_MAX = 6182;
5190
- projectNameRegex = /^[a-z][a-z0-9-]{0,39}$/;
5191
- ProjectMetadataSchema = external_exports.object({
5192
- name: external_exports.string().regex(projectNameRegex, {
5193
- message: "kebab-case, \u5C0F\u5199\u5B57\u6BCD\u5F00\u5934\uFF0C\u53EA\u5141\u8BB8\u5C0F\u5199\u5B57\u6BCD / \u6570\u5B57 / -\uFF0C\u6700\u957F 40 \u5B57\u7B26"
5194
- }),
5195
- port: external_exports.number().int().min(PROJECT_PORT_MIN).max(PROJECT_PORT_MAX),
5196
- createdAt: external_exports.string().datetime()
5197
- });
5198
- ProjectWithStatusSchema = ProjectMetadataSchema.extend({
5199
- isRunning: external_exports.boolean(),
5200
- boundSessionId: external_exports.string().nullable()
5201
- });
5202
- ListProjectsArgsSchema = external_exports.object({}).strict();
5203
- ListProjectsResultSchema = external_exports.object({
5204
- projects: external_exports.array(ProjectWithStatusSchema)
5205
- }).strict();
5206
- CreateProjectArgsSchema = external_exports.object({
5207
- name: external_exports.string()
5208
- }).strict();
5209
- CreateProjectResultSchema = external_exports.object({
5210
- project: ProjectMetadataSchema
5211
- }).strict();
5212
- DeleteProjectArgsSchema = external_exports.object({
5213
- name: external_exports.string()
5214
- }).strict();
5215
- DeleteProjectResultSchema = external_exports.object({}).strict();
5216
- UpdateProjectPortArgsSchema = external_exports.object({
5217
- name: external_exports.string(),
5218
- newPort: external_exports.number().int()
5219
- }).strict();
5220
- UpdateProjectPortResultSchema = external_exports.object({
5221
- project: ProjectMetadataSchema
5222
- }).strict();
5223
5189
  }
5224
5190
  });
5225
5191
 
@@ -5628,6 +5594,93 @@ var init_extension = __esm({
5628
5594
  }
5629
5595
  });
5630
5596
 
5597
+ // ../protocol/src/feishu-auth.ts
5598
+ var FEISHU_GATED_METHODS, HubEntrySchema, HubListResponseSchema, HubConnectArgsSchema, HubConnectResponseSchema, FeishuIdentitySchema, AuthLoginStartResponseSchema, AuthGetIdentityResponseSchema, AuthLogoutResponseSchema, AuthLoginDoneEventSchema, AuthLoginFailedEventSchema;
5599
+ var init_feishu_auth = __esm({
5600
+ "../protocol/src/feishu-auth.ts"() {
5601
+ "use strict";
5602
+ init_zod();
5603
+ FEISHU_GATED_METHODS = [
5604
+ // capability / 邀请码(生成添加码 = 把自己的身份分享出去)
5605
+ "capability:list",
5606
+ "capability:delete",
5607
+ "personal-cap:get",
5608
+ // 联系人(添加朋友 / 联系人列表 / 自动重连)
5609
+ "received-capability:list",
5610
+ "received-capability:add",
5611
+ "received-capability:remove",
5612
+ "received-capability:autoAttach",
5613
+ // DM / 跨用户通知
5614
+ "inbox:list",
5615
+ "inbox:markRead",
5616
+ "inbox:postMessage",
5617
+ // mirror peer sessions(远端会话镜像)
5618
+ "peerSession:upsert",
5619
+ "peerSession:remove",
5620
+ // Phase 2: hub 目录 + 连接(中心 server 代理,需要 owner 的 ttcJwt)
5621
+ "hub:list",
5622
+ "hub:connect"
5623
+ ];
5624
+ HubEntrySchema = external_exports.object({
5625
+ /** hub owner 的飞书 union_id */
5626
+ hubId: external_exports.string().min(1),
5627
+ /** 显示名 */
5628
+ name: external_exports.string().min(1),
5629
+ /** ws/tunnel 地址 */
5630
+ url: external_exports.string().min(1)
5631
+ });
5632
+ HubListResponseSchema = external_exports.object({
5633
+ type: external_exports.literal("hub:list:ok"),
5634
+ hubs: external_exports.array(HubEntrySchema)
5635
+ });
5636
+ HubConnectArgsSchema = external_exports.object({
5637
+ /** 目标 hub 的 hubId(= hub owner unionId) */
5638
+ hubId: external_exports.string().min(1),
5639
+ /** 目标 hub 的 ws/tunnel 地址 */
5640
+ url: external_exports.string().min(1),
5641
+ /** 显示名(公共 hub 来自目录;个人 hub 链接可缺省,连上后用 whoami 回填) */
5642
+ name: external_exports.string().optional()
5643
+ });
5644
+ HubConnectResponseSchema = external_exports.object({
5645
+ type: external_exports.literal("hub:connect:ok"),
5646
+ hubId: external_exports.string(),
5647
+ name: external_exports.string(),
5648
+ url: external_exports.string()
5649
+ });
5650
+ FeishuIdentitySchema = external_exports.object({
5651
+ /** 飞书 union_id(租户维度,跨应用稳定)。TTC user_info 接口 data.union_id */
5652
+ unionId: external_exports.string().min(1),
5653
+ /** 飞书姓名。TTC user_info 接口 data.name */
5654
+ displayName: external_exports.string().min(1),
5655
+ /** TTC user_info 接口 data.avatar_url,可缺省 */
5656
+ avatarUrl: external_exports.string().optional()
5657
+ });
5658
+ AuthLoginStartResponseSchema = external_exports.object({
5659
+ type: external_exports.literal("auth:login:start:ok"),
5660
+ /** 完整 TTC 授权页 URL(含 callback_url + state + auto=1),UI 直接 window.open */
5661
+ authUrl: external_exports.string().min(1),
5662
+ /** 防 CSRF 的一次性 nonce;回调时 daemon 校验 */
5663
+ state: external_exports.string().min(1)
5664
+ });
5665
+ AuthGetIdentityResponseSchema = external_exports.object({
5666
+ type: external_exports.literal("auth:getIdentity:ok"),
5667
+ /** null = 未登录 */
5668
+ identity: FeishuIdentitySchema.nullable()
5669
+ });
5670
+ AuthLogoutResponseSchema = external_exports.object({
5671
+ type: external_exports.literal("auth:logout:ok")
5672
+ });
5673
+ AuthLoginDoneEventSchema = external_exports.object({
5674
+ type: external_exports.literal("auth:login:done"),
5675
+ identity: FeishuIdentitySchema
5676
+ });
5677
+ AuthLoginFailedEventSchema = external_exports.object({
5678
+ type: external_exports.literal("auth:login:failed"),
5679
+ reason: external_exports.string()
5680
+ });
5681
+ }
5682
+ });
5683
+
5631
5684
  // ../protocol/src/runtime.ts
5632
5685
  var init_runtime = __esm({
5633
5686
  "../protocol/src/runtime.ts"() {
@@ -5645,6 +5698,7 @@ var init_runtime = __esm({
5645
5698
  init_inbox();
5646
5699
  init_received_capability();
5647
5700
  init_extension();
5701
+ init_feishu_auth();
5648
5702
  }
5649
5703
  });
5650
5704
 
@@ -5919,8 +5973,8 @@ var require_req = __commonJS({
5919
5973
  if (req.originalUrl) {
5920
5974
  _req.url = req.originalUrl;
5921
5975
  } else {
5922
- const path59 = req.path;
5923
- _req.url = typeof path59 === "string" ? path59 : req.url ? req.url.path || req.url : void 0;
5976
+ const path60 = req.path;
5977
+ _req.url = typeof path60 === "string" ? path60 : req.url ? req.url.path || req.url : void 0;
5924
5978
  }
5925
5979
  if (req.query) {
5926
5980
  _req.query = req.query;
@@ -6085,14 +6139,14 @@ var require_redact = __commonJS({
6085
6139
  }
6086
6140
  return obj;
6087
6141
  }
6088
- function parsePath(path59) {
6142
+ function parsePath(path60) {
6089
6143
  const parts = [];
6090
6144
  let current = "";
6091
6145
  let inBrackets = false;
6092
6146
  let inQuotes = false;
6093
6147
  let quoteChar = "";
6094
- for (let i = 0; i < path59.length; i++) {
6095
- const char = path59[i];
6148
+ for (let i = 0; i < path60.length; i++) {
6149
+ const char = path60[i];
6096
6150
  if (!inBrackets && char === ".") {
6097
6151
  if (current) {
6098
6152
  parts.push(current);
@@ -6223,10 +6277,10 @@ var require_redact = __commonJS({
6223
6277
  return current;
6224
6278
  }
6225
6279
  function redactPaths(obj, paths, censor, remove = false) {
6226
- for (const path59 of paths) {
6227
- const parts = parsePath(path59);
6280
+ for (const path60 of paths) {
6281
+ const parts = parsePath(path60);
6228
6282
  if (parts.includes("*")) {
6229
- redactWildcardPath(obj, parts, censor, path59, remove);
6283
+ redactWildcardPath(obj, parts, censor, path60, remove);
6230
6284
  } else {
6231
6285
  if (remove) {
6232
6286
  removeKey(obj, parts);
@@ -6311,8 +6365,8 @@ var require_redact = __commonJS({
6311
6365
  }
6312
6366
  } else {
6313
6367
  if (afterWildcard.includes("*")) {
6314
- const wrappedCensor = typeof censor === "function" ? (value, path59) => {
6315
- const fullPath = [...pathArray.slice(0, pathLength), ...path59];
6368
+ const wrappedCensor = typeof censor === "function" ? (value, path60) => {
6369
+ const fullPath = [...pathArray.slice(0, pathLength), ...path60];
6316
6370
  return censor(value, fullPath);
6317
6371
  } : censor;
6318
6372
  redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
@@ -6347,8 +6401,8 @@ var require_redact = __commonJS({
6347
6401
  return null;
6348
6402
  }
6349
6403
  const pathStructure = /* @__PURE__ */ new Map();
6350
- for (const path59 of pathsToClone) {
6351
- const parts = parsePath(path59);
6404
+ for (const path60 of pathsToClone) {
6405
+ const parts = parsePath(path60);
6352
6406
  let current = pathStructure;
6353
6407
  for (let i = 0; i < parts.length; i++) {
6354
6408
  const part = parts[i];
@@ -6400,24 +6454,24 @@ var require_redact = __commonJS({
6400
6454
  }
6401
6455
  return cloneSelectively(obj, pathStructure);
6402
6456
  }
6403
- function validatePath(path59) {
6404
- if (typeof path59 !== "string") {
6457
+ function validatePath(path60) {
6458
+ if (typeof path60 !== "string") {
6405
6459
  throw new Error("Paths must be (non-empty) strings");
6406
6460
  }
6407
- if (path59 === "") {
6461
+ if (path60 === "") {
6408
6462
  throw new Error("Invalid redaction path ()");
6409
6463
  }
6410
- if (path59.includes("..")) {
6411
- throw new Error(`Invalid redaction path (${path59})`);
6464
+ if (path60.includes("..")) {
6465
+ throw new Error(`Invalid redaction path (${path60})`);
6412
6466
  }
6413
- if (path59.includes(",")) {
6414
- throw new Error(`Invalid redaction path (${path59})`);
6467
+ if (path60.includes(",")) {
6468
+ throw new Error(`Invalid redaction path (${path60})`);
6415
6469
  }
6416
6470
  let bracketCount = 0;
6417
6471
  let inQuotes = false;
6418
6472
  let quoteChar = "";
6419
- for (let i = 0; i < path59.length; i++) {
6420
- const char = path59[i];
6473
+ for (let i = 0; i < path60.length; i++) {
6474
+ const char = path60[i];
6421
6475
  if ((char === '"' || char === "'") && bracketCount > 0) {
6422
6476
  if (!inQuotes) {
6423
6477
  inQuotes = true;
@@ -6431,20 +6485,20 @@ var require_redact = __commonJS({
6431
6485
  } else if (char === "]" && !inQuotes) {
6432
6486
  bracketCount--;
6433
6487
  if (bracketCount < 0) {
6434
- throw new Error(`Invalid redaction path (${path59})`);
6488
+ throw new Error(`Invalid redaction path (${path60})`);
6435
6489
  }
6436
6490
  }
6437
6491
  }
6438
6492
  if (bracketCount !== 0) {
6439
- throw new Error(`Invalid redaction path (${path59})`);
6493
+ throw new Error(`Invalid redaction path (${path60})`);
6440
6494
  }
6441
6495
  }
6442
6496
  function validatePaths(paths) {
6443
6497
  if (!Array.isArray(paths)) {
6444
6498
  throw new TypeError("paths must be an array");
6445
6499
  }
6446
- for (const path59 of paths) {
6447
- validatePath(path59);
6500
+ for (const path60 of paths) {
6501
+ validatePath(path60);
6448
6502
  }
6449
6503
  }
6450
6504
  function slowRedact(options = {}) {
@@ -6612,8 +6666,8 @@ var require_redaction = __commonJS({
6612
6666
  if (shape[k2] === null) {
6613
6667
  o[k2] = (value) => topCensor(value, [k2]);
6614
6668
  } else {
6615
- const wrappedCensor = typeof censor === "function" ? (value, path59) => {
6616
- return censor(value, [k2, ...path59]);
6669
+ const wrappedCensor = typeof censor === "function" ? (value, path60) => {
6670
+ return censor(value, [k2, ...path60]);
6617
6671
  } : censor;
6618
6672
  o[k2] = Redact({
6619
6673
  paths: shape[k2],
@@ -6832,9 +6886,9 @@ var require_sonic_boom = __commonJS({
6832
6886
  "../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
6833
6887
  "use strict";
6834
6888
  var fs48 = require("fs");
6835
- var EventEmitter3 = require("events");
6889
+ var EventEmitter2 = require("events");
6836
6890
  var inherits = require("util").inherits;
6837
- var path59 = require("path");
6891
+ var path60 = require("path");
6838
6892
  var sleep = require_atomic_sleep();
6839
6893
  var assert = require("assert");
6840
6894
  var BUSY_WRITE_TIMEOUT = 100;
@@ -6888,7 +6942,7 @@ var require_sonic_boom = __commonJS({
6888
6942
  const mode = sonic.mode;
6889
6943
  if (sonic.sync) {
6890
6944
  try {
6891
- if (sonic.mkdir) fs48.mkdirSync(path59.dirname(file), { recursive: true });
6945
+ if (sonic.mkdir) fs48.mkdirSync(path60.dirname(file), { recursive: true });
6892
6946
  const fd = fs48.openSync(file, flags, mode);
6893
6947
  fileOpened(null, fd);
6894
6948
  } catch (err) {
@@ -6896,7 +6950,7 @@ var require_sonic_boom = __commonJS({
6896
6950
  throw err;
6897
6951
  }
6898
6952
  } else if (sonic.mkdir) {
6899
- fs48.mkdir(path59.dirname(file), { recursive: true }, (err) => {
6953
+ fs48.mkdir(path60.dirname(file), { recursive: true }, (err) => {
6900
6954
  if (err) return fileOpened(err);
6901
6955
  fs48.open(file, flags, mode, fileOpened);
6902
6956
  });
@@ -7069,7 +7123,7 @@ var require_sonic_boom = __commonJS({
7069
7123
  sonic._asyncDrainScheduled = false;
7070
7124
  sonic.emit("drain");
7071
7125
  }
7072
- inherits(SonicBoom, EventEmitter3);
7126
+ inherits(SonicBoom, EventEmitter2);
7073
7127
  function mergeBuf(bufs, len) {
7074
7128
  if (bufs.length === 0) {
7075
7129
  return kEmptyBuffer;
@@ -7647,9 +7701,9 @@ var require_thread_stream = __commonJS({
7647
7701
  "../node_modules/.pnpm/thread-stream@3.1.0/node_modules/thread-stream/index.js"(exports2, module2) {
7648
7702
  "use strict";
7649
7703
  var { version: version2 } = require_package();
7650
- var { EventEmitter: EventEmitter3 } = require("events");
7704
+ var { EventEmitter: EventEmitter2 } = require("events");
7651
7705
  var { Worker } = require("worker_threads");
7652
- var { join: join12 } = require("path");
7706
+ var { join: join11 } = require("path");
7653
7707
  var { pathToFileURL } = require("url");
7654
7708
  var { wait } = require_wait();
7655
7709
  var {
@@ -7685,7 +7739,7 @@ var require_thread_stream = __commonJS({
7685
7739
  function createWorker(stream, opts) {
7686
7740
  const { filename, workerData } = opts;
7687
7741
  const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
7688
- const toExecute = bundlerOverrides["thread-stream-worker"] || join12(__dirname, "lib", "worker.js");
7742
+ const toExecute = bundlerOverrides["thread-stream-worker"] || join11(__dirname, "lib", "worker.js");
7689
7743
  const worker = new Worker(toExecute, {
7690
7744
  ...opts.workerOpts,
7691
7745
  trackUnmanagedFds: false,
@@ -7803,7 +7857,7 @@ var require_thread_stream = __commonJS({
7803
7857
  stream.worker.off("exit", onWorkerExit);
7804
7858
  destroy(stream, code !== 0 ? new Error("the worker thread exited") : null);
7805
7859
  }
7806
- var ThreadStream = class extends EventEmitter3 {
7860
+ var ThreadStream = class extends EventEmitter2 {
7807
7861
  constructor(opts = {}) {
7808
7862
  super();
7809
7863
  if (opts.bufferSize < 4) {
@@ -8071,7 +8125,7 @@ var require_transport = __commonJS({
8071
8125
  "use strict";
8072
8126
  var { createRequire } = require("module");
8073
8127
  var getCallers = require_caller();
8074
- var { join: join12, isAbsolute: isAbsolute2, sep: sep3 } = require("path");
8128
+ var { join: join11, isAbsolute: isAbsolute2, sep: sep3 } = require("path");
8075
8129
  var sleep = require_atomic_sleep();
8076
8130
  var onExit = require_on_exit_leak_free();
8077
8131
  var ThreadStream = require_thread_stream();
@@ -8134,7 +8188,7 @@ var require_transport = __commonJS({
8134
8188
  throw new Error("only one of target or targets can be specified");
8135
8189
  }
8136
8190
  if (targets) {
8137
- target = bundlerOverrides["pino-worker"] || join12(__dirname, "worker.js");
8191
+ target = bundlerOverrides["pino-worker"] || join11(__dirname, "worker.js");
8138
8192
  options.targets = targets.filter((dest) => dest.target).map((dest) => {
8139
8193
  return {
8140
8194
  ...dest,
@@ -8152,7 +8206,7 @@ var require_transport = __commonJS({
8152
8206
  });
8153
8207
  });
8154
8208
  } else if (pipeline3) {
8155
- target = bundlerOverrides["pino-worker"] || join12(__dirname, "worker.js");
8209
+ target = bundlerOverrides["pino-worker"] || join11(__dirname, "worker.js");
8156
8210
  options.pipelines = [pipeline3.map((dest) => {
8157
8211
  return {
8158
8212
  ...dest,
@@ -8174,7 +8228,7 @@ var require_transport = __commonJS({
8174
8228
  return origin;
8175
8229
  }
8176
8230
  if (origin === "pino/file") {
8177
- return join12(__dirname, "..", "file.js");
8231
+ return join11(__dirname, "..", "file.js");
8178
8232
  }
8179
8233
  let fixTarget2;
8180
8234
  for (const filePath of callers) {
@@ -8761,7 +8815,7 @@ var require_meta = __commonJS({
8761
8815
  var require_proto = __commonJS({
8762
8816
  "../node_modules/.pnpm/pino@9.14.0/node_modules/pino/lib/proto.js"(exports2, module2) {
8763
8817
  "use strict";
8764
- var { EventEmitter: EventEmitter3 } = require("events");
8818
+ var { EventEmitter: EventEmitter2 } = require("events");
8765
8819
  var {
8766
8820
  lsCacheSym,
8767
8821
  levelValSym,
@@ -8843,7 +8897,7 @@ var require_proto = __commonJS({
8843
8897
  [getLevelSym]: getLevel,
8844
8898
  [setLevelSym]: setLevel
8845
8899
  };
8846
- Object.setPrototypeOf(prototype, EventEmitter3.prototype);
8900
+ Object.setPrototypeOf(prototype, EventEmitter2.prototype);
8847
8901
  module2.exports = function() {
8848
8902
  return Object.create(prototype);
8849
8903
  };
@@ -9164,7 +9218,7 @@ var require_safe_stable_stringify = __commonJS({
9164
9218
  return circularValue;
9165
9219
  }
9166
9220
  let res = "";
9167
- let join12 = ",";
9221
+ let join11 = ",";
9168
9222
  const originalIndentation = indentation;
9169
9223
  if (Array.isArray(value)) {
9170
9224
  if (value.length === 0) {
@@ -9178,7 +9232,7 @@ var require_safe_stable_stringify = __commonJS({
9178
9232
  indentation += spacer;
9179
9233
  res += `
9180
9234
  ${indentation}`;
9181
- join12 = `,
9235
+ join11 = `,
9182
9236
  ${indentation}`;
9183
9237
  }
9184
9238
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
@@ -9186,13 +9240,13 @@ ${indentation}`;
9186
9240
  for (; i < maximumValuesToStringify - 1; i++) {
9187
9241
  const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
9188
9242
  res += tmp2 !== void 0 ? tmp2 : "null";
9189
- res += join12;
9243
+ res += join11;
9190
9244
  }
9191
9245
  const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
9192
9246
  res += tmp !== void 0 ? tmp : "null";
9193
9247
  if (value.length - 1 > maximumBreadth) {
9194
9248
  const removedKeys = value.length - maximumBreadth - 1;
9195
- res += `${join12}"... ${getItemCount(removedKeys)} not stringified"`;
9249
+ res += `${join11}"... ${getItemCount(removedKeys)} not stringified"`;
9196
9250
  }
9197
9251
  if (spacer !== "") {
9198
9252
  res += `
@@ -9213,7 +9267,7 @@ ${originalIndentation}`;
9213
9267
  let separator = "";
9214
9268
  if (spacer !== "") {
9215
9269
  indentation += spacer;
9216
- join12 = `,
9270
+ join11 = `,
9217
9271
  ${indentation}`;
9218
9272
  whitespace = " ";
9219
9273
  }
@@ -9227,13 +9281,13 @@ ${indentation}`;
9227
9281
  const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
9228
9282
  if (tmp !== void 0) {
9229
9283
  res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
9230
- separator = join12;
9284
+ separator = join11;
9231
9285
  }
9232
9286
  }
9233
9287
  if (keyLength > maximumBreadth) {
9234
9288
  const removedKeys = keyLength - maximumBreadth;
9235
9289
  res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
9236
- separator = join12;
9290
+ separator = join11;
9237
9291
  }
9238
9292
  if (spacer !== "" && separator.length > 1) {
9239
9293
  res = `
@@ -9274,7 +9328,7 @@ ${originalIndentation}`;
9274
9328
  }
9275
9329
  const originalIndentation = indentation;
9276
9330
  let res = "";
9277
- let join12 = ",";
9331
+ let join11 = ",";
9278
9332
  if (Array.isArray(value)) {
9279
9333
  if (value.length === 0) {
9280
9334
  return "[]";
@@ -9287,7 +9341,7 @@ ${originalIndentation}`;
9287
9341
  indentation += spacer;
9288
9342
  res += `
9289
9343
  ${indentation}`;
9290
- join12 = `,
9344
+ join11 = `,
9291
9345
  ${indentation}`;
9292
9346
  }
9293
9347
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
@@ -9295,13 +9349,13 @@ ${indentation}`;
9295
9349
  for (; i < maximumValuesToStringify - 1; i++) {
9296
9350
  const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
9297
9351
  res += tmp2 !== void 0 ? tmp2 : "null";
9298
- res += join12;
9352
+ res += join11;
9299
9353
  }
9300
9354
  const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
9301
9355
  res += tmp !== void 0 ? tmp : "null";
9302
9356
  if (value.length - 1 > maximumBreadth) {
9303
9357
  const removedKeys = value.length - maximumBreadth - 1;
9304
- res += `${join12}"... ${getItemCount(removedKeys)} not stringified"`;
9358
+ res += `${join11}"... ${getItemCount(removedKeys)} not stringified"`;
9305
9359
  }
9306
9360
  if (spacer !== "") {
9307
9361
  res += `
@@ -9314,7 +9368,7 @@ ${originalIndentation}`;
9314
9368
  let whitespace = "";
9315
9369
  if (spacer !== "") {
9316
9370
  indentation += spacer;
9317
- join12 = `,
9371
+ join11 = `,
9318
9372
  ${indentation}`;
9319
9373
  whitespace = " ";
9320
9374
  }
@@ -9323,7 +9377,7 @@ ${indentation}`;
9323
9377
  const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
9324
9378
  if (tmp !== void 0) {
9325
9379
  res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
9326
- separator = join12;
9380
+ separator = join11;
9327
9381
  }
9328
9382
  }
9329
9383
  if (spacer !== "" && separator.length > 1) {
@@ -9381,20 +9435,20 @@ ${originalIndentation}`;
9381
9435
  indentation += spacer;
9382
9436
  let res2 = `
9383
9437
  ${indentation}`;
9384
- const join13 = `,
9438
+ const join12 = `,
9385
9439
  ${indentation}`;
9386
9440
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
9387
9441
  let i = 0;
9388
9442
  for (; i < maximumValuesToStringify - 1; i++) {
9389
9443
  const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
9390
9444
  res2 += tmp2 !== void 0 ? tmp2 : "null";
9391
- res2 += join13;
9445
+ res2 += join12;
9392
9446
  }
9393
9447
  const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
9394
9448
  res2 += tmp !== void 0 ? tmp : "null";
9395
9449
  if (value.length - 1 > maximumBreadth) {
9396
9450
  const removedKeys = value.length - maximumBreadth - 1;
9397
- res2 += `${join13}"... ${getItemCount(removedKeys)} not stringified"`;
9451
+ res2 += `${join12}"... ${getItemCount(removedKeys)} not stringified"`;
9398
9452
  }
9399
9453
  res2 += `
9400
9454
  ${originalIndentation}`;
@@ -9410,16 +9464,16 @@ ${originalIndentation}`;
9410
9464
  return '"[Object]"';
9411
9465
  }
9412
9466
  indentation += spacer;
9413
- const join12 = `,
9467
+ const join11 = `,
9414
9468
  ${indentation}`;
9415
9469
  let res = "";
9416
9470
  let separator = "";
9417
9471
  let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
9418
9472
  if (isTypedArrayWithEntries(value)) {
9419
- res += stringifyTypedArray(value, join12, maximumBreadth);
9473
+ res += stringifyTypedArray(value, join11, maximumBreadth);
9420
9474
  keys = keys.slice(value.length);
9421
9475
  maximumPropertiesToStringify -= value.length;
9422
- separator = join12;
9476
+ separator = join11;
9423
9477
  }
9424
9478
  if (deterministic) {
9425
9479
  keys = sort(keys, comparator);
@@ -9430,13 +9484,13 @@ ${indentation}`;
9430
9484
  const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
9431
9485
  if (tmp !== void 0) {
9432
9486
  res += `${separator}${strEscape(key2)}: ${tmp}`;
9433
- separator = join12;
9487
+ separator = join11;
9434
9488
  }
9435
9489
  }
9436
9490
  if (keyLength > maximumBreadth) {
9437
9491
  const removedKeys = keyLength - maximumBreadth;
9438
9492
  res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
9439
- separator = join12;
9493
+ separator = join11;
9440
9494
  }
9441
9495
  if (separator !== "") {
9442
9496
  res = `
@@ -10527,11 +10581,11 @@ var init_lib = __esm({
10527
10581
  }
10528
10582
  }
10529
10583
  },
10530
- addToPath: function addToPath(path59, added, removed, oldPosInc, options) {
10531
- var last = path59.lastComponent;
10584
+ addToPath: function addToPath(path60, added, removed, oldPosInc, options) {
10585
+ var last = path60.lastComponent;
10532
10586
  if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
10533
10587
  return {
10534
- oldPos: path59.oldPos + oldPosInc,
10588
+ oldPos: path60.oldPos + oldPosInc,
10535
10589
  lastComponent: {
10536
10590
  count: last.count + 1,
10537
10591
  added,
@@ -10541,7 +10595,7 @@ var init_lib = __esm({
10541
10595
  };
10542
10596
  } else {
10543
10597
  return {
10544
- oldPos: path59.oldPos + oldPosInc,
10598
+ oldPos: path60.oldPos + oldPosInc,
10545
10599
  lastComponent: {
10546
10600
  count: 1,
10547
10601
  added,
@@ -10987,10 +11041,10 @@ function attachmentToHistoryMessage(o, ts) {
10987
11041
  const memories = raw.map((m2) => {
10988
11042
  if (!m2 || typeof m2 !== "object") return null;
10989
11043
  const rec = m2;
10990
- const path59 = typeof rec.path === "string" ? rec.path : null;
11044
+ const path60 = typeof rec.path === "string" ? rec.path : null;
10991
11045
  const content = typeof rec.content === "string" ? rec.content : null;
10992
- if (!path59 || content == null) return null;
10993
- const entry = { path: path59, content };
11046
+ if (!path60 || content == null) return null;
11047
+ const entry = { path: path60, content };
10994
11048
  if (typeof rec.mtimeMs === "number") entry.mtimeMs = rec.mtimeMs;
10995
11049
  return entry;
10996
11050
  }).filter((m2) => m2 !== null);
@@ -11821,10 +11875,10 @@ function parseAttachment(obj) {
11821
11875
  const memories = raw.map((m2) => {
11822
11876
  if (!m2 || typeof m2 !== "object") return null;
11823
11877
  const rec = m2;
11824
- const path59 = typeof rec.path === "string" ? rec.path : null;
11878
+ const path60 = typeof rec.path === "string" ? rec.path : null;
11825
11879
  const content = typeof rec.content === "string" ? rec.content : null;
11826
- if (!path59 || content == null) return null;
11827
- const out = { path: path59, content };
11880
+ if (!path60 || content == null) return null;
11881
+ const out = { path: path60, content };
11828
11882
  if (typeof rec.mtimeMs === "number") out.mtimeMs = rec.mtimeMs;
11829
11883
  return out;
11830
11884
  }).filter((m2) => m2 !== null);
@@ -19318,10 +19372,10 @@ var require_extension = __commonJS({
19318
19372
  var require_websocket = __commonJS({
19319
19373
  "../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/websocket.js"(exports2, module2) {
19320
19374
  "use strict";
19321
- var EventEmitter3 = require("events");
19375
+ var EventEmitter2 = require("events");
19322
19376
  var https = require("https");
19323
- var http3 = require("http");
19324
- var net3 = require("net");
19377
+ var http2 = require("http");
19378
+ var net2 = require("net");
19325
19379
  var tls = require("tls");
19326
19380
  var { randomBytes, createHash: createHash2 } = require("crypto");
19327
19381
  var { Duplex, Readable: Readable3 } = require("stream");
@@ -19350,7 +19404,7 @@ var require_websocket = __commonJS({
19350
19404
  var protocolVersions = [8, 13];
19351
19405
  var readyStates = ["CONNECTING", "OPEN", "CLOSING", "CLOSED"];
19352
19406
  var subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;
19353
- var WebSocket2 = class _WebSocket extends EventEmitter3 {
19407
+ var WebSocket2 = class _WebSocket extends EventEmitter2 {
19354
19408
  /**
19355
19409
  * Create a new `WebSocket`.
19356
19410
  *
@@ -19854,7 +19908,7 @@ var require_websocket = __commonJS({
19854
19908
  }
19855
19909
  const defaultPort = isSecure ? 443 : 80;
19856
19910
  const key = randomBytes(16).toString("base64");
19857
- const request = isSecure ? https.request : http3.request;
19911
+ const request = isSecure ? https.request : http2.request;
19858
19912
  const protocolSet = /* @__PURE__ */ new Set();
19859
19913
  let perMessageDeflate;
19860
19914
  opts.createConnection = opts.createConnection || (isSecure ? tlsConnect : netConnect);
@@ -20055,12 +20109,12 @@ var require_websocket = __commonJS({
20055
20109
  }
20056
20110
  function netConnect(options) {
20057
20111
  options.path = options.socketPath;
20058
- return net3.connect(options);
20112
+ return net2.connect(options);
20059
20113
  }
20060
20114
  function tlsConnect(options) {
20061
20115
  options.path = void 0;
20062
20116
  if (!options.servername && options.servername !== "") {
20063
- options.servername = net3.isIP(options.host) ? "" : options.host;
20117
+ options.servername = net2.isIP(options.host) ? "" : options.host;
20064
20118
  }
20065
20119
  return tls.connect(options);
20066
20120
  }
@@ -20347,8 +20401,8 @@ var require_subprotocol = __commonJS({
20347
20401
  var require_websocket_server = __commonJS({
20348
20402
  "../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/websocket-server.js"(exports2, module2) {
20349
20403
  "use strict";
20350
- var EventEmitter3 = require("events");
20351
- var http3 = require("http");
20404
+ var EventEmitter2 = require("events");
20405
+ var http2 = require("http");
20352
20406
  var { Duplex } = require("stream");
20353
20407
  var { createHash: createHash2 } = require("crypto");
20354
20408
  var extension2 = require_extension();
@@ -20360,7 +20414,7 @@ var require_websocket_server = __commonJS({
20360
20414
  var RUNNING = 0;
20361
20415
  var CLOSING = 1;
20362
20416
  var CLOSED = 2;
20363
- var WebSocketServer2 = class extends EventEmitter3 {
20417
+ var WebSocketServer2 = class extends EventEmitter2 {
20364
20418
  /**
20365
20419
  * Create a `WebSocketServer` instance.
20366
20420
  *
@@ -20423,8 +20477,8 @@ var require_websocket_server = __commonJS({
20423
20477
  );
20424
20478
  }
20425
20479
  if (options.port != null) {
20426
- this._server = http3.createServer((req, res) => {
20427
- const body = http3.STATUS_CODES[426];
20480
+ this._server = http2.createServer((req, res) => {
20481
+ const body = http2.STATUS_CODES[426];
20428
20482
  res.writeHead(426, {
20429
20483
  "Content-Length": body.length,
20430
20484
  "Content-Type": "text/plain"
@@ -20711,7 +20765,7 @@ var require_websocket_server = __commonJS({
20711
20765
  this.destroy();
20712
20766
  }
20713
20767
  function abortHandshake(socket, code, message, headers) {
20714
- message = message || http3.STATUS_CODES[code];
20768
+ message = message || http2.STATUS_CODES[code];
20715
20769
  headers = {
20716
20770
  Connection: "close",
20717
20771
  "Content-Type": "text/html",
@@ -20720,7 +20774,7 @@ var require_websocket_server = __commonJS({
20720
20774
  };
20721
20775
  socket.once("finish", socket.destroy);
20722
20776
  socket.end(
20723
- `HTTP/1.1 ${code} ${http3.STATUS_CODES[code]}\r
20777
+ `HTTP/1.1 ${code} ${http2.STATUS_CODES[code]}\r
20724
20778
  ` + Object.keys(headers).map((h) => `${h}: ${headers[h]}`).join("\r\n") + "\r\n\r\n" + message
20725
20779
  );
20726
20780
  }
@@ -21023,7 +21077,7 @@ var require_BufferList = __commonJS({
21023
21077
  this.head = this.tail = null;
21024
21078
  this.length = 0;
21025
21079
  };
21026
- BufferList.prototype.join = function join12(s) {
21080
+ BufferList.prototype.join = function join11(s) {
21027
21081
  if (this.length === 0) return "";
21028
21082
  var p2 = this.head;
21029
21083
  var ret = "" + p2.data;
@@ -23613,8 +23667,8 @@ var require_utils = __commonJS({
23613
23667
  var result = transform[inputType][outputType](input);
23614
23668
  return result;
23615
23669
  };
23616
- exports2.resolve = function(path59) {
23617
- var parts = path59.split("/");
23670
+ exports2.resolve = function(path60) {
23671
+ var parts = path60.split("/");
23618
23672
  var result = [];
23619
23673
  for (var index = 0; index < parts.length; index++) {
23620
23674
  var part = parts[index];
@@ -29467,18 +29521,18 @@ var require_object = __commonJS({
29467
29521
  var object = new ZipObject(name, zipObjectContent, o);
29468
29522
  this.files[name] = object;
29469
29523
  };
29470
- var parentFolder = function(path59) {
29471
- if (path59.slice(-1) === "/") {
29472
- path59 = path59.substring(0, path59.length - 1);
29524
+ var parentFolder = function(path60) {
29525
+ if (path60.slice(-1) === "/") {
29526
+ path60 = path60.substring(0, path60.length - 1);
29473
29527
  }
29474
- var lastSlash = path59.lastIndexOf("/");
29475
- return lastSlash > 0 ? path59.substring(0, lastSlash) : "";
29528
+ var lastSlash = path60.lastIndexOf("/");
29529
+ return lastSlash > 0 ? path60.substring(0, lastSlash) : "";
29476
29530
  };
29477
- var forceTrailingSlash = function(path59) {
29478
- if (path59.slice(-1) !== "/") {
29479
- path59 += "/";
29531
+ var forceTrailingSlash = function(path60) {
29532
+ if (path60.slice(-1) !== "/") {
29533
+ path60 += "/";
29480
29534
  }
29481
- return path59;
29535
+ return path60;
29482
29536
  };
29483
29537
  var folderAdd = function(name, createFolders) {
29484
29538
  createFolders = typeof createFolders !== "undefined" ? createFolders : defaults.createFolders;
@@ -30933,8 +30987,6 @@ function resolveConfig(opts) {
30933
30987
  const frpcBinary = env.CLAWD_FRPC_BIN ?? null;
30934
30988
  const rawMode = env.CLAWD_CC_MODE;
30935
30989
  const mode = DAEMON_MODE_VALUES.includes(rawMode ?? "") ? rawMode : "sdk";
30936
- const filePreviewPorts = Array.isArray(fileCfg.previewPorts) ? fileCfg.previewPorts.map((n) => Number(n)).filter((n) => Number.isInteger(n) && n > 0) : null;
30937
- 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;
30938
30990
  return {
30939
30991
  port,
30940
30992
  host,
@@ -30946,8 +30998,7 @@ function resolveConfig(opts) {
30946
30998
  authToken,
30947
30999
  noTunnelPersist,
30948
31000
  frpcBinary,
30949
- mode,
30950
- previewPorts
31001
+ mode
30951
31002
  };
30952
31003
  }
30953
31004
  var HELP_TEXT = `clawd [options]
@@ -32587,6 +32638,14 @@ function derivePersonaSpawnCwd(file, personaRoot) {
32587
32638
  }
32588
32639
  return import_node_path7.default.join(personaRoot, safeFileName(personaId));
32589
32640
  }
32641
+ function deriveGuestWorkspaceCwd(personaId, capId, personaRoot) {
32642
+ if (!personaRoot) {
32643
+ throw new Error(
32644
+ `deriveGuestWorkspaceCwd: personaRoot missing (personaId=${personaId}, capId=${capId})`
32645
+ );
32646
+ }
32647
+ return import_node_path7.default.join(personaRoot, safeFileName(personaId), "workspaces", safeFileName(capId));
32648
+ }
32590
32649
  function makeInitialState(file, subSessionMeta) {
32591
32650
  return {
32592
32651
  file,
@@ -32948,17 +33007,38 @@ var SessionManager = class {
32948
33007
  create(args, creatorPrincipalId, creatorOwnerPrincipalId) {
32949
33008
  let cwd;
32950
33009
  if (args.ownerPersonaId) {
32951
- cwd = derivePersonaSpawnCwd(
33010
+ const probeScope = scopeForFile(
32952
33011
  {
32953
33012
  sessionId: "",
32954
33013
  cwd: args.cwd ?? "",
32955
33014
  tool: "claude",
32956
33015
  ownerPersonaId: args.ownerPersonaId,
33016
+ creatorPrincipalId,
32957
33017
  createdAt: "",
32958
33018
  updatedAt: ""
32959
33019
  },
32960
- this.deps.personaRoot
33020
+ this.deps.ownerPrincipalId
32961
33021
  );
33022
+ if (probeScope.kind === "persona" && probeScope.mode === "guest") {
33023
+ cwd = deriveGuestWorkspaceCwd(
33024
+ args.ownerPersonaId,
33025
+ probeScope.capId,
33026
+ this.deps.personaRoot
33027
+ );
33028
+ import_node_fs6.default.mkdirSync(cwd, { recursive: true });
33029
+ } else {
33030
+ cwd = derivePersonaSpawnCwd(
33031
+ {
33032
+ sessionId: "",
33033
+ cwd: args.cwd ?? "",
33034
+ tool: "claude",
33035
+ ownerPersonaId: args.ownerPersonaId,
33036
+ createdAt: "",
33037
+ updatedAt: ""
33038
+ },
33039
+ this.deps.personaRoot
33040
+ );
33041
+ }
32962
33042
  } else if (args.cwd) {
32963
33043
  cwd = args.cwd;
32964
33044
  } else {
@@ -32986,7 +33066,6 @@ var SessionManager = class {
32986
33066
  iconKey: args.iconKey,
32987
33067
  forkedFromSessionId: args.forkedFromSessionId,
32988
33068
  ownerPersonaId: args.ownerPersonaId,
32989
- appBuilderProject: args.appBuilderProject,
32990
33069
  creatorPrincipalId,
32991
33070
  ...creatorOwnerPrincipalId ? { creatorOwnerPrincipalId } : {},
32992
33071
  createdAt: iso,
@@ -34106,6 +34185,35 @@ var PersonaStore = class {
34106
34185
  if (!fs7.existsSync(p2)) return null;
34107
34186
  return fs7.readFileSync(p2, "utf8");
34108
34187
  }
34188
+ /**
34189
+ * Read-modify-write 更新 sandbox.network.allowedDomains(开发型 persona 网络白名单)。
34190
+ *
34191
+ * 只动 network.allowedDomains 一个字段:sandbox-settings.json 是 owner 可手改文件
34192
+ * (manager.spec.ts 锁定 "updating personality does NOT overwrite a customized
34193
+ * sandbox-settings.json"),整文件覆盖会丢手改内容。
34194
+ *
34195
+ * 文件不存在 / JSON 损坏 → 以 DEFAULT_SETTINGS 为底重建。
34196
+ * (损坏文件无法做 merge,与其让 RPC 失败不如重建——读路径 readSandboxSettings
34197
+ * 对损坏返回 null 不修复,写路径必须产出合法文件,两者职责不同。)
34198
+ */
34199
+ updateNetworkAllowedDomains(personaId, domains) {
34200
+ const p2 = this.sandboxSettingsPath(personaId);
34201
+ let current;
34202
+ try {
34203
+ current = JSON.parse(fs7.readFileSync(p2, "utf8"));
34204
+ if (!current || typeof current !== "object" || Array.isArray(current)) {
34205
+ current = structuredClone(DEFAULT_SETTINGS);
34206
+ }
34207
+ } catch {
34208
+ current = structuredClone(DEFAULT_SETTINGS);
34209
+ }
34210
+ const sandboxRaw = current.sandbox;
34211
+ const sandbox = sandboxRaw && typeof sandboxRaw === "object" && !Array.isArray(sandboxRaw) ? sandboxRaw : current.sandbox = {};
34212
+ const networkRaw = sandbox.network;
34213
+ const network = networkRaw && typeof networkRaw === "object" && !Array.isArray(networkRaw) ? networkRaw : sandbox.network = {};
34214
+ network.allowedDomains = domains;
34215
+ this.atomicWrite(p2, JSON.stringify(current, null, 2));
34216
+ }
34109
34217
  /**
34110
34218
  * 读 sandbox-settings.json 当前内容;文件不存在 / JSON 解析失败均返回 null。
34111
34219
  * owner 手改文件可能写出非法形状,daemon 不强校验,UI 拿到 null → 显示空状态。
@@ -34536,6 +34644,20 @@ var PersonaManager = class {
34536
34644
  this.deps.registry.set(updated);
34537
34645
  return updated;
34538
34646
  }
34647
+ /**
34648
+ * 更新开发型 persona 的网络域名白名单(sandbox.network.allowedDomains)。
34649
+ * store 层做 read-modify-write(保留 owner 手改字段);这里只负责存在性校验 +
34650
+ * touch updatedAt + registry 同步,与 update() 的元数据维护行为一致。
34651
+ */
34652
+ updateNetworkAllowedDomains(personaId, domains) {
34653
+ const existing = this.deps.registry.get(personaId);
34654
+ if (!existing) throw new Error(`persona not found: ${personaId}`);
34655
+ this.deps.store.updateNetworkAllowedDomains(personaId, domains);
34656
+ const updated = { ...existing, updatedAt: Date.now() };
34657
+ this.deps.store.writeMeta(updated);
34658
+ this.deps.registry.set(updated);
34659
+ return updated;
34660
+ }
34539
34661
  /** 读取 CLAUDE.md 内容;persona 目录或文件不存在时返回 null。透传给 handler 拼响应。 */
34540
34662
  readPersonality(personaId) {
34541
34663
  return this.deps.store.readPersonality(personaId);
@@ -34623,15 +34745,6 @@ var DEFAULT_PERSONAS = [
34623
34745
  model: "opus",
34624
34746
  iconKey: "assist",
34625
34747
  public: false
34626
- },
34627
- {
34628
- // app-builder:全栈应用搭建师,绑 extension-kit 模板 + multi-project picker
34629
- // spec: superpowers/specs/2026-06-01-app-builder-project-picker-design.md
34630
- personaId: "persona-app-builder",
34631
- label: "App Builder",
34632
- model: "opus",
34633
- iconKey: "assist",
34634
- public: false
34635
34748
  }
34636
34749
  ];
34637
34750
  function findDefaultsRoot() {
@@ -36328,63 +36441,13 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
36328
36441
  var wrapper_default = import_websocket.default;
36329
36442
 
36330
36443
  // src/transport/local-ws-server.ts
36331
- var import_node_http2 = __toESM(require("http"), 1);
36332
-
36333
- // src/transport/preview-proxy.ts
36334
36444
  var import_node_http = __toESM(require("http"), 1);
36335
- var import_node_net = __toESM(require("net"), 1);
36336
- var PREVIEW_RE = /^\/preview\/(\d+)(?:\/.*)?$/;
36337
- function matchPreviewPort(pathname) {
36338
- const m2 = pathname.match(PREVIEW_RE);
36339
- return m2 ? Number(m2[1]) : null;
36340
- }
36341
- function isPreviewPortAllowed(port, cfg) {
36342
- return cfg.allowedPorts.includes(port);
36343
- }
36344
- function proxyPreviewHttp(req, res, port) {
36345
- return new Promise((resolve6) => {
36346
- const upstream = import_node_http.default.request(
36347
- { host: "localhost", port, method: req.method, path: req.url, headers: req.headers },
36348
- (upRes) => {
36349
- res.writeHead(upRes.statusCode || 502, upRes.headers);
36350
- upRes.pipe(res);
36351
- upRes.on("end", () => resolve6());
36352
- }
36353
- );
36354
- upstream.on("error", (e) => {
36355
- if (!res.headersSent) {
36356
- res.writeHead(502, { "content-type": "text/plain; charset=utf-8" });
36357
- }
36358
- res.end(`preview upstream error: ${e.message}`);
36359
- resolve6();
36360
- });
36361
- req.pipe(upstream);
36362
- });
36363
- }
36364
- function proxyPreviewUpgrade(req, socket, head, port) {
36365
- const upstream = import_node_net.default.connect(port, "localhost", () => {
36366
- let raw = `${req.method} ${req.url} HTTP/1.1\r
36367
- `;
36368
- for (let i = 0; i < req.rawHeaders.length; i += 2) {
36369
- raw += `${req.rawHeaders[i]}: ${req.rawHeaders[i + 1]}\r
36370
- `;
36371
- }
36372
- raw += "\r\n";
36373
- upstream.write(raw);
36374
- if (head && head.length) upstream.write(head);
36375
- upstream.pipe(socket);
36376
- socket.pipe(upstream);
36377
- });
36378
- upstream.on("error", () => socket.destroy());
36379
- socket.on("error", () => upstream.destroy());
36380
- }
36381
-
36382
- // src/transport/local-ws-server.ts
36383
36445
  var LocalWsServer = class {
36384
36446
  constructor(opts) {
36385
36447
  this.opts = opts;
36386
36448
  this.logger = opts.logger;
36387
36449
  this.pingIntervalMs = opts.pingIntervalMs ?? 3e4;
36450
+ this.currentOwnerPrincipalId = opts.ownerPrincipalId;
36388
36451
  }
36389
36452
  opts;
36390
36453
  wss = null;
@@ -36396,28 +36459,19 @@ var LocalWsServer = class {
36396
36459
  capabilityIdToClients = /* @__PURE__ */ new Map();
36397
36460
  logger;
36398
36461
  pingIntervalMs;
36462
+ // 飞书热切换(spec 决策 #9,2026-06-01 拍板):登录后实时更新,broadcastToPrincipal
36463
+ // 路由用最新身份;初始值来自 opts.ownerPrincipalId
36464
+ currentOwnerPrincipalId;
36465
+ /** 飞书热切换:登录回调成功后由 daemon wiring 调用,更新 DM 路由身份 */
36466
+ setOwnerPrincipalId(id) {
36467
+ this.currentOwnerPrincipalId = id;
36468
+ }
36399
36469
  async start() {
36400
36470
  const host = this.opts.host ?? "127.0.0.1";
36401
36471
  await new Promise((resolve6, reject) => {
36402
- const httpServer = import_node_http2.default.createServer((req, res) => this.handleHttpRequest(req, res));
36472
+ const httpServer = import_node_http.default.createServer((req, res) => this.handleHttpRequest(req, res));
36403
36473
  const wss = new import_websocket_server.default({ noServer: true, clientTracking: true });
36404
36474
  httpServer.on("upgrade", (req, socket, head) => {
36405
- if (req.url?.startsWith("/preview/")) {
36406
- const pathname = (() => {
36407
- try {
36408
- return new URL(req.url, "http://localhost").pathname;
36409
- } catch {
36410
- return "/";
36411
- }
36412
- })();
36413
- const port = matchPreviewPort(pathname);
36414
- if (port != null && (this.opts.previewPorts ?? []).includes(port)) {
36415
- proxyPreviewUpgrade(req, socket, head, port);
36416
- } else {
36417
- socket.destroy();
36418
- }
36419
- return;
36420
- }
36421
36475
  wss.handleUpgrade(req, socket, head, (ws) => {
36422
36476
  this.routeConnection(ws, req);
36423
36477
  });
@@ -36531,7 +36585,7 @@ var LocalWsServer = class {
36531
36585
  // principalId === 'cap_xxx' → 该 capability 的所有 guest ws (多端 / 多 tab)
36532
36586
  // 用于 DM inbox:event 路由: from + to 两侧各播一次, 让双方 UI 实时看到 thread 更新.
36533
36587
  broadcastToPrincipal(principalId, frame) {
36534
- if (principalId === "owner" || principalId === this.opts.ownerPrincipalId) {
36588
+ if (principalId === "owner" || principalId === this.currentOwnerPrincipalId) {
36535
36589
  this.broadcastToOwners(frame);
36536
36590
  return;
36537
36591
  }
@@ -36583,6 +36637,21 @@ var LocalWsServer = class {
36583
36637
  getClientContext(clientId) {
36584
36638
  return this.clients.get(clientId)?.ctx ?? null;
36585
36639
  }
36640
+ // 飞书热切换(spec 决策 #9):身份切换后踢掉所有连接强制重连——在线连接的 ctx
36641
+ // (attachClientContext 时绑定)持有旧身份,重连后用新身份重建。
36642
+ // close code 4408(非 4401!4401 = token 撤销,客户端会停止重连):
36643
+ // 客户端按普通断连处理 → 自动重连 → 新身份 ctx。
36644
+ closeAllClients(code = 4408, reason = "IDENTITY_SWITCHED") {
36645
+ const ids = [...this.clients.keys()];
36646
+ for (const id of ids) {
36647
+ const c = this.clients.get(id);
36648
+ if (!c) continue;
36649
+ try {
36650
+ c.ws.close(code, reason);
36651
+ } catch {
36652
+ }
36653
+ }
36654
+ }
36586
36655
  // Task 1.10 撤销级联:关闭某 capability 的所有活跃 ws 连接。close code 4401
36587
36656
  // 让客户端识别 'capability revoked' 而非普通断连。
36588
36657
  closeConnectionsByCapability(capabilityId, code = 4401, reason = "TOKEN_REVOKED") {
@@ -38065,13 +38134,23 @@ function createHttpRouter(deps) {
38065
38134
  });
38066
38135
  return true;
38067
38136
  }
38068
- if (url.pathname.startsWith("/preview/")) {
38069
- const port = matchPreviewPort(url.pathname);
38070
- if (port != null && isPreviewPortAllowed(port, { allowedPorts: deps.previewPorts ?? [] })) {
38071
- await proxyPreviewHttp(req, res, port);
38137
+ if (url.pathname === "/auth/callback") {
38138
+ if (req.method !== "GET") {
38139
+ sendJson(res, 404, { code: "NOT_FOUND", message: `no route for ${req.method} ${url.pathname}` });
38072
38140
  return true;
38073
38141
  }
38074
- sendJson(res, 403, { code: "PREVIEW_PORT_FORBIDDEN", message: "preview port not allowed" });
38142
+ if (!deps.feishuLogin) {
38143
+ sendJson(res, 501, { code: "NOT_IMPLEMENTED", message: "feishu login not wired" });
38144
+ return true;
38145
+ }
38146
+ const result = await deps.feishuLogin.handleLoginCallback({
38147
+ token: url.searchParams.get("token") ?? "",
38148
+ state: url.searchParams.get("state") ?? "",
38149
+ expiresAt: url.searchParams.get("expires_at")
38150
+ });
38151
+ 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>`;
38152
+ res.writeHead(result.ok ? 200 : 400, { "Content-Type": "text/html; charset=utf-8" });
38153
+ res.end(html);
38075
38154
  return true;
38076
38155
  }
38077
38156
  if (!url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/") && url.pathname !== "/attachments/upload" && url.pathname !== "/extensions/import") {
@@ -38309,6 +38388,9 @@ function parseUrl(rawUrl) {
38309
38388
  return null;
38310
38389
  }
38311
38390
  }
38391
+ function escapeHtml(s) {
38392
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
38393
+ }
38312
38394
  function sendJson(res, status, body, extraHeaders) {
38313
38395
  res.writeHead(status, {
38314
38396
  "Content-Type": "application/json; charset=utf-8",
@@ -39331,6 +39413,160 @@ function loadOwnerDisplayName(dataDir) {
39331
39413
  return displayName;
39332
39414
  }
39333
39415
 
39416
+ // src/feishu-auth/owner-identity-store.ts
39417
+ var import_node_fs27 = __toESM(require("fs"), 1);
39418
+ var import_node_path31 = __toESM(require("path"), 1);
39419
+ var OWNER_IDENTITY_FILE_NAME = "owner-identity.json";
39420
+ var OwnerIdentityStore = class {
39421
+ file;
39422
+ constructor(dataDir) {
39423
+ this.file = import_node_path31.default.join(dataDir, OWNER_IDENTITY_FILE_NAME);
39424
+ }
39425
+ read() {
39426
+ let raw;
39427
+ try {
39428
+ raw = import_node_fs27.default.readFileSync(this.file, "utf8");
39429
+ } catch {
39430
+ return null;
39431
+ }
39432
+ let parsed;
39433
+ try {
39434
+ parsed = JSON.parse(raw);
39435
+ } catch {
39436
+ return null;
39437
+ }
39438
+ const r = parsed;
39439
+ 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) {
39440
+ return null;
39441
+ }
39442
+ return {
39443
+ identity: {
39444
+ unionId: r.identity.unionId,
39445
+ displayName: r.identity.displayName,
39446
+ ...typeof r.identity.avatarUrl === "string" ? { avatarUrl: r.identity.avatarUrl } : {}
39447
+ },
39448
+ ttcToken: r.ttcToken,
39449
+ ttcTokenExpiresAt: typeof r.ttcTokenExpiresAt === "number" ? r.ttcTokenExpiresAt : null,
39450
+ loginAt: typeof r.loginAt === "string" ? r.loginAt : (/* @__PURE__ */ new Date(0)).toISOString()
39451
+ };
39452
+ }
39453
+ write(record) {
39454
+ import_node_fs27.default.mkdirSync(import_node_path31.default.dirname(this.file), { recursive: true });
39455
+ import_node_fs27.default.writeFileSync(this.file, JSON.stringify(record, null, 2), { mode: 384 });
39456
+ try {
39457
+ import_node_fs27.default.chmodSync(this.file, 384);
39458
+ } catch {
39459
+ }
39460
+ }
39461
+ clear() {
39462
+ try {
39463
+ import_node_fs27.default.unlinkSync(this.file);
39464
+ } catch (err) {
39465
+ const code = err?.code;
39466
+ if (code !== "ENOENT") throw err;
39467
+ }
39468
+ }
39469
+ };
39470
+
39471
+ // src/feishu-auth/login-flow.ts
39472
+ var import_node_crypto11 = __toESM(require("crypto"), 1);
39473
+ var STATE_TTL_MS = 5 * 60 * 1e3;
39474
+ var LoginFlow = class {
39475
+ constructor(deps) {
39476
+ this.deps = deps;
39477
+ }
39478
+ deps;
39479
+ pendingStates = /* @__PURE__ */ new Map();
39480
+ start() {
39481
+ const state = import_node_crypto11.default.randomBytes(16).toString("base64url");
39482
+ const now = (this.deps.now ?? Date.now)();
39483
+ this.pendingStates.set(state, now);
39484
+ this.gcExpired(now);
39485
+ const url = new URL("/auth/authorize", this.deps.ttcAuthBase);
39486
+ url.searchParams.set("callback_url", this.deps.getCallbackUrl());
39487
+ url.searchParams.set("state", state);
39488
+ url.searchParams.set("auto", "1");
39489
+ return { authUrl: url.toString(), state };
39490
+ }
39491
+ async handleCallback(params) {
39492
+ const now = (this.deps.now ?? Date.now)();
39493
+ const issuedAt = this.pendingStates.get(params.state);
39494
+ if (issuedAt === void 0) {
39495
+ return { ok: false, reason: "state mismatch" };
39496
+ }
39497
+ this.pendingStates.delete(params.state);
39498
+ if (now - issuedAt > STATE_TTL_MS) {
39499
+ return { ok: false, reason: "state expired" };
39500
+ }
39501
+ if (!params.token) {
39502
+ return { ok: false, reason: "missing token" };
39503
+ }
39504
+ let identity;
39505
+ try {
39506
+ identity = await this.deps.fetchUserInfo(params.token);
39507
+ } catch (err) {
39508
+ return { ok: false, reason: `user_info failed: ${err.message}` };
39509
+ }
39510
+ const expiresAtNum = params.expiresAt ? Number.parseInt(params.expiresAt, 10) : NaN;
39511
+ this.deps.store.write({
39512
+ identity,
39513
+ ttcToken: params.token,
39514
+ ttcTokenExpiresAt: Number.isFinite(expiresAtNum) ? expiresAtNum : null,
39515
+ loginAt: new Date(now).toISOString()
39516
+ });
39517
+ return { ok: true, identity };
39518
+ }
39519
+ gcExpired(now) {
39520
+ for (const [state, issuedAt] of this.pendingStates) {
39521
+ if (now - issuedAt > STATE_TTL_MS) this.pendingStates.delete(state);
39522
+ }
39523
+ }
39524
+ };
39525
+
39526
+ // src/feishu-auth/ttc-client.ts
39527
+ var TTC_HOSTS = {
39528
+ auth: "https://app.ttcadvisory.com",
39529
+ api: "https://api.ttcadvisory.com"
39530
+ };
39531
+ var TtcUserInfoError = class extends Error {
39532
+ constructor(code, message) {
39533
+ super(message);
39534
+ this.code = code;
39535
+ this.name = "TtcUserInfoError";
39536
+ }
39537
+ code;
39538
+ };
39539
+ async function fetchTtcUserInfo(opts) {
39540
+ const doFetch = opts.fetchImpl ?? fetch;
39541
+ let res;
39542
+ try {
39543
+ res = await doFetch(`${opts.apiBase}/api/user_service/v1/login/user`, {
39544
+ headers: { Authorization: `Bearer ${opts.token}` }
39545
+ });
39546
+ } catch (err) {
39547
+ throw new TtcUserInfoError("NETWORK", `TTC user_info request failed: ${err.message}`);
39548
+ }
39549
+ if (!res.ok) {
39550
+ if (res.status === 401) {
39551
+ throw new TtcUserInfoError("UNAUTHORIZED", "TTC token invalid or expired");
39552
+ }
39553
+ throw new TtcUserInfoError("API_ERROR", `TTC user_info HTTP ${res.status}`);
39554
+ }
39555
+ const body = await res.json();
39556
+ if (body.code !== 0) {
39557
+ throw new TtcUserInfoError("API_ERROR", `TTC user_info code=${body.code}: ${body.message ?? ""}`);
39558
+ }
39559
+ const d = body.data;
39560
+ if (!d || typeof d.union_id !== "string" || d.union_id.length === 0 || typeof d.name !== "string" || d.name.length === 0) {
39561
+ throw new TtcUserInfoError("BAD_RESPONSE", "TTC user_info missing union_id or name");
39562
+ }
39563
+ return {
39564
+ unionId: d.union_id,
39565
+ displayName: d.name,
39566
+ ...typeof d.avatar_url === "string" && d.avatar_url.length > 0 ? { avatarUrl: d.avatar_url } : {}
39567
+ };
39568
+ }
39569
+
39334
39570
  // src/index.ts
39335
39571
  init_protocol();
39336
39572
 
@@ -39338,12 +39574,12 @@ init_protocol();
39338
39574
  init_protocol();
39339
39575
 
39340
39576
  // src/session/fork.ts
39341
- var import_node_fs27 = __toESM(require("fs"), 1);
39577
+ var import_node_fs28 = __toESM(require("fs"), 1);
39342
39578
  var import_node_os14 = __toESM(require("os"), 1);
39343
- var import_node_path31 = __toESM(require("path"), 1);
39579
+ var import_node_path32 = __toESM(require("path"), 1);
39344
39580
  init_claude_history();
39345
39581
  function readJsonlEntries(file) {
39346
- const raw = import_node_fs27.default.readFileSync(file, "utf8");
39582
+ const raw = import_node_fs28.default.readFileSync(file, "utf8");
39347
39583
  const out = [];
39348
39584
  for (const line of raw.split("\n")) {
39349
39585
  const t = line.trim();
@@ -39356,10 +39592,10 @@ function readJsonlEntries(file) {
39356
39592
  return out;
39357
39593
  }
39358
39594
  function forkSession(input) {
39359
- const baseDir = input.baseDir ?? import_node_path31.default.join(import_node_os14.default.homedir(), ".claude");
39360
- const projectDir = import_node_path31.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
39361
- const sourceFile = import_node_path31.default.join(projectDir, `${input.toolSessionId}.jsonl`);
39362
- if (!import_node_fs27.default.existsSync(sourceFile)) {
39595
+ const baseDir = input.baseDir ?? import_node_path32.default.join(import_node_os14.default.homedir(), ".claude");
39596
+ const projectDir = import_node_path32.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
39597
+ const sourceFile = import_node_path32.default.join(projectDir, `${input.toolSessionId}.jsonl`);
39598
+ if (!import_node_fs28.default.existsSync(sourceFile)) {
39363
39599
  throw new Error(`fork: source transcript not found: ${sourceFile}`);
39364
39600
  }
39365
39601
  const entries = readJsonlEntries(sourceFile);
@@ -39389,9 +39625,9 @@ function forkSession(input) {
39389
39625
  }
39390
39626
  forkedLines.push(JSON.stringify(forked));
39391
39627
  }
39392
- const forkedFilePath = import_node_path31.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
39393
- import_node_fs27.default.mkdirSync(projectDir, { recursive: true });
39394
- import_node_fs27.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
39628
+ const forkedFilePath = import_node_path32.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
39629
+ import_node_fs28.default.mkdirSync(projectDir, { recursive: true });
39630
+ import_node_fs28.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
39395
39631
  return { forkedToolSessionId, forkedFilePath };
39396
39632
  }
39397
39633
 
@@ -39491,67 +39727,7 @@ function buildSessionHandlers(deps) {
39491
39727
  const update = async (frame, _client, ctx) => {
39492
39728
  const args = SessionUpdateArgs.parse(frame);
39493
39729
  ensureSessionAccess(ctx, args.sessionId, "send");
39494
- const prevFile = manager.findOwnedSession(args.sessionId);
39495
- const prevProject = prevFile?.appBuilderProject ?? null;
39496
- const nextProject = args.patch.appBuilderProject ?? prevProject;
39497
39730
  const { response, broadcast } = manager.update(args);
39498
- if (args.patch.appBuilderProject !== void 0) {
39499
- deps.logger?.info("session-update.appBuilderProject", {
39500
- sessionId: args.sessionId,
39501
- prevProject,
39502
- nextProject,
39503
- hasLifecycle: !!deps.devServerLifecycle,
39504
- hasStore: !!deps.appBuilderStore
39505
- });
39506
- }
39507
- if (deps.devServerLifecycle && deps.appBuilderStore && args.patch.appBuilderProject !== void 0) {
39508
- const lifecycle = deps.devServerLifecycle;
39509
- const projectStore = deps.appBuilderStore;
39510
- const tunnelUrl = deps.getTunnelUrl?.() ?? null;
39511
- try {
39512
- if (prevProject && prevProject !== nextProject) {
39513
- await lifecycle.stopForSession(args.sessionId);
39514
- }
39515
- if (nextProject && nextProject !== "") {
39516
- const occupantSessionId = lifecycle.boundSessionId(nextProject);
39517
- const alreadyRunningForThisSession = lifecycle.isRunning(nextProject) && occupantSessionId === args.sessionId;
39518
- deps.logger?.info("session-update.maybe-start-dev-server", {
39519
- sessionId: args.sessionId,
39520
- projectName: nextProject,
39521
- occupantSessionId,
39522
- alreadyRunningForThisSession
39523
- });
39524
- if (!alreadyRunningForThisSession) {
39525
- if (occupantSessionId && occupantSessionId !== args.sessionId) {
39526
- await lifecycle.stopForSession(occupantSessionId);
39527
- }
39528
- const projects = await projectStore.list();
39529
- const target = projects.find((p2) => p2.name === nextProject);
39530
- deps.logger?.info("session-update.start-dev-server", {
39531
- sessionId: args.sessionId,
39532
- projectName: nextProject,
39533
- targetFound: !!target,
39534
- projectCwd: target ? projectStore.projectDir(nextProject) : null,
39535
- tunnelUrl
39536
- });
39537
- if (target) {
39538
- lifecycle.startForSession({
39539
- sessionId: args.sessionId,
39540
- projectName: nextProject,
39541
- cwd: projectStore.projectDir(nextProject),
39542
- port: target.port,
39543
- tunnelHost: tunnelUrl ? new URL(tunnelUrl.replace(/^wss?:\/\//i, "https://")).host : null
39544
- });
39545
- }
39546
- }
39547
- }
39548
- } catch (err) {
39549
- deps.logger?.warn("session-update.lifecycle-failed", {
39550
- sessionId: args.sessionId,
39551
- error: err instanceof Error ? err.message : String(err)
39552
- });
39553
- }
39554
- }
39555
39731
  return { response: { type: "session:info", ...response }, broadcast };
39556
39732
  };
39557
39733
  const del = async (frame, _client, ctx) => {
@@ -39803,7 +39979,7 @@ function buildPermissionHandlers(deps) {
39803
39979
  }
39804
39980
 
39805
39981
  // src/handlers/history.ts
39806
- var path41 = __toESM(require("path"), 1);
39982
+ var path42 = __toESM(require("path"), 1);
39807
39983
  init_protocol();
39808
39984
 
39809
39985
  // src/session/recent-dirs.ts
@@ -39821,7 +39997,7 @@ function listRecentDirs(store, limit = 50) {
39821
39997
  }
39822
39998
 
39823
39999
  // src/permission/persona-paths.ts
39824
- var path40 = __toESM(require("path"), 1);
40000
+ var path41 = __toESM(require("path"), 1);
39825
40001
  function getAllowedPersonaIds(grants, action) {
39826
40002
  const ids = /* @__PURE__ */ new Set();
39827
40003
  for (const g2 of grants) {
@@ -39834,26 +40010,26 @@ function getAllowedPersonaIds(grants, action) {
39834
40010
  return ids;
39835
40011
  }
39836
40012
  function isGuestPathAllowed(grants, absPath, personaRoot, action = "read") {
39837
- const root = path40.resolve(personaRoot);
39838
- const target = path40.resolve(absPath);
39839
- const sep3 = root.endsWith(path40.sep) ? "" : path40.sep;
40013
+ const root = path41.resolve(personaRoot);
40014
+ const target = path41.resolve(absPath);
40015
+ const sep3 = root.endsWith(path41.sep) ? "" : path41.sep;
39840
40016
  if (!target.startsWith(root + sep3)) return false;
39841
- const rel = path40.relative(root, target);
40017
+ const rel = path41.relative(root, target);
39842
40018
  if (!rel || rel.startsWith("..")) return false;
39843
- const personaId = rel.split(path40.sep)[0];
40019
+ const personaId = rel.split(path41.sep)[0];
39844
40020
  if (!personaId) return false;
39845
40021
  const allowed = getAllowedPersonaIds(grants, action);
39846
40022
  if (allowed === "*") return true;
39847
40023
  return allowed.has(personaId);
39848
40024
  }
39849
40025
  function personaIdFromPath(absPath, personaRoot) {
39850
- const root = path40.resolve(personaRoot);
39851
- const target = path40.resolve(absPath);
39852
- const sep3 = root.endsWith(path40.sep) ? "" : path40.sep;
40026
+ const root = path41.resolve(personaRoot);
40027
+ const target = path41.resolve(absPath);
40028
+ const sep3 = root.endsWith(path41.sep) ? "" : path41.sep;
39853
40029
  if (!target.startsWith(root + sep3)) return null;
39854
- const rel = path40.relative(root, target);
40030
+ const rel = path41.relative(root, target);
39855
40031
  if (!rel || rel.startsWith("..")) return null;
39856
- const id = rel.split(path40.sep)[0];
40032
+ const id = rel.split(path41.sep)[0];
39857
40033
  return id || null;
39858
40034
  }
39859
40035
 
@@ -39878,7 +40054,7 @@ function buildHistoryHandlers(deps) {
39878
40054
  if (!pid) return false;
39879
40055
  return isGuestPathAllowed(
39880
40056
  ctx.grants,
39881
- path41.join(personaRoot, pid),
40057
+ path42.join(personaRoot, pid),
39882
40058
  personaRoot,
39883
40059
  "read"
39884
40060
  );
@@ -39889,7 +40065,7 @@ function buildHistoryHandlers(deps) {
39889
40065
  };
39890
40066
  const list = async (frame, _client, ctx) => {
39891
40067
  const args = HistoryListArgs.parse(frame);
39892
- assertGuestPath(ctx, path41.resolve(args.projectPath), personaRoot, "history:list");
40068
+ assertGuestPath(ctx, path42.resolve(args.projectPath), personaRoot, "history:list");
39893
40069
  const sessions = await history.listSessions(args);
39894
40070
  return { response: { type: "history:list", sessions } };
39895
40071
  };
@@ -39921,13 +40097,13 @@ function buildHistoryHandlers(deps) {
39921
40097
  };
39922
40098
  const subagents = async (frame, _client, ctx) => {
39923
40099
  const args = HistorySubagentsArgs.parse(frame);
39924
- assertGuestPath(ctx, path41.resolve(args.cwd), personaRoot, "history:subagents");
40100
+ assertGuestPath(ctx, path42.resolve(args.cwd), personaRoot, "history:subagents");
39925
40101
  const subs = await history.listSubagents(args);
39926
40102
  return { response: { type: "history:subagents", subagents: subs } };
39927
40103
  };
39928
40104
  const subagentRead = async (frame, _client, ctx) => {
39929
40105
  const args = HistorySubagentReadArgs.parse(frame);
39930
- assertGuestPath(ctx, path41.resolve(args.cwd), personaRoot, "history:subagent-read");
40106
+ assertGuestPath(ctx, path42.resolve(args.cwd), personaRoot, "history:subagent-read");
39931
40107
  const res = await history.readSubagent(args);
39932
40108
  return { response: { type: "history:subagent-read", ...res } };
39933
40109
  };
@@ -39935,7 +40111,7 @@ function buildHistoryHandlers(deps) {
39935
40111
  const dirs = listRecentDirs(store);
39936
40112
  if (ctx?.principal.kind === "guest" && personaRoot) {
39937
40113
  const filtered = dirs.filter(
39938
- (d) => isGuestPathAllowed(ctx.grants, path41.resolve(d.cwd), personaRoot, "read")
40114
+ (d) => isGuestPathAllowed(ctx.grants, path42.resolve(d.cwd), personaRoot, "read")
39939
40115
  );
39940
40116
  return { response: { type: "history:recentDirs", dirs: filtered } };
39941
40117
  }
@@ -39952,7 +40128,7 @@ function buildHistoryHandlers(deps) {
39952
40128
  }
39953
40129
 
39954
40130
  // src/handlers/workspace.ts
39955
- var path42 = __toESM(require("path"), 1);
40131
+ var path43 = __toESM(require("path"), 1);
39956
40132
  var os15 = __toESM(require("os"), 1);
39957
40133
  init_protocol();
39958
40134
  init_protocol();
@@ -39993,22 +40169,22 @@ function buildWorkspaceHandlers(deps) {
39993
40169
  const args = WorkspaceListArgs.parse(frame);
39994
40170
  const isGuest = ctx?.principal.kind === "guest";
39995
40171
  const fallbackCwd = isGuest && personaRoot ? personaRoot : os15.homedir();
39996
- const resolvedCwd = path42.resolve(args.cwd ?? fallbackCwd);
39997
- const target = args.path ? path42.resolve(resolvedCwd, args.path) : resolvedCwd;
40172
+ const resolvedCwd = path43.resolve(args.cwd ?? fallbackCwd);
40173
+ const target = args.path ? path43.resolve(resolvedCwd, args.path) : resolvedCwd;
39998
40174
  assertGuestPath2(ctx, target, personaRoot, "workspace:list");
39999
40175
  const res = workspace.list({ ...args, cwd: resolvedCwd });
40000
40176
  return { response: { type: "workspace:list", ...res } };
40001
40177
  };
40002
40178
  const read = async (frame, _client, ctx) => {
40003
40179
  const args = WorkspaceReadArgs.parse(frame);
40004
- const target = path42.isAbsolute(args.path) ? path42.resolve(args.path) : path42.resolve(args.cwd, args.path);
40180
+ const target = path43.isAbsolute(args.path) ? path43.resolve(args.path) : path43.resolve(args.cwd, args.path);
40005
40181
  assertGuestPath2(ctx, target, personaRoot, "workspace:read");
40006
40182
  const res = workspace.read(args);
40007
40183
  return { response: { type: "workspace:read", ...res } };
40008
40184
  };
40009
40185
  const skillsList = async (frame, _client, ctx) => {
40010
40186
  const args = SkillsListArgs.parse(frame);
40011
- const cwdAbs = path42.resolve(args.cwd);
40187
+ const cwdAbs = path43.resolve(args.cwd);
40012
40188
  assertGuestPath2(ctx, cwdAbs, personaRoot, "skills:list");
40013
40189
  const list2 = skills.list(args);
40014
40190
  if (ctx?.principal.kind === "guest" && personaRoot) {
@@ -40020,7 +40196,7 @@ function buildWorkspaceHandlers(deps) {
40020
40196
  };
40021
40197
  const agentsList = async (frame, _client, ctx) => {
40022
40198
  const args = AgentsListArgs.parse(frame);
40023
- const cwdAbs = path42.resolve(args.cwd);
40199
+ const cwdAbs = path43.resolve(args.cwd);
40024
40200
  assertGuestPath2(ctx, cwdAbs, personaRoot, "agents:list");
40025
40201
  const list2 = agents.list(args);
40026
40202
  if (ctx?.principal.kind === "guest" && personaRoot) {
@@ -40039,20 +40215,20 @@ function buildWorkspaceHandlers(deps) {
40039
40215
  }
40040
40216
 
40041
40217
  // src/handlers/git.ts
40042
- var path44 = __toESM(require("path"), 1);
40218
+ var path45 = __toESM(require("path"), 1);
40043
40219
  init_protocol();
40044
40220
  init_protocol();
40045
40221
 
40046
40222
  // src/workspace/git.ts
40047
40223
  var import_node_child_process6 = require("child_process");
40048
- var import_node_fs28 = __toESM(require("fs"), 1);
40049
- var import_node_path32 = __toESM(require("path"), 1);
40224
+ var import_node_fs29 = __toESM(require("fs"), 1);
40225
+ var import_node_path33 = __toESM(require("path"), 1);
40050
40226
  var import_node_util = require("util");
40051
40227
  var pexec = (0, import_node_util.promisify)(import_node_child_process6.execFile);
40052
40228
  function normalizePath(p2) {
40053
- const resolved = import_node_path32.default.resolve(p2);
40229
+ const resolved = import_node_path33.default.resolve(p2);
40054
40230
  try {
40055
- return import_node_fs28.default.realpathSync(resolved);
40231
+ return import_node_fs29.default.realpathSync(resolved);
40056
40232
  } catch {
40057
40233
  return resolved;
40058
40234
  }
@@ -40125,7 +40301,7 @@ async function listGitBranches(cwd) {
40125
40301
  // src/handlers/git.ts
40126
40302
  function assertGuestCwd(ctx, cwd, personaRoot, method) {
40127
40303
  if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
40128
- if (!isGuestPathAllowed(ctx.grants, path44.resolve(cwd), personaRoot, "read")) {
40304
+ if (!isGuestPathAllowed(ctx.grants, path45.resolve(cwd), personaRoot, "read")) {
40129
40305
  throw new ClawdError(
40130
40306
  ERROR_CODES.UNAUTHORIZED,
40131
40307
  `guest ${ctx.principal.id} cannot ${method} cwd ${cwd}`
@@ -40487,6 +40663,36 @@ function buildWhoamiHandler(deps) {
40487
40663
  };
40488
40664
  }
40489
40665
 
40666
+ // src/handlers/feishu-auth.ts
40667
+ function buildFeishuAuthHandlers(deps) {
40668
+ const loginStart = async () => {
40669
+ const { authUrl, state } = deps.loginFlow.start();
40670
+ return {
40671
+ response: { type: "auth:login:start:ok", authUrl, state }
40672
+ };
40673
+ };
40674
+ const getIdentity = async () => {
40675
+ const record = deps.ownerIdentityStore.read();
40676
+ return {
40677
+ response: {
40678
+ type: "auth:getIdentity:ok",
40679
+ identity: record ? record.identity : null
40680
+ }
40681
+ };
40682
+ };
40683
+ const logout = async () => {
40684
+ deps.ownerIdentityStore.clear();
40685
+ return {
40686
+ response: { type: "auth:logout:ok" }
40687
+ };
40688
+ };
40689
+ return {
40690
+ "auth:login:start": loginStart,
40691
+ "auth:getIdentity": getIdentity,
40692
+ "auth:logout": logout
40693
+ };
40694
+ }
40695
+
40490
40696
  // src/handlers/meta.ts
40491
40697
  var import_node_os15 = __toESM(require("os"), 1);
40492
40698
  init_protocol();
@@ -40593,8 +40799,14 @@ function buildPersonaHandlers(deps) {
40593
40799
  const update = async (frame) => {
40594
40800
  const { type: _type, requestId: _requestId, ...rest } = frame;
40595
40801
  const args = PersonaUpdateArgsSchema.parse(rest);
40596
- const { personality, ...metaPatch } = args.patch;
40597
- const persona = personaManager.update(args.personaId, metaPatch, personality);
40802
+ const { personality, networkAllowedDomains, ...metaPatch } = args.patch;
40803
+ let persona = personaManager.update(args.personaId, metaPatch, personality);
40804
+ if (networkAllowedDomains !== void 0) {
40805
+ persona = personaManager.updateNetworkAllowedDomains(
40806
+ args.personaId,
40807
+ networkAllowedDomains
40808
+ );
40809
+ }
40598
40810
  return { response: { type: "persona:info", ...persona } };
40599
40811
  };
40600
40812
  const del = async (frame) => {
@@ -40614,7 +40826,7 @@ function buildPersonaHandlers(deps) {
40614
40826
  }
40615
40827
 
40616
40828
  // src/handlers/attachment.ts
40617
- var import_node_path33 = __toESM(require("path"), 1);
40829
+ var import_node_path34 = __toESM(require("path"), 1);
40618
40830
  init_protocol();
40619
40831
  init_protocol();
40620
40832
  var DEFAULT_TTL_SECONDS = 24 * 3600;
@@ -40668,12 +40880,12 @@ function buildAttachmentHandlers(deps) {
40668
40880
  `session ${args.sessionId} scope unresolved`
40669
40881
  );
40670
40882
  }
40671
- const cwdAbs = import_node_path33.default.resolve(sessionFile.cwd);
40672
- const candidateAbs = import_node_path33.default.isAbsolute(args.relPath) ? import_node_path33.default.resolve(args.relPath) : import_node_path33.default.resolve(cwdAbs, args.relPath);
40883
+ const cwdAbs = import_node_path34.default.resolve(sessionFile.cwd);
40884
+ const candidateAbs = import_node_path34.default.isAbsolute(args.relPath) ? import_node_path34.default.resolve(args.relPath) : import_node_path34.default.resolve(cwdAbs, args.relPath);
40673
40885
  assertGuestAttachmentPath(ctx, candidateAbs, deps.personaRoot, "attachment.signUrl");
40674
40886
  const entries = deps.groupFileStore.list(scope, args.sessionId);
40675
40887
  const entry = entries.find((e) => {
40676
- const storedAbs = import_node_path33.default.isAbsolute(e.relPath) ? import_node_path33.default.resolve(e.relPath) : import_node_path33.default.resolve(cwdAbs, e.relPath);
40888
+ const storedAbs = import_node_path34.default.isAbsolute(e.relPath) ? import_node_path34.default.resolve(e.relPath) : import_node_path34.default.resolve(cwdAbs, e.relPath);
40677
40889
  return storedAbs === candidateAbs && !e.stale;
40678
40890
  });
40679
40891
  if (!entry) {
@@ -40697,7 +40909,7 @@ function buildAttachmentHandlers(deps) {
40697
40909
  if (!ctx || ctx.principal.kind !== "guest" || !deps.personaRoot || !deps.sessionStore) return;
40698
40910
  const f = deps.sessionStore.read(sessionId);
40699
40911
  if (!f) return;
40700
- assertGuestAttachmentPath(ctx, import_node_path33.default.resolve(f.cwd), deps.personaRoot, method);
40912
+ assertGuestAttachmentPath(ctx, import_node_path34.default.resolve(f.cwd), deps.personaRoot, method);
40701
40913
  }
40702
40914
  const groupAdd = async (frame, _client, ctx) => {
40703
40915
  if (!deps.groupFileStore || !deps.getSessionScope) {
@@ -40769,19 +40981,19 @@ function buildAttachmentHandlers(deps) {
40769
40981
 
40770
40982
  // src/handlers/extension.ts
40771
40983
  var import_promises7 = __toESM(require("fs/promises"), 1);
40772
- var import_node_path38 = __toESM(require("path"), 1);
40984
+ var import_node_path39 = __toESM(require("path"), 1);
40773
40985
  init_protocol();
40774
40986
 
40775
40987
  // src/extension/bundle-zip.ts
40776
40988
  var import_promises4 = __toESM(require("fs/promises"), 1);
40777
- var import_node_path34 = __toESM(require("path"), 1);
40778
- var import_node_crypto11 = __toESM(require("crypto"), 1);
40989
+ var import_node_path35 = __toESM(require("path"), 1);
40990
+ var import_node_crypto12 = __toESM(require("crypto"), 1);
40779
40991
  var import_jszip2 = __toESM(require_lib3(), 1);
40780
40992
  async function bundleExtensionDir(dir) {
40781
40993
  const entries = await listFilesSorted(dir);
40782
40994
  const zip = new import_jszip2.default();
40783
40995
  for (const rel of entries) {
40784
- const abs = import_node_path34.default.join(dir, rel);
40996
+ const abs = import_node_path35.default.join(dir, rel);
40785
40997
  const content = await import_promises4.default.readFile(abs);
40786
40998
  zip.file(rel, content, { date: FIXED_DATE });
40787
40999
  }
@@ -40790,7 +41002,7 @@ async function bundleExtensionDir(dir) {
40790
41002
  compression: "DEFLATE",
40791
41003
  compressionOptions: { level: 6 }
40792
41004
  });
40793
- const sha256 = import_node_crypto11.default.createHash("sha256").update(buffer).digest("hex");
41005
+ const sha256 = import_node_crypto12.default.createHash("sha256").update(buffer).digest("hex");
40794
41006
  return { buffer, sha256 };
40795
41007
  }
40796
41008
  var FIXED_DATE = /* @__PURE__ */ new Date("2020-01-01T00:00:00.000Z");
@@ -40802,7 +41014,7 @@ async function listFilesSorted(rootDir) {
40802
41014
  return out;
40803
41015
  }
40804
41016
  async function walk(absRoot, relPrefix, out) {
40805
- const dirAbs = import_node_path34.default.join(absRoot, relPrefix);
41017
+ const dirAbs = import_node_path35.default.join(absRoot, relPrefix);
40806
41018
  const entries = await import_promises4.default.readdir(dirAbs, { withFileTypes: true });
40807
41019
  for (const e of entries) {
40808
41020
  if (IGNORE_BASENAMES.has(e.name)) continue;
@@ -40856,25 +41068,25 @@ function computePublishCheck(args) {
40856
41068
 
40857
41069
  // src/extension/install-flow.ts
40858
41070
  var import_promises5 = __toESM(require("fs/promises"), 1);
40859
- var import_node_path36 = __toESM(require("path"), 1);
41071
+ var import_node_path37 = __toESM(require("path"), 1);
40860
41072
  var import_node_os17 = __toESM(require("os"), 1);
40861
- var import_node_crypto12 = __toESM(require("crypto"), 1);
41073
+ var import_node_crypto13 = __toESM(require("crypto"), 1);
40862
41074
  var import_jszip3 = __toESM(require_lib3(), 1);
40863
41075
 
40864
41076
  // src/extension/paths.ts
40865
41077
  var import_node_os16 = __toESM(require("os"), 1);
40866
- var import_node_path35 = __toESM(require("path"), 1);
41078
+ var import_node_path36 = __toESM(require("path"), 1);
40867
41079
  function clawdHomeRoot(override) {
40868
- return override ?? process.env.CLAWD_HOME ?? import_node_path35.default.join(import_node_os16.default.homedir(), ".clawd");
41080
+ return override ?? process.env.CLAWD_HOME ?? import_node_path36.default.join(import_node_os16.default.homedir(), ".clawd");
40869
41081
  }
40870
41082
  function extensionsRoot(override) {
40871
- return import_node_path35.default.join(clawdHomeRoot(override), "extensions");
41083
+ return import_node_path36.default.join(clawdHomeRoot(override), "extensions");
40872
41084
  }
40873
41085
  function publishedChannelsFile(override) {
40874
- return import_node_path35.default.join(clawdHomeRoot(override), "extensions-published.json");
41086
+ return import_node_path36.default.join(clawdHomeRoot(override), "extensions-published.json");
40875
41087
  }
40876
41088
  function bundleCacheRoot(override) {
40877
- return import_node_path35.default.join(clawdHomeRoot(override), "extension-bundles");
41089
+ return import_node_path36.default.join(clawdHomeRoot(override), "extension-bundles");
40878
41090
  }
40879
41091
 
40880
41092
  // src/extension/install-flow.ts
@@ -40887,7 +41099,7 @@ var InstallError = class extends Error {
40887
41099
  };
40888
41100
  async function installFromChannel(args, deps) {
40889
41101
  const { channelRef, snapshotHash, bundleZip } = args;
40890
- const computed = import_node_crypto12.default.createHash("sha256").update(bundleZip).digest("hex");
41102
+ const computed = import_node_crypto13.default.createHash("sha256").update(bundleZip).digest("hex");
40891
41103
  if (computed !== snapshotHash) {
40892
41104
  throw new InstallError(
40893
41105
  "HASH_MISMATCH",
@@ -40901,7 +41113,7 @@ async function installFromChannel(args, deps) {
40901
41113
  throw new InstallError("ZIP_INVALID", `failed to load zip: ${e.message}`);
40902
41114
  }
40903
41115
  for (const name of Object.keys(zip.files)) {
40904
- if (name.includes("..") || name.startsWith("/") || import_node_path36.default.isAbsolute(name)) {
41116
+ if (name.includes("..") || name.startsWith("/") || import_node_path37.default.isAbsolute(name)) {
40905
41117
  throw new InstallError("ZIP_INVALID", `unsafe zip entry: ${name}`);
40906
41118
  }
40907
41119
  }
@@ -40933,7 +41145,7 @@ async function installFromChannel(args, deps) {
40933
41145
  );
40934
41146
  }
40935
41147
  const localExtId = namespacedExtId(ownerSlug, channelRef.ownerPrincipalId);
40936
- const destDir = import_node_path36.default.join(deps.extensionsRoot, localExtId);
41148
+ const destDir = import_node_path37.default.join(deps.extensionsRoot, localExtId);
40937
41149
  let destExists = false;
40938
41150
  try {
40939
41151
  await import_promises5.default.access(destDir);
@@ -40947,16 +41159,16 @@ async function installFromChannel(args, deps) {
40947
41159
  );
40948
41160
  }
40949
41161
  const stage = await import_promises5.default.mkdtemp(
40950
- import_node_path36.default.join(import_node_os17.default.tmpdir(), `clawd-ext-install-${localExtId}-`)
41162
+ import_node_path37.default.join(import_node_os17.default.tmpdir(), `clawd-ext-install-${localExtId}-`)
40951
41163
  );
40952
41164
  try {
40953
41165
  for (const [name, entry] of Object.entries(zip.files)) {
40954
- const dest = import_node_path36.default.join(stage, name);
41166
+ const dest = import_node_path37.default.join(stage, name);
40955
41167
  if (entry.dir) {
40956
41168
  await import_promises5.default.mkdir(dest, { recursive: true });
40957
41169
  continue;
40958
41170
  }
40959
- await import_promises5.default.mkdir(import_node_path36.default.dirname(dest), { recursive: true });
41171
+ await import_promises5.default.mkdir(import_node_path37.default.dirname(dest), { recursive: true });
40960
41172
  if (name === "manifest.json") {
40961
41173
  const rewritten = { ...parsed.data, id: localExtId };
40962
41174
  await import_promises5.default.writeFile(dest, JSON.stringify(rewritten, null, 2));
@@ -40977,9 +41189,9 @@ async function installFromChannel(args, deps) {
40977
41189
 
40978
41190
  // src/extension/update-flow.ts
40979
41191
  var import_promises6 = __toESM(require("fs/promises"), 1);
40980
- var import_node_path37 = __toESM(require("path"), 1);
41192
+ var import_node_path38 = __toESM(require("path"), 1);
40981
41193
  var import_node_os18 = __toESM(require("os"), 1);
40982
- var import_node_crypto13 = __toESM(require("crypto"), 1);
41194
+ var import_node_crypto14 = __toESM(require("crypto"), 1);
40983
41195
  var import_jszip4 = __toESM(require_lib3(), 1);
40984
41196
  var UpdateError = class extends Error {
40985
41197
  constructor(code, message) {
@@ -40994,11 +41206,11 @@ async function updateFromChannel(args, deps) {
40994
41206
  channelRef.extId,
40995
41207
  channelRef.ownerPrincipalId
40996
41208
  );
40997
- const liveDir = import_node_path37.default.join(deps.extensionsRoot, localExtId);
41209
+ const liveDir = import_node_path38.default.join(deps.extensionsRoot, localExtId);
40998
41210
  const prevDir = `${liveDir}.prev`;
40999
41211
  let existingVersion;
41000
41212
  try {
41001
- const raw = await import_promises6.default.readFile(import_node_path37.default.join(liveDir, "manifest.json"), "utf8");
41213
+ const raw = await import_promises6.default.readFile(import_node_path38.default.join(liveDir, "manifest.json"), "utf8");
41002
41214
  const parsed2 = ExtensionManifestSchema.safeParse(JSON.parse(raw));
41003
41215
  if (!parsed2.success) {
41004
41216
  throw new UpdateError(
@@ -41017,7 +41229,7 @@ async function updateFromChannel(args, deps) {
41017
41229
  if (e instanceof UpdateError) throw e;
41018
41230
  throw e;
41019
41231
  }
41020
- const computed = import_node_crypto13.default.createHash("sha256").update(bundleZip).digest("hex");
41232
+ const computed = import_node_crypto14.default.createHash("sha256").update(bundleZip).digest("hex");
41021
41233
  if (computed !== snapshotHash) {
41022
41234
  throw new UpdateError(
41023
41235
  "HASH_MISMATCH",
@@ -41031,7 +41243,7 @@ async function updateFromChannel(args, deps) {
41031
41243
  throw new UpdateError("ZIP_INVALID", `failed to load zip: ${e.message}`);
41032
41244
  }
41033
41245
  for (const name of Object.keys(zip.files)) {
41034
- if (name.includes("..") || name.startsWith("/") || import_node_path37.default.isAbsolute(name)) {
41246
+ if (name.includes("..") || name.startsWith("/") || import_node_path38.default.isAbsolute(name)) {
41035
41247
  throw new UpdateError("ZIP_INVALID", `unsafe zip entry: ${name}`);
41036
41248
  }
41037
41249
  }
@@ -41066,16 +41278,16 @@ async function updateFromChannel(args, deps) {
41066
41278
  await import_promises6.default.rm(prevDir, { recursive: true, force: true });
41067
41279
  await import_promises6.default.rename(liveDir, prevDir);
41068
41280
  const stage = await import_promises6.default.mkdtemp(
41069
- import_node_path37.default.join(import_node_os18.default.tmpdir(), `clawd-ext-update-${localExtId}-`)
41281
+ import_node_path38.default.join(import_node_os18.default.tmpdir(), `clawd-ext-update-${localExtId}-`)
41070
41282
  );
41071
41283
  try {
41072
41284
  for (const [name, entry] of Object.entries(zip.files)) {
41073
- const dest = import_node_path37.default.join(stage, name);
41285
+ const dest = import_node_path38.default.join(stage, name);
41074
41286
  if (entry.dir) {
41075
41287
  await import_promises6.default.mkdir(dest, { recursive: true });
41076
41288
  continue;
41077
41289
  }
41078
- await import_promises6.default.mkdir(import_node_path37.default.dirname(dest), { recursive: true });
41290
+ await import_promises6.default.mkdir(import_node_path38.default.dirname(dest), { recursive: true });
41079
41291
  if (name === "manifest.json") {
41080
41292
  const rewritten = { ...parsed.data, id: localExtId };
41081
41293
  await import_promises6.default.writeFile(dest, JSON.stringify(rewritten, null, 2));
@@ -41168,7 +41380,7 @@ async function rewriteManifestVersion(root, extId, newVersion, previousPublished
41168
41380
  );
41169
41381
  }
41170
41382
  }
41171
- const manifestPath = import_node_path38.default.join(root, extId, "manifest.json");
41383
+ const manifestPath = import_node_path39.default.join(root, extId, "manifest.json");
41172
41384
  const manifest = await readManifest(root, extId);
41173
41385
  const next = { ...manifest, version: newVersion };
41174
41386
  const tmp = `${manifestPath}.tmp`;
@@ -41176,7 +41388,7 @@ async function rewriteManifestVersion(root, extId, newVersion, previousPublished
41176
41388
  await import_promises7.default.rename(tmp, manifestPath);
41177
41389
  }
41178
41390
  async function readManifest(root, extId) {
41179
- const file = import_node_path38.default.join(root, extId, "manifest.json");
41391
+ const file = import_node_path39.default.join(root, extId, "manifest.json");
41180
41392
  let raw;
41181
41393
  try {
41182
41394
  raw = await import_promises7.default.readFile(file, "utf8");
@@ -41267,7 +41479,7 @@ function buildExtensionHandlers(deps) {
41267
41479
  };
41268
41480
  async function buildSnapshotMeta(extId) {
41269
41481
  const manifest = await readManifest(deps.root, extId);
41270
- const { sha256, buffer } = await bundleExtensionDir(import_node_path38.default.join(deps.root, extId));
41482
+ const { sha256, buffer } = await bundleExtensionDir(import_node_path39.default.join(deps.root, extId));
41271
41483
  return { manifest, contentHash: sha256, buffer };
41272
41484
  }
41273
41485
  const publish = async (frame, _client, ctx) => {
@@ -41447,64 +41659,9 @@ function buildExtensionHandlers(deps) {
41447
41659
  };
41448
41660
  }
41449
41661
 
41450
- // src/handlers/app-builder.ts
41451
- function buildAppBuilderHandlers(deps) {
41452
- const { store, deleteSessionsByProject, devServerLifecycle } = deps;
41453
- const listProjects = async () => {
41454
- const projects = await store.list();
41455
- return {
41456
- response: {
41457
- projects: projects.map((p2) => ({
41458
- ...p2,
41459
- isRunning: devServerLifecycle.isRunning(p2.name),
41460
- boundSessionId: devServerLifecycle.boundSessionId(p2.name)
41461
- }))
41462
- }
41463
- };
41464
- };
41465
- const createProject = async (frame) => {
41466
- const name = frame.name;
41467
- if (typeof name !== "string") throw new Error("createProject: name must be string");
41468
- const project = await store.create(name);
41469
- return { response: { project } };
41470
- };
41471
- const deleteProject = async (frame) => {
41472
- const name = frame.name;
41473
- if (typeof name !== "string") throw new Error("deleteProject: name must be string");
41474
- await devServerLifecycle.stopForProject(name);
41475
- await deleteSessionsByProject(name);
41476
- await store.delete(name);
41477
- return { response: {} };
41478
- };
41479
- const updateProjectPort = async (frame) => {
41480
- const f = frame;
41481
- if (typeof f.name !== "string") throw new Error("updateProjectPort: name must be string");
41482
- if (typeof f.newPort !== "number") throw new Error("updateProjectPort: newPort must be number");
41483
- const entry = devServerLifecycle.boundEntry(f.name);
41484
- await devServerLifecycle.stopForProject(f.name);
41485
- const project = await store.updatePort(f.name, f.newPort);
41486
- if (entry) {
41487
- await devServerLifecycle.restartForProjectAt({
41488
- projectName: f.name,
41489
- newPort: f.newPort,
41490
- sessionId: entry.sessionId,
41491
- cwd: entry.cwd,
41492
- tunnelHost: entry.tunnelHost
41493
- });
41494
- }
41495
- return { response: { project } };
41496
- };
41497
- return {
41498
- "appBuilder:listProjects": listProjects,
41499
- "appBuilder:createProject": createProject,
41500
- "appBuilder:deleteProject": deleteProject,
41501
- "appBuilder:updateProjectPort": updateProjectPort
41502
- };
41503
- }
41504
-
41505
41662
  // src/extension/registry.ts
41506
41663
  var import_promises8 = __toESM(require("fs/promises"), 1);
41507
- var import_node_path39 = __toESM(require("path"), 1);
41664
+ var import_node_path40 = __toESM(require("path"), 1);
41508
41665
  async function loadAll(root) {
41509
41666
  let entries;
41510
41667
  try {
@@ -41517,13 +41674,13 @@ async function loadAll(root) {
41517
41674
  for (const ent of entries) {
41518
41675
  if (!ent.isDirectory()) continue;
41519
41676
  if (ent.name.startsWith(".")) continue;
41520
- records.push(await loadOne(import_node_path39.default.join(root, ent.name), ent.name));
41677
+ records.push(await loadOne(import_node_path40.default.join(root, ent.name), ent.name));
41521
41678
  }
41522
41679
  records.sort((a, b2) => a.extId < b2.extId ? -1 : a.extId > b2.extId ? 1 : 0);
41523
41680
  return records;
41524
41681
  }
41525
41682
  async function loadOne(dir, dirName) {
41526
- const manifestPath = import_node_path39.default.join(dir, "manifest.json");
41683
+ const manifestPath = import_node_path40.default.join(dir, "manifest.json");
41527
41684
  let raw;
41528
41685
  try {
41529
41686
  raw = await import_promises8.default.readFile(manifestPath, "utf8");
@@ -41568,7 +41725,7 @@ async function loadOne(dir, dirName) {
41568
41725
 
41569
41726
  // src/extension/uninstall.ts
41570
41727
  var import_promises9 = __toESM(require("fs/promises"), 1);
41571
- var import_node_path40 = __toESM(require("path"), 1);
41728
+ var import_node_path41 = __toESM(require("path"), 1);
41572
41729
  var UninstallError = class extends Error {
41573
41730
  constructor(code, message) {
41574
41731
  super(message);
@@ -41577,7 +41734,7 @@ var UninstallError = class extends Error {
41577
41734
  code;
41578
41735
  };
41579
41736
  async function uninstall(deps) {
41580
- const dir = import_node_path40.default.join(deps.root, deps.extId);
41737
+ const dir = import_node_path41.default.join(deps.root, deps.extId);
41581
41738
  try {
41582
41739
  await import_promises9.default.access(dir);
41583
41740
  } catch {
@@ -41590,13 +41747,7 @@ async function uninstall(deps) {
41590
41747
  // src/handlers/index.ts
41591
41748
  function buildMethodHandlers(deps) {
41592
41749
  return {
41593
- ...buildSessionHandlers({
41594
- ...deps,
41595
- devServerLifecycle: deps.devServerLifecycle,
41596
- appBuilderStore: deps.appBuilderStore,
41597
- getTunnelUrl: deps.getTunnelUrl,
41598
- logger: deps.logger
41599
- }),
41750
+ ...buildSessionHandlers(deps),
41600
41751
  ...buildPermissionHandlers(deps),
41601
41752
  ...buildHistoryHandlers(deps),
41602
41753
  ...buildWorkspaceHandlers(deps),
@@ -41629,6 +41780,7 @@ function buildMethodHandlers(deps) {
41629
41780
  personaStore: deps.personaStore,
41630
41781
  capabilityManager: deps.capabilityManager
41631
41782
  }),
41783
+ ...buildFeishuAuthHandlers(deps.feishuAuth),
41632
41784
  ...deps.attachment ? buildAttachmentHandlers(deps.attachment) : {},
41633
41785
  ...buildExtensionHandlers({
41634
41786
  loadAll,
@@ -41637,237 +41789,10 @@ function buildMethodHandlers(deps) {
41637
41789
  root: extensionsRoot(),
41638
41790
  publishedChannelStore: deps.publishedChannelStore,
41639
41791
  bundleCache: deps.bundleCache
41640
- }),
41641
- ...buildAppBuilderHandlers({
41642
- store: deps.appBuilderStore,
41643
- deleteSessionsByProject: deps.deleteSessionsByProject,
41644
- devServerLifecycle: deps.devServerLifecycle
41645
41792
  })
41646
41793
  };
41647
41794
  }
41648
41795
 
41649
- // src/app-builder/project-store.ts
41650
- var import_node_fs29 = require("fs");
41651
- var import_node_path41 = require("path");
41652
- init_protocol();
41653
- var PROJECTS_DIR = "projects";
41654
- var META_FILE = ".clawd-project.json";
41655
- var ProjectStore = class {
41656
- /** @param personaRoot persona 目录,例如 `~/.clawd/personas/persona-app-builder/` */
41657
- constructor(personaRoot) {
41658
- this.personaRoot = personaRoot;
41659
- }
41660
- personaRoot;
41661
- /** projects/<name>/.clawd-project.json 路径 */
41662
- metaPath(name) {
41663
- return (0, import_node_path41.join)(this.projectsRoot(), name, META_FILE);
41664
- }
41665
- /** projects/<name>/ 目录路径(cwd 用) */
41666
- projectDir(name) {
41667
- return (0, import_node_path41.join)(this.projectsRoot(), name);
41668
- }
41669
- projectsRoot() {
41670
- return (0, import_node_path41.join)(this.personaRoot, PROJECTS_DIR);
41671
- }
41672
- async list() {
41673
- let entries;
41674
- try {
41675
- entries = await import_node_fs29.promises.readdir(this.projectsRoot());
41676
- } catch (err) {
41677
- if (err.code === "ENOENT") return [];
41678
- throw err;
41679
- }
41680
- const out = [];
41681
- for (const name of entries) {
41682
- try {
41683
- const raw = await import_node_fs29.promises.readFile(this.metaPath(name), "utf8");
41684
- const parsed = ProjectMetadataSchema.safeParse(JSON.parse(raw));
41685
- if (parsed.success) out.push(parsed.data);
41686
- } catch {
41687
- }
41688
- }
41689
- return out.sort((a, b2) => a.name.localeCompare(b2.name));
41690
- }
41691
- async create(name) {
41692
- const existing = await this.list();
41693
- if (existing.some((p2) => p2.name === name)) {
41694
- throw new Error(`project "${name}" already exists / \u5DF2\u5B58\u5728`);
41695
- }
41696
- const usedPorts = new Set(existing.map((p2) => p2.port));
41697
- let port = -1;
41698
- for (let p2 = PROJECT_PORT_MIN; p2 <= PROJECT_PORT_MAX; p2++) {
41699
- if (!usedPorts.has(p2)) {
41700
- port = p2;
41701
- break;
41702
- }
41703
- }
41704
- if (port < 0) {
41705
- throw new Error(
41706
- `port pool exhausted / \u7AEF\u53E3\u6C60\u5DF2\u6EE1\uFF08${PROJECT_PORT_MIN}-${PROJECT_PORT_MAX}\uFF09\uFF0C\u5148\u5220\u4E00\u4E2A project \u91CA\u653E\u7AEF\u53E3`
41707
- );
41708
- }
41709
- const meta = {
41710
- name,
41711
- port,
41712
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
41713
- };
41714
- const validated = ProjectMetadataSchema.safeParse(meta);
41715
- if (!validated.success) {
41716
- throw new Error(`invalid name "${name}": ${validated.error.message}`);
41717
- }
41718
- const dir = this.projectDir(name);
41719
- await import_node_fs29.promises.mkdir(dir, { recursive: true });
41720
- await import_node_fs29.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
41721
- return meta;
41722
- }
41723
- async delete(name) {
41724
- const dir = this.projectDir(name);
41725
- await import_node_fs29.promises.rm(dir, { recursive: true, force: true });
41726
- }
41727
- async updatePort(name, newPort) {
41728
- if (newPort < PROJECT_PORT_MIN || newPort > PROJECT_PORT_MAX) {
41729
- throw new Error(
41730
- `port range invalid: must be ${PROJECT_PORT_MIN}-${PROJECT_PORT_MAX}, got ${newPort}`
41731
- );
41732
- }
41733
- const list = await this.list();
41734
- const target = list.find((p2) => p2.name === name);
41735
- if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
41736
- const conflict = list.find((p2) => p2.name !== name && p2.port === newPort);
41737
- if (conflict) {
41738
- throw new Error(`port ${newPort} already used / \u5DF2\u88AB project "${conflict.name}" \u5360\u7528`);
41739
- }
41740
- const updated = { ...target, port: newPort };
41741
- await import_node_fs29.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
41742
- return updated;
41743
- }
41744
- };
41745
-
41746
- // src/app-builder/dev-server-supervisor.ts
41747
- var import_node_child_process7 = require("child_process");
41748
- var import_node_events2 = require("events");
41749
- var DevServerSupervisor = class extends import_node_events2.EventEmitter {
41750
- running = /* @__PURE__ */ new Map();
41751
- // key: sessionId
41752
- logger = { warn: () => {
41753
- }, info: () => {
41754
- } };
41755
- /** daemon entry 启动后注入 logger,让 child 输出走 clawd.log */
41756
- setLogger(logger) {
41757
- this.logger = logger;
41758
- }
41759
- startForSession(args) {
41760
- if (this.running.has(args.sessionId)) return;
41761
- const augmentedPath = [
41762
- process.env.PATH ?? "",
41763
- "/opt/homebrew/bin",
41764
- "/usr/local/bin",
41765
- `${process.env.HOME}/.nvm/versions/node`,
41766
- `${process.env.HOME}/.local/share/pnpm`
41767
- ].filter(Boolean).join(":");
41768
- const env = {
41769
- ...process.env,
41770
- PATH: augmentedPath,
41771
- CLAWD_TUNNEL_HOST: args.tunnelHost ?? "",
41772
- CLAWD_PREVIEW_PORT: String(args.port)
41773
- };
41774
- this.logger.info("dev-server.start", {
41775
- projectName: args.projectName,
41776
- port: args.port,
41777
- cwd: args.cwd,
41778
- sessionId: args.sessionId,
41779
- tunnelHost: args.tunnelHost
41780
- });
41781
- const child = (0, import_node_child_process7.spawn)("pnpm", ["dev"], { cwd: args.cwd, env, stdio: "pipe", shell: true });
41782
- const entry = { ...args, process: child };
41783
- this.running.set(args.sessionId, entry);
41784
- child.stdout?.on("data", (chunk) => {
41785
- this.logger.info("dev-server.stdout", {
41786
- projectName: args.projectName,
41787
- port: args.port,
41788
- chunk: chunk.toString().trimEnd()
41789
- });
41790
- });
41791
- child.stderr?.on("data", (chunk) => {
41792
- this.logger.warn("dev-server.stderr", {
41793
- projectName: args.projectName,
41794
- port: args.port,
41795
- chunk: chunk.toString().trimEnd()
41796
- });
41797
- });
41798
- child.on("error", (err) => {
41799
- this.running.delete(args.sessionId);
41800
- this.logger.warn("dev-server.spawn-failed", {
41801
- projectName: args.projectName,
41802
- port: args.port,
41803
- error: err.message,
41804
- code: err.code
41805
- });
41806
- this.emit("devServer:failed", {
41807
- sessionId: args.sessionId,
41808
- projectName: args.projectName,
41809
- port: args.port,
41810
- exitCode: null
41811
- });
41812
- });
41813
- child.on("exit", (code) => {
41814
- this.running.delete(args.sessionId);
41815
- this.logger.info("dev-server.exit", { projectName: args.projectName, port: args.port, code });
41816
- if (code !== 0 && code !== null) {
41817
- this.emit("devServer:failed", {
41818
- sessionId: args.sessionId,
41819
- projectName: args.projectName,
41820
- port: args.port,
41821
- exitCode: code
41822
- });
41823
- }
41824
- });
41825
- }
41826
- stopForSession(sessionId) {
41827
- const entry = this.running.get(sessionId);
41828
- if (!entry) return Promise.resolve();
41829
- return new Promise((resolve6) => {
41830
- entry.process.once("exit", () => {
41831
- this.running.delete(sessionId);
41832
- resolve6();
41833
- });
41834
- entry.process.kill("SIGTERM");
41835
- });
41836
- }
41837
- async stopForProject(projectName) {
41838
- const entry = [...this.running.values()].find((e) => e.projectName === projectName);
41839
- if (!entry) return;
41840
- await this.stopForSession(entry.sessionId);
41841
- }
41842
- async restartForProjectAt(args) {
41843
- await this.stopForSession(args.sessionId);
41844
- this.startForSession({
41845
- sessionId: args.sessionId,
41846
- projectName: args.projectName,
41847
- port: args.newPort,
41848
- cwd: args.cwd,
41849
- tunnelHost: args.tunnelHost
41850
- });
41851
- }
41852
- isRunning(projectName) {
41853
- return [...this.running.values()].some((e) => e.projectName === projectName);
41854
- }
41855
- boundSessionId(projectName) {
41856
- const entry = [...this.running.values()].find((e) => e.projectName === projectName);
41857
- return entry?.sessionId ?? null;
41858
- }
41859
- boundEntry(projectName) {
41860
- const entry = [...this.running.values()].find((e) => e.projectName === projectName);
41861
- if (!entry) return null;
41862
- return {
41863
- sessionId: entry.sessionId,
41864
- cwd: entry.cwd,
41865
- port: entry.port,
41866
- tunnelHost: entry.tunnelHost
41867
- };
41868
- }
41869
- };
41870
-
41871
41796
  // src/handlers/method-grants.ts
41872
41797
  var ADMIN_ANY = {
41873
41798
  kind: "fixed",
@@ -41998,14 +41923,15 @@ var METHOD_GRANT_MAP = {
41998
41923
  // guest-self:guest 在自己 daemon 上触发安装与更新(无持久化 subscription 概念)
41999
41924
  "extension.install-from-channel": ADMIN_ANY,
42000
41925
  "extension.update-from-channel": ADMIN_ANY,
42001
- // ---- app-builder Project pickerspec 2026-06-01-app-builder-project-picker-design) ----
42002
- // owner-only:picker UI 给 owner 管理本机 build 中的 project(fs + 端口分配)。
42003
- // 即使 persona-app-builder 是 PreinstalledPersona,project 管理也属本机 owner 行为,
42004
- // 不暴露给 guest(远端接入者)。
42005
- "appBuilder:listProjects": ADMIN_ANY,
42006
- "appBuilder:createProject": ADMIN_ANY,
42007
- "appBuilder:deleteProject": ADMIN_ANY,
42008
- "appBuilder:updateProjectPort": ADMIN_ANY
41926
+ // ---- auth:* 飞书统一身份 Phase 1owner-only) ----
41927
+ // 登录/登出/查身份都是 owner 本机操作,guest 不可调
41928
+ "auth:login:start": ADMIN_ANY,
41929
+ "auth:getIdentity": ADMIN_ANY,
41930
+ "auth:logout": ADMIN_ANY,
41931
+ // ---- hub:* 飞书统一身份 Phase 2(owner-only) ----
41932
+ // hub 目录 / 连接都是 owner 本机操作(用 owner 的 ttcJwt 找中心 server),guest 不可调
41933
+ "hub:list": ADMIN_ANY,
41934
+ "hub:connect": ADMIN_ANY
42009
41935
  };
42010
41936
  function computeGrantForFrame(method, frame) {
42011
41937
  const rule = METHOD_GRANT_MAP[method];
@@ -42021,12 +41947,12 @@ function computeGrantForFrame(method, frame) {
42021
41947
  }
42022
41948
 
42023
41949
  // src/extension/runtime.ts
42024
- var import_node_child_process8 = require("child_process");
41950
+ var import_node_child_process7 = require("child_process");
42025
41951
  var import_node_path42 = __toESM(require("path"), 1);
42026
41952
  var import_promises10 = require("timers/promises");
42027
41953
 
42028
41954
  // src/extension/port-allocator.ts
42029
- var import_node_net2 = __toESM(require("net"), 1);
41955
+ var import_node_net = __toESM(require("net"), 1);
42030
41956
  var PortExhaustedError = class extends Error {
42031
41957
  constructor(min, max) {
42032
41958
  super(`no free port in [${min},${max}]`);
@@ -42039,7 +41965,7 @@ var PortExhaustedError = class extends Error {
42039
41965
  };
42040
41966
  function probe(port) {
42041
41967
  return new Promise((resolve6) => {
42042
- const srv = import_node_net2.default.createServer();
41968
+ const srv = import_node_net.default.createServer();
42043
41969
  srv.once("error", () => resolve6(false));
42044
41970
  srv.once("listening", () => {
42045
41971
  srv.close(() => resolve6(true));
@@ -42129,7 +42055,7 @@ var Runtime = class {
42129
42055
  CLAWOS_EXT_PORT: String(port),
42130
42056
  CLAWOS_EXT_ID: extId
42131
42057
  };
42132
- const child = (0, import_node_child_process8.spawn)("sh", ["-c", cmd], {
42058
+ const child = (0, import_node_child_process7.spawn)("sh", ["-c", cmd], {
42133
42059
  cwd: dir,
42134
42060
  env,
42135
42061
  stdio: ["ignore", "pipe", "pipe"],
@@ -42395,8 +42321,11 @@ async function startDaemon(config) {
42395
42321
  } else if (config.tunnel) {
42396
42322
  resolvedAuthToken = authFile.token;
42397
42323
  }
42398
- const ownerPrincipalId = authFile.ownerPrincipalId;
42399
- const ownerDisplayName = loadOwnerDisplayName(config.dataDir);
42324
+ const ownerIdentityStore = new OwnerIdentityStore(config.dataDir);
42325
+ const feishuIdentity = ownerIdentityStore.read();
42326
+ let feishuActive = feishuIdentity !== null;
42327
+ let ownerPrincipalId = feishuIdentity?.identity.unionId ?? authFile.ownerPrincipalId;
42328
+ let ownerDisplayName = feishuIdentity?.identity.displayName ?? loadOwnerDisplayName(config.dataDir);
42400
42329
  const authMode = resolvedAuthToken == null ? "none" : "first-message";
42401
42330
  const inboxStore = new InboxStore(config.dataDir);
42402
42331
  const inboxManager = new InboxManager(inboxStore, (_peerOwnerId, frame) => {
@@ -42438,7 +42367,9 @@ async function startDaemon(config) {
42438
42367
  isOwnerToken: (x) => resolvedAuthToken != null && constantTimeEqual(x, resolvedAuthToken),
42439
42368
  ownerPrincipalId,
42440
42369
  ownerDisplayName,
42441
- capabilityRegistry,
42370
+ // 飞书 gate(spec 决策 #9):未激活飞书身份时不接受 guest(capability token)入站——
42371
+ // 不传 registry 让 guest token 一律 BAD_TOKEN。owner 自己连接不受影响。
42372
+ ...feishuActive ? { capabilityRegistry } : {},
42442
42373
  selfPrincipalId,
42443
42374
  selfDisplayName
42444
42375
  });
@@ -42595,19 +42526,13 @@ async function startDaemon(config) {
42595
42526
  const publishedChannelStore = new PublishedChannelStore(publishedChannelsFile());
42596
42527
  await publishedChannelStore.load();
42597
42528
  const bundleCache = new BundleCache(bundleCacheRoot());
42598
- const appBuilderStore = new ProjectStore(
42599
- import_node_path45.default.join(config.dataDir, "personas/persona-app-builder")
42600
- );
42601
- const devServerSupervisor = new DevServerSupervisor();
42602
- devServerSupervisor.setLogger(logger);
42603
- const appBuilderPortRange = [];
42604
- for (let p2 = PROJECT_PORT_MIN; p2 <= PROJECT_PORT_MAX; p2++) appBuilderPortRange.push(p2);
42605
- const effectivePreviewPorts = Array.from(
42606
- /* @__PURE__ */ new Set([...config.previewPorts ?? [], ...appBuilderPortRange])
42607
- );
42608
- const deleteSessionsByProject = async (_name) => {
42609
- };
42610
- const handlers = buildMethodHandlers({
42529
+ const loginFlow = new LoginFlow({
42530
+ store: ownerIdentityStore,
42531
+ fetchUserInfo: (ttcToken) => fetchTtcUserInfo({ apiBase: TTC_HOSTS.api, token: ttcToken }),
42532
+ ttcAuthBase: TTC_HOSTS.auth,
42533
+ getCallbackUrl: () => `http://127.0.0.1:${config.port}/auth/callback`
42534
+ });
42535
+ const makeHandlers = () => buildMethodHandlers({
42611
42536
  manager,
42612
42537
  workspace,
42613
42538
  skills,
@@ -42680,12 +42605,10 @@ async function startDaemon(config) {
42680
42605
  // extension sharing v1 — owner-side published-channels store.
42681
42606
  publishedChannelStore,
42682
42607
  bundleCache,
42683
- // app-builder
42684
- appBuilderStore,
42685
- deleteSessionsByProject,
42686
- devServerLifecycle: devServerSupervisor,
42687
- logger
42608
+ // 飞书统一身份 Phase 1:登录 RPC handlers(auth:login:start / getIdentity / logout)
42609
+ feishuAuth: { loginFlow, ownerIdentityStore }
42688
42610
  });
42611
+ let handlers = makeHandlers();
42689
42612
  const authResolver = new AuthContextResolver({
42690
42613
  ownerToken: resolvedAuthToken
42691
42614
  });
@@ -42711,8 +42634,29 @@ async function startDaemon(config) {
42711
42634
  },
42712
42635
  // POST /extensions/import lands in ~/.clawd/extensions/<id>/
42713
42636
  extensionsRoot: () => extensionsRoot(),
42714
- // app-builder build 模式:/preview/<port>/ 反代 dev server 的端口白名单(见 spec §三.2)
42715
- previewPorts: effectivePreviewPorts
42637
+ // 飞书登录 loopback 回调(热切换,2026-06-01 拍板):成功落盘后——
42638
+ // 1. 进程内实时切换身份(let binding + 值快照持有者重建)
42639
+ // 2. 广播 auth:login:done 给在线 owner 连接(UI 即时反馈)
42640
+ // 3. 踢掉所有 ws 连接(在线连接的 ctx 是旧身份)→ UI 自动重连 → 新身份 ctx →
42641
+ // People 立即解锁,不需要重启 daemon
42642
+ // 失败则广播 auth:login:failed。
42643
+ feishuLogin: {
42644
+ handleLoginCallback: async (params) => {
42645
+ const result = await loginFlow.handleCallback(params);
42646
+ if (result.ok) {
42647
+ feishuActive = true;
42648
+ ownerPrincipalId = result.identity.unionId;
42649
+ ownerDisplayName = result.identity.displayName;
42650
+ handlers = makeHandlers();
42651
+ wsServer?.setOwnerPrincipalId(result.identity.unionId);
42652
+ wsServer?.broadcastToOwners({ type: "auth:login:done", identity: result.identity });
42653
+ setImmediate(() => wsServer?.closeAllClients());
42654
+ } else {
42655
+ wsServer?.broadcastToOwners({ type: "auth:login:failed", reason: result.reason });
42656
+ }
42657
+ return result;
42658
+ }
42659
+ }
42716
42660
  });
42717
42661
  wsServer = new LocalWsServer({
42718
42662
  host: config.host,
@@ -42751,8 +42695,6 @@ async function startDaemon(config) {
42751
42695
  },
42752
42696
  // file-sharing HTTP 路由复用 daemon 同端口(spec §5 第 3 条);router 自己处理 auth + 404
42753
42697
  httpRequestHandler: httpRouter,
42754
- // app-builder build 模式:/preview/<port>/ HMR websocket upgrade 转发的端口白名单(同 http-router 配置)
42755
- previewPorts: effectivePreviewPorts,
42756
42698
  // 订阅成功后给该 client 重放 in-flight pendingQuestions(plan: clawd-question-server-truth)。
42757
42699
  // daemon 是 pendingQuestions 的唯一 source of truth;新 client 接入 / 刷新页面时
42758
42700
  // 把当前所有未决 question 以 session:question 帧定向回放,让 UI 不再误显示 Ended。
@@ -42797,6 +42739,12 @@ async function startDaemon(config) {
42797
42739
  const requestId = typeof frame.requestId === "string" ? frame.requestId : void 0;
42798
42740
  const handler = handlers[type];
42799
42741
  if (!handler) throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, `not implemented: ${type}`);
42742
+ if (!feishuActive && FEISHU_GATED_METHODS.includes(type)) {
42743
+ throw new ClawdError(
42744
+ ERROR_CODES.FEISHU_LOGIN_REQUIRED,
42745
+ `${type} requires feishu login (People/remote identity)`
42746
+ );
42747
+ }
42800
42748
  const ctx = wsServer.getClientContext(client.id) ?? ownerContext(ownerPrincipalId, ownerDisplayName);
42801
42749
  const verdict = computeGrantForFrame(type, frame);
42802
42750
  if (verdict.kind === "check") {
@@ -42869,7 +42817,6 @@ async function startDaemon(config) {
42869
42817
  stateSnapshot = { ...stateSnapshot, tunnelUrl: r.url, tunnelError: void 0 };
42870
42818
  stateMgr.write(stateSnapshot);
42871
42819
  currentTunnelUrl = r.url;
42872
- wss.broadcastAll({ type: "tunnel:ready", url: r.url, subdomain: r.subdomain });
42873
42820
  const connectUrl = resolvedAuthToken ? `${r.url}#token=${resolvedAuthToken}` : r.url;
42874
42821
  const lines = [
42875
42822
  `Tunnel: ${r.url}`,