@clawos-dev/clawd 0.2.66 → 0.2.67-beta.119.a74a0cd

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.cjs +182 -1005
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -85,24 +85,17 @@ var init_methods = __esm({
85
85
  "git:root",
86
86
  "git:branch",
87
87
  "git:branches",
88
- "git:worktree:prefix",
89
- "git:worktree:create",
90
- "git:worktree:remove",
91
88
  "capabilities:get",
92
- // ---- persona:* (老板侧管理 + 插话;alice 走 persona-bound WS 简化协议,不在此白名单) ----
89
+ // ---- persona:* (persona 管理) ----
93
90
  "persona:create",
94
91
  "persona:list",
95
92
  "persona:get",
96
93
  "persona:update",
97
94
  "persona:delete",
95
+ // token 颁发 / 吊销:file-sharing HTTP Bearer 鉴权用(PersonaRegistry.findByToken
96
+ // 反查 personaId / label)。owner UI 在 PersonaSettingsDrawer "分享 Token" section 管理。
98
97
  "persona:issueToken",
99
98
  "persona:revokeToken",
100
- "persona:listSubSessions",
101
- // owner-side bootstrap RPC:拉 (personaId, token) tuple 下所有 listener sub-session
102
- // 元数据。后续增量靠 persona:listenerChat:* push 维护,本 RPC 仅在 owner ws connect /
103
- // reconnect 时一次性补齐 cache。
104
- "persona:listListenerChatsForToken",
105
- "persona:appendOwnerMessage",
106
99
  // ---- session:pty 双向透传(仅 CLAWD_CC_MODE=tui 才生效,对应 session:pty push 帧的上行) ----
107
100
  // session:pty:input — UI 把用户键盘字节(base64 UTF-8)发给 daemon,daemon 直写 pty stdin。
108
101
  // 高频帧(每次按键 1 帧),不入 reducer / 不广播。response 是 fire-and-forget 风格 ack。
@@ -612,8 +605,8 @@ var init_parseUtil = __esm({
612
605
  init_errors2();
613
606
  init_en();
614
607
  makeIssue = (params) => {
615
- const { data, path: path31, errorMaps, issueData } = params;
616
- const fullPath = [...path31, ...issueData.path || []];
608
+ const { data, path: path32, errorMaps, issueData } = params;
609
+ const fullPath = [...path32, ...issueData.path || []];
617
610
  const fullIssue = {
618
611
  ...issueData,
619
612
  path: fullPath
@@ -924,11 +917,11 @@ var init_types = __esm({
924
917
  init_parseUtil();
925
918
  init_util();
926
919
  ParseInputLazyPath = class {
927
- constructor(parent, value, path31, key) {
920
+ constructor(parent, value, path32, key) {
928
921
  this._cachedPath = [];
929
922
  this.parent = parent;
930
923
  this.data = value;
931
- this._path = path31;
924
+ this._path = path32;
932
925
  this._key = key;
933
926
  }
934
927
  get path() {
@@ -4338,9 +4331,11 @@ var init_attachment_schemas = __esm({
4338
4331
  stale: external_exports.boolean().optional()
4339
4332
  });
4340
4333
  AttachmentSignUrlArgs = external_exports.object({
4341
- /** 要分享的绝对路径;签名只关心 absPath,不区分 persona/session */
4342
- absPath: external_exports.string().min(1),
4343
- /** TTL 秒数;缺省 24h;'never' null 不带 exp 字段(永久有效) */
4334
+ /** 群文件所属的 session */
4335
+ sessionId: external_exports.string().min(1),
4336
+ /** 相对 session.cwd 的路径;允许传绝对路径,daemon 端会归一化(必须在 cwd 内) */
4337
+ relPath: external_exports.string().min(1),
4338
+ /** TTL 秒数;缺省 24h;null 走永久 URL(不带 exp 字段) */
4344
4339
  ttlSeconds: external_exports.number().int().positive().nullable().optional()
4345
4340
  });
4346
4341
  AttachmentSignUrlResponseSchema = external_exports.object({
@@ -4386,7 +4381,7 @@ var init_attachment_schemas = __esm({
4386
4381
  });
4387
4382
 
4388
4383
  // ../protocol/src/persona-schemas.ts
4389
- var PersonaTokenEntrySchema, PersonaFileSchema, PersonaSkillSummarySchema, PersonaSandboxSettingsSchema, PersonaInfoResponseSchema, PersonaCreateArgsSchema, PersonaIdArgsSchema, PersonaUpdateArgsSchema, PersonaIssueTokenArgsSchema, PersonaRevokeTokenArgsSchema, PersonaAppendOwnerMessageArgsSchema, FRAME_TYPE_CHAT_OPEN, FRAME_TYPE_CHAT_RENAME, FRAME_TYPE_CHAT_DELETE, FRAME_TYPE_CHAT_LIST, FRAME_TYPE_CHAT_CREATED, FRAME_TYPE_CHAT_RENAMED, FRAME_TYPE_CHAT_DELETED, FRAME_TYPE_PERSONA_LISTENER_CHAT_CREATED, FRAME_TYPE_PERSONA_LISTENER_CHAT_RENAMED, FRAME_TYPE_PERSONA_LISTENER_CHAT_DELETED, FRAME_TYPE_PERSONA_LISTENER_STATUS, ChatSummarySchema, ChatOpenRequestSchema, ChatRenameRequestSchema, ChatDeleteRequestSchema, ChatRenameResponseSchema, ChatDeleteResponseSchema, ChatListPushSchema, ChatCreatedFrameSchema, ChatRenamedFrameSchema, ChatDeletedFrameSchema, PersonaListenerChatCreatedFrameSchema, PersonaListenerChatRenamedFrameSchema, PersonaListenerChatDeletedFrameSchema, PersonaListenerStatusFrameSchema, PersonaListListenerChatsForTokenArgsSchema, PersonaListListenerChatsForTokenResponseSchema;
4384
+ var PersonaTokenEntrySchema, PersonaFileSchema, PersonaSkillSummarySchema, PersonaSandboxSettingsSchema, PersonaInfoResponseSchema, PersonaCreateArgsSchema, PersonaIdArgsSchema, PersonaUpdateArgsSchema, PersonaIssueTokenArgsSchema, PersonaRevokeTokenArgsSchema;
4390
4385
  var init_persona_schemas = __esm({
4391
4386
  "../protocol/src/persona-schemas.ts"() {
4392
4387
  "use strict";
@@ -4404,7 +4399,13 @@ var init_persona_schemas = __esm({
4404
4399
  // 8-key set defined UI-side in `lib/session-icons.ts`; protocol stays plain string
4405
4400
  // for forward compatibility, consumer fallbacks to default when key not found.
4406
4401
  iconKey: external_exports.string().optional(),
4407
- tokenMap: external_exports.record(external_exports.string(), PersonaTokenEntrySchema),
4402
+ /**
4403
+ * Persona token 列表:file-sharing HTTP Bearer 鉴权用。
4404
+ * - 写:`PersonaManager.issueToken` / `revokeToken`
4405
+ * - 读:`PersonaRegistry.findByToken` 反向查表(需 persona.public === true)
4406
+ * - optional:兼容历史 persona.json 不含此字段的情形(旧 daemon 写文件时省略)
4407
+ */
4408
+ tokenMap: external_exports.record(external_exports.string(), PersonaTokenEntrySchema).optional(),
4408
4409
  createdAt: external_exports.number(),
4409
4410
  updatedAt: external_exports.number()
4410
4411
  }).strict();
@@ -4465,126 +4466,11 @@ var init_persona_schemas = __esm({
4465
4466
  personaId: external_exports.string().min(1),
4466
4467
  token: external_exports.string().min(1)
4467
4468
  });
4468
- PersonaAppendOwnerMessageArgsSchema = external_exports.object({
4469
- personaId: external_exports.string().min(1),
4470
- subSessionId: external_exports.string().min(1),
4471
- text: external_exports.string().min(1)
4472
- });
4473
- FRAME_TYPE_CHAT_OPEN = "chat:open";
4474
- FRAME_TYPE_CHAT_RENAME = "chat:rename";
4475
- FRAME_TYPE_CHAT_DELETE = "chat:delete";
4476
- FRAME_TYPE_CHAT_LIST = "chat:list";
4477
- FRAME_TYPE_CHAT_CREATED = "chat:created";
4478
- FRAME_TYPE_CHAT_RENAMED = "chat:renamed";
4479
- FRAME_TYPE_CHAT_DELETED = "chat:deleted";
4480
- FRAME_TYPE_PERSONA_LISTENER_CHAT_CREATED = "persona:listenerChat:created";
4481
- FRAME_TYPE_PERSONA_LISTENER_CHAT_RENAMED = "persona:listenerChat:renamed";
4482
- FRAME_TYPE_PERSONA_LISTENER_CHAT_DELETED = "persona:listenerChat:deleted";
4483
- FRAME_TYPE_PERSONA_LISTENER_STATUS = "persona:listenerStatus";
4484
- ChatSummarySchema = external_exports.object({
4485
- sessionId: external_exports.string().min(1),
4486
- /** 派生 sessionId 时使用的原始 chatId(永远三元组 hash 派生,无 default 特例) */
4487
- chatId: external_exports.string().min(1),
4488
- /** sub-session label,listener 视为 chat 名 */
4489
- label: external_exports.string(),
4490
- /** SessionFile.createdAt(ISO string) */
4491
- createdAt: external_exports.string()
4492
- });
4493
- ChatOpenRequestSchema = external_exports.object({
4494
- type: external_exports.literal(FRAME_TYPE_CHAT_OPEN),
4495
- /** 客户端生成的 chatId(uuid 推荐);daemon 用三元组派生 sub-session sessionId */
4496
- chatId: external_exports.string().min(1),
4497
- /** 不存在时是否自动创建。true = 创建(典型:新建对话);false/缺省 = 命中已有,否则 NOT_FOUND */
4498
- autoCreate: external_exports.boolean().optional(),
4499
- requestId: external_exports.string().min(1).optional()
4500
- });
4501
- ChatRenameRequestSchema = external_exports.object({
4502
- type: external_exports.literal(FRAME_TYPE_CHAT_RENAME),
4503
- requestId: external_exports.string().min(1).optional(),
4504
- sessionId: external_exports.string().min(1),
4505
- label: external_exports.string().min(1)
4506
- });
4507
- ChatDeleteRequestSchema = external_exports.object({
4508
- type: external_exports.literal(FRAME_TYPE_CHAT_DELETE),
4509
- requestId: external_exports.string().min(1).optional(),
4510
- sessionId: external_exports.string().min(1)
4511
- });
4512
- ChatRenameResponseSchema = external_exports.object({
4513
- type: external_exports.literal(FRAME_TYPE_CHAT_RENAME),
4514
- requestId: external_exports.string().min(1).optional(),
4515
- ok: external_exports.literal(true)
4516
- });
4517
- ChatDeleteResponseSchema = external_exports.object({
4518
- type: external_exports.literal(FRAME_TYPE_CHAT_DELETE),
4519
- requestId: external_exports.string().min(1).optional(),
4520
- ok: external_exports.literal(true)
4521
- });
4522
- ChatListPushSchema = external_exports.object({
4523
- type: external_exports.literal(FRAME_TYPE_CHAT_LIST),
4524
- chats: external_exports.array(ChatSummarySchema)
4525
- });
4526
- ChatCreatedFrameSchema = external_exports.object({
4527
- type: external_exports.literal(FRAME_TYPE_CHAT_CREATED),
4528
- sessionId: external_exports.string().min(1),
4529
- chatId: external_exports.string().min(1),
4530
- label: external_exports.string().optional(),
4531
- /** ISO timestamp */
4532
- createdAt: external_exports.string().min(1)
4533
- });
4534
- ChatRenamedFrameSchema = external_exports.object({
4535
- type: external_exports.literal(FRAME_TYPE_CHAT_RENAMED),
4536
- sessionId: external_exports.string().min(1),
4537
- label: external_exports.string().min(1)
4538
- });
4539
- ChatDeletedFrameSchema = external_exports.object({
4540
- type: external_exports.literal(FRAME_TYPE_CHAT_DELETED),
4541
- sessionId: external_exports.string().min(1)
4542
- });
4543
- PersonaListenerChatCreatedFrameSchema = external_exports.object({
4544
- type: external_exports.literal(FRAME_TYPE_PERSONA_LISTENER_CHAT_CREATED),
4545
- personaId: external_exports.string().min(1),
4546
- /** owner 同时持有 token 字符串:sidebar 已经按 token 维度渲染 listener leaf,
4547
- * push 帧用 token 直接定位归属 leaf。token 不是密钥,owner 本来就持有。 */
4548
- token: external_exports.string().min(1),
4549
- sessionId: external_exports.string().min(1),
4550
- chatId: external_exports.string().min(1),
4551
- label: external_exports.string().optional(),
4552
- createdAt: external_exports.string().min(1)
4553
- });
4554
- PersonaListenerChatRenamedFrameSchema = external_exports.object({
4555
- type: external_exports.literal(FRAME_TYPE_PERSONA_LISTENER_CHAT_RENAMED),
4556
- personaId: external_exports.string().min(1),
4557
- token: external_exports.string().min(1),
4558
- sessionId: external_exports.string().min(1),
4559
- label: external_exports.string().min(1)
4560
- });
4561
- PersonaListenerChatDeletedFrameSchema = external_exports.object({
4562
- type: external_exports.literal(FRAME_TYPE_PERSONA_LISTENER_CHAT_DELETED),
4563
- personaId: external_exports.string().min(1),
4564
- token: external_exports.string().min(1),
4565
- sessionId: external_exports.string().min(1),
4566
- /** 原始 chatId(用于 owner UI 比对 personaView.chatId 清理 stale selection) */
4567
- chatId: external_exports.string().min(1)
4568
- });
4569
- PersonaListenerStatusFrameSchema = external_exports.object({
4570
- type: external_exports.literal(FRAME_TYPE_PERSONA_LISTENER_STATUS),
4571
- personaId: external_exports.string().min(1),
4572
- token: external_exports.string().min(1),
4573
- status: external_exports.enum(["online", "offline"]),
4574
- activeWsCount: external_exports.number().int().nonnegative()
4575
- });
4576
- PersonaListListenerChatsForTokenArgsSchema = external_exports.object({
4577
- personaId: external_exports.string().min(1),
4578
- token: external_exports.string().min(1)
4579
- });
4580
- PersonaListListenerChatsForTokenResponseSchema = external_exports.object({
4581
- chats: external_exports.array(ChatSummarySchema)
4582
- });
4583
4469
  }
4584
4470
  });
4585
4471
 
4586
4472
  // ../protocol/src/schemas.ts
4587
- 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, SessionReorderPinsArgs, GitRootArgs, GitRootResponseSchema, GitBranchArgs, GitBranchResponseSchema, GitBranchesArgs, GitBranchesResponseSchema, GitWorktreePrefixArgs, GitWorktreePrefixResponseSchema, GitWorktreeCreateArgs, GitWorktreeCreateResponseSchema, GitWorktreeRemoveArgs, GitWorktreeRemoveResponseSchema, HistoryRecentDirsArgs, RecentDirEntrySchema, HistoryRecentDirsResponseSchema, SessionQuestionFrameSchema, SessionQuestionClearedFrameSchema, AnswerQuestionArgs, AnswerQuestionResponseSchema, CancelQuestionArgs, CancelQuestionResponseSchema, AuthRequestFrameSchema, AuthOkFrameSchema, TunnelExitedEventSchema, InfoRunningSessionSchema, InfoResponseSchema;
4473
+ 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, SessionReorderPinsArgs, GitRootArgs, GitRootResponseSchema, GitBranchArgs, GitBranchResponseSchema, GitBranchesArgs, GitBranchesResponseSchema, HistoryRecentDirsArgs, RecentDirEntrySchema, HistoryRecentDirsResponseSchema, SessionQuestionFrameSchema, SessionQuestionClearedFrameSchema, AnswerQuestionArgs, AnswerQuestionResponseSchema, CancelQuestionArgs, CancelQuestionResponseSchema, AuthRequestFrameSchema, AuthOkFrameSchema, TunnelExitedEventSchema, InfoRunningSessionSchema, InfoResponseSchema;
4588
4474
  var init_schemas = __esm({
4589
4475
  "../protocol/src/schemas.ts"() {
4590
4476
  "use strict";
@@ -4674,9 +4560,6 @@ var init_schemas = __esm({
4674
4560
  pinSortOrder: external_exports.number().int().nullable().optional(),
4675
4561
  // 用户在 NewSessionDialog 或 Edit modal 选择的 icon 标识;UI 端做 enum 兜底
4676
4562
  iconKey: external_exports.string().optional(),
4677
- // NewSessionDialog 勾选 worktree 创建的 session 才会持久化这两个字段
4678
- worktreeRoot: external_exports.string().min(1).optional(),
4679
- worktreeBranch: external_exports.string().min(1).optional(),
4680
4563
  // 由 session:fork 派生的 session 在 create 时记录源 session 的 sessionId(稳定 key,
4681
4564
  // 不受 parent clear / re-spawn 影响);sidebar 据此挂 fork 图标。非 fork session 该字段缺省。
4682
4565
  // 旧字段 forkedFromToolSessionId 已弃用,daemon 启动时一次性迁移老数据
@@ -4897,9 +4780,6 @@ var init_schemas = __esm({
4897
4780
  effort: external_exports.string().optional(),
4898
4781
  // 用户在 NewSessionDialog 选择的 icon 标识;UI 端做 enum 兜底
4899
4782
  iconKey: external_exports.string().optional(),
4900
- // NewSessionDialog 勾选 worktree 时由 UI 传入,daemon 原样持久化,不做额外副作用
4901
- worktreeRoot: external_exports.string().min(1).optional(),
4902
- worktreeBranch: external_exports.string().min(1).optional(),
4903
4783
  // session:fork 派生 session 时由 UI 传入源 sessionId,daemon 写到 SessionFile.forkedFromSessionId
4904
4784
  forkedFromSessionId: external_exports.string().min(1).optional(),
4905
4785
  // owner-mode persona session:传此字段时 daemon 自行派生 cwd 和启动参数,
@@ -5064,25 +4944,6 @@ var init_schemas = __esm({
5064
4944
  branches: external_exports.array(external_exports.string()),
5065
4945
  head: external_exports.string().nullable()
5066
4946
  });
5067
- GitWorktreePrefixArgs = external_exports.object({}).optional();
5068
- GitWorktreePrefixResponseSchema = external_exports.object({
5069
- prefix: external_exports.string().min(1)
5070
- });
5071
- GitWorktreeCreateArgs = external_exports.object({
5072
- cwd: external_exports.string().min(1),
5073
- baseBranch: external_exports.string().min(1),
5074
- label: external_exports.string().min(1)
5075
- });
5076
- GitWorktreeCreateResponseSchema = external_exports.object({
5077
- worktreeRoot: external_exports.string().min(1),
5078
- branch: external_exports.string().min(1),
5079
- baseBranch: external_exports.string().min(1)
5080
- });
5081
- GitWorktreeRemoveArgs = external_exports.object({
5082
- worktreeRoot: external_exports.string().min(1),
5083
- worktreeBranch: external_exports.string().min(1)
5084
- });
5085
- GitWorktreeRemoveResponseSchema = external_exports.object({ ok: external_exports.literal(true) });
5086
4947
  HistoryRecentDirsArgs = external_exports.object({}).optional();
5087
4948
  RecentDirEntrySchema = external_exports.object({
5088
4949
  cwd: external_exports.string().min(1),
@@ -5469,8 +5330,8 @@ var require_req = __commonJS({
5469
5330
  if (req.originalUrl) {
5470
5331
  _req.url = req.originalUrl;
5471
5332
  } else {
5472
- const path31 = req.path;
5473
- _req.url = typeof path31 === "string" ? path31 : req.url ? req.url.path || req.url : void 0;
5333
+ const path32 = req.path;
5334
+ _req.url = typeof path32 === "string" ? path32 : req.url ? req.url.path || req.url : void 0;
5474
5335
  }
5475
5336
  if (req.query) {
5476
5337
  _req.query = req.query;
@@ -5635,14 +5496,14 @@ var require_redact = __commonJS({
5635
5496
  }
5636
5497
  return obj;
5637
5498
  }
5638
- function parsePath(path31) {
5499
+ function parsePath(path32) {
5639
5500
  const parts = [];
5640
5501
  let current = "";
5641
5502
  let inBrackets = false;
5642
5503
  let inQuotes = false;
5643
5504
  let quoteChar = "";
5644
- for (let i = 0; i < path31.length; i++) {
5645
- const char = path31[i];
5505
+ for (let i = 0; i < path32.length; i++) {
5506
+ const char = path32[i];
5646
5507
  if (!inBrackets && char === ".") {
5647
5508
  if (current) {
5648
5509
  parts.push(current);
@@ -5773,10 +5634,10 @@ var require_redact = __commonJS({
5773
5634
  return current;
5774
5635
  }
5775
5636
  function redactPaths(obj, paths, censor, remove = false) {
5776
- for (const path31 of paths) {
5777
- const parts = parsePath(path31);
5637
+ for (const path32 of paths) {
5638
+ const parts = parsePath(path32);
5778
5639
  if (parts.includes("*")) {
5779
- redactWildcardPath(obj, parts, censor, path31, remove);
5640
+ redactWildcardPath(obj, parts, censor, path32, remove);
5780
5641
  } else {
5781
5642
  if (remove) {
5782
5643
  removeKey(obj, parts);
@@ -5861,8 +5722,8 @@ var require_redact = __commonJS({
5861
5722
  }
5862
5723
  } else {
5863
5724
  if (afterWildcard.includes("*")) {
5864
- const wrappedCensor = typeof censor === "function" ? (value, path31) => {
5865
- const fullPath = [...pathArray.slice(0, pathLength), ...path31];
5725
+ const wrappedCensor = typeof censor === "function" ? (value, path32) => {
5726
+ const fullPath = [...pathArray.slice(0, pathLength), ...path32];
5866
5727
  return censor(value, fullPath);
5867
5728
  } : censor;
5868
5729
  redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
@@ -5897,8 +5758,8 @@ var require_redact = __commonJS({
5897
5758
  return null;
5898
5759
  }
5899
5760
  const pathStructure = /* @__PURE__ */ new Map();
5900
- for (const path31 of pathsToClone) {
5901
- const parts = parsePath(path31);
5761
+ for (const path32 of pathsToClone) {
5762
+ const parts = parsePath(path32);
5902
5763
  let current = pathStructure;
5903
5764
  for (let i = 0; i < parts.length; i++) {
5904
5765
  const part = parts[i];
@@ -5950,24 +5811,24 @@ var require_redact = __commonJS({
5950
5811
  }
5951
5812
  return cloneSelectively(obj, pathStructure);
5952
5813
  }
5953
- function validatePath(path31) {
5954
- if (typeof path31 !== "string") {
5814
+ function validatePath(path32) {
5815
+ if (typeof path32 !== "string") {
5955
5816
  throw new Error("Paths must be (non-empty) strings");
5956
5817
  }
5957
- if (path31 === "") {
5818
+ if (path32 === "") {
5958
5819
  throw new Error("Invalid redaction path ()");
5959
5820
  }
5960
- if (path31.includes("..")) {
5961
- throw new Error(`Invalid redaction path (${path31})`);
5821
+ if (path32.includes("..")) {
5822
+ throw new Error(`Invalid redaction path (${path32})`);
5962
5823
  }
5963
- if (path31.includes(",")) {
5964
- throw new Error(`Invalid redaction path (${path31})`);
5824
+ if (path32.includes(",")) {
5825
+ throw new Error(`Invalid redaction path (${path32})`);
5965
5826
  }
5966
5827
  let bracketCount = 0;
5967
5828
  let inQuotes = false;
5968
5829
  let quoteChar = "";
5969
- for (let i = 0; i < path31.length; i++) {
5970
- const char = path31[i];
5830
+ for (let i = 0; i < path32.length; i++) {
5831
+ const char = path32[i];
5971
5832
  if ((char === '"' || char === "'") && bracketCount > 0) {
5972
5833
  if (!inQuotes) {
5973
5834
  inQuotes = true;
@@ -5981,20 +5842,20 @@ var require_redact = __commonJS({
5981
5842
  } else if (char === "]" && !inQuotes) {
5982
5843
  bracketCount--;
5983
5844
  if (bracketCount < 0) {
5984
- throw new Error(`Invalid redaction path (${path31})`);
5845
+ throw new Error(`Invalid redaction path (${path32})`);
5985
5846
  }
5986
5847
  }
5987
5848
  }
5988
5849
  if (bracketCount !== 0) {
5989
- throw new Error(`Invalid redaction path (${path31})`);
5850
+ throw new Error(`Invalid redaction path (${path32})`);
5990
5851
  }
5991
5852
  }
5992
5853
  function validatePaths(paths) {
5993
5854
  if (!Array.isArray(paths)) {
5994
5855
  throw new TypeError("paths must be an array");
5995
5856
  }
5996
- for (const path31 of paths) {
5997
- validatePath(path31);
5857
+ for (const path32 of paths) {
5858
+ validatePath(path32);
5998
5859
  }
5999
5860
  }
6000
5861
  function slowRedact(options = {}) {
@@ -6162,8 +6023,8 @@ var require_redaction = __commonJS({
6162
6023
  if (shape[k2] === null) {
6163
6024
  o[k2] = (value) => topCensor(value, [k2]);
6164
6025
  } else {
6165
- const wrappedCensor = typeof censor === "function" ? (value, path31) => {
6166
- return censor(value, [k2, ...path31]);
6026
+ const wrappedCensor = typeof censor === "function" ? (value, path32) => {
6027
+ return censor(value, [k2, ...path32]);
6167
6028
  } : censor;
6168
6029
  o[k2] = Redact({
6169
6030
  paths: shape[k2],
@@ -6384,7 +6245,7 @@ var require_sonic_boom = __commonJS({
6384
6245
  var fs28 = require("fs");
6385
6246
  var EventEmitter2 = require("events");
6386
6247
  var inherits = require("util").inherits;
6387
- var path31 = require("path");
6248
+ var path32 = require("path");
6388
6249
  var sleep = require_atomic_sleep();
6389
6250
  var assert = require("assert");
6390
6251
  var BUSY_WRITE_TIMEOUT = 100;
@@ -6438,7 +6299,7 @@ var require_sonic_boom = __commonJS({
6438
6299
  const mode = sonic.mode;
6439
6300
  if (sonic.sync) {
6440
6301
  try {
6441
- if (sonic.mkdir) fs28.mkdirSync(path31.dirname(file), { recursive: true });
6302
+ if (sonic.mkdir) fs28.mkdirSync(path32.dirname(file), { recursive: true });
6442
6303
  const fd = fs28.openSync(file, flags, mode);
6443
6304
  fileOpened(null, fd);
6444
6305
  } catch (err) {
@@ -6446,7 +6307,7 @@ var require_sonic_boom = __commonJS({
6446
6307
  throw err;
6447
6308
  }
6448
6309
  } else if (sonic.mkdir) {
6449
- fs28.mkdir(path31.dirname(file), { recursive: true }, (err) => {
6310
+ fs28.mkdir(path32.dirname(file), { recursive: true }, (err) => {
6450
6311
  if (err) return fileOpened(err);
6451
6312
  fs28.open(file, flags, mode, fileOpened);
6452
6313
  });
@@ -9306,7 +9167,7 @@ var require_multistream = __commonJS({
9306
9167
  var require_pino = __commonJS({
9307
9168
  "../node_modules/.pnpm/pino@9.14.0/node_modules/pino/pino.js"(exports2, module2) {
9308
9169
  "use strict";
9309
- var os16 = require("os");
9170
+ var os15 = require("os");
9310
9171
  var stdSerializers = require_pino_std_serializers();
9311
9172
  var caller = require_caller();
9312
9173
  var redaction = require_redaction();
@@ -9353,7 +9214,7 @@ var require_pino = __commonJS({
9353
9214
  } = symbols;
9354
9215
  var { epochTime, nullTime } = time;
9355
9216
  var { pid } = process;
9356
- var hostname = os16.hostname();
9217
+ var hostname = os15.hostname();
9357
9218
  var defaultErrorSerializer = stdSerializers.err;
9358
9219
  var defaultOptions = {
9359
9220
  level: "info",
@@ -10077,11 +9938,11 @@ var init_lib = __esm({
10077
9938
  }
10078
9939
  }
10079
9940
  },
10080
- addToPath: function addToPath(path31, added, removed, oldPosInc, options) {
10081
- var last = path31.lastComponent;
9941
+ addToPath: function addToPath(path32, added, removed, oldPosInc, options) {
9942
+ var last = path32.lastComponent;
10082
9943
  if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
10083
9944
  return {
10084
- oldPos: path31.oldPos + oldPosInc,
9945
+ oldPos: path32.oldPos + oldPosInc,
10085
9946
  lastComponent: {
10086
9947
  count: last.count + 1,
10087
9948
  added,
@@ -10091,7 +9952,7 @@ var init_lib = __esm({
10091
9952
  };
10092
9953
  } else {
10093
9954
  return {
10094
- oldPos: path31.oldPos + oldPosInc,
9955
+ oldPos: path32.oldPos + oldPosInc,
10095
9956
  lastComponent: {
10096
9957
  count: 1,
10097
9958
  added,
@@ -10522,10 +10383,10 @@ function attachmentToHistoryMessage(o, ts) {
10522
10383
  const memories = raw.map((m2) => {
10523
10384
  if (!m2 || typeof m2 !== "object") return null;
10524
10385
  const rec = m2;
10525
- const path31 = typeof rec.path === "string" ? rec.path : null;
10386
+ const path32 = typeof rec.path === "string" ? rec.path : null;
10526
10387
  const content = typeof rec.content === "string" ? rec.content : null;
10527
- if (!path31 || content == null) return null;
10528
- const entry = { path: path31, content };
10388
+ if (!path32 || content == null) return null;
10389
+ const entry = { path: path32, content };
10529
10390
  if (typeof rec.mtimeMs === "number") entry.mtimeMs = rec.mtimeMs;
10530
10391
  return entry;
10531
10392
  }).filter((m2) => m2 !== null);
@@ -11329,10 +11190,10 @@ function parseAttachment(obj) {
11329
11190
  const memories = raw.map((m2) => {
11330
11191
  if (!m2 || typeof m2 !== "object") return null;
11331
11192
  const rec = m2;
11332
- const path31 = typeof rec.path === "string" ? rec.path : null;
11193
+ const path32 = typeof rec.path === "string" ? rec.path : null;
11333
11194
  const content = typeof rec.content === "string" ? rec.content : null;
11334
- if (!path31 || content == null) return null;
11335
- const out = { path: path31, content };
11195
+ if (!path32 || content == null) return null;
11196
+ const out = { path: path32, content };
11336
11197
  if (typeof rec.mtimeMs === "number") out.mtimeMs = rec.mtimeMs;
11337
11198
  return out;
11338
11199
  }).filter((m2) => m2 !== null);
@@ -20246,7 +20107,7 @@ var require_websocket_server = __commonJS({
20246
20107
  // src/run-case/recorder.ts
20247
20108
  function startRunCaseRecorder(opts) {
20248
20109
  const now = opts.now ?? Date.now;
20249
- const dir = import_node_path27.default.dirname(opts.recordPath);
20110
+ const dir = import_node_path28.default.dirname(opts.recordPath);
20250
20111
  let stream = null;
20251
20112
  let closing = false;
20252
20113
  let closedSettled = false;
@@ -20286,12 +20147,12 @@ function startRunCaseRecorder(opts) {
20286
20147
  };
20287
20148
  return { tap, close, closed };
20288
20149
  }
20289
- var import_node_fs25, import_node_path27;
20150
+ var import_node_fs25, import_node_path28;
20290
20151
  var init_recorder = __esm({
20291
20152
  "src/run-case/recorder.ts"() {
20292
20153
  "use strict";
20293
20154
  import_node_fs25 = __toESM(require("fs"), 1);
20294
- import_node_path27 = __toESM(require("path"), 1);
20155
+ import_node_path28 = __toESM(require("path"), 1);
20295
20156
  }
20296
20157
  });
20297
20158
 
@@ -20334,7 +20195,7 @@ var init_wire = __esm({
20334
20195
  // src/run-case/controller.ts
20335
20196
  async function runController(opts) {
20336
20197
  const now = opts.now ?? Date.now;
20337
- const cwd = opts.cwd ?? (0, import_node_fs26.mkdtempSync)(import_node_path28.default.join(import_node_os15.default.tmpdir(), "clawd-runcase-"));
20198
+ const cwd = opts.cwd ?? (0, import_node_fs26.mkdtempSync)(import_node_path29.default.join(import_node_os14.default.tmpdir(), "clawd-runcase-"));
20338
20199
  const ownsCwd = opts.cwd === void 0;
20339
20200
  const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
20340
20201
  const spawnCtx = { cwd };
@@ -20501,13 +20362,13 @@ async function runController(opts) {
20501
20362
  }
20502
20363
  return exitCode ?? 0;
20503
20364
  }
20504
- var import_node_fs26, import_node_os15, import_node_path28;
20365
+ var import_node_fs26, import_node_os14, import_node_path29;
20505
20366
  var init_controller = __esm({
20506
20367
  "src/run-case/controller.ts"() {
20507
20368
  "use strict";
20508
20369
  import_node_fs26 = require("fs");
20509
- import_node_os15 = __toESM(require("os"), 1);
20510
- import_node_path28 = __toESM(require("path"), 1);
20370
+ import_node_os14 = __toESM(require("os"), 1);
20371
+ import_node_path29 = __toESM(require("path"), 1);
20511
20372
  init_claude();
20512
20373
  init_stdout_splitter();
20513
20374
  init_permission_stdio();
@@ -20739,7 +20600,7 @@ Env (advanced):
20739
20600
  `;
20740
20601
 
20741
20602
  // src/index.ts
20742
- var import_node_path26 = __toESM(require("path"), 1);
20603
+ var import_node_path27 = __toESM(require("path"), 1);
20743
20604
  var import_node_fs24 = __toESM(require("fs"), 1);
20744
20605
 
20745
20606
  // src/logger.ts
@@ -22436,8 +22297,6 @@ var SessionManager = class {
22436
22297
  permissionMode: args.permissionMode,
22437
22298
  effort: args.effort,
22438
22299
  iconKey: args.iconKey,
22439
- worktreeRoot: args.worktreeRoot,
22440
- worktreeBranch: args.worktreeBranch,
22441
22300
  forkedFromSessionId: args.forkedFromSessionId,
22442
22301
  ownerPersonaId: args.ownerPersonaId,
22443
22302
  createdAt: iso,
@@ -23460,15 +23319,6 @@ var PersonaRegistry = class {
23460
23319
  list() {
23461
23320
  return Array.from(this.cache.values());
23462
23321
  }
23463
- verifyToken(personaId, token) {
23464
- const persona = this.cache.get(personaId);
23465
- if (!persona) return { ok: false, code: "PERSONA_DELETED" };
23466
- if (!persona.public) return { ok: false, code: "PERSONA_NOT_PUBLIC" };
23467
- const entry = persona.tokenMap[token];
23468
- if (!entry) return { ok: false, code: "TOKEN_INVALID" };
23469
- if (entry.revoked) return { ok: false, code: "TOKEN_REVOKED" };
23470
- return { ok: true, label: entry.label };
23471
- }
23472
23322
  /**
23473
23323
  * file-sharing HTTP auth-context 用的逆向查表:只有 Bearer token,没有 personaId。
23474
23324
  * 线性扫所有 persona.tokenMap 找到第一条 ok 命中的;revoked / non-public persona 跳过。
@@ -23478,7 +23328,7 @@ var PersonaRegistry = class {
23478
23328
  if (!token) return null;
23479
23329
  for (const persona of this.cache.values()) {
23480
23330
  if (!persona.public) continue;
23481
- const entry = persona.tokenMap[token];
23331
+ const entry = persona.tokenMap?.[token];
23482
23332
  if (!entry || entry.revoked) continue;
23483
23333
  return { personaId: persona.personaId, label: entry.label };
23484
23334
  }
@@ -23777,6 +23627,8 @@ var PersonaManager = class {
23777
23627
  model: args.model,
23778
23628
  public: args.public ?? false,
23779
23629
  iconKey: args.iconKey,
23630
+ // 初始化空 token 集合;后续由 issueToken / revokeToken 维护,
23631
+ // file-sharing HTTP Bearer 鉴权用 PersonaRegistry.findByToken 反查
23780
23632
  tokenMap: {},
23781
23633
  createdAt: now,
23782
23634
  updatedAt: now
@@ -23820,15 +23672,16 @@ var PersonaManager = class {
23820
23672
  }
23821
23673
  /**
23822
23674
  * 删除 persona。
23823
- * PersonaStore.remove(personaId) 已级联删除整个 <personaRoot>/<personaId>/ 目录,
23824
- * 其中包含 .clawd/sub-sessions/——无需额外清理。
23825
- * 旧的 <dataDir>/sessions/<personaId>/ 路径已废弃,不再维护。
23675
+ * PersonaStore.remove(personaId) 已级联删除整个 <personaRoot>/<personaId>/ 目录。
23826
23676
  */
23827
23677
  delete(personaId) {
23828
23678
  this.deps.store.remove(personaId);
23829
23679
  this.deps.registry.remove(personaId);
23830
23680
  }
23831
- /** 生成 32-char base64url token(24 bytes 随机),label 必填 */
23681
+ /**
23682
+ * 生成 32-char base64url token 并写入 tokenMap,给 file-sharing HTTP Bearer
23683
+ * 鉴权用(PersonaRegistry.findByToken 反查 personaId/label)。
23684
+ */
23832
23685
  issueToken(personaId, label) {
23833
23686
  const existing = this.deps.registry.get(personaId);
23834
23687
  if (!existing) throw new Error(`persona not found: ${personaId}`);
@@ -23840,22 +23693,23 @@ var PersonaManager = class {
23840
23693
  };
23841
23694
  const updated = {
23842
23695
  ...existing,
23843
- tokenMap: { ...existing.tokenMap, [token]: entry },
23696
+ tokenMap: { ...existing.tokenMap ?? {}, [token]: entry },
23844
23697
  updatedAt: Date.now()
23845
23698
  };
23846
23699
  this.deps.store.writeMeta(updated);
23847
23700
  this.deps.registry.set(updated);
23848
23701
  return { token, persona: updated };
23849
23702
  }
23703
+ /** 标记 token 为 revoked(保留条目用于审计);不存在的 token 抛错。 */
23850
23704
  revokeToken(personaId, token) {
23851
23705
  const existing = this.deps.registry.get(personaId);
23852
23706
  if (!existing) throw new Error(`persona not found: ${personaId}`);
23853
- const tokenEntry = existing.tokenMap[token];
23707
+ const tokenEntry = existing.tokenMap?.[token];
23854
23708
  if (!tokenEntry) throw new Error(`token not found in persona ${personaId}`);
23855
23709
  const updated = {
23856
23710
  ...existing,
23857
23711
  tokenMap: {
23858
- ...existing.tokenMap,
23712
+ ...existing.tokenMap ?? {},
23859
23713
  [token]: { ...tokenEntry, revoked: true }
23860
23714
  },
23861
23715
  updatedAt: Date.now()
@@ -23864,70 +23718,6 @@ var PersonaManager = class {
23864
23718
  this.deps.registry.set(updated);
23865
23719
  return updated;
23866
23720
  }
23867
- /**
23868
- * 拿到(或按需创建)该 (token, chatId) 对应的 sub-session。subSessionId 由 personaId + token +
23869
- * chatId 三元组 hash 派生,保证 idempotent:同一 tuple 永远复用同一个 sub-session。
23870
- *
23871
- * chatId 必填 —— listener 协议从 v2 起拆 phase:客户端 auth 后通过 chat:open(chatId, autoCreate?)
23872
- * 显式进入某个 chat;不再有 'default' 默认值。chatId 通常是客户端生成的 uuid,落 listener 端
23873
- * 持久化由客户端负责(daemon 不替客户端记忆"最近哪个 chat")。
23874
- *
23875
- * 创建路径:开启 idleKillEnabled,cwd 取 persona 目录,不再硬编码 permission mode / tool allowlist
23876
- * (sandbox 约束移到 persona dir 下的 .claude/settings.json,由 OS-level Seatbelt 执行)
23877
- */
23878
- getOrCreateSubSession(personaId, token, chatId) {
23879
- const persona = this.deps.registry.get(personaId);
23880
- if (!persona) throw new Error(`persona not found: ${personaId}`);
23881
- if (!chatId) throw new Error("chatId is required");
23882
- const subSessionId = this.deriveSubSessionId(personaId, token, chatId);
23883
- const scope = { kind: "persona", personaId, mode: "listener" };
23884
- const existing = this.deps.sessionManager.readForScope(subSessionId, scope);
23885
- if (existing) {
23886
- return { sessionFile: existing, isNew: false };
23887
- }
23888
- const tokenEntry = persona.tokenMap[token];
23889
- const subLabel = tokenEntry?.label ?? "unknown";
23890
- const sessionFile = this.deps.sessionManager.createForScope({
23891
- sessionId: subSessionId,
23892
- scope,
23893
- cwd: this.deps.store.personaDirPath(personaId),
23894
- tool: "claude",
23895
- label: subLabel,
23896
- model: persona.model,
23897
- chatId
23898
- });
23899
- return { sessionFile, isNew: true };
23900
- }
23901
- /**
23902
- * 检查 (token, chatId) 对应的 sub-session 是否存在;不存在返回 null。
23903
- * chat:open(autoCreate=false) 路径用:listener 想"只切已有 chat"时不能撞 auto-create。
23904
- */
23905
- getSubSession(personaId, token, chatId) {
23906
- if (!chatId) return null;
23907
- const subSessionId = this.deriveSubSessionId(personaId, token, chatId);
23908
- const scope = { kind: "persona", personaId, mode: "listener" };
23909
- return this.deps.sessionManager.readForScope(subSessionId, scope);
23910
- }
23911
- /**
23912
- * 校验 sessionId 是否属于 (personaId, token) tuple(前 12 字符 tokenHash 一致)。
23913
- * chat:rename / chat:delete 用:listener 只能操作自己 tuple 内的 chat。
23914
- */
23915
- tokenPrefixFor(personaId, token) {
23916
- const tokenHash = import_node_crypto3.default.createHash("sha256").update(token).digest("hex").slice(0, 12);
23917
- return `${personaId}-${tokenHash}`;
23918
- }
23919
- /**
23920
- * 老板插话:把"老板的话"作为 meta-text + metaSource='owner' 推到 sub-session 的事件流。
23921
- * 委托 SessionManager.injectOwnerMessage 路由到 reducer 'inject-owner-text' input
23922
- */
23923
- appendOwnerMessage(personaId, subSessionId, text) {
23924
- this.deps.sessionManager.injectOwnerMessage({
23925
- sessionId: subSessionId,
23926
- scope: { kind: "persona", personaId, mode: "listener" },
23927
- text
23928
- });
23929
- }
23930
- // ---------------- 内部 ----------------
23931
23721
  /**
23932
23722
  * label 转 4-16 char slug。优先用 `persona-<slug>`;若 slug 已被占用,追加 4 char
23933
23723
  * base64url 随机后缀直到不撞为止(最多 5 次,理论冲突 ≈ 2^-24,留个 panic 上限)。
@@ -23943,15 +23733,6 @@ var PersonaManager = class {
23943
23733
  }
23944
23734
  throw new Error(`failed to generate unique personaId for label=${label}`);
23945
23735
  }
23946
- /**
23947
- * subSessionId 派生:永远三元组 `${personaId}-${tokenHash12}-${chatHash8}`,无 default 特例。
23948
- * 旧 v1 实现保留 default 二元组兼容已废除。
23949
- */
23950
- deriveSubSessionId(personaId, token, chatId) {
23951
- const tokenHash = import_node_crypto3.default.createHash("sha256").update(token).digest("hex").slice(0, 12);
23952
- const chatHash = import_node_crypto3.default.createHash("sha256").update(chatId).digest("hex").slice(0, 8);
23953
- return `${personaId}-${tokenHash}-${chatHash}`;
23954
- }
23955
23736
  };
23956
23737
 
23957
23738
  // src/persona/seed.ts
@@ -25426,7 +25207,6 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
25426
25207
 
25427
25208
  // src/transport/local-ws-server.ts
25428
25209
  var import_node_http = __toESM(require("http"), 1);
25429
- var PERSONA_PATH_RE = /^\/personas\/([a-zA-Z0-9._-]+)$/;
25430
25210
  var LocalWsServer = class {
25431
25211
  constructor(opts) {
25432
25212
  this.opts = opts;
@@ -25549,8 +25329,7 @@ var LocalWsServer = class {
25549
25329
  if (!c) return;
25550
25330
  this.safeSend(c.ws, frame);
25551
25331
  }
25552
- // URL path 路由:'/' → owner mode(first-message authToken gate 走 onConnection),
25553
- // '/personas/<id>' → personaBoundHandler,其它 → close 4404
25332
+ // URL path 路由:'/' → 主连接路径;其他 close 4404
25554
25333
  routeConnection(socket, req) {
25555
25334
  const remoteAddress = req?.socket?.remoteAddress;
25556
25335
  const urlPath = (() => {
@@ -25564,15 +25343,6 @@ var LocalWsServer = class {
25564
25343
  this.onConnection(socket, remoteAddress);
25565
25344
  return;
25566
25345
  }
25567
- const personaMatch = urlPath.match(PERSONA_PATH_RE);
25568
- if (personaMatch && this.opts.personaBoundHandler) {
25569
- this.logger?.info("persona ws connected", {
25570
- personaId: personaMatch[1],
25571
- remoteAddress
25572
- });
25573
- this.opts.personaBoundHandler.handle(socket, personaMatch[1]);
25574
- return;
25575
- }
25576
25346
  try {
25577
25347
  socket.close(4404, "unknown path");
25578
25348
  } catch {
@@ -25715,434 +25485,6 @@ function removeSubscription(client, sessionId) {
25715
25485
  else client.subscribedSessions.set(sessionId, next);
25716
25486
  }
25717
25487
 
25718
- // src/transport/persona-bound-handler.ts
25719
- init_protocol();
25720
- var PersonaBoundHandler = class {
25721
- constructor(deps) {
25722
- this.deps = deps;
25723
- }
25724
- deps;
25725
- // tuple fan-out 表:tokenPrefix → 同 tuple 所有 listener ws 的 send fn 集合
25726
- tupleSubscribers = /* @__PURE__ */ new Map();
25727
- // tuple → 当前活跃 ws 数;从 0→1 emit online;从 1→0 emit offline
25728
- listenerWsCount = /* @__PURE__ */ new Map();
25729
- handle(ws, personaId) {
25730
- let phase = "pre-auth";
25731
- let scope = null;
25732
- let unsubscribeSession = null;
25733
- const send = (frame) => {
25734
- try {
25735
- ws.send(JSON.stringify(frame));
25736
- } catch (err) {
25737
- this.deps.logger.warn(`persona ws send failed: ${err.message}`);
25738
- }
25739
- };
25740
- const sendError = (requestId, code, message) => {
25741
- const errFrame = { type: "error", code, message };
25742
- if (requestId) errFrame.requestId = requestId;
25743
- send(errFrame);
25744
- };
25745
- const cleanupTupleRegistration = () => {
25746
- if (!scope) return;
25747
- const subs = this.tupleSubscribers.get(scope.tokenPrefix);
25748
- if (subs) {
25749
- subs.delete(send);
25750
- if (subs.size === 0) this.tupleSubscribers.delete(scope.tokenPrefix);
25751
- }
25752
- const before = this.listenerWsCount.get(scope.tokenPrefix) ?? 0;
25753
- const after = before - 1;
25754
- if (after <= 0) {
25755
- this.listenerWsCount.delete(scope.tokenPrefix);
25756
- this.deps.ownerBroadcast(
25757
- PersonaListenerStatusFrameSchema.parse({
25758
- type: FRAME_TYPE_PERSONA_LISTENER_STATUS,
25759
- personaId: scope.personaId,
25760
- token: scope.token,
25761
- status: "offline",
25762
- activeWsCount: 0
25763
- })
25764
- );
25765
- } else {
25766
- this.listenerWsCount.set(scope.tokenPrefix, after);
25767
- this.deps.ownerBroadcast(
25768
- PersonaListenerStatusFrameSchema.parse({
25769
- type: FRAME_TYPE_PERSONA_LISTENER_STATUS,
25770
- personaId: scope.personaId,
25771
- token: scope.token,
25772
- status: "online",
25773
- activeWsCount: after
25774
- })
25775
- );
25776
- }
25777
- };
25778
- ws.on("message", (raw) => {
25779
- let parsedRaw;
25780
- try {
25781
- parsedRaw = JSON.parse(raw.toString());
25782
- } catch {
25783
- ws.close(4400, "PROTOCOL_VIOLATION");
25784
- return;
25785
- }
25786
- if (typeof parsedRaw !== "object" || parsedRaw === null) {
25787
- ws.close(4400, "PROTOCOL_VIOLATION");
25788
- return;
25789
- }
25790
- const frame = parsedRaw;
25791
- if (phase === "pre-auth") {
25792
- const authParse = AuthRequestFrameSchema.safeParse(frame);
25793
- if (!authParse.success) {
25794
- ws.close(4400, "PROTOCOL_VIOLATION");
25795
- return;
25796
- }
25797
- const { token } = authParse.data;
25798
- const verify = this.deps.registry.verifyToken(personaId, token);
25799
- if (!verify.ok) {
25800
- const code = verify.code === "PERSONA_DELETED" ? 4404 : verify.code === "PERSONA_NOT_PUBLIC" ? 4403 : 4401;
25801
- ws.close(code, verify.code);
25802
- return;
25803
- }
25804
- const persona = this.deps.registry.get(personaId);
25805
- if (!persona) {
25806
- ws.close(4404, "PERSONA_DELETED");
25807
- return;
25808
- }
25809
- const tokenPrefix = this.deps.personaManager.tokenPrefixFor(personaId, token);
25810
- scope = { personaId, token, tokenPrefix };
25811
- phase = "tuple-watch";
25812
- let subs = this.tupleSubscribers.get(tokenPrefix);
25813
- if (!subs) {
25814
- subs = /* @__PURE__ */ new Set();
25815
- this.tupleSubscribers.set(tokenPrefix, subs);
25816
- }
25817
- subs.add(send);
25818
- const nextCount = (this.listenerWsCount.get(tokenPrefix) ?? 0) + 1;
25819
- this.listenerWsCount.set(tokenPrefix, nextCount);
25820
- this.deps.ownerBroadcast(
25821
- PersonaListenerStatusFrameSchema.parse({
25822
- type: FRAME_TYPE_PERSONA_LISTENER_STATUS,
25823
- personaId,
25824
- token,
25825
- status: "online",
25826
- activeWsCount: nextCount
25827
- })
25828
- );
25829
- send({ type: "auth:ok" });
25830
- send(
25831
- ChatListPushSchema.parse({
25832
- type: FRAME_TYPE_CHAT_LIST,
25833
- chats: this.listChatsForTuple(personaId, tokenPrefix)
25834
- })
25835
- );
25836
- return;
25837
- }
25838
- const type = frame.type;
25839
- const requestId = typeof frame.requestId === "string" ? frame.requestId : void 0;
25840
- if (type === "auth") {
25841
- ws.close(4400, "PROTOCOL_VIOLATION");
25842
- return;
25843
- }
25844
- if (type === "ping") {
25845
- send({ type: "pong", at: Date.now() });
25846
- return;
25847
- }
25848
- if (!scope) {
25849
- ws.close(4400, "PROTOCOL_VIOLATION");
25850
- return;
25851
- }
25852
- if (type === "chat:open") {
25853
- const parsed = ChatOpenRequestSchema.safeParse(frame);
25854
- if (!parsed.success) {
25855
- sendError(requestId, "VALIDATION_ERROR", parsed.error.message);
25856
- return;
25857
- }
25858
- const { chatId, autoCreate } = parsed.data;
25859
- let sessionFile;
25860
- let isNew = false;
25861
- try {
25862
- if (autoCreate) {
25863
- const r = this.deps.personaManager.getOrCreateSubSession(scope.personaId, scope.token, chatId);
25864
- sessionFile = r.sessionFile;
25865
- isNew = r.isNew;
25866
- } else {
25867
- sessionFile = this.deps.personaManager.getSubSession(scope.personaId, scope.token, chatId);
25868
- if (!sessionFile) {
25869
- sendError(requestId, "SESSION_NOT_FOUND", `chatId not found: ${chatId}`);
25870
- return;
25871
- }
25872
- }
25873
- } catch (err) {
25874
- sendError(requestId, "INTERNAL", err.message);
25875
- return;
25876
- }
25877
- if (unsubscribeSession && scope.activeSubSessionId !== sessionFile.sessionId) {
25878
- unsubscribeSession();
25879
- unsubscribeSession = null;
25880
- }
25881
- if (!unsubscribeSession) {
25882
- unsubscribeSession = this.deps.sessionManager.subscribe(
25883
- sessionFile.sessionId,
25884
- { kind: "persona", personaId: scope.personaId, mode: "listener" },
25885
- (eventFrame) => send(eventFrame)
25886
- );
25887
- }
25888
- scope.activeSubSessionId = sessionFile.sessionId;
25889
- phase = "chat-active";
25890
- const persona = this.deps.registry.get(scope.personaId);
25891
- const infoFrame = {
25892
- type: "session:info",
25893
- sessionId: sessionFile.sessionId,
25894
- chatId,
25895
- label: persona?.label ?? "",
25896
- cwd: sessionFile.cwd
25897
- };
25898
- if (persona?.model) infoFrame.model = persona.model;
25899
- if (requestId) infoFrame.requestId = requestId;
25900
- send(infoFrame);
25901
- send({
25902
- type: "session:status",
25903
- sessionId: sessionFile.sessionId,
25904
- status: this.deps.sessionManager.getCurrentStatus(sessionFile.sessionId)
25905
- });
25906
- if (isNew) {
25907
- const createdBase = {
25908
- sessionId: sessionFile.sessionId,
25909
- chatId,
25910
- label: sessionFile.label,
25911
- createdAt: sessionFile.createdAt
25912
- };
25913
- this.fanoutTuple(
25914
- scope.tokenPrefix,
25915
- ChatCreatedFrameSchema.parse({ type: FRAME_TYPE_CHAT_CREATED, ...createdBase })
25916
- );
25917
- this.deps.ownerBroadcast(
25918
- PersonaListenerChatCreatedFrameSchema.parse({
25919
- type: FRAME_TYPE_PERSONA_LISTENER_CHAT_CREATED,
25920
- personaId: scope.personaId,
25921
- token: scope.token,
25922
- ...createdBase
25923
- })
25924
- );
25925
- }
25926
- return;
25927
- }
25928
- if (type === "chat:rename") {
25929
- const parsed = ChatRenameRequestSchema.safeParse(frame);
25930
- if (!parsed.success) {
25931
- sendError(requestId, "VALIDATION_ERROR", parsed.error.message);
25932
- return;
25933
- }
25934
- const { sessionId: targetId, label } = parsed.data;
25935
- if (!this.isInTuple(targetId, scope.tokenPrefix)) {
25936
- sendError(requestId, "FORBIDDEN", "sessionId out of (persona, token) scope");
25937
- return;
25938
- }
25939
- try {
25940
- this.deps.sessionManager.renameForScope({
25941
- sessionId: targetId,
25942
- scope: { kind: "persona", personaId: scope.personaId, mode: "listener" },
25943
- label
25944
- });
25945
- } catch (err) {
25946
- const e = err;
25947
- sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
25948
- return;
25949
- }
25950
- if (requestId) send({ type: FRAME_TYPE_CHAT_RENAME, requestId, ok: true });
25951
- this.fanoutTuple(
25952
- scope.tokenPrefix,
25953
- ChatRenamedFrameSchema.parse({ type: FRAME_TYPE_CHAT_RENAMED, sessionId: targetId, label })
25954
- );
25955
- this.deps.ownerBroadcast(
25956
- PersonaListenerChatRenamedFrameSchema.parse({
25957
- type: FRAME_TYPE_PERSONA_LISTENER_CHAT_RENAMED,
25958
- personaId: scope.personaId,
25959
- token: scope.token,
25960
- sessionId: targetId,
25961
- label
25962
- })
25963
- );
25964
- return;
25965
- }
25966
- if (type === "chat:delete") {
25967
- const parsed = ChatDeleteRequestSchema.safeParse(frame);
25968
- if (!parsed.success) {
25969
- sendError(requestId, "VALIDATION_ERROR", parsed.error.message);
25970
- return;
25971
- }
25972
- const { sessionId: targetId } = parsed.data;
25973
- if (!this.isInTuple(targetId, scope.tokenPrefix)) {
25974
- sendError(requestId, "FORBIDDEN", "sessionId out of (persona, token) scope");
25975
- return;
25976
- }
25977
- const before = this.deps.sessionManager.readForScope(targetId, {
25978
- kind: "persona",
25979
- personaId: scope.personaId,
25980
- mode: "listener"
25981
- });
25982
- const chatIdForFrame = before?.chatId ?? "unknown";
25983
- try {
25984
- this.deps.sessionManager.deleteForScope({
25985
- sessionId: targetId,
25986
- scope: { kind: "persona", personaId: scope.personaId, mode: "listener" }
25987
- });
25988
- } catch (err) {
25989
- const e = err;
25990
- sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
25991
- return;
25992
- }
25993
- if (requestId) send({ type: FRAME_TYPE_CHAT_DELETE, requestId, ok: true });
25994
- this.fanoutTuple(
25995
- scope.tokenPrefix,
25996
- ChatDeletedFrameSchema.parse({ type: FRAME_TYPE_CHAT_DELETED, sessionId: targetId })
25997
- );
25998
- this.deps.ownerBroadcast(
25999
- PersonaListenerChatDeletedFrameSchema.parse({
26000
- type: FRAME_TYPE_PERSONA_LISTENER_CHAT_DELETED,
26001
- personaId: scope.personaId,
26002
- token: scope.token,
26003
- sessionId: targetId,
26004
- chatId: chatIdForFrame
26005
- })
26006
- );
26007
- if (scope.activeSubSessionId === targetId) {
26008
- unsubscribeSession?.();
26009
- unsubscribeSession = null;
26010
- scope.activeSubSessionId = void 0;
26011
- phase = "tuple-watch";
26012
- }
26013
- return;
26014
- }
26015
- if (phase !== "chat-active" || !scope.activeSubSessionId) {
26016
- sendError(
26017
- requestId,
26018
- "METHOD_NOT_ALLOWED",
26019
- `${typeof type === "string" ? type : "unknown"} requires chat:open first`
26020
- );
26021
- return;
26022
- }
26023
- const requireScopedSession = () => {
26024
- if (frame.sessionId !== scope.activeSubSessionId) {
26025
- sendError(requestId, "FORBIDDEN", "sessionId out of scope");
26026
- return false;
26027
- }
26028
- return true;
26029
- };
26030
- const listenerScope = () => ({
26031
- kind: "persona",
26032
- personaId: scope.personaId,
26033
- mode: "listener"
26034
- });
26035
- switch (type) {
26036
- case "session:send": {
26037
- if (!requireScopedSession()) return;
26038
- const text = frame.text;
26039
- if (typeof text !== "string") {
26040
- sendError(requestId, "VALIDATION_ERROR", "text must be string");
26041
- return;
26042
- }
26043
- try {
26044
- this.deps.sessionManager.sendForScope({
26045
- sessionId: scope.activeSubSessionId,
26046
- scope: listenerScope(),
26047
- text
26048
- });
26049
- if (requestId) send({ type: "session:send", ok: true, requestId });
26050
- } catch (err) {
26051
- const e = err;
26052
- sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
26053
- }
26054
- return;
26055
- }
26056
- case "session:new": {
26057
- if (!requireScopedSession()) return;
26058
- try {
26059
- this.deps.sessionManager.resetForScope({
26060
- sessionId: scope.activeSubSessionId,
26061
- scope: listenerScope()
26062
- });
26063
- if (requestId)
26064
- send({ type: "session:info", sessionId: scope.activeSubSessionId, requestId });
26065
- } catch (err) {
26066
- const e = err;
26067
- sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
26068
- }
26069
- return;
26070
- }
26071
- case "history:read": {
26072
- if (!requireScopedSession()) return;
26073
- const limit = typeof frame.limit === "number" && frame.limit > 0 ? frame.limit : 50;
26074
- const offset = typeof frame.offset === "number" && frame.offset >= 0 ? frame.offset : 0;
26075
- try {
26076
- const page = this.deps.sessionManager.readHistoryPageForScope(
26077
- scope.activeSubSessionId,
26078
- listenerScope(),
26079
- limit,
26080
- offset
26081
- );
26082
- if (requestId) {
26083
- send({
26084
- type: "history:read",
26085
- requestId,
26086
- events: page.events,
26087
- offset,
26088
- total: page.total
26089
- });
26090
- }
26091
- } catch (err) {
26092
- const e = err;
26093
- sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
26094
- }
26095
- return;
26096
- }
26097
- default:
26098
- sendError(
26099
- requestId,
26100
- "METHOD_NOT_ALLOWED",
26101
- `${typeof type === "string" ? type : "unknown"} not allowed for listener`
26102
- );
26103
- return;
26104
- }
26105
- });
26106
- ws.on("close", () => {
26107
- unsubscribeSession?.();
26108
- unsubscribeSession = null;
26109
- cleanupTupleRegistration();
26110
- });
26111
- }
26112
- // ---- helpers ----
26113
- fanoutTuple(tokenPrefix, frame) {
26114
- const subs = this.tupleSubscribers.get(tokenPrefix);
26115
- if (!subs) return;
26116
- for (const fn of subs) {
26117
- try {
26118
- fn(frame);
26119
- } catch {
26120
- }
26121
- }
26122
- }
26123
- isInTuple(sessionId, tokenPrefix) {
26124
- return sessionId === tokenPrefix || sessionId.startsWith(`${tokenPrefix}-`);
26125
- }
26126
- listChatsForTuple(personaId, tokenPrefix) {
26127
- const all = this.deps.sessionManager.listPersonaSessions(personaId, "listener");
26128
- return all.filter((s) => s.sessionId === tokenPrefix || s.sessionId.startsWith(`${tokenPrefix}-`)).map((s) => ({
26129
- sessionId: s.sessionId,
26130
- chatId: s.chatId ?? "unknown",
26131
- label: s.label ?? "",
26132
- createdAt: s.createdAt
26133
- }));
26134
- }
26135
- // ---------------- owner-side helpers ----------------
26136
- /**
26137
- * owner-side bootstrap RPC:拉 (personaId, token) tuple 下所有 listener sub-session 元数据。
26138
- * 由 handlers/persona.ts persona:listListenerChatsForToken 调用。
26139
- */
26140
- listChatsForOwner(personaId, token) {
26141
- const tokenPrefix = this.deps.personaManager.tokenPrefixFor(personaId, token);
26142
- return this.listChatsForTuple(personaId, tokenPrefix);
26143
- }
26144
- };
26145
-
26146
25488
  // src/transport/auth.ts
26147
25489
  init_protocol();
26148
25490
  var AuthGate = class {
@@ -27505,14 +26847,24 @@ var AUTH_FILE_NAME = "auth.json";
27505
26847
  function authFilePath(dataDir) {
27506
26848
  return import_node_path22.default.join(dataDir, AUTH_FILE_NAME);
27507
26849
  }
27508
- function loadOrCreateAuthToken(opts) {
26850
+ function loadOrCreateAuthFile(opts) {
27509
26851
  const file = authFilePath(opts.dataDir);
26852
+ const generate = opts.generate ?? defaultGenerate;
26853
+ const now = opts.now ?? (() => /* @__PURE__ */ new Date());
27510
26854
  const existing = readAuthFile(file);
27511
- if (existing && existing.token) return existing.token;
27512
- const token = (opts.generate ?? defaultGenerate)();
27513
- const now = (opts.now ?? (() => /* @__PURE__ */ new Date()))();
27514
- writeAuthFile(file, { token, createdAt: now.toISOString() });
27515
- return token;
26855
+ if (existing && existing.token && existing.signSecret) {
26856
+ return {
26857
+ token: existing.token,
26858
+ signSecret: existing.signSecret,
26859
+ createdAt: existing.createdAt ?? (/* @__PURE__ */ new Date(0)).toISOString()
26860
+ };
26861
+ }
26862
+ const token = existing?.token || generate();
26863
+ const signSecret = existing?.signSecret || generate();
26864
+ const createdAt = existing?.createdAt || now().toISOString();
26865
+ const next = { token, signSecret, createdAt };
26866
+ writeAuthFile(file, next);
26867
+ return next;
27516
26868
  }
27517
26869
  function defaultGenerate() {
27518
26870
  return import_node_crypto8.default.randomBytes(32).toString("base64url");
@@ -27521,13 +26873,14 @@ function readAuthFile(file) {
27521
26873
  try {
27522
26874
  const raw = import_node_fs20.default.readFileSync(file, "utf8");
27523
26875
  const parsed = JSON.parse(raw);
27524
- if (typeof parsed?.token === "string" && parsed.token.length > 0) {
27525
- return {
27526
- token: parsed.token,
27527
- createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : (/* @__PURE__ */ new Date(0)).toISOString()
27528
- };
26876
+ if (typeof parsed?.token !== "string" || parsed.token.length === 0) {
26877
+ return null;
27529
26878
  }
27530
- return null;
26879
+ return {
26880
+ token: parsed.token,
26881
+ signSecret: typeof parsed.signSecret === "string" && parsed.signSecret.length > 0 ? parsed.signSecret : void 0,
26882
+ createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : void 0
26883
+ };
27531
26884
  } catch (err) {
27532
26885
  const code = err?.code;
27533
26886
  if (code === "ENOENT") return null;
@@ -27957,21 +27310,9 @@ init_protocol();
27957
27310
  // src/workspace/git.ts
27958
27311
  var import_node_child_process6 = require("child_process");
27959
27312
  var import_node_fs23 = __toESM(require("fs"), 1);
27960
- var import_node_os13 = __toESM(require("os"), 1);
27961
27313
  var import_node_path25 = __toESM(require("path"), 1);
27962
27314
  var import_node_util = require("util");
27963
27315
  var pexec = (0, import_node_util.promisify)(import_node_child_process6.execFile);
27964
- function formatChildProcessError(err) {
27965
- const e = err;
27966
- const stderrLine = (e.stderr ?? "").split("\n").map((s) => s.trim()).find((s) => s.length > 0);
27967
- if (stderrLine) return stderrLine;
27968
- const parts = [];
27969
- if (e.killed) parts.push("killed");
27970
- if (e.signal) parts.push(`signal=${e.signal}`);
27971
- if (e.code !== void 0 && e.code !== null) parts.push(`code=${e.code}`);
27972
- if (parts.length > 0) return parts.join(" ");
27973
- return e.message ?? "unknown error";
27974
- }
27975
27316
  function normalizePath(p2) {
27976
27317
  const resolved = import_node_path25.default.resolve(p2);
27977
27318
  try {
@@ -28044,160 +27385,6 @@ async function listGitBranches(cwd) {
28044
27385
  return { branches: [], head: null };
28045
27386
  }
28046
27387
  }
28047
- var LABEL_WHITELIST = /^[a-zA-Z0-9 _-]+$/;
28048
- var MAX_SANITIZED_LABEL_LEN = 40;
28049
- function sanitizeLabel(raw) {
28050
- if (typeof raw !== "string") return { sanitized: null, reason: "empty" };
28051
- if (raw.length === 0) return { sanitized: null, reason: "empty" };
28052
- if (!LABEL_WHITELIST.test(raw)) return { sanitized: null, reason: "invalid-chars" };
28053
- let s = raw.toLowerCase();
28054
- s = s.replace(/\s+/g, "-");
28055
- s = s.replace(/-+/g, "-");
28056
- s = s.replace(/^[-_]+|[-_]+$/g, "");
28057
- s = s.slice(0, MAX_SANITIZED_LABEL_LEN);
28058
- s = s.replace(/^[-_]+|[-_]+$/g, "");
28059
- if (!s) return { sanitized: null, reason: "empty" };
28060
- return { sanitized: s, reason: null };
28061
- }
28062
- function computePrefix() {
28063
- let username;
28064
- try {
28065
- username = import_node_os13.default.userInfo().username;
28066
- } catch {
28067
- username = void 0;
28068
- }
28069
- const { sanitized } = sanitizeLabel(username);
28070
- if (sanitized) return sanitized;
28071
- const rand = Math.floor(1e3 + Math.random() * 9e3).toString();
28072
- return `user-${rand}`;
28073
- }
28074
- function flattenToDirName(branch) {
28075
- return branch.replace(/\//g, "-");
28076
- }
28077
- function encodeClaudeProjectDir(absPath) {
28078
- if (!absPath || typeof absPath !== "string") return "";
28079
- let canonical = import_node_path25.default.resolve(absPath);
28080
- try {
28081
- canonical = import_node_fs23.default.realpathSync(canonical);
28082
- } catch {
28083
- try {
28084
- const parent = import_node_fs23.default.realpathSync(import_node_path25.default.dirname(canonical));
28085
- canonical = import_node_path25.default.join(parent, import_node_path25.default.basename(canonical));
28086
- } catch {
28087
- }
28088
- }
28089
- return canonical.replace(/[/.]/g, "-");
28090
- }
28091
- async function createWorktree(input) {
28092
- const { cwd, baseBranch, label } = input;
28093
- if (!cwd || typeof cwd !== "string") throw new Error("cwd \u4E0D\u80FD\u4E3A\u7A7A");
28094
- if (!baseBranch || typeof baseBranch !== "string") throw new Error("baseBranch \u4E0D\u80FD\u4E3A\u7A7A");
28095
- const labelResult = sanitizeLabel(label);
28096
- if (labelResult.reason === "invalid-chars") {
28097
- throw new Error("label \u4E0D\u80FD\u542B\u4E2D\u6587\u6216\u7279\u6B8A\u5B57\u7B26\uFF0C\u53EA\u5141\u8BB8\u82F1\u6587\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u7A7A\u683C\u3001\u8FDE\u5B57\u7B26\u3001\u4E0B\u5212\u7EBF");
28098
- }
28099
- if (labelResult.reason === "empty" || !labelResult.sanitized) {
28100
- throw new Error("label \u5FC5\u987B\u5305\u542B\u81F3\u5C11\u4E00\u4E2A\u82F1\u6587\u5B57\u6BCD\u6216\u6570\u5B57");
28101
- }
28102
- const prefix = computePrefix();
28103
- const branch = `${prefix}/${labelResult.sanitized}`;
28104
- const dirName = flattenToDirName(branch);
28105
- const { isGitRoot } = await getGitRoot(cwd);
28106
- if (!isGitRoot) {
28107
- throw new Error(`\u76EE\u5F55 ${cwd} \u4E0D\u662F git repo \u6839`);
28108
- }
28109
- const parent = import_node_path25.default.dirname(import_node_path25.default.resolve(cwd));
28110
- if (parent === "/" || parent === import_node_path25.default.resolve(cwd)) {
28111
- throw new Error("repo \u5728\u78C1\u76D8\u6839\u76EE\u5F55\uFF0C\u65E0\u6CD5\u5728\u540C\u7EA7\u521B\u5EFA worktree");
28112
- }
28113
- const worktreeRoot = import_node_path25.default.join(parent, dirName);
28114
- try {
28115
- await pexec("git", ["-C", cwd, "fetch", "origin", baseBranch, "--no-tags"], {
28116
- timeout: 3e4
28117
- });
28118
- } catch (err) {
28119
- throw new Error(
28120
- `\u57FA\u51C6\u5206\u652F ${baseBranch} \u4E0D\u5B58\u5728\u6216 fetch \u8FDC\u7AEF\u5931\u8D25\uFF1A${formatChildProcessError(err)}`
28121
- );
28122
- }
28123
- try {
28124
- await pexec("git", ["-C", cwd, "rev-parse", "--verify", `refs/heads/${branch}`], {
28125
- timeout: 3e3
28126
- });
28127
- throw new Error(`\u5206\u652F ${branch} \u5DF2\u5B58\u5728\uFF0C\u8BF7\u6362\u4E00\u4E2A label`);
28128
- } catch (err) {
28129
- const msg = err.message;
28130
- if (msg.startsWith("\u5206\u652F ")) throw err;
28131
- }
28132
- if (import_node_fs23.default.existsSync(worktreeRoot)) {
28133
- throw new Error(`\u76EE\u5F55 ${worktreeRoot} \u5DF2\u5B58\u5728\uFF0C\u8BF7\u6362\u4E00\u4E2A label \u6216\u6E05\u7406\u540E\u91CD\u8BD5`);
28134
- }
28135
- try {
28136
- await pexec(
28137
- "git",
28138
- ["-C", cwd, "worktree", "add", worktreeRoot, "-b", branch, `origin/${baseBranch}`],
28139
- { timeout: 15e3 }
28140
- );
28141
- } catch (err) {
28142
- throw new Error(`\u521B\u5EFA worktree \u5931\u8D25\uFF1A${formatChildProcessError(err)}`);
28143
- }
28144
- return { worktreeRoot, branch, baseBranch };
28145
- }
28146
- async function removeWorktree(input) {
28147
- const { worktreeRoot, worktreeBranch } = input;
28148
- if (!worktreeRoot || !worktreeBranch) {
28149
- throw new Error("worktreeRoot \u548C worktreeBranch \u90FD\u4E0D\u80FD\u4E3A\u7A7A");
28150
- }
28151
- let repoRoot = null;
28152
- try {
28153
- const { stdout } = await pexec(
28154
- "git",
28155
- ["-C", worktreeRoot, "rev-parse", "--git-common-dir"],
28156
- { timeout: 3e3 }
28157
- );
28158
- const gitCommonDir = stdout.trim();
28159
- if (!gitCommonDir) throw new Error("empty git-common-dir");
28160
- const absGitCommon = import_node_path25.default.isAbsolute(gitCommonDir) ? gitCommonDir : import_node_path25.default.resolve(worktreeRoot, gitCommonDir);
28161
- repoRoot = import_node_path25.default.dirname(absGitCommon);
28162
- } catch {
28163
- repoRoot = null;
28164
- }
28165
- if (repoRoot) {
28166
- try {
28167
- await pexec("git", ["-C", repoRoot, "worktree", "remove", worktreeRoot, "--force"], {
28168
- timeout: 1e4
28169
- });
28170
- } catch (err) {
28171
- const stderr = err.stderr ?? "";
28172
- const lower = stderr.toLowerCase();
28173
- const vanished = lower.includes("not a working tree") || lower.includes("is not a working tree") || !import_node_fs23.default.existsSync(worktreeRoot);
28174
- if (!vanished) {
28175
- throw new Error(`\u6E05\u7406 worktree \u5931\u8D25\uFF1A${formatChildProcessError(err)}`);
28176
- }
28177
- }
28178
- try {
28179
- await pexec("git", ["-C", repoRoot, "branch", "-D", worktreeBranch], { timeout: 5e3 });
28180
- } catch (err) {
28181
- const stderr = err.stderr ?? "";
28182
- const lower = stderr.toLowerCase();
28183
- const vanished = lower.includes("not found") || lower.includes("no such");
28184
- if (!vanished) {
28185
- throw new Error(`\u6E05\u7406\u5206\u652F\u5931\u8D25\uFF1A${formatChildProcessError(err)}`);
28186
- }
28187
- }
28188
- }
28189
- try {
28190
- const encoded = encodeClaudeProjectDir(worktreeRoot);
28191
- if (encoded) {
28192
- const projectsRoot = import_node_path25.default.join(import_node_os13.default.homedir(), ".claude", "projects");
28193
- const target = import_node_path25.default.resolve(projectsRoot, encoded);
28194
- if (target.startsWith(projectsRoot + import_node_path25.default.sep) && target !== projectsRoot) {
28195
- import_node_fs23.default.rmSync(target, { recursive: true, force: true });
28196
- }
28197
- }
28198
- } catch {
28199
- }
28200
- }
28201
27388
 
28202
27389
  // src/handlers/git.ts
28203
27390
  function buildGitHandlers() {
@@ -28216,36 +27403,10 @@ function buildGitHandlers() {
28216
27403
  const res = await listGitBranches(args.cwd);
28217
27404
  return { response: { type: "git:branches", ...res } };
28218
27405
  };
28219
- const worktreePrefix = async () => {
28220
- return {
28221
- response: { type: "git:worktree:prefix", prefix: computePrefix() }
28222
- };
28223
- };
28224
- const worktreeCreate = async (frame) => {
28225
- const args = GitWorktreeCreateArgs.parse(frame);
28226
- try {
28227
- const res = await createWorktree(args);
28228
- return { response: { type: "git:worktree:create", ...res } };
28229
- } catch (err) {
28230
- throw new ClawdError(ERROR_CODES.INTERNAL, err.message);
28231
- }
28232
- };
28233
- const worktreeRemove = async (frame) => {
28234
- const args = GitWorktreeRemoveArgs.parse(frame);
28235
- try {
28236
- await removeWorktree(args);
28237
- return { response: { type: "git:worktree:remove", ok: true } };
28238
- } catch (err) {
28239
- throw new ClawdError(ERROR_CODES.INTERNAL, err.message);
28240
- }
28241
- };
28242
27406
  return {
28243
27407
  "git:root": root,
28244
27408
  "git:branch": branch,
28245
- "git:branches": branches,
28246
- "git:worktree:prefix": worktreePrefix,
28247
- "git:worktree:create": worktreeCreate,
28248
- "git:worktree:remove": worktreeRemove
27409
+ "git:branches": branches
28249
27410
  };
28250
27411
  }
28251
27412
 
@@ -28264,7 +27425,7 @@ function buildCapabilitiesHandlers(deps) {
28264
27425
  }
28265
27426
 
28266
27427
  // src/handlers/meta.ts
28267
- var import_node_os14 = __toESM(require("os"), 1);
27428
+ var import_node_os13 = __toESM(require("os"), 1);
28268
27429
  init_protocol();
28269
27430
 
28270
27431
  // src/version.ts
@@ -28294,7 +27455,7 @@ function buildReadyFrame(deps, client) {
28294
27455
  return {
28295
27456
  version,
28296
27457
  protocolVersion: PROTOCOL_VERSION,
28297
- hostname: import_node_os14.default.hostname(),
27458
+ hostname: import_node_os13.default.hostname(),
28298
27459
  os: process.platform,
28299
27460
  tools,
28300
27461
  runningSessions: info.runningSessions,
@@ -28319,7 +27480,7 @@ function buildMetaHandlers(deps) {
28319
27480
  // src/handlers/persona.ts
28320
27481
  init_protocol();
28321
27482
  function buildPersonaHandlers(deps) {
28322
- const { personaManager, personaRegistry, sessionManager, personaBoundHandler } = deps;
27483
+ const { personaManager, personaRegistry } = deps;
28323
27484
  const create = async (frame) => {
28324
27485
  const { type: _type, requestId: _requestId, ...rest } = frame;
28325
27486
  const args = PersonaCreateArgsSchema.parse(rest);
@@ -28376,27 +27537,6 @@ function buildPersonaHandlers(deps) {
28376
27537
  const persona = personaManager.revokeToken(args.personaId, args.token);
28377
27538
  return { response: { type: "persona:info", ...persona } };
28378
27539
  };
28379
- const listSubSessions = async (frame) => {
28380
- const args = PersonaIdArgsSchema.parse(frame);
28381
- const sessions = sessionManager.listPersonaSessions(args.personaId, "listener");
28382
- return {
28383
- response: { type: "persona:subSessions", sessions }
28384
- };
28385
- };
28386
- const appendOwnerMessage = async (frame) => {
28387
- const args = PersonaAppendOwnerMessageArgsSchema.parse(frame);
28388
- personaManager.appendOwnerMessage(args.personaId, args.subSessionId, args.text);
28389
- return {
28390
- response: { type: "persona:appendOwnerMessage", ok: true }
28391
- };
28392
- };
28393
- const listListenerChatsForToken = async (frame) => {
28394
- const args = PersonaListListenerChatsForTokenArgsSchema.parse(frame);
28395
- const chats = personaBoundHandler.listChatsForOwner(args.personaId, args.token);
28396
- return {
28397
- response: { type: "persona:listListenerChatsForToken", chats }
28398
- };
28399
- };
28400
27540
  return {
28401
27541
  "persona:create": create,
28402
27542
  "persona:list": list,
@@ -28404,14 +27544,12 @@ function buildPersonaHandlers(deps) {
28404
27544
  "persona:update": update,
28405
27545
  "persona:delete": del,
28406
27546
  "persona:issueToken": issueToken,
28407
- "persona:revokeToken": revokeToken,
28408
- "persona:listSubSessions": listSubSessions,
28409
- "persona:listListenerChatsForToken": listListenerChatsForToken,
28410
- "persona:appendOwnerMessage": appendOwnerMessage
27547
+ "persona:revokeToken": revokeToken
28411
27548
  };
28412
27549
  }
28413
27550
 
28414
27551
  // src/handlers/attachment.ts
27552
+ var import_node_path26 = __toESM(require("path"), 1);
28415
27553
  init_protocol();
28416
27554
  init_protocol();
28417
27555
  var DEFAULT_TTL_SECONDS = 24 * 3600;
@@ -28436,8 +27574,51 @@ function buildAttachmentHandlers(deps) {
28436
27574
  "httpBaseUrl unavailable (daemon HTTP not ready)"
28437
27575
  );
28438
27576
  }
27577
+ if (!deps.sessionStore || !deps.getSessionScope || !deps.groupFileStore) {
27578
+ throw new ClawdError(
27579
+ ERROR_CODES.METHOD_NOT_IMPLEMENTED,
27580
+ "signUrl requires session/group stores"
27581
+ );
27582
+ }
27583
+ const sessionFile = deps.sessionStore.read(args.sessionId);
27584
+ if (!sessionFile) {
27585
+ throw new ClawdError(
27586
+ ERROR_CODES.VALIDATION_ERROR,
27587
+ `session ${args.sessionId} not found`
27588
+ );
27589
+ }
27590
+ const scope = deps.getSessionScope(args.sessionId);
27591
+ if (!scope) {
27592
+ throw new ClawdError(
27593
+ ERROR_CODES.VALIDATION_ERROR,
27594
+ `session ${args.sessionId} scope unresolved`
27595
+ );
27596
+ }
27597
+ const cwdAbs = import_node_path26.default.resolve(sessionFile.cwd);
27598
+ const candidateAbs = import_node_path26.default.isAbsolute(args.relPath) ? import_node_path26.default.resolve(args.relPath) : import_node_path26.default.resolve(cwdAbs, args.relPath);
27599
+ if (!isContainedIn2(candidateAbs, cwdAbs)) {
27600
+ throw new ClawdError(
27601
+ ERROR_CODES.VALIDATION_ERROR,
27602
+ "relPath escapes session cwd"
27603
+ );
27604
+ }
27605
+ const relPath = import_node_path26.default.relative(cwdAbs, candidateAbs);
27606
+ if (relPath === "" || relPath.startsWith("..")) {
27607
+ throw new ClawdError(
27608
+ ERROR_CODES.VALIDATION_ERROR,
27609
+ "relPath escapes session cwd"
27610
+ );
27611
+ }
27612
+ const entries = deps.groupFileStore.list(scope, args.sessionId);
27613
+ const entry = entries.find((e) => e.relPath === relPath && !e.stale);
27614
+ if (!entry) {
27615
+ throw new ClawdError(
27616
+ ERROR_CODES.VALIDATION_ERROR,
27617
+ `relPath not in session group files or stale: ${relPath}`
27618
+ );
27619
+ }
28439
27620
  const ttl = args.ttlSeconds === null ? null : args.ttlSeconds ?? DEFAULT_TTL_SECONDS;
28440
- const parts = signUrlParts(secret, args.absPath, ttl);
27621
+ const parts = signUrlParts(secret, candidateAbs, ttl);
28441
27622
  const url = buildSignedFileUrl(httpBaseUrl, parts);
28442
27623
  return {
28443
27624
  response: {
@@ -28523,6 +27704,12 @@ function buildAttachmentHandlers(deps) {
28523
27704
  "attachment.groupListPersona": groupListPersona
28524
27705
  };
28525
27706
  }
27707
+ function isContainedIn2(abs, root) {
27708
+ const normalized = import_node_path26.default.resolve(abs);
27709
+ const normalizedRoot = import_node_path26.default.resolve(root);
27710
+ if (normalized === normalizedRoot) return true;
27711
+ return normalized.startsWith(normalizedRoot + import_node_path26.default.sep);
27712
+ }
28526
27713
 
28527
27714
  // src/handlers/index.ts
28528
27715
  function buildMethodHandlers(deps) {
@@ -28536,9 +27723,7 @@ function buildMethodHandlers(deps) {
28536
27723
  ...buildMetaHandlers(deps),
28537
27724
  ...buildPersonaHandlers({
28538
27725
  personaManager: deps.personaManager,
28539
- personaRegistry: deps.personaRegistry,
28540
- sessionManager: deps.manager,
28541
- personaBoundHandler: deps.personaBoundHandler
27726
+ personaRegistry: deps.personaRegistry
28542
27727
  }),
28543
27728
  ...deps.attachment ? buildAttachmentHandlers(deps.attachment) : {}
28544
27729
  };
@@ -28548,7 +27733,7 @@ function buildMethodHandlers(deps) {
28548
27733
  async function startDaemon(config) {
28549
27734
  const logger = createLogger({
28550
27735
  level: config.logLevel,
28551
- file: import_node_path26.default.join(config.dataDir, "clawd.log")
27736
+ file: import_node_path27.default.join(config.dataDir, "clawd.log")
28552
27737
  });
28553
27738
  logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
28554
27739
  const stateMgr = new StateFileManager({ dataDir: config.dataDir });
@@ -28560,10 +27745,12 @@ async function startDaemon(config) {
28560
27745
  logger.warn("stale state file detected, overwriting", { pid: pre.existing.pid });
28561
27746
  }
28562
27747
  let resolvedAuthToken = null;
27748
+ let authFile = null;
28563
27749
  if (config.authToken && config.authToken.trim()) {
28564
27750
  resolvedAuthToken = config.authToken.trim();
28565
27751
  } else if (config.tunnel) {
28566
- resolvedAuthToken = loadOrCreateAuthToken({ dataDir: config.dataDir });
27752
+ authFile = loadOrCreateAuthFile({ dataDir: config.dataDir });
27753
+ resolvedAuthToken = authFile.token;
28567
27754
  }
28568
27755
  const authMode = resolvedAuthToken == null ? "none" : "first-message";
28569
27756
  let wsServer = null;
@@ -28580,7 +27767,7 @@ async function startDaemon(config) {
28580
27767
  const agents = new AgentsScanner();
28581
27768
  const history = new ClaudeHistoryReader();
28582
27769
  let transport = null;
28583
- const personaStore = new PersonaStore(import_node_path26.default.join(config.dataDir, "personas"));
27770
+ const personaStore = new PersonaStore(import_node_path27.default.join(config.dataDir, "personas"));
28584
27771
  const defaultsRoot = findDefaultsRoot();
28585
27772
  if (defaultsRoot) {
28586
27773
  seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
@@ -28595,7 +27782,7 @@ async function startDaemon(config) {
28595
27782
  getAdapter,
28596
27783
  historyReader: history,
28597
27784
  dataDir: config.dataDir,
28598
- personaRoot: import_node_path26.default.join(config.dataDir, "personas"),
27785
+ personaRoot: import_node_path27.default.join(config.dataDir, "personas"),
28599
27786
  personaStore,
28600
27787
  ownerDisplayName,
28601
27788
  mode: config.mode,
@@ -28618,7 +27805,7 @@ async function startDaemon(config) {
28618
27805
  // 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
28619
27806
  attachmentGroup: {
28620
27807
  onFileEdit: (input) => {
28621
- const absPath = import_node_path26.default.isAbsolute(input.relPath) ? input.relPath : import_node_path26.default.join(input.cwd, input.relPath);
27808
+ const absPath = import_node_path27.default.isAbsolute(input.relPath) ? input.relPath : import_node_path27.default.join(input.cwd, input.relPath);
28622
27809
  let size = 0;
28623
27810
  try {
28624
27811
  size = import_node_fs24.default.statSync(absPath).size;
@@ -28695,16 +27882,6 @@ async function startDaemon(config) {
28695
27882
  }
28696
27883
  return `http://${config.host}:${config.port}`;
28697
27884
  };
28698
- const personaBoundHandler = new PersonaBoundHandler({
28699
- registry: personaRegistry,
28700
- personaManager,
28701
- personaStore,
28702
- sessionManager: manager,
28703
- logger,
28704
- ownerBroadcast: (frame) => {
28705
- transport?.broadcastAll(frame);
28706
- }
28707
- });
28708
27885
  const handlers = buildMethodHandlers({
28709
27886
  manager,
28710
27887
  workspace,
@@ -28716,7 +27893,6 @@ async function startDaemon(config) {
28716
27893
  store,
28717
27894
  personaManager,
28718
27895
  personaRegistry,
28719
- personaBoundHandler,
28720
27896
  getTunnelUrl: () => currentTunnelUrl,
28721
27897
  // ready / info 帧的 mode = daemon CC spawn 模式('sdk' | 'tui')。UI 据此挂 XtermPanel +
28722
27898
  // 订阅 session:pty / session:control;业务帧名两种 mode 完全一致,UI 业务订阅代码不变
@@ -28730,11 +27906,12 @@ async function startDaemon(config) {
28730
27906
  // 根据 sessionId 反查 scope 写盘。
28731
27907
  attachment: {
28732
27908
  groupFileStore,
27909
+ sessionStore: store,
28733
27910
  getHttpBaseUrl,
28734
- // HMAC sign secret:复用 ~/.clawd/auth.json owner token(持久跨重启)。
28735
- // noAuth 模式 resolvedAuthToken 为 null → handler 自己返 NOT_IMPLEMENTED。
28736
- getSignSecret: () => resolvedAuthToken ?? "",
28737
- // group RPC:根据 sessionId 反查 scope;owner-mode persona session 走
27911
+ // HMAC sign secret:~/.clawd/auth.json signSecret 字段(与 WS Bearer token 独立)。
27912
+ // --auth-token CLI 模式 / noAuth 模式 authFile 为 null → handler 自己返 NOT_IMPLEMENTED。
27913
+ getSignSecret: () => authFile?.signSecret ?? "",
27914
+ // group RPC + sign 都用:根据 sessionId 反查 scope;owner-mode persona session 走
28738
27915
  // 'persona/<pid>/owner',default 走 'default'。
28739
27916
  getSessionScope: (sessionId) => {
28740
27917
  const file = store.read(sessionId);
@@ -28757,8 +27934,9 @@ async function startDaemon(config) {
28757
27934
  personaStore,
28758
27935
  groupFileStore,
28759
27936
  sessionStore: store,
28760
- // /files HMAC verify 用同一份 owner token secret(与 attachment.signUrl 同源)
28761
- getSignSecret: () => resolvedAuthToken ?? null
27937
+ // /files HMAC verify auth.json signSecret 字段(与 attachment.signUrl 同源)。
27938
+ // --auth-token CLI 模式没 signSecret → 路由返 501,sign URL 功能整体禁用。
27939
+ getSignSecret: () => authFile?.signSecret ?? null
28762
27940
  });
28763
27941
  wsServer = new LocalWsServer({
28764
27942
  host: config.host,
@@ -28779,7 +27957,6 @@ async function startDaemon(config) {
28779
27957
  ),
28780
27958
  protocolVersion: PROTOCOL_VERSION,
28781
27959
  authGate: authGate ?? void 0,
28782
- personaBoundHandler,
28783
27960
  // file-sharing HTTP 路由复用 daemon 同端口(spec §5 第 3 条);router 自己处理 auth + 404
28784
27961
  httpRequestHandler: httpRouter,
28785
27962
  // 订阅成功后给该 client 重放 in-flight pendingQuestions(plan: clawd-question-server-truth)。
@@ -28897,8 +28074,8 @@ async function startDaemon(config) {
28897
28074
  const lines = [
28898
28075
  `Tunnel: ${r.url}`,
28899
28076
  ...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
28900
- `Frpc config: ${import_node_path26.default.join(config.dataDir, "frpc.toml")}`,
28901
- `Frpc log: ${import_node_path26.default.join(config.dataDir, "frpc.log")}`
28077
+ `Frpc config: ${import_node_path27.default.join(config.dataDir, "frpc.toml")}`,
28078
+ `Frpc log: ${import_node_path27.default.join(config.dataDir, "frpc.log")}`
28902
28079
  ];
28903
28080
  const width = Math.max(...lines.map((l) => l.length));
28904
28081
  const bar = "\u2550".repeat(width + 4);
@@ -28911,7 +28088,7 @@ ${bar}
28911
28088
 
28912
28089
  `);
28913
28090
  try {
28914
- const connectPath = import_node_path26.default.join(config.dataDir, "connect.txt");
28091
+ const connectPath = import_node_path27.default.join(config.dataDir, "connect.txt");
28915
28092
  import_node_fs24.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
28916
28093
  } catch {
28917
28094
  }