@clawos-dev/clawd 0.2.67-beta.115.8d05743 → 0.2.67

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 +175 -776
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -86,20 +86,16 @@ var init_methods = __esm({
86
86
  "git:branch",
87
87
  "git:branches",
88
88
  "capabilities:get",
89
- // ---- persona:* (老板侧管理 + 插话;alice 走 persona-bound WS 简化协议,不在此白名单) ----
89
+ // ---- persona:* (persona 管理) ----
90
90
  "persona:create",
91
91
  "persona:list",
92
92
  "persona:get",
93
93
  "persona:update",
94
94
  "persona:delete",
95
+ // token 颁发 / 吊销:file-sharing HTTP Bearer 鉴权用(PersonaRegistry.findByToken
96
+ // 反查 personaId / label)。owner UI 在 PersonaSettingsDrawer "分享 Token" section 管理。
95
97
  "persona:issueToken",
96
98
  "persona:revokeToken",
97
- "persona:listSubSessions",
98
- // owner-side bootstrap RPC:拉 (personaId, token) tuple 下所有 listener sub-session
99
- // 元数据。后续增量靠 persona:listenerChat:* push 维护,本 RPC 仅在 owner ws connect /
100
- // reconnect 时一次性补齐 cache。
101
- "persona:listListenerChatsForToken",
102
- "persona:appendOwnerMessage",
103
99
  // ---- session:pty 双向透传(仅 CLAWD_CC_MODE=tui 才生效,对应 session:pty push 帧的上行) ----
104
100
  // session:pty:input — UI 把用户键盘字节(base64 UTF-8)发给 daemon,daemon 直写 pty stdin。
105
101
  // 高频帧(每次按键 1 帧),不入 reducer / 不广播。response 是 fire-and-forget 风格 ack。
@@ -609,8 +605,8 @@ var init_parseUtil = __esm({
609
605
  init_errors2();
610
606
  init_en();
611
607
  makeIssue = (params) => {
612
- const { data, path: path31, errorMaps, issueData } = params;
613
- const fullPath = [...path31, ...issueData.path || []];
608
+ const { data, path: path32, errorMaps, issueData } = params;
609
+ const fullPath = [...path32, ...issueData.path || []];
614
610
  const fullIssue = {
615
611
  ...issueData,
616
612
  path: fullPath
@@ -921,11 +917,11 @@ var init_types = __esm({
921
917
  init_parseUtil();
922
918
  init_util();
923
919
  ParseInputLazyPath = class {
924
- constructor(parent, value, path31, key) {
920
+ constructor(parent, value, path32, key) {
925
921
  this._cachedPath = [];
926
922
  this.parent = parent;
927
923
  this.data = value;
928
- this._path = path31;
924
+ this._path = path32;
929
925
  this._key = key;
930
926
  }
931
927
  get path() {
@@ -4335,9 +4331,11 @@ var init_attachment_schemas = __esm({
4335
4331
  stale: external_exports.boolean().optional()
4336
4332
  });
4337
4333
  AttachmentSignUrlArgs = external_exports.object({
4338
- /** 要分享的绝对路径;签名只关心 absPath,不区分 persona/session */
4339
- absPath: external_exports.string().min(1),
4340
- /** 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 字段) */
4341
4339
  ttlSeconds: external_exports.number().int().positive().nullable().optional()
4342
4340
  });
4343
4341
  AttachmentSignUrlResponseSchema = external_exports.object({
@@ -4383,7 +4381,7 @@ var init_attachment_schemas = __esm({
4383
4381
  });
4384
4382
 
4385
4383
  // ../protocol/src/persona-schemas.ts
4386
- 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;
4387
4385
  var init_persona_schemas = __esm({
4388
4386
  "../protocol/src/persona-schemas.ts"() {
4389
4387
  "use strict";
@@ -4401,7 +4399,13 @@ var init_persona_schemas = __esm({
4401
4399
  // 8-key set defined UI-side in `lib/session-icons.ts`; protocol stays plain string
4402
4400
  // for forward compatibility, consumer fallbacks to default when key not found.
4403
4401
  iconKey: external_exports.string().optional(),
4404
- 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(),
4405
4409
  createdAt: external_exports.number(),
4406
4410
  updatedAt: external_exports.number()
4407
4411
  }).strict();
@@ -4462,121 +4466,6 @@ var init_persona_schemas = __esm({
4462
4466
  personaId: external_exports.string().min(1),
4463
4467
  token: external_exports.string().min(1)
4464
4468
  });
4465
- PersonaAppendOwnerMessageArgsSchema = external_exports.object({
4466
- personaId: external_exports.string().min(1),
4467
- subSessionId: external_exports.string().min(1),
4468
- text: external_exports.string().min(1)
4469
- });
4470
- FRAME_TYPE_CHAT_OPEN = "chat:open";
4471
- FRAME_TYPE_CHAT_RENAME = "chat:rename";
4472
- FRAME_TYPE_CHAT_DELETE = "chat:delete";
4473
- FRAME_TYPE_CHAT_LIST = "chat:list";
4474
- FRAME_TYPE_CHAT_CREATED = "chat:created";
4475
- FRAME_TYPE_CHAT_RENAMED = "chat:renamed";
4476
- FRAME_TYPE_CHAT_DELETED = "chat:deleted";
4477
- FRAME_TYPE_PERSONA_LISTENER_CHAT_CREATED = "persona:listenerChat:created";
4478
- FRAME_TYPE_PERSONA_LISTENER_CHAT_RENAMED = "persona:listenerChat:renamed";
4479
- FRAME_TYPE_PERSONA_LISTENER_CHAT_DELETED = "persona:listenerChat:deleted";
4480
- FRAME_TYPE_PERSONA_LISTENER_STATUS = "persona:listenerStatus";
4481
- ChatSummarySchema = external_exports.object({
4482
- sessionId: external_exports.string().min(1),
4483
- /** 派生 sessionId 时使用的原始 chatId(永远三元组 hash 派生,无 default 特例) */
4484
- chatId: external_exports.string().min(1),
4485
- /** sub-session label,listener 视为 chat 名 */
4486
- label: external_exports.string(),
4487
- /** SessionFile.createdAt(ISO string) */
4488
- createdAt: external_exports.string()
4489
- });
4490
- ChatOpenRequestSchema = external_exports.object({
4491
- type: external_exports.literal(FRAME_TYPE_CHAT_OPEN),
4492
- /** 客户端生成的 chatId(uuid 推荐);daemon 用三元组派生 sub-session sessionId */
4493
- chatId: external_exports.string().min(1),
4494
- /** 不存在时是否自动创建。true = 创建(典型:新建对话);false/缺省 = 命中已有,否则 NOT_FOUND */
4495
- autoCreate: external_exports.boolean().optional(),
4496
- requestId: external_exports.string().min(1).optional()
4497
- });
4498
- ChatRenameRequestSchema = external_exports.object({
4499
- type: external_exports.literal(FRAME_TYPE_CHAT_RENAME),
4500
- requestId: external_exports.string().min(1).optional(),
4501
- sessionId: external_exports.string().min(1),
4502
- label: external_exports.string().min(1)
4503
- });
4504
- ChatDeleteRequestSchema = external_exports.object({
4505
- type: external_exports.literal(FRAME_TYPE_CHAT_DELETE),
4506
- requestId: external_exports.string().min(1).optional(),
4507
- sessionId: external_exports.string().min(1)
4508
- });
4509
- ChatRenameResponseSchema = external_exports.object({
4510
- type: external_exports.literal(FRAME_TYPE_CHAT_RENAME),
4511
- requestId: external_exports.string().min(1).optional(),
4512
- ok: external_exports.literal(true)
4513
- });
4514
- ChatDeleteResponseSchema = external_exports.object({
4515
- type: external_exports.literal(FRAME_TYPE_CHAT_DELETE),
4516
- requestId: external_exports.string().min(1).optional(),
4517
- ok: external_exports.literal(true)
4518
- });
4519
- ChatListPushSchema = external_exports.object({
4520
- type: external_exports.literal(FRAME_TYPE_CHAT_LIST),
4521
- chats: external_exports.array(ChatSummarySchema)
4522
- });
4523
- ChatCreatedFrameSchema = external_exports.object({
4524
- type: external_exports.literal(FRAME_TYPE_CHAT_CREATED),
4525
- sessionId: external_exports.string().min(1),
4526
- chatId: external_exports.string().min(1),
4527
- label: external_exports.string().optional(),
4528
- /** ISO timestamp */
4529
- createdAt: external_exports.string().min(1)
4530
- });
4531
- ChatRenamedFrameSchema = external_exports.object({
4532
- type: external_exports.literal(FRAME_TYPE_CHAT_RENAMED),
4533
- sessionId: external_exports.string().min(1),
4534
- label: external_exports.string().min(1)
4535
- });
4536
- ChatDeletedFrameSchema = external_exports.object({
4537
- type: external_exports.literal(FRAME_TYPE_CHAT_DELETED),
4538
- sessionId: external_exports.string().min(1)
4539
- });
4540
- PersonaListenerChatCreatedFrameSchema = external_exports.object({
4541
- type: external_exports.literal(FRAME_TYPE_PERSONA_LISTENER_CHAT_CREATED),
4542
- personaId: external_exports.string().min(1),
4543
- /** owner 同时持有 token 字符串:sidebar 已经按 token 维度渲染 listener leaf,
4544
- * push 帧用 token 直接定位归属 leaf。token 不是密钥,owner 本来就持有。 */
4545
- token: external_exports.string().min(1),
4546
- sessionId: external_exports.string().min(1),
4547
- chatId: external_exports.string().min(1),
4548
- label: external_exports.string().optional(),
4549
- createdAt: external_exports.string().min(1)
4550
- });
4551
- PersonaListenerChatRenamedFrameSchema = external_exports.object({
4552
- type: external_exports.literal(FRAME_TYPE_PERSONA_LISTENER_CHAT_RENAMED),
4553
- personaId: external_exports.string().min(1),
4554
- token: external_exports.string().min(1),
4555
- sessionId: external_exports.string().min(1),
4556
- label: external_exports.string().min(1)
4557
- });
4558
- PersonaListenerChatDeletedFrameSchema = external_exports.object({
4559
- type: external_exports.literal(FRAME_TYPE_PERSONA_LISTENER_CHAT_DELETED),
4560
- personaId: external_exports.string().min(1),
4561
- token: external_exports.string().min(1),
4562
- sessionId: external_exports.string().min(1),
4563
- /** 原始 chatId(用于 owner UI 比对 personaView.chatId 清理 stale selection) */
4564
- chatId: external_exports.string().min(1)
4565
- });
4566
- PersonaListenerStatusFrameSchema = external_exports.object({
4567
- type: external_exports.literal(FRAME_TYPE_PERSONA_LISTENER_STATUS),
4568
- personaId: external_exports.string().min(1),
4569
- token: external_exports.string().min(1),
4570
- status: external_exports.enum(["online", "offline"]),
4571
- activeWsCount: external_exports.number().int().nonnegative()
4572
- });
4573
- PersonaListListenerChatsForTokenArgsSchema = external_exports.object({
4574
- personaId: external_exports.string().min(1),
4575
- token: external_exports.string().min(1)
4576
- });
4577
- PersonaListListenerChatsForTokenResponseSchema = external_exports.object({
4578
- chats: external_exports.array(ChatSummarySchema)
4579
- });
4580
4469
  }
4581
4470
  });
4582
4471
 
@@ -5441,8 +5330,8 @@ var require_req = __commonJS({
5441
5330
  if (req.originalUrl) {
5442
5331
  _req.url = req.originalUrl;
5443
5332
  } else {
5444
- const path31 = req.path;
5445
- _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;
5446
5335
  }
5447
5336
  if (req.query) {
5448
5337
  _req.query = req.query;
@@ -5607,14 +5496,14 @@ var require_redact = __commonJS({
5607
5496
  }
5608
5497
  return obj;
5609
5498
  }
5610
- function parsePath(path31) {
5499
+ function parsePath(path32) {
5611
5500
  const parts = [];
5612
5501
  let current = "";
5613
5502
  let inBrackets = false;
5614
5503
  let inQuotes = false;
5615
5504
  let quoteChar = "";
5616
- for (let i = 0; i < path31.length; i++) {
5617
- const char = path31[i];
5505
+ for (let i = 0; i < path32.length; i++) {
5506
+ const char = path32[i];
5618
5507
  if (!inBrackets && char === ".") {
5619
5508
  if (current) {
5620
5509
  parts.push(current);
@@ -5745,10 +5634,10 @@ var require_redact = __commonJS({
5745
5634
  return current;
5746
5635
  }
5747
5636
  function redactPaths(obj, paths, censor, remove = false) {
5748
- for (const path31 of paths) {
5749
- const parts = parsePath(path31);
5637
+ for (const path32 of paths) {
5638
+ const parts = parsePath(path32);
5750
5639
  if (parts.includes("*")) {
5751
- redactWildcardPath(obj, parts, censor, path31, remove);
5640
+ redactWildcardPath(obj, parts, censor, path32, remove);
5752
5641
  } else {
5753
5642
  if (remove) {
5754
5643
  removeKey(obj, parts);
@@ -5833,8 +5722,8 @@ var require_redact = __commonJS({
5833
5722
  }
5834
5723
  } else {
5835
5724
  if (afterWildcard.includes("*")) {
5836
- const wrappedCensor = typeof censor === "function" ? (value, path31) => {
5837
- const fullPath = [...pathArray.slice(0, pathLength), ...path31];
5725
+ const wrappedCensor = typeof censor === "function" ? (value, path32) => {
5726
+ const fullPath = [...pathArray.slice(0, pathLength), ...path32];
5838
5727
  return censor(value, fullPath);
5839
5728
  } : censor;
5840
5729
  redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
@@ -5869,8 +5758,8 @@ var require_redact = __commonJS({
5869
5758
  return null;
5870
5759
  }
5871
5760
  const pathStructure = /* @__PURE__ */ new Map();
5872
- for (const path31 of pathsToClone) {
5873
- const parts = parsePath(path31);
5761
+ for (const path32 of pathsToClone) {
5762
+ const parts = parsePath(path32);
5874
5763
  let current = pathStructure;
5875
5764
  for (let i = 0; i < parts.length; i++) {
5876
5765
  const part = parts[i];
@@ -5922,24 +5811,24 @@ var require_redact = __commonJS({
5922
5811
  }
5923
5812
  return cloneSelectively(obj, pathStructure);
5924
5813
  }
5925
- function validatePath(path31) {
5926
- if (typeof path31 !== "string") {
5814
+ function validatePath(path32) {
5815
+ if (typeof path32 !== "string") {
5927
5816
  throw new Error("Paths must be (non-empty) strings");
5928
5817
  }
5929
- if (path31 === "") {
5818
+ if (path32 === "") {
5930
5819
  throw new Error("Invalid redaction path ()");
5931
5820
  }
5932
- if (path31.includes("..")) {
5933
- throw new Error(`Invalid redaction path (${path31})`);
5821
+ if (path32.includes("..")) {
5822
+ throw new Error(`Invalid redaction path (${path32})`);
5934
5823
  }
5935
- if (path31.includes(",")) {
5936
- throw new Error(`Invalid redaction path (${path31})`);
5824
+ if (path32.includes(",")) {
5825
+ throw new Error(`Invalid redaction path (${path32})`);
5937
5826
  }
5938
5827
  let bracketCount = 0;
5939
5828
  let inQuotes = false;
5940
5829
  let quoteChar = "";
5941
- for (let i = 0; i < path31.length; i++) {
5942
- const char = path31[i];
5830
+ for (let i = 0; i < path32.length; i++) {
5831
+ const char = path32[i];
5943
5832
  if ((char === '"' || char === "'") && bracketCount > 0) {
5944
5833
  if (!inQuotes) {
5945
5834
  inQuotes = true;
@@ -5953,20 +5842,20 @@ var require_redact = __commonJS({
5953
5842
  } else if (char === "]" && !inQuotes) {
5954
5843
  bracketCount--;
5955
5844
  if (bracketCount < 0) {
5956
- throw new Error(`Invalid redaction path (${path31})`);
5845
+ throw new Error(`Invalid redaction path (${path32})`);
5957
5846
  }
5958
5847
  }
5959
5848
  }
5960
5849
  if (bracketCount !== 0) {
5961
- throw new Error(`Invalid redaction path (${path31})`);
5850
+ throw new Error(`Invalid redaction path (${path32})`);
5962
5851
  }
5963
5852
  }
5964
5853
  function validatePaths(paths) {
5965
5854
  if (!Array.isArray(paths)) {
5966
5855
  throw new TypeError("paths must be an array");
5967
5856
  }
5968
- for (const path31 of paths) {
5969
- validatePath(path31);
5857
+ for (const path32 of paths) {
5858
+ validatePath(path32);
5970
5859
  }
5971
5860
  }
5972
5861
  function slowRedact(options = {}) {
@@ -6134,8 +6023,8 @@ var require_redaction = __commonJS({
6134
6023
  if (shape[k2] === null) {
6135
6024
  o[k2] = (value) => topCensor(value, [k2]);
6136
6025
  } else {
6137
- const wrappedCensor = typeof censor === "function" ? (value, path31) => {
6138
- return censor(value, [k2, ...path31]);
6026
+ const wrappedCensor = typeof censor === "function" ? (value, path32) => {
6027
+ return censor(value, [k2, ...path32]);
6139
6028
  } : censor;
6140
6029
  o[k2] = Redact({
6141
6030
  paths: shape[k2],
@@ -6356,7 +6245,7 @@ var require_sonic_boom = __commonJS({
6356
6245
  var fs28 = require("fs");
6357
6246
  var EventEmitter2 = require("events");
6358
6247
  var inherits = require("util").inherits;
6359
- var path31 = require("path");
6248
+ var path32 = require("path");
6360
6249
  var sleep = require_atomic_sleep();
6361
6250
  var assert = require("assert");
6362
6251
  var BUSY_WRITE_TIMEOUT = 100;
@@ -6410,7 +6299,7 @@ var require_sonic_boom = __commonJS({
6410
6299
  const mode = sonic.mode;
6411
6300
  if (sonic.sync) {
6412
6301
  try {
6413
- if (sonic.mkdir) fs28.mkdirSync(path31.dirname(file), { recursive: true });
6302
+ if (sonic.mkdir) fs28.mkdirSync(path32.dirname(file), { recursive: true });
6414
6303
  const fd = fs28.openSync(file, flags, mode);
6415
6304
  fileOpened(null, fd);
6416
6305
  } catch (err) {
@@ -6418,7 +6307,7 @@ var require_sonic_boom = __commonJS({
6418
6307
  throw err;
6419
6308
  }
6420
6309
  } else if (sonic.mkdir) {
6421
- fs28.mkdir(path31.dirname(file), { recursive: true }, (err) => {
6310
+ fs28.mkdir(path32.dirname(file), { recursive: true }, (err) => {
6422
6311
  if (err) return fileOpened(err);
6423
6312
  fs28.open(file, flags, mode, fileOpened);
6424
6313
  });
@@ -10049,11 +9938,11 @@ var init_lib = __esm({
10049
9938
  }
10050
9939
  }
10051
9940
  },
10052
- addToPath: function addToPath(path31, added, removed, oldPosInc, options) {
10053
- var last = path31.lastComponent;
9941
+ addToPath: function addToPath(path32, added, removed, oldPosInc, options) {
9942
+ var last = path32.lastComponent;
10054
9943
  if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
10055
9944
  return {
10056
- oldPos: path31.oldPos + oldPosInc,
9945
+ oldPos: path32.oldPos + oldPosInc,
10057
9946
  lastComponent: {
10058
9947
  count: last.count + 1,
10059
9948
  added,
@@ -10063,7 +9952,7 @@ var init_lib = __esm({
10063
9952
  };
10064
9953
  } else {
10065
9954
  return {
10066
- oldPos: path31.oldPos + oldPosInc,
9955
+ oldPos: path32.oldPos + oldPosInc,
10067
9956
  lastComponent: {
10068
9957
  count: 1,
10069
9958
  added,
@@ -10494,10 +10383,10 @@ function attachmentToHistoryMessage(o, ts) {
10494
10383
  const memories = raw.map((m2) => {
10495
10384
  if (!m2 || typeof m2 !== "object") return null;
10496
10385
  const rec = m2;
10497
- const path31 = typeof rec.path === "string" ? rec.path : null;
10386
+ const path32 = typeof rec.path === "string" ? rec.path : null;
10498
10387
  const content = typeof rec.content === "string" ? rec.content : null;
10499
- if (!path31 || content == null) return null;
10500
- const entry = { path: path31, content };
10388
+ if (!path32 || content == null) return null;
10389
+ const entry = { path: path32, content };
10501
10390
  if (typeof rec.mtimeMs === "number") entry.mtimeMs = rec.mtimeMs;
10502
10391
  return entry;
10503
10392
  }).filter((m2) => m2 !== null);
@@ -11301,10 +11190,10 @@ function parseAttachment(obj) {
11301
11190
  const memories = raw.map((m2) => {
11302
11191
  if (!m2 || typeof m2 !== "object") return null;
11303
11192
  const rec = m2;
11304
- const path31 = typeof rec.path === "string" ? rec.path : null;
11193
+ const path32 = typeof rec.path === "string" ? rec.path : null;
11305
11194
  const content = typeof rec.content === "string" ? rec.content : null;
11306
- if (!path31 || content == null) return null;
11307
- const out = { path: path31, content };
11195
+ if (!path32 || content == null) return null;
11196
+ const out = { path: path32, content };
11308
11197
  if (typeof rec.mtimeMs === "number") out.mtimeMs = rec.mtimeMs;
11309
11198
  return out;
11310
11199
  }).filter((m2) => m2 !== null);
@@ -20218,7 +20107,7 @@ var require_websocket_server = __commonJS({
20218
20107
  // src/run-case/recorder.ts
20219
20108
  function startRunCaseRecorder(opts) {
20220
20109
  const now = opts.now ?? Date.now;
20221
- const dir = import_node_path27.default.dirname(opts.recordPath);
20110
+ const dir = import_node_path28.default.dirname(opts.recordPath);
20222
20111
  let stream = null;
20223
20112
  let closing = false;
20224
20113
  let closedSettled = false;
@@ -20258,12 +20147,12 @@ function startRunCaseRecorder(opts) {
20258
20147
  };
20259
20148
  return { tap, close, closed };
20260
20149
  }
20261
- var import_node_fs25, import_node_path27;
20150
+ var import_node_fs25, import_node_path28;
20262
20151
  var init_recorder = __esm({
20263
20152
  "src/run-case/recorder.ts"() {
20264
20153
  "use strict";
20265
20154
  import_node_fs25 = __toESM(require("fs"), 1);
20266
- import_node_path27 = __toESM(require("path"), 1);
20155
+ import_node_path28 = __toESM(require("path"), 1);
20267
20156
  }
20268
20157
  });
20269
20158
 
@@ -20306,7 +20195,7 @@ var init_wire = __esm({
20306
20195
  // src/run-case/controller.ts
20307
20196
  async function runController(opts) {
20308
20197
  const now = opts.now ?? Date.now;
20309
- const cwd = opts.cwd ?? (0, import_node_fs26.mkdtempSync)(import_node_path28.default.join(import_node_os14.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-"));
20310
20199
  const ownsCwd = opts.cwd === void 0;
20311
20200
  const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
20312
20201
  const spawnCtx = { cwd };
@@ -20473,13 +20362,13 @@ async function runController(opts) {
20473
20362
  }
20474
20363
  return exitCode ?? 0;
20475
20364
  }
20476
- var import_node_fs26, import_node_os14, import_node_path28;
20365
+ var import_node_fs26, import_node_os14, import_node_path29;
20477
20366
  var init_controller = __esm({
20478
20367
  "src/run-case/controller.ts"() {
20479
20368
  "use strict";
20480
20369
  import_node_fs26 = require("fs");
20481
20370
  import_node_os14 = __toESM(require("os"), 1);
20482
- import_node_path28 = __toESM(require("path"), 1);
20371
+ import_node_path29 = __toESM(require("path"), 1);
20483
20372
  init_claude();
20484
20373
  init_stdout_splitter();
20485
20374
  init_permission_stdio();
@@ -20711,7 +20600,7 @@ Env (advanced):
20711
20600
  `;
20712
20601
 
20713
20602
  // src/index.ts
20714
- var import_node_path26 = __toESM(require("path"), 1);
20603
+ var import_node_path27 = __toESM(require("path"), 1);
20715
20604
  var import_node_fs24 = __toESM(require("fs"), 1);
20716
20605
 
20717
20606
  // src/logger.ts
@@ -23430,15 +23319,6 @@ var PersonaRegistry = class {
23430
23319
  list() {
23431
23320
  return Array.from(this.cache.values());
23432
23321
  }
23433
- verifyToken(personaId, token) {
23434
- const persona = this.cache.get(personaId);
23435
- if (!persona) return { ok: false, code: "PERSONA_DELETED" };
23436
- if (!persona.public) return { ok: false, code: "PERSONA_NOT_PUBLIC" };
23437
- const entry = persona.tokenMap[token];
23438
- if (!entry) return { ok: false, code: "TOKEN_INVALID" };
23439
- if (entry.revoked) return { ok: false, code: "TOKEN_REVOKED" };
23440
- return { ok: true, label: entry.label };
23441
- }
23442
23322
  /**
23443
23323
  * file-sharing HTTP auth-context 用的逆向查表:只有 Bearer token,没有 personaId。
23444
23324
  * 线性扫所有 persona.tokenMap 找到第一条 ok 命中的;revoked / non-public persona 跳过。
@@ -23448,7 +23328,7 @@ var PersonaRegistry = class {
23448
23328
  if (!token) return null;
23449
23329
  for (const persona of this.cache.values()) {
23450
23330
  if (!persona.public) continue;
23451
- const entry = persona.tokenMap[token];
23331
+ const entry = persona.tokenMap?.[token];
23452
23332
  if (!entry || entry.revoked) continue;
23453
23333
  return { personaId: persona.personaId, label: entry.label };
23454
23334
  }
@@ -23747,6 +23627,8 @@ var PersonaManager = class {
23747
23627
  model: args.model,
23748
23628
  public: args.public ?? false,
23749
23629
  iconKey: args.iconKey,
23630
+ // 初始化空 token 集合;后续由 issueToken / revokeToken 维护,
23631
+ // file-sharing HTTP Bearer 鉴权用 PersonaRegistry.findByToken 反查
23750
23632
  tokenMap: {},
23751
23633
  createdAt: now,
23752
23634
  updatedAt: now
@@ -23790,15 +23672,16 @@ var PersonaManager = class {
23790
23672
  }
23791
23673
  /**
23792
23674
  * 删除 persona。
23793
- * PersonaStore.remove(personaId) 已级联删除整个 <personaRoot>/<personaId>/ 目录,
23794
- * 其中包含 .clawd/sub-sessions/——无需额外清理。
23795
- * 旧的 <dataDir>/sessions/<personaId>/ 路径已废弃,不再维护。
23675
+ * PersonaStore.remove(personaId) 已级联删除整个 <personaRoot>/<personaId>/ 目录。
23796
23676
  */
23797
23677
  delete(personaId) {
23798
23678
  this.deps.store.remove(personaId);
23799
23679
  this.deps.registry.remove(personaId);
23800
23680
  }
23801
- /** 生成 32-char base64url token(24 bytes 随机),label 必填 */
23681
+ /**
23682
+ * 生成 32-char base64url token 并写入 tokenMap,给 file-sharing HTTP Bearer
23683
+ * 鉴权用(PersonaRegistry.findByToken 反查 personaId/label)。
23684
+ */
23802
23685
  issueToken(personaId, label) {
23803
23686
  const existing = this.deps.registry.get(personaId);
23804
23687
  if (!existing) throw new Error(`persona not found: ${personaId}`);
@@ -23810,22 +23693,23 @@ var PersonaManager = class {
23810
23693
  };
23811
23694
  const updated = {
23812
23695
  ...existing,
23813
- tokenMap: { ...existing.tokenMap, [token]: entry },
23696
+ tokenMap: { ...existing.tokenMap ?? {}, [token]: entry },
23814
23697
  updatedAt: Date.now()
23815
23698
  };
23816
23699
  this.deps.store.writeMeta(updated);
23817
23700
  this.deps.registry.set(updated);
23818
23701
  return { token, persona: updated };
23819
23702
  }
23703
+ /** 标记 token 为 revoked(保留条目用于审计);不存在的 token 抛错。 */
23820
23704
  revokeToken(personaId, token) {
23821
23705
  const existing = this.deps.registry.get(personaId);
23822
23706
  if (!existing) throw new Error(`persona not found: ${personaId}`);
23823
- const tokenEntry = existing.tokenMap[token];
23707
+ const tokenEntry = existing.tokenMap?.[token];
23824
23708
  if (!tokenEntry) throw new Error(`token not found in persona ${personaId}`);
23825
23709
  const updated = {
23826
23710
  ...existing,
23827
23711
  tokenMap: {
23828
- ...existing.tokenMap,
23712
+ ...existing.tokenMap ?? {},
23829
23713
  [token]: { ...tokenEntry, revoked: true }
23830
23714
  },
23831
23715
  updatedAt: Date.now()
@@ -23834,70 +23718,6 @@ var PersonaManager = class {
23834
23718
  this.deps.registry.set(updated);
23835
23719
  return updated;
23836
23720
  }
23837
- /**
23838
- * 拿到(或按需创建)该 (token, chatId) 对应的 sub-session。subSessionId 由 personaId + token +
23839
- * chatId 三元组 hash 派生,保证 idempotent:同一 tuple 永远复用同一个 sub-session。
23840
- *
23841
- * chatId 必填 —— listener 协议从 v2 起拆 phase:客户端 auth 后通过 chat:open(chatId, autoCreate?)
23842
- * 显式进入某个 chat;不再有 'default' 默认值。chatId 通常是客户端生成的 uuid,落 listener 端
23843
- * 持久化由客户端负责(daemon 不替客户端记忆"最近哪个 chat")。
23844
- *
23845
- * 创建路径:开启 idleKillEnabled,cwd 取 persona 目录,不再硬编码 permission mode / tool allowlist
23846
- * (sandbox 约束移到 persona dir 下的 .claude/settings.json,由 OS-level Seatbelt 执行)
23847
- */
23848
- getOrCreateSubSession(personaId, token, chatId) {
23849
- const persona = this.deps.registry.get(personaId);
23850
- if (!persona) throw new Error(`persona not found: ${personaId}`);
23851
- if (!chatId) throw new Error("chatId is required");
23852
- const subSessionId = this.deriveSubSessionId(personaId, token, chatId);
23853
- const scope = { kind: "persona", personaId, mode: "listener" };
23854
- const existing = this.deps.sessionManager.readForScope(subSessionId, scope);
23855
- if (existing) {
23856
- return { sessionFile: existing, isNew: false };
23857
- }
23858
- const tokenEntry = persona.tokenMap[token];
23859
- const subLabel = tokenEntry?.label ?? "unknown";
23860
- const sessionFile = this.deps.sessionManager.createForScope({
23861
- sessionId: subSessionId,
23862
- scope,
23863
- cwd: this.deps.store.personaDirPath(personaId),
23864
- tool: "claude",
23865
- label: subLabel,
23866
- model: persona.model,
23867
- chatId
23868
- });
23869
- return { sessionFile, isNew: true };
23870
- }
23871
- /**
23872
- * 检查 (token, chatId) 对应的 sub-session 是否存在;不存在返回 null。
23873
- * chat:open(autoCreate=false) 路径用:listener 想"只切已有 chat"时不能撞 auto-create。
23874
- */
23875
- getSubSession(personaId, token, chatId) {
23876
- if (!chatId) return null;
23877
- const subSessionId = this.deriveSubSessionId(personaId, token, chatId);
23878
- const scope = { kind: "persona", personaId, mode: "listener" };
23879
- return this.deps.sessionManager.readForScope(subSessionId, scope);
23880
- }
23881
- /**
23882
- * 校验 sessionId 是否属于 (personaId, token) tuple(前 12 字符 tokenHash 一致)。
23883
- * chat:rename / chat:delete 用:listener 只能操作自己 tuple 内的 chat。
23884
- */
23885
- tokenPrefixFor(personaId, token) {
23886
- const tokenHash = import_node_crypto3.default.createHash("sha256").update(token).digest("hex").slice(0, 12);
23887
- return `${personaId}-${tokenHash}`;
23888
- }
23889
- /**
23890
- * 老板插话:把"老板的话"作为 meta-text + metaSource='owner' 推到 sub-session 的事件流。
23891
- * 委托 SessionManager.injectOwnerMessage 路由到 reducer 'inject-owner-text' input
23892
- */
23893
- appendOwnerMessage(personaId, subSessionId, text) {
23894
- this.deps.sessionManager.injectOwnerMessage({
23895
- sessionId: subSessionId,
23896
- scope: { kind: "persona", personaId, mode: "listener" },
23897
- text
23898
- });
23899
- }
23900
- // ---------------- 内部 ----------------
23901
23721
  /**
23902
23722
  * label 转 4-16 char slug。优先用 `persona-<slug>`;若 slug 已被占用,追加 4 char
23903
23723
  * base64url 随机后缀直到不撞为止(最多 5 次,理论冲突 ≈ 2^-24,留个 panic 上限)。
@@ -23913,15 +23733,6 @@ var PersonaManager = class {
23913
23733
  }
23914
23734
  throw new Error(`failed to generate unique personaId for label=${label}`);
23915
23735
  }
23916
- /**
23917
- * subSessionId 派生:永远三元组 `${personaId}-${tokenHash12}-${chatHash8}`,无 default 特例。
23918
- * 旧 v1 实现保留 default 二元组兼容已废除。
23919
- */
23920
- deriveSubSessionId(personaId, token, chatId) {
23921
- const tokenHash = import_node_crypto3.default.createHash("sha256").update(token).digest("hex").slice(0, 12);
23922
- const chatHash = import_node_crypto3.default.createHash("sha256").update(chatId).digest("hex").slice(0, 8);
23923
- return `${personaId}-${tokenHash}-${chatHash}`;
23924
- }
23925
23736
  };
23926
23737
 
23927
23738
  // src/persona/seed.ts
@@ -25396,7 +25207,6 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
25396
25207
 
25397
25208
  // src/transport/local-ws-server.ts
25398
25209
  var import_node_http = __toESM(require("http"), 1);
25399
- var PERSONA_PATH_RE = /^\/personas\/([a-zA-Z0-9._-]+)$/;
25400
25210
  var LocalWsServer = class {
25401
25211
  constructor(opts) {
25402
25212
  this.opts = opts;
@@ -25519,8 +25329,7 @@ var LocalWsServer = class {
25519
25329
  if (!c) return;
25520
25330
  this.safeSend(c.ws, frame);
25521
25331
  }
25522
- // URL path 路由:'/' → owner mode(first-message authToken gate 走 onConnection),
25523
- // '/personas/<id>' → personaBoundHandler,其它 → close 4404
25332
+ // URL path 路由:'/' → 主连接路径;其他 close 4404
25524
25333
  routeConnection(socket, req) {
25525
25334
  const remoteAddress = req?.socket?.remoteAddress;
25526
25335
  const urlPath = (() => {
@@ -25534,15 +25343,6 @@ var LocalWsServer = class {
25534
25343
  this.onConnection(socket, remoteAddress);
25535
25344
  return;
25536
25345
  }
25537
- const personaMatch = urlPath.match(PERSONA_PATH_RE);
25538
- if (personaMatch && this.opts.personaBoundHandler) {
25539
- this.logger?.info("persona ws connected", {
25540
- personaId: personaMatch[1],
25541
- remoteAddress
25542
- });
25543
- this.opts.personaBoundHandler.handle(socket, personaMatch[1]);
25544
- return;
25545
- }
25546
25346
  try {
25547
25347
  socket.close(4404, "unknown path");
25548
25348
  } catch {
@@ -25685,434 +25485,6 @@ function removeSubscription(client, sessionId) {
25685
25485
  else client.subscribedSessions.set(sessionId, next);
25686
25486
  }
25687
25487
 
25688
- // src/transport/persona-bound-handler.ts
25689
- init_protocol();
25690
- var PersonaBoundHandler = class {
25691
- constructor(deps) {
25692
- this.deps = deps;
25693
- }
25694
- deps;
25695
- // tuple fan-out 表:tokenPrefix → 同 tuple 所有 listener ws 的 send fn 集合
25696
- tupleSubscribers = /* @__PURE__ */ new Map();
25697
- // tuple → 当前活跃 ws 数;从 0→1 emit online;从 1→0 emit offline
25698
- listenerWsCount = /* @__PURE__ */ new Map();
25699
- handle(ws, personaId) {
25700
- let phase = "pre-auth";
25701
- let scope = null;
25702
- let unsubscribeSession = null;
25703
- const send = (frame) => {
25704
- try {
25705
- ws.send(JSON.stringify(frame));
25706
- } catch (err) {
25707
- this.deps.logger.warn(`persona ws send failed: ${err.message}`);
25708
- }
25709
- };
25710
- const sendError = (requestId, code, message) => {
25711
- const errFrame = { type: "error", code, message };
25712
- if (requestId) errFrame.requestId = requestId;
25713
- send(errFrame);
25714
- };
25715
- const cleanupTupleRegistration = () => {
25716
- if (!scope) return;
25717
- const subs = this.tupleSubscribers.get(scope.tokenPrefix);
25718
- if (subs) {
25719
- subs.delete(send);
25720
- if (subs.size === 0) this.tupleSubscribers.delete(scope.tokenPrefix);
25721
- }
25722
- const before = this.listenerWsCount.get(scope.tokenPrefix) ?? 0;
25723
- const after = before - 1;
25724
- if (after <= 0) {
25725
- this.listenerWsCount.delete(scope.tokenPrefix);
25726
- this.deps.ownerBroadcast(
25727
- PersonaListenerStatusFrameSchema.parse({
25728
- type: FRAME_TYPE_PERSONA_LISTENER_STATUS,
25729
- personaId: scope.personaId,
25730
- token: scope.token,
25731
- status: "offline",
25732
- activeWsCount: 0
25733
- })
25734
- );
25735
- } else {
25736
- this.listenerWsCount.set(scope.tokenPrefix, after);
25737
- this.deps.ownerBroadcast(
25738
- PersonaListenerStatusFrameSchema.parse({
25739
- type: FRAME_TYPE_PERSONA_LISTENER_STATUS,
25740
- personaId: scope.personaId,
25741
- token: scope.token,
25742
- status: "online",
25743
- activeWsCount: after
25744
- })
25745
- );
25746
- }
25747
- };
25748
- ws.on("message", (raw) => {
25749
- let parsedRaw;
25750
- try {
25751
- parsedRaw = JSON.parse(raw.toString());
25752
- } catch {
25753
- ws.close(4400, "PROTOCOL_VIOLATION");
25754
- return;
25755
- }
25756
- if (typeof parsedRaw !== "object" || parsedRaw === null) {
25757
- ws.close(4400, "PROTOCOL_VIOLATION");
25758
- return;
25759
- }
25760
- const frame = parsedRaw;
25761
- if (phase === "pre-auth") {
25762
- const authParse = AuthRequestFrameSchema.safeParse(frame);
25763
- if (!authParse.success) {
25764
- ws.close(4400, "PROTOCOL_VIOLATION");
25765
- return;
25766
- }
25767
- const { token } = authParse.data;
25768
- const verify = this.deps.registry.verifyToken(personaId, token);
25769
- if (!verify.ok) {
25770
- const code = verify.code === "PERSONA_DELETED" ? 4404 : verify.code === "PERSONA_NOT_PUBLIC" ? 4403 : 4401;
25771
- ws.close(code, verify.code);
25772
- return;
25773
- }
25774
- const persona = this.deps.registry.get(personaId);
25775
- if (!persona) {
25776
- ws.close(4404, "PERSONA_DELETED");
25777
- return;
25778
- }
25779
- const tokenPrefix = this.deps.personaManager.tokenPrefixFor(personaId, token);
25780
- scope = { personaId, token, tokenPrefix };
25781
- phase = "tuple-watch";
25782
- let subs = this.tupleSubscribers.get(tokenPrefix);
25783
- if (!subs) {
25784
- subs = /* @__PURE__ */ new Set();
25785
- this.tupleSubscribers.set(tokenPrefix, subs);
25786
- }
25787
- subs.add(send);
25788
- const nextCount = (this.listenerWsCount.get(tokenPrefix) ?? 0) + 1;
25789
- this.listenerWsCount.set(tokenPrefix, nextCount);
25790
- this.deps.ownerBroadcast(
25791
- PersonaListenerStatusFrameSchema.parse({
25792
- type: FRAME_TYPE_PERSONA_LISTENER_STATUS,
25793
- personaId,
25794
- token,
25795
- status: "online",
25796
- activeWsCount: nextCount
25797
- })
25798
- );
25799
- send({ type: "auth:ok" });
25800
- send(
25801
- ChatListPushSchema.parse({
25802
- type: FRAME_TYPE_CHAT_LIST,
25803
- chats: this.listChatsForTuple(personaId, tokenPrefix)
25804
- })
25805
- );
25806
- return;
25807
- }
25808
- const type = frame.type;
25809
- const requestId = typeof frame.requestId === "string" ? frame.requestId : void 0;
25810
- if (type === "auth") {
25811
- ws.close(4400, "PROTOCOL_VIOLATION");
25812
- return;
25813
- }
25814
- if (type === "ping") {
25815
- send({ type: "pong", at: Date.now() });
25816
- return;
25817
- }
25818
- if (!scope) {
25819
- ws.close(4400, "PROTOCOL_VIOLATION");
25820
- return;
25821
- }
25822
- if (type === "chat:open") {
25823
- const parsed = ChatOpenRequestSchema.safeParse(frame);
25824
- if (!parsed.success) {
25825
- sendError(requestId, "VALIDATION_ERROR", parsed.error.message);
25826
- return;
25827
- }
25828
- const { chatId, autoCreate } = parsed.data;
25829
- let sessionFile;
25830
- let isNew = false;
25831
- try {
25832
- if (autoCreate) {
25833
- const r = this.deps.personaManager.getOrCreateSubSession(scope.personaId, scope.token, chatId);
25834
- sessionFile = r.sessionFile;
25835
- isNew = r.isNew;
25836
- } else {
25837
- sessionFile = this.deps.personaManager.getSubSession(scope.personaId, scope.token, chatId);
25838
- if (!sessionFile) {
25839
- sendError(requestId, "SESSION_NOT_FOUND", `chatId not found: ${chatId}`);
25840
- return;
25841
- }
25842
- }
25843
- } catch (err) {
25844
- sendError(requestId, "INTERNAL", err.message);
25845
- return;
25846
- }
25847
- if (unsubscribeSession && scope.activeSubSessionId !== sessionFile.sessionId) {
25848
- unsubscribeSession();
25849
- unsubscribeSession = null;
25850
- }
25851
- if (!unsubscribeSession) {
25852
- unsubscribeSession = this.deps.sessionManager.subscribe(
25853
- sessionFile.sessionId,
25854
- { kind: "persona", personaId: scope.personaId, mode: "listener" },
25855
- (eventFrame) => send(eventFrame)
25856
- );
25857
- }
25858
- scope.activeSubSessionId = sessionFile.sessionId;
25859
- phase = "chat-active";
25860
- const persona = this.deps.registry.get(scope.personaId);
25861
- const infoFrame = {
25862
- type: "session:info",
25863
- sessionId: sessionFile.sessionId,
25864
- chatId,
25865
- label: persona?.label ?? "",
25866
- cwd: sessionFile.cwd
25867
- };
25868
- if (persona?.model) infoFrame.model = persona.model;
25869
- if (requestId) infoFrame.requestId = requestId;
25870
- send(infoFrame);
25871
- send({
25872
- type: "session:status",
25873
- sessionId: sessionFile.sessionId,
25874
- status: this.deps.sessionManager.getCurrentStatus(sessionFile.sessionId)
25875
- });
25876
- if (isNew) {
25877
- const createdBase = {
25878
- sessionId: sessionFile.sessionId,
25879
- chatId,
25880
- label: sessionFile.label,
25881
- createdAt: sessionFile.createdAt
25882
- };
25883
- this.fanoutTuple(
25884
- scope.tokenPrefix,
25885
- ChatCreatedFrameSchema.parse({ type: FRAME_TYPE_CHAT_CREATED, ...createdBase })
25886
- );
25887
- this.deps.ownerBroadcast(
25888
- PersonaListenerChatCreatedFrameSchema.parse({
25889
- type: FRAME_TYPE_PERSONA_LISTENER_CHAT_CREATED,
25890
- personaId: scope.personaId,
25891
- token: scope.token,
25892
- ...createdBase
25893
- })
25894
- );
25895
- }
25896
- return;
25897
- }
25898
- if (type === "chat:rename") {
25899
- const parsed = ChatRenameRequestSchema.safeParse(frame);
25900
- if (!parsed.success) {
25901
- sendError(requestId, "VALIDATION_ERROR", parsed.error.message);
25902
- return;
25903
- }
25904
- const { sessionId: targetId, label } = parsed.data;
25905
- if (!this.isInTuple(targetId, scope.tokenPrefix)) {
25906
- sendError(requestId, "FORBIDDEN", "sessionId out of (persona, token) scope");
25907
- return;
25908
- }
25909
- try {
25910
- this.deps.sessionManager.renameForScope({
25911
- sessionId: targetId,
25912
- scope: { kind: "persona", personaId: scope.personaId, mode: "listener" },
25913
- label
25914
- });
25915
- } catch (err) {
25916
- const e = err;
25917
- sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
25918
- return;
25919
- }
25920
- if (requestId) send({ type: FRAME_TYPE_CHAT_RENAME, requestId, ok: true });
25921
- this.fanoutTuple(
25922
- scope.tokenPrefix,
25923
- ChatRenamedFrameSchema.parse({ type: FRAME_TYPE_CHAT_RENAMED, sessionId: targetId, label })
25924
- );
25925
- this.deps.ownerBroadcast(
25926
- PersonaListenerChatRenamedFrameSchema.parse({
25927
- type: FRAME_TYPE_PERSONA_LISTENER_CHAT_RENAMED,
25928
- personaId: scope.personaId,
25929
- token: scope.token,
25930
- sessionId: targetId,
25931
- label
25932
- })
25933
- );
25934
- return;
25935
- }
25936
- if (type === "chat:delete") {
25937
- const parsed = ChatDeleteRequestSchema.safeParse(frame);
25938
- if (!parsed.success) {
25939
- sendError(requestId, "VALIDATION_ERROR", parsed.error.message);
25940
- return;
25941
- }
25942
- const { sessionId: targetId } = parsed.data;
25943
- if (!this.isInTuple(targetId, scope.tokenPrefix)) {
25944
- sendError(requestId, "FORBIDDEN", "sessionId out of (persona, token) scope");
25945
- return;
25946
- }
25947
- const before = this.deps.sessionManager.readForScope(targetId, {
25948
- kind: "persona",
25949
- personaId: scope.personaId,
25950
- mode: "listener"
25951
- });
25952
- const chatIdForFrame = before?.chatId ?? "unknown";
25953
- try {
25954
- this.deps.sessionManager.deleteForScope({
25955
- sessionId: targetId,
25956
- scope: { kind: "persona", personaId: scope.personaId, mode: "listener" }
25957
- });
25958
- } catch (err) {
25959
- const e = err;
25960
- sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
25961
- return;
25962
- }
25963
- if (requestId) send({ type: FRAME_TYPE_CHAT_DELETE, requestId, ok: true });
25964
- this.fanoutTuple(
25965
- scope.tokenPrefix,
25966
- ChatDeletedFrameSchema.parse({ type: FRAME_TYPE_CHAT_DELETED, sessionId: targetId })
25967
- );
25968
- this.deps.ownerBroadcast(
25969
- PersonaListenerChatDeletedFrameSchema.parse({
25970
- type: FRAME_TYPE_PERSONA_LISTENER_CHAT_DELETED,
25971
- personaId: scope.personaId,
25972
- token: scope.token,
25973
- sessionId: targetId,
25974
- chatId: chatIdForFrame
25975
- })
25976
- );
25977
- if (scope.activeSubSessionId === targetId) {
25978
- unsubscribeSession?.();
25979
- unsubscribeSession = null;
25980
- scope.activeSubSessionId = void 0;
25981
- phase = "tuple-watch";
25982
- }
25983
- return;
25984
- }
25985
- if (phase !== "chat-active" || !scope.activeSubSessionId) {
25986
- sendError(
25987
- requestId,
25988
- "METHOD_NOT_ALLOWED",
25989
- `${typeof type === "string" ? type : "unknown"} requires chat:open first`
25990
- );
25991
- return;
25992
- }
25993
- const requireScopedSession = () => {
25994
- if (frame.sessionId !== scope.activeSubSessionId) {
25995
- sendError(requestId, "FORBIDDEN", "sessionId out of scope");
25996
- return false;
25997
- }
25998
- return true;
25999
- };
26000
- const listenerScope = () => ({
26001
- kind: "persona",
26002
- personaId: scope.personaId,
26003
- mode: "listener"
26004
- });
26005
- switch (type) {
26006
- case "session:send": {
26007
- if (!requireScopedSession()) return;
26008
- const text = frame.text;
26009
- if (typeof text !== "string") {
26010
- sendError(requestId, "VALIDATION_ERROR", "text must be string");
26011
- return;
26012
- }
26013
- try {
26014
- this.deps.sessionManager.sendForScope({
26015
- sessionId: scope.activeSubSessionId,
26016
- scope: listenerScope(),
26017
- text
26018
- });
26019
- if (requestId) send({ type: "session:send", ok: true, requestId });
26020
- } catch (err) {
26021
- const e = err;
26022
- sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
26023
- }
26024
- return;
26025
- }
26026
- case "session:new": {
26027
- if (!requireScopedSession()) return;
26028
- try {
26029
- this.deps.sessionManager.resetForScope({
26030
- sessionId: scope.activeSubSessionId,
26031
- scope: listenerScope()
26032
- });
26033
- if (requestId)
26034
- send({ type: "session:info", sessionId: scope.activeSubSessionId, requestId });
26035
- } catch (err) {
26036
- const e = err;
26037
- sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
26038
- }
26039
- return;
26040
- }
26041
- case "history:read": {
26042
- if (!requireScopedSession()) return;
26043
- const limit = typeof frame.limit === "number" && frame.limit > 0 ? frame.limit : 50;
26044
- const offset = typeof frame.offset === "number" && frame.offset >= 0 ? frame.offset : 0;
26045
- try {
26046
- const page = this.deps.sessionManager.readHistoryPageForScope(
26047
- scope.activeSubSessionId,
26048
- listenerScope(),
26049
- limit,
26050
- offset
26051
- );
26052
- if (requestId) {
26053
- send({
26054
- type: "history:read",
26055
- requestId,
26056
- events: page.events,
26057
- offset,
26058
- total: page.total
26059
- });
26060
- }
26061
- } catch (err) {
26062
- const e = err;
26063
- sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
26064
- }
26065
- return;
26066
- }
26067
- default:
26068
- sendError(
26069
- requestId,
26070
- "METHOD_NOT_ALLOWED",
26071
- `${typeof type === "string" ? type : "unknown"} not allowed for listener`
26072
- );
26073
- return;
26074
- }
26075
- });
26076
- ws.on("close", () => {
26077
- unsubscribeSession?.();
26078
- unsubscribeSession = null;
26079
- cleanupTupleRegistration();
26080
- });
26081
- }
26082
- // ---- helpers ----
26083
- fanoutTuple(tokenPrefix, frame) {
26084
- const subs = this.tupleSubscribers.get(tokenPrefix);
26085
- if (!subs) return;
26086
- for (const fn of subs) {
26087
- try {
26088
- fn(frame);
26089
- } catch {
26090
- }
26091
- }
26092
- }
26093
- isInTuple(sessionId, tokenPrefix) {
26094
- return sessionId === tokenPrefix || sessionId.startsWith(`${tokenPrefix}-`);
26095
- }
26096
- listChatsForTuple(personaId, tokenPrefix) {
26097
- const all = this.deps.sessionManager.listPersonaSessions(personaId, "listener");
26098
- return all.filter((s) => s.sessionId === tokenPrefix || s.sessionId.startsWith(`${tokenPrefix}-`)).map((s) => ({
26099
- sessionId: s.sessionId,
26100
- chatId: s.chatId ?? "unknown",
26101
- label: s.label ?? "",
26102
- createdAt: s.createdAt
26103
- }));
26104
- }
26105
- // ---------------- owner-side helpers ----------------
26106
- /**
26107
- * owner-side bootstrap RPC:拉 (personaId, token) tuple 下所有 listener sub-session 元数据。
26108
- * 由 handlers/persona.ts persona:listListenerChatsForToken 调用。
26109
- */
26110
- listChatsForOwner(personaId, token) {
26111
- const tokenPrefix = this.deps.personaManager.tokenPrefixFor(personaId, token);
26112
- return this.listChatsForTuple(personaId, tokenPrefix);
26113
- }
26114
- };
26115
-
26116
25488
  // src/transport/auth.ts
26117
25489
  init_protocol();
26118
25490
  var AuthGate = class {
@@ -27475,14 +26847,24 @@ var AUTH_FILE_NAME = "auth.json";
27475
26847
  function authFilePath(dataDir) {
27476
26848
  return import_node_path22.default.join(dataDir, AUTH_FILE_NAME);
27477
26849
  }
27478
- function loadOrCreateAuthToken(opts) {
26850
+ function loadOrCreateAuthFile(opts) {
27479
26851
  const file = authFilePath(opts.dataDir);
26852
+ const generate = opts.generate ?? defaultGenerate;
26853
+ const now = opts.now ?? (() => /* @__PURE__ */ new Date());
27480
26854
  const existing = readAuthFile(file);
27481
- if (existing && existing.token) return existing.token;
27482
- const token = (opts.generate ?? defaultGenerate)();
27483
- const now = (opts.now ?? (() => /* @__PURE__ */ new Date()))();
27484
- writeAuthFile(file, { token, createdAt: now.toISOString() });
27485
- 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;
27486
26868
  }
27487
26869
  function defaultGenerate() {
27488
26870
  return import_node_crypto8.default.randomBytes(32).toString("base64url");
@@ -27491,13 +26873,14 @@ function readAuthFile(file) {
27491
26873
  try {
27492
26874
  const raw = import_node_fs20.default.readFileSync(file, "utf8");
27493
26875
  const parsed = JSON.parse(raw);
27494
- if (typeof parsed?.token === "string" && parsed.token.length > 0) {
27495
- return {
27496
- token: parsed.token,
27497
- createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : (/* @__PURE__ */ new Date(0)).toISOString()
27498
- };
26876
+ if (typeof parsed?.token !== "string" || parsed.token.length === 0) {
26877
+ return null;
27499
26878
  }
27500
- 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
+ };
27501
26884
  } catch (err) {
27502
26885
  const code = err?.code;
27503
26886
  if (code === "ENOENT") return null;
@@ -28097,7 +27480,7 @@ function buildMetaHandlers(deps) {
28097
27480
  // src/handlers/persona.ts
28098
27481
  init_protocol();
28099
27482
  function buildPersonaHandlers(deps) {
28100
- const { personaManager, personaRegistry, sessionManager, personaBoundHandler } = deps;
27483
+ const { personaManager, personaRegistry } = deps;
28101
27484
  const create = async (frame) => {
28102
27485
  const { type: _type, requestId: _requestId, ...rest } = frame;
28103
27486
  const args = PersonaCreateArgsSchema.parse(rest);
@@ -28154,27 +27537,6 @@ function buildPersonaHandlers(deps) {
28154
27537
  const persona = personaManager.revokeToken(args.personaId, args.token);
28155
27538
  return { response: { type: "persona:info", ...persona } };
28156
27539
  };
28157
- const listSubSessions = async (frame) => {
28158
- const args = PersonaIdArgsSchema.parse(frame);
28159
- const sessions = sessionManager.listPersonaSessions(args.personaId, "listener");
28160
- return {
28161
- response: { type: "persona:subSessions", sessions }
28162
- };
28163
- };
28164
- const appendOwnerMessage = async (frame) => {
28165
- const args = PersonaAppendOwnerMessageArgsSchema.parse(frame);
28166
- personaManager.appendOwnerMessage(args.personaId, args.subSessionId, args.text);
28167
- return {
28168
- response: { type: "persona:appendOwnerMessage", ok: true }
28169
- };
28170
- };
28171
- const listListenerChatsForToken = async (frame) => {
28172
- const args = PersonaListListenerChatsForTokenArgsSchema.parse(frame);
28173
- const chats = personaBoundHandler.listChatsForOwner(args.personaId, args.token);
28174
- return {
28175
- response: { type: "persona:listListenerChatsForToken", chats }
28176
- };
28177
- };
28178
27540
  return {
28179
27541
  "persona:create": create,
28180
27542
  "persona:list": list,
@@ -28182,14 +27544,12 @@ function buildPersonaHandlers(deps) {
28182
27544
  "persona:update": update,
28183
27545
  "persona:delete": del,
28184
27546
  "persona:issueToken": issueToken,
28185
- "persona:revokeToken": revokeToken,
28186
- "persona:listSubSessions": listSubSessions,
28187
- "persona:listListenerChatsForToken": listListenerChatsForToken,
28188
- "persona:appendOwnerMessage": appendOwnerMessage
27547
+ "persona:revokeToken": revokeToken
28189
27548
  };
28190
27549
  }
28191
27550
 
28192
27551
  // src/handlers/attachment.ts
27552
+ var import_node_path26 = __toESM(require("path"), 1);
28193
27553
  init_protocol();
28194
27554
  init_protocol();
28195
27555
  var DEFAULT_TTL_SECONDS = 24 * 3600;
@@ -28214,8 +27574,51 @@ function buildAttachmentHandlers(deps) {
28214
27574
  "httpBaseUrl unavailable (daemon HTTP not ready)"
28215
27575
  );
28216
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
+ }
28217
27620
  const ttl = args.ttlSeconds === null ? null : args.ttlSeconds ?? DEFAULT_TTL_SECONDS;
28218
- const parts = signUrlParts(secret, args.absPath, ttl);
27621
+ const parts = signUrlParts(secret, candidateAbs, ttl);
28219
27622
  const url = buildSignedFileUrl(httpBaseUrl, parts);
28220
27623
  return {
28221
27624
  response: {
@@ -28301,6 +27704,12 @@ function buildAttachmentHandlers(deps) {
28301
27704
  "attachment.groupListPersona": groupListPersona
28302
27705
  };
28303
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
+ }
28304
27713
 
28305
27714
  // src/handlers/index.ts
28306
27715
  function buildMethodHandlers(deps) {
@@ -28314,9 +27723,7 @@ function buildMethodHandlers(deps) {
28314
27723
  ...buildMetaHandlers(deps),
28315
27724
  ...buildPersonaHandlers({
28316
27725
  personaManager: deps.personaManager,
28317
- personaRegistry: deps.personaRegistry,
28318
- sessionManager: deps.manager,
28319
- personaBoundHandler: deps.personaBoundHandler
27726
+ personaRegistry: deps.personaRegistry
28320
27727
  }),
28321
27728
  ...deps.attachment ? buildAttachmentHandlers(deps.attachment) : {}
28322
27729
  };
@@ -28326,7 +27733,7 @@ function buildMethodHandlers(deps) {
28326
27733
  async function startDaemon(config) {
28327
27734
  const logger = createLogger({
28328
27735
  level: config.logLevel,
28329
- file: import_node_path26.default.join(config.dataDir, "clawd.log")
27736
+ file: import_node_path27.default.join(config.dataDir, "clawd.log")
28330
27737
  });
28331
27738
  logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
28332
27739
  const stateMgr = new StateFileManager({ dataDir: config.dataDir });
@@ -28338,10 +27745,12 @@ async function startDaemon(config) {
28338
27745
  logger.warn("stale state file detected, overwriting", { pid: pre.existing.pid });
28339
27746
  }
28340
27747
  let resolvedAuthToken = null;
27748
+ let authFile = null;
28341
27749
  if (config.authToken && config.authToken.trim()) {
28342
27750
  resolvedAuthToken = config.authToken.trim();
28343
27751
  } else if (config.tunnel) {
28344
- resolvedAuthToken = loadOrCreateAuthToken({ dataDir: config.dataDir });
27752
+ authFile = loadOrCreateAuthFile({ dataDir: config.dataDir });
27753
+ resolvedAuthToken = authFile.token;
28345
27754
  }
28346
27755
  const authMode = resolvedAuthToken == null ? "none" : "first-message";
28347
27756
  let wsServer = null;
@@ -28358,7 +27767,7 @@ async function startDaemon(config) {
28358
27767
  const agents = new AgentsScanner();
28359
27768
  const history = new ClaudeHistoryReader();
28360
27769
  let transport = null;
28361
- 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"));
28362
27771
  const defaultsRoot = findDefaultsRoot();
28363
27772
  if (defaultsRoot) {
28364
27773
  seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
@@ -28373,7 +27782,7 @@ async function startDaemon(config) {
28373
27782
  getAdapter,
28374
27783
  historyReader: history,
28375
27784
  dataDir: config.dataDir,
28376
- personaRoot: import_node_path26.default.join(config.dataDir, "personas"),
27785
+ personaRoot: import_node_path27.default.join(config.dataDir, "personas"),
28377
27786
  personaStore,
28378
27787
  ownerDisplayName,
28379
27788
  mode: config.mode,
@@ -28396,7 +27805,7 @@ async function startDaemon(config) {
28396
27805
  // 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
28397
27806
  attachmentGroup: {
28398
27807
  onFileEdit: (input) => {
28399
- 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);
28400
27809
  let size = 0;
28401
27810
  try {
28402
27811
  size = import_node_fs24.default.statSync(absPath).size;
@@ -28473,16 +27882,6 @@ async function startDaemon(config) {
28473
27882
  }
28474
27883
  return `http://${config.host}:${config.port}`;
28475
27884
  };
28476
- const personaBoundHandler = new PersonaBoundHandler({
28477
- registry: personaRegistry,
28478
- personaManager,
28479
- personaStore,
28480
- sessionManager: manager,
28481
- logger,
28482
- ownerBroadcast: (frame) => {
28483
- transport?.broadcastAll(frame);
28484
- }
28485
- });
28486
27885
  const handlers = buildMethodHandlers({
28487
27886
  manager,
28488
27887
  workspace,
@@ -28494,7 +27893,6 @@ async function startDaemon(config) {
28494
27893
  store,
28495
27894
  personaManager,
28496
27895
  personaRegistry,
28497
- personaBoundHandler,
28498
27896
  getTunnelUrl: () => currentTunnelUrl,
28499
27897
  // ready / info 帧的 mode = daemon CC spawn 模式('sdk' | 'tui')。UI 据此挂 XtermPanel +
28500
27898
  // 订阅 session:pty / session:control;业务帧名两种 mode 完全一致,UI 业务订阅代码不变
@@ -28508,11 +27906,12 @@ async function startDaemon(config) {
28508
27906
  // 根据 sessionId 反查 scope 写盘。
28509
27907
  attachment: {
28510
27908
  groupFileStore,
27909
+ sessionStore: store,
28511
27910
  getHttpBaseUrl,
28512
- // HMAC sign secret:复用 ~/.clawd/auth.json owner token(持久跨重启)。
28513
- // noAuth 模式 resolvedAuthToken 为 null → handler 自己返 NOT_IMPLEMENTED。
28514
- getSignSecret: () => resolvedAuthToken ?? "",
28515
- // 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 走
28516
27915
  // 'persona/<pid>/owner',default 走 'default'。
28517
27916
  getSessionScope: (sessionId) => {
28518
27917
  const file = store.read(sessionId);
@@ -28535,8 +27934,9 @@ async function startDaemon(config) {
28535
27934
  personaStore,
28536
27935
  groupFileStore,
28537
27936
  sessionStore: store,
28538
- // /files HMAC verify 用同一份 owner token secret(与 attachment.signUrl 同源)
28539
- 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
28540
27940
  });
28541
27941
  wsServer = new LocalWsServer({
28542
27942
  host: config.host,
@@ -28557,7 +27957,6 @@ async function startDaemon(config) {
28557
27957
  ),
28558
27958
  protocolVersion: PROTOCOL_VERSION,
28559
27959
  authGate: authGate ?? void 0,
28560
- personaBoundHandler,
28561
27960
  // file-sharing HTTP 路由复用 daemon 同端口(spec §5 第 3 条);router 自己处理 auth + 404
28562
27961
  httpRequestHandler: httpRouter,
28563
27962
  // 订阅成功后给该 client 重放 in-flight pendingQuestions(plan: clawd-question-server-truth)。
@@ -28675,8 +28074,8 @@ async function startDaemon(config) {
28675
28074
  const lines = [
28676
28075
  `Tunnel: ${r.url}`,
28677
28076
  ...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
28678
- `Frpc config: ${import_node_path26.default.join(config.dataDir, "frpc.toml")}`,
28679
- `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")}`
28680
28079
  ];
28681
28080
  const width = Math.max(...lines.map((l) => l.length));
28682
28081
  const bar = "\u2550".repeat(width + 4);
@@ -28689,7 +28088,7 @@ ${bar}
28689
28088
 
28690
28089
  `);
28691
28090
  try {
28692
- const connectPath = import_node_path26.default.join(config.dataDir, "connect.txt");
28091
+ const connectPath = import_node_path27.default.join(config.dataDir, "connect.txt");
28693
28092
  import_node_fs24.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
28694
28093
  } catch {
28695
28094
  }