@clawos-dev/clawd 0.2.87 → 0.2.89-beta.165.fab5549

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 +338 -597
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -65,6 +65,14 @@ var init_methods = __esm({
65
65
  "session:unsubscribe",
66
66
  "session:pin",
67
67
  "session:reorderPins",
68
+ // mirror peer sessions (2026-05-26): owner-only RPC,写/删本机 mirror SessionFile —
69
+ // mirror 是「**我**在朋友 daemon 上自己创建的 session」的本机持久化副本(不是朋友的会话),
70
+ // 方便 sidebar 跨设备聚合。daemon dumb 写删,**不广播任何 push 帧** (不发 session:info /
71
+ // session:deleted);UI 是写入唯一源,双写完成后通过 cache.setData('sessions', ...) 同步
72
+ // 本地 sidebar state。native session:* handler 完全不感知 mirror (UI 操作 mirror 不复用
73
+ // native 路径)。
74
+ "peerSession:upsert",
75
+ "peerSession:remove",
68
76
  "permission:respond",
69
77
  // AskUserQuestion 表单回写:UI 答完所有 question 后调用,daemon 把答案合并进 updated_input
70
78
  // 写一条 control_response 到 CC stdin(详见 events.ts session:question JSDoc)
@@ -643,8 +651,8 @@ var init_parseUtil = __esm({
643
651
  init_errors2();
644
652
  init_en();
645
653
  makeIssue = (params) => {
646
- const { data, path: path46, errorMaps, issueData } = params;
647
- const fullPath = [...path46, ...issueData.path || []];
654
+ const { data, path: path44, errorMaps, issueData } = params;
655
+ const fullPath = [...path44, ...issueData.path || []];
648
656
  const fullIssue = {
649
657
  ...issueData,
650
658
  path: fullPath
@@ -955,11 +963,11 @@ var init_types = __esm({
955
963
  init_parseUtil();
956
964
  init_util();
957
965
  ParseInputLazyPath = class {
958
- constructor(parent, value, path46, key) {
966
+ constructor(parent, value, path44, key) {
959
967
  this._cachedPath = [];
960
968
  this.parent = parent;
961
969
  this.data = value;
962
- this._path = path46;
970
+ this._path = path44;
963
971
  this._key = key;
964
972
  }
965
973
  get path() {
@@ -4343,13 +4351,13 @@ var init_zod = __esm({
4343
4351
  });
4344
4352
 
4345
4353
  // ../protocol/src/attachment-schemas.ts
4346
- var TOKEN_ROLES, GROUP_FILE_SOURCES, GroupFileEntrySchema, AttachmentSignUrlArgs, AttachmentSignUrlResponseSchema, AttachmentGroupAddArgs, AttachmentGroupAddResponseSchema, AttachmentGroupRemoveArgs, AttachmentGroupRemoveResponseSchema, AttachmentGroupListArgs, AttachmentGroupListResponseSchema, AttachmentUploadResponseSchema;
4354
+ var TOKEN_ROLES, GROUP_FILE_SOURCES, GroupFileEntrySchema, AttachmentSignUrlArgs, AttachmentSignUrlResponseSchema, AttachmentGroupAddArgs, AttachmentGroupAddResponseSchema, AttachmentGroupRemoveArgs, AttachmentGroupRemoveResponseSchema, AttachmentGroupListArgs, AttachmentGroupListResponseSchema;
4347
4355
  var init_attachment_schemas = __esm({
4348
4356
  "../protocol/src/attachment-schemas.ts"() {
4349
4357
  "use strict";
4350
4358
  init_zod();
4351
4359
  TOKEN_ROLES = ["owner"];
4352
- GROUP_FILE_SOURCES = ["agent", "owner", "user-upload"];
4360
+ GROUP_FILE_SOURCES = ["agent", "owner"];
4353
4361
  GroupFileEntrySchema = external_exports.object({
4354
4362
  /** daemon 派发的稳定 id(用于 RPC remove / UI key) */
4355
4363
  id: external_exports.string().min(1),
@@ -4404,16 +4412,6 @@ var init_attachment_schemas = __esm({
4404
4412
  AttachmentGroupListResponseSchema = external_exports.object({
4405
4413
  entries: external_exports.array(GroupFileEntrySchema)
4406
4414
  });
4407
- AttachmentUploadResponseSchema = external_exports.object({
4408
- /** sha256(bytes) 前 16 位,前端持有的稳定 ref */
4409
- attachmentId: external_exports.string().min(1),
4410
- /** 完整 tunnel signed URL,前端拼 marker / 直接送 Anthropic 都用它 */
4411
- url: external_exports.string().min(1),
4412
- /** 落在 session 目录下的相对路径(后续 group RPC / drawer 展示用) */
4413
- relPath: external_exports.string().min(1),
4414
- sizeBytes: external_exports.number().int().nonnegative(),
4415
- mimeType: external_exports.string().min(1)
4416
- });
4417
4415
  }
4418
4416
  });
4419
4417
 
@@ -4493,7 +4491,7 @@ var init_persona_schemas = __esm({
4493
4491
  });
4494
4492
 
4495
4493
  // ../protocol/src/schemas.ts
4496
- 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, TunnelUnavailableEventSchema, InfoRunningSessionSchema, InfoResponseSchema;
4494
+ var SessionStatusSchema, UsageSchema, ContextUsageSchema, sessionMetaShape, SessionMetaSchema, ModelInfoSchema, ModeInfoSchema, ConfigFieldSchemaSchema, CapabilitiesGetArgs, CapabilitiesResponseSchema, AllowRuleSchema, SessionFileSchema, ParsedEventBase, HistoryUserMetaSchema, SubagentToolStatsSchema, StructuredPatchHunkSchema, ToolResultExtraSchema, MemoryEntrySchema, AskQuestionOptionSchema, AskQuestionItemSchema, ParsedEventSchema, SessionCreateArgs, SessionIdArgs, SessionUpdateArgs, SessionSendArgs, SessionRewindArgs, SessionRewindResponseSchema, SessionRewindDiffArgs, RewindDiffHunkSchema, RewindDiffFileSchema, SessionRewindDiffResponseSchema, SessionRewindableMessageIdsArgs, SessionRewindableMessageIdsResponseSchema, SessionResumeArgs, SessionForkArgs, SessionForkResponseSchema, SessionObserveArgs, SessionEventsArgs, PermissionRespondArgs, HistoryListArgs, HistoryReadArgs, HistorySubagentsArgs, HistorySubagentReadArgs, WorkspaceListArgs, WorkspaceReadArgs, SkillsListArgs, SkillEntrySchema, AgentEntrySchema, AgentsListArgs, AgentsListResponseSchema, SessionSubscribeArgs, SessionPinArgs, PeerSessionUpsertArgs, PeerSessionUpsertResponseSchema, PeerSessionRemoveArgs, PeerSessionRemoveResponseSchema, SessionReorderPinsArgs, GitRootArgs, GitRootResponseSchema, GitBranchArgs, GitBranchResponseSchema, GitBranchesArgs, GitBranchesResponseSchema, HistoryRecentDirsArgs, RecentDirEntrySchema, HistoryRecentDirsResponseSchema, SessionQuestionFrameSchema, SessionQuestionClearedFrameSchema, AnswerQuestionArgs, AnswerQuestionResponseSchema, CancelQuestionArgs, CancelQuestionResponseSchema, AuthRequestFrameSchema, AuthOkFrameSchema, TunnelExitedEventSchema, TunnelUnavailableEventSchema, InfoRunningSessionSchema, InfoResponseSchema;
4497
4495
  var init_schemas = __esm({
4498
4496
  "../protocol/src/schemas.ts"() {
4499
4497
  "use strict";
@@ -4621,6 +4619,35 @@ var init_schemas = __esm({
4621
4619
  * 当前为 caller 自报,daemon 不密码学验证真伪;强身份验签是独立工作(B 方案)。
4622
4620
  */
4623
4621
  creatorOwnerPrincipalId: external_exports.string().min(1).optional(),
4622
+ /**
4623
+ * Mirror peer sessions (2026-05-26): mirror 标识。**我在朋友 daemon 上以 guest ctx
4624
+ * 创建的 session**,远端是 source of truth,本机是该 session 的持久化副本,方便
4625
+ * sidebar 跨设备聚合。字段值是远端 daemon 的 ownerPrincipalId(即我接入的那位朋友)。
4626
+ *
4627
+ * 语义关键澄清:mirror **不是朋友创建给我的会话**——它是我自己的会话,只是在朋友
4628
+ * 的机器上跑。sidebar chip 显示 `@<朋友>` 暗示「位置」,不是「来源」。
4629
+ *
4630
+ * - undefined / 缺省 → 本机 native session(在我自己机器上跑)
4631
+ * - 非空 → 我的会话在该 ownerPrincipalId 朋友的 daemon 上跑,本机为持久化副本
4632
+ *
4633
+ * 与 `creatorPrincipalId` / `creatorOwnerPrincipalId` 在四类 session 下取值矩阵:
4634
+ * | 形态 | creatorPrincipalId | creatorOwnerPrincipalId | originOwnerPrincipalId |
4635
+ * |-------------------------------------|--------------------------|-------------------------|------------------------|
4636
+ * | 本机 owner 自创 | 本机 ownerPrincipalId | 本机 ownerPrincipalId | undefined |
4637
+ * | 本机被别人通过 personal token 接入创建 | 远端接入者 cap.id | 远端接入者 ownerPrincipalId | undefined |
4638
+ * | 我在朋友 daemon 上以 guest ctx 创建(mirror 回本机) | 朋友 daemon personal cap.id | 我自己 ownerPrincipalId | 朋友 ownerPrincipalId |
4639
+ *
4640
+ * UI 用法:
4641
+ * - sidebar Active/Pinned/Recents 过滤:`origin 非空 → 一律视作 self`(绕过 #739 隔离)
4642
+ * - 行内显示 `@<朋友>` chip 暗示位置
4643
+ * - 进 ChatPanel 时按 origin 路由到 `pool.ensureRemoteForOwner(origin)` 走远端
4644
+ *
4645
+ * daemon 用法:
4646
+ * - `peerSession:upsert` / `peerSession:remove` 一族 RPC 专管 mirror 写删;
4647
+ * daemon dumb write/delete,**不广播任何 push 帧**,UI 是写入唯一源
4648
+ * - native `session:*` handler 完全不感知 mirror(UI 操作 mirror 不走 native 路径)
4649
+ */
4650
+ originOwnerPrincipalId: external_exports.string().min(1).optional(),
4624
4651
  createdAt: external_exports.string().min(1),
4625
4652
  updatedAt: external_exports.string().min(1)
4626
4653
  });
@@ -4976,6 +5003,14 @@ var init_schemas = __esm({
4976
5003
  sessionId: external_exports.string().min(1),
4977
5004
  pinned: external_exports.boolean()
4978
5005
  });
5006
+ PeerSessionUpsertArgs = SessionFileSchema;
5007
+ PeerSessionUpsertResponseSchema = external_exports.object({
5008
+ type: external_exports.literal("peerSession:upsert:ok")
5009
+ });
5010
+ PeerSessionRemoveArgs = external_exports.object({ sessionId: external_exports.string().min(1) });
5011
+ PeerSessionRemoveResponseSchema = external_exports.object({
5012
+ type: external_exports.literal("peerSession:remove:ok")
5013
+ });
4979
5014
  SessionReorderPinsArgs = external_exports.object({
4980
5015
  orderedIds: external_exports.array(external_exports.string().min(1)).min(1)
4981
5016
  });
@@ -5590,8 +5625,8 @@ var require_req = __commonJS({
5590
5625
  if (req.originalUrl) {
5591
5626
  _req.url = req.originalUrl;
5592
5627
  } else {
5593
- const path46 = req.path;
5594
- _req.url = typeof path46 === "string" ? path46 : req.url ? req.url.path || req.url : void 0;
5628
+ const path44 = req.path;
5629
+ _req.url = typeof path44 === "string" ? path44 : req.url ? req.url.path || req.url : void 0;
5595
5630
  }
5596
5631
  if (req.query) {
5597
5632
  _req.query = req.query;
@@ -5756,14 +5791,14 @@ var require_redact = __commonJS({
5756
5791
  }
5757
5792
  return obj;
5758
5793
  }
5759
- function parsePath(path46) {
5794
+ function parsePath(path44) {
5760
5795
  const parts = [];
5761
5796
  let current = "";
5762
5797
  let inBrackets = false;
5763
5798
  let inQuotes = false;
5764
5799
  let quoteChar = "";
5765
- for (let i = 0; i < path46.length; i++) {
5766
- const char = path46[i];
5800
+ for (let i = 0; i < path44.length; i++) {
5801
+ const char = path44[i];
5767
5802
  if (!inBrackets && char === ".") {
5768
5803
  if (current) {
5769
5804
  parts.push(current);
@@ -5894,10 +5929,10 @@ var require_redact = __commonJS({
5894
5929
  return current;
5895
5930
  }
5896
5931
  function redactPaths(obj, paths, censor, remove = false) {
5897
- for (const path46 of paths) {
5898
- const parts = parsePath(path46);
5932
+ for (const path44 of paths) {
5933
+ const parts = parsePath(path44);
5899
5934
  if (parts.includes("*")) {
5900
- redactWildcardPath(obj, parts, censor, path46, remove);
5935
+ redactWildcardPath(obj, parts, censor, path44, remove);
5901
5936
  } else {
5902
5937
  if (remove) {
5903
5938
  removeKey(obj, parts);
@@ -5982,8 +6017,8 @@ var require_redact = __commonJS({
5982
6017
  }
5983
6018
  } else {
5984
6019
  if (afterWildcard.includes("*")) {
5985
- const wrappedCensor = typeof censor === "function" ? (value, path46) => {
5986
- const fullPath = [...pathArray.slice(0, pathLength), ...path46];
6020
+ const wrappedCensor = typeof censor === "function" ? (value, path44) => {
6021
+ const fullPath = [...pathArray.slice(0, pathLength), ...path44];
5987
6022
  return censor(value, fullPath);
5988
6023
  } : censor;
5989
6024
  redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
@@ -6018,8 +6053,8 @@ var require_redact = __commonJS({
6018
6053
  return null;
6019
6054
  }
6020
6055
  const pathStructure = /* @__PURE__ */ new Map();
6021
- for (const path46 of pathsToClone) {
6022
- const parts = parsePath(path46);
6056
+ for (const path44 of pathsToClone) {
6057
+ const parts = parsePath(path44);
6023
6058
  let current = pathStructure;
6024
6059
  for (let i = 0; i < parts.length; i++) {
6025
6060
  const part = parts[i];
@@ -6071,24 +6106,24 @@ var require_redact = __commonJS({
6071
6106
  }
6072
6107
  return cloneSelectively(obj, pathStructure);
6073
6108
  }
6074
- function validatePath(path46) {
6075
- if (typeof path46 !== "string") {
6109
+ function validatePath(path44) {
6110
+ if (typeof path44 !== "string") {
6076
6111
  throw new Error("Paths must be (non-empty) strings");
6077
6112
  }
6078
- if (path46 === "") {
6113
+ if (path44 === "") {
6079
6114
  throw new Error("Invalid redaction path ()");
6080
6115
  }
6081
- if (path46.includes("..")) {
6082
- throw new Error(`Invalid redaction path (${path46})`);
6116
+ if (path44.includes("..")) {
6117
+ throw new Error(`Invalid redaction path (${path44})`);
6083
6118
  }
6084
- if (path46.includes(",")) {
6085
- throw new Error(`Invalid redaction path (${path46})`);
6119
+ if (path44.includes(",")) {
6120
+ throw new Error(`Invalid redaction path (${path44})`);
6086
6121
  }
6087
6122
  let bracketCount = 0;
6088
6123
  let inQuotes = false;
6089
6124
  let quoteChar = "";
6090
- for (let i = 0; i < path46.length; i++) {
6091
- const char = path46[i];
6125
+ for (let i = 0; i < path44.length; i++) {
6126
+ const char = path44[i];
6092
6127
  if ((char === '"' || char === "'") && bracketCount > 0) {
6093
6128
  if (!inQuotes) {
6094
6129
  inQuotes = true;
@@ -6102,20 +6137,20 @@ var require_redact = __commonJS({
6102
6137
  } else if (char === "]" && !inQuotes) {
6103
6138
  bracketCount--;
6104
6139
  if (bracketCount < 0) {
6105
- throw new Error(`Invalid redaction path (${path46})`);
6140
+ throw new Error(`Invalid redaction path (${path44})`);
6106
6141
  }
6107
6142
  }
6108
6143
  }
6109
6144
  if (bracketCount !== 0) {
6110
- throw new Error(`Invalid redaction path (${path46})`);
6145
+ throw new Error(`Invalid redaction path (${path44})`);
6111
6146
  }
6112
6147
  }
6113
6148
  function validatePaths(paths) {
6114
6149
  if (!Array.isArray(paths)) {
6115
6150
  throw new TypeError("paths must be an array");
6116
6151
  }
6117
- for (const path46 of paths) {
6118
- validatePath(path46);
6152
+ for (const path44 of paths) {
6153
+ validatePath(path44);
6119
6154
  }
6120
6155
  }
6121
6156
  function slowRedact(options = {}) {
@@ -6283,8 +6318,8 @@ var require_redaction = __commonJS({
6283
6318
  if (shape[k2] === null) {
6284
6319
  o[k2] = (value) => topCensor(value, [k2]);
6285
6320
  } else {
6286
- const wrappedCensor = typeof censor === "function" ? (value, path46) => {
6287
- return censor(value, [k2, ...path46]);
6321
+ const wrappedCensor = typeof censor === "function" ? (value, path44) => {
6322
+ return censor(value, [k2, ...path44]);
6288
6323
  } : censor;
6289
6324
  o[k2] = Redact({
6290
6325
  paths: shape[k2],
@@ -6502,10 +6537,10 @@ var require_atomic_sleep = __commonJS({
6502
6537
  var require_sonic_boom = __commonJS({
6503
6538
  "../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
6504
6539
  "use strict";
6505
- var fs37 = require("fs");
6540
+ var fs35 = require("fs");
6506
6541
  var EventEmitter2 = require("events");
6507
6542
  var inherits = require("util").inherits;
6508
- var path46 = require("path");
6543
+ var path44 = require("path");
6509
6544
  var sleep = require_atomic_sleep();
6510
6545
  var assert = require("assert");
6511
6546
  var BUSY_WRITE_TIMEOUT = 100;
@@ -6559,20 +6594,20 @@ var require_sonic_boom = __commonJS({
6559
6594
  const mode = sonic.mode;
6560
6595
  if (sonic.sync) {
6561
6596
  try {
6562
- if (sonic.mkdir) fs37.mkdirSync(path46.dirname(file), { recursive: true });
6563
- const fd = fs37.openSync(file, flags, mode);
6597
+ if (sonic.mkdir) fs35.mkdirSync(path44.dirname(file), { recursive: true });
6598
+ const fd = fs35.openSync(file, flags, mode);
6564
6599
  fileOpened(null, fd);
6565
6600
  } catch (err) {
6566
6601
  fileOpened(err);
6567
6602
  throw err;
6568
6603
  }
6569
6604
  } else if (sonic.mkdir) {
6570
- fs37.mkdir(path46.dirname(file), { recursive: true }, (err) => {
6605
+ fs35.mkdir(path44.dirname(file), { recursive: true }, (err) => {
6571
6606
  if (err) return fileOpened(err);
6572
- fs37.open(file, flags, mode, fileOpened);
6607
+ fs35.open(file, flags, mode, fileOpened);
6573
6608
  });
6574
6609
  } else {
6575
- fs37.open(file, flags, mode, fileOpened);
6610
+ fs35.open(file, flags, mode, fileOpened);
6576
6611
  }
6577
6612
  }
6578
6613
  function SonicBoom(opts) {
@@ -6613,8 +6648,8 @@ var require_sonic_boom = __commonJS({
6613
6648
  this.flush = flushBuffer;
6614
6649
  this.flushSync = flushBufferSync;
6615
6650
  this._actualWrite = actualWriteBuffer;
6616
- fsWriteSync = () => fs37.writeSync(this.fd, this._writingBuf);
6617
- fsWrite = () => fs37.write(this.fd, this._writingBuf, this.release);
6651
+ fsWriteSync = () => fs35.writeSync(this.fd, this._writingBuf);
6652
+ fsWrite = () => fs35.write(this.fd, this._writingBuf, this.release);
6618
6653
  } else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
6619
6654
  this._writingBuf = "";
6620
6655
  this.write = write;
@@ -6623,15 +6658,15 @@ var require_sonic_boom = __commonJS({
6623
6658
  this._actualWrite = actualWrite;
6624
6659
  fsWriteSync = () => {
6625
6660
  if (Buffer.isBuffer(this._writingBuf)) {
6626
- return fs37.writeSync(this.fd, this._writingBuf);
6661
+ return fs35.writeSync(this.fd, this._writingBuf);
6627
6662
  }
6628
- return fs37.writeSync(this.fd, this._writingBuf, "utf8");
6663
+ return fs35.writeSync(this.fd, this._writingBuf, "utf8");
6629
6664
  };
6630
6665
  fsWrite = () => {
6631
6666
  if (Buffer.isBuffer(this._writingBuf)) {
6632
- return fs37.write(this.fd, this._writingBuf, this.release);
6667
+ return fs35.write(this.fd, this._writingBuf, this.release);
6633
6668
  }
6634
- return fs37.write(this.fd, this._writingBuf, "utf8", this.release);
6669
+ return fs35.write(this.fd, this._writingBuf, "utf8", this.release);
6635
6670
  };
6636
6671
  } else {
6637
6672
  throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
@@ -6688,7 +6723,7 @@ var require_sonic_boom = __commonJS({
6688
6723
  }
6689
6724
  }
6690
6725
  if (this._fsync) {
6691
- fs37.fsyncSync(this.fd);
6726
+ fs35.fsyncSync(this.fd);
6692
6727
  }
6693
6728
  const len = this._len;
6694
6729
  if (this._reopening) {
@@ -6802,7 +6837,7 @@ var require_sonic_boom = __commonJS({
6802
6837
  const onDrain = () => {
6803
6838
  if (!this._fsync) {
6804
6839
  try {
6805
- fs37.fsync(this.fd, (err) => {
6840
+ fs35.fsync(this.fd, (err) => {
6806
6841
  this._flushPending = false;
6807
6842
  cb(err);
6808
6843
  });
@@ -6904,7 +6939,7 @@ var require_sonic_boom = __commonJS({
6904
6939
  const fd = this.fd;
6905
6940
  this.once("ready", () => {
6906
6941
  if (fd !== this.fd) {
6907
- fs37.close(fd, (err) => {
6942
+ fs35.close(fd, (err) => {
6908
6943
  if (err) {
6909
6944
  return this.emit("error", err);
6910
6945
  }
@@ -6953,7 +6988,7 @@ var require_sonic_boom = __commonJS({
6953
6988
  buf = this._bufs[0];
6954
6989
  }
6955
6990
  try {
6956
- const n = Buffer.isBuffer(buf) ? fs37.writeSync(this.fd, buf) : fs37.writeSync(this.fd, buf, "utf8");
6991
+ const n = Buffer.isBuffer(buf) ? fs35.writeSync(this.fd, buf) : fs35.writeSync(this.fd, buf, "utf8");
6957
6992
  const releasedBufObj = releaseWritingBuf(buf, this._len, n);
6958
6993
  buf = releasedBufObj.writingBuf;
6959
6994
  this._len = releasedBufObj.len;
@@ -6969,7 +7004,7 @@ var require_sonic_boom = __commonJS({
6969
7004
  }
6970
7005
  }
6971
7006
  try {
6972
- fs37.fsyncSync(this.fd);
7007
+ fs35.fsyncSync(this.fd);
6973
7008
  } catch {
6974
7009
  }
6975
7010
  }
@@ -6990,7 +7025,7 @@ var require_sonic_boom = __commonJS({
6990
7025
  buf = mergeBuf(this._bufs[0], this._lens[0]);
6991
7026
  }
6992
7027
  try {
6993
- const n = fs37.writeSync(this.fd, buf);
7028
+ const n = fs35.writeSync(this.fd, buf);
6994
7029
  buf = buf.subarray(n);
6995
7030
  this._len = Math.max(this._len - n, 0);
6996
7031
  if (buf.length <= 0) {
@@ -7018,13 +7053,13 @@ var require_sonic_boom = __commonJS({
7018
7053
  this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
7019
7054
  if (this.sync) {
7020
7055
  try {
7021
- const written = Buffer.isBuffer(this._writingBuf) ? fs37.writeSync(this.fd, this._writingBuf) : fs37.writeSync(this.fd, this._writingBuf, "utf8");
7056
+ const written = Buffer.isBuffer(this._writingBuf) ? fs35.writeSync(this.fd, this._writingBuf) : fs35.writeSync(this.fd, this._writingBuf, "utf8");
7022
7057
  release(null, written);
7023
7058
  } catch (err) {
7024
7059
  release(err);
7025
7060
  }
7026
7061
  } else {
7027
- fs37.write(this.fd, this._writingBuf, release);
7062
+ fs35.write(this.fd, this._writingBuf, release);
7028
7063
  }
7029
7064
  }
7030
7065
  function actualWriteBuffer() {
@@ -7033,7 +7068,7 @@ var require_sonic_boom = __commonJS({
7033
7068
  this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
7034
7069
  if (this.sync) {
7035
7070
  try {
7036
- const written = fs37.writeSync(this.fd, this._writingBuf);
7071
+ const written = fs35.writeSync(this.fd, this._writingBuf);
7037
7072
  release(null, written);
7038
7073
  } catch (err) {
7039
7074
  release(err);
@@ -7042,7 +7077,7 @@ var require_sonic_boom = __commonJS({
7042
7077
  if (kCopyBuffer) {
7043
7078
  this._writingBuf = Buffer.from(this._writingBuf);
7044
7079
  }
7045
- fs37.write(this.fd, this._writingBuf, release);
7080
+ fs35.write(this.fd, this._writingBuf, release);
7046
7081
  }
7047
7082
  }
7048
7083
  function actualClose(sonic) {
@@ -7058,12 +7093,12 @@ var require_sonic_boom = __commonJS({
7058
7093
  sonic._lens = [];
7059
7094
  assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
7060
7095
  try {
7061
- fs37.fsync(sonic.fd, closeWrapped);
7096
+ fs35.fsync(sonic.fd, closeWrapped);
7062
7097
  } catch {
7063
7098
  }
7064
7099
  function closeWrapped() {
7065
7100
  if (sonic.fd !== 1 && sonic.fd !== 2) {
7066
- fs37.close(sonic.fd, done);
7101
+ fs35.close(sonic.fd, done);
7067
7102
  } else {
7068
7103
  done();
7069
7104
  }
@@ -7794,7 +7829,7 @@ var require_transport = __commonJS({
7794
7829
  stream.flushSync();
7795
7830
  }
7796
7831
  function transport(fullOptions) {
7797
- const { pipeline: pipeline3, targets, levels, dedupe, worker = {}, caller = getCallers(), sync = false } = fullOptions;
7832
+ const { pipeline: pipeline2, targets, levels, dedupe, worker = {}, caller = getCallers(), sync = false } = fullOptions;
7798
7833
  const options = {
7799
7834
  ...fullOptions.options
7800
7835
  };
@@ -7822,9 +7857,9 @@ var require_transport = __commonJS({
7822
7857
  };
7823
7858
  });
7824
7859
  });
7825
- } else if (pipeline3) {
7860
+ } else if (pipeline2) {
7826
7861
  target = bundlerOverrides["pino-worker"] || join11(__dirname, "worker.js");
7827
- options.pipelines = [pipeline3.map((dest) => {
7862
+ options.pipelines = [pipeline2.map((dest) => {
7828
7863
  return {
7829
7864
  ...dest,
7830
7865
  target: fixTarget(dest.target)
@@ -10198,11 +10233,11 @@ var init_lib = __esm({
10198
10233
  }
10199
10234
  }
10200
10235
  },
10201
- addToPath: function addToPath(path46, added, removed, oldPosInc, options) {
10202
- var last = path46.lastComponent;
10236
+ addToPath: function addToPath(path44, added, removed, oldPosInc, options) {
10237
+ var last = path44.lastComponent;
10203
10238
  if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
10204
10239
  return {
10205
- oldPos: path46.oldPos + oldPosInc,
10240
+ oldPos: path44.oldPos + oldPosInc,
10206
10241
  lastComponent: {
10207
10242
  count: last.count + 1,
10208
10243
  added,
@@ -10212,7 +10247,7 @@ var init_lib = __esm({
10212
10247
  };
10213
10248
  } else {
10214
10249
  return {
10215
- oldPos: path46.oldPos + oldPosInc,
10250
+ oldPos: path44.oldPos + oldPosInc,
10216
10251
  lastComponent: {
10217
10252
  count: 1,
10218
10253
  added,
@@ -10643,10 +10678,10 @@ function attachmentToHistoryMessage(o, ts) {
10643
10678
  const memories = raw.map((m2) => {
10644
10679
  if (!m2 || typeof m2 !== "object") return null;
10645
10680
  const rec = m2;
10646
- const path46 = typeof rec.path === "string" ? rec.path : null;
10681
+ const path44 = typeof rec.path === "string" ? rec.path : null;
10647
10682
  const content = typeof rec.content === "string" ? rec.content : null;
10648
- if (!path46 || content == null) return null;
10649
- const entry = { path: path46, content };
10683
+ if (!path44 || content == null) return null;
10684
+ const entry = { path: path44, content };
10650
10685
  if (typeof rec.mtimeMs === "number") entry.mtimeMs = rec.mtimeMs;
10651
10686
  return entry;
10652
10687
  }).filter((m2) => m2 !== null);
@@ -11472,10 +11507,10 @@ function parseAttachment(obj) {
11472
11507
  const memories = raw.map((m2) => {
11473
11508
  if (!m2 || typeof m2 !== "object") return null;
11474
11509
  const rec = m2;
11475
- const path46 = typeof rec.path === "string" ? rec.path : null;
11510
+ const path44 = typeof rec.path === "string" ? rec.path : null;
11476
11511
  const content = typeof rec.content === "string" ? rec.content : null;
11477
- if (!path46 || content == null) return null;
11478
- const out = { path: path46, content };
11512
+ if (!path44 || content == null) return null;
11513
+ const out = { path: path44, content };
11479
11514
  if (typeof rec.mtimeMs === "number") out.mtimeMs = rec.mtimeMs;
11480
11515
  return out;
11481
11516
  }).filter((m2) => m2 !== null);
@@ -20390,7 +20425,7 @@ var require_websocket_server = __commonJS({
20390
20425
  // src/run-case/recorder.ts
20391
20426
  function startRunCaseRecorder(opts) {
20392
20427
  const now = opts.now ?? Date.now;
20393
- const dir = import_node_path32.default.dirname(opts.recordPath);
20428
+ const dir = import_node_path30.default.dirname(opts.recordPath);
20394
20429
  let stream = null;
20395
20430
  let closing = false;
20396
20431
  let closedSettled = false;
@@ -20404,8 +20439,8 @@ function startRunCaseRecorder(opts) {
20404
20439
  });
20405
20440
  const ensureStream = () => {
20406
20441
  if (stream) return stream;
20407
- import_node_fs29.default.mkdirSync(dir, { recursive: true });
20408
- stream = import_node_fs29.default.createWriteStream(opts.recordPath, { flags: "a" });
20442
+ import_node_fs27.default.mkdirSync(dir, { recursive: true });
20443
+ stream = import_node_fs27.default.createWriteStream(opts.recordPath, { flags: "a" });
20409
20444
  stream.on("close", () => closedResolve());
20410
20445
  return stream;
20411
20446
  };
@@ -20430,12 +20465,12 @@ function startRunCaseRecorder(opts) {
20430
20465
  };
20431
20466
  return { tap, close, closed };
20432
20467
  }
20433
- var import_node_fs29, import_node_path32;
20468
+ var import_node_fs27, import_node_path30;
20434
20469
  var init_recorder = __esm({
20435
20470
  "src/run-case/recorder.ts"() {
20436
20471
  "use strict";
20437
- import_node_fs29 = __toESM(require("fs"), 1);
20438
- import_node_path32 = __toESM(require("path"), 1);
20472
+ import_node_fs27 = __toESM(require("fs"), 1);
20473
+ import_node_path30 = __toESM(require("path"), 1);
20439
20474
  }
20440
20475
  });
20441
20476
 
@@ -20478,7 +20513,7 @@ var init_wire = __esm({
20478
20513
  // src/run-case/controller.ts
20479
20514
  async function runController(opts) {
20480
20515
  const now = opts.now ?? Date.now;
20481
- const cwd = opts.cwd ?? (0, import_node_fs30.mkdtempSync)(import_node_path33.default.join(import_node_os15.default.tmpdir(), "clawd-runcase-"));
20516
+ const cwd = opts.cwd ?? (0, import_node_fs28.mkdtempSync)(import_node_path31.default.join(import_node_os15.default.tmpdir(), "clawd-runcase-"));
20482
20517
  const ownsCwd = opts.cwd === void 0;
20483
20518
  const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
20484
20519
  const spawnCtx = { cwd };
@@ -20639,19 +20674,19 @@ async function runController(opts) {
20639
20674
  if (sigintHandler) process.off("SIGINT", sigintHandler);
20640
20675
  if (ownsCwd) {
20641
20676
  try {
20642
- (0, import_node_fs30.rmSync)(cwd, { recursive: true, force: true });
20677
+ (0, import_node_fs28.rmSync)(cwd, { recursive: true, force: true });
20643
20678
  } catch {
20644
20679
  }
20645
20680
  }
20646
20681
  return exitCode ?? 0;
20647
20682
  }
20648
- var import_node_fs30, import_node_os15, import_node_path33;
20683
+ var import_node_fs28, import_node_os15, import_node_path31;
20649
20684
  var init_controller = __esm({
20650
20685
  "src/run-case/controller.ts"() {
20651
20686
  "use strict";
20652
- import_node_fs30 = require("fs");
20687
+ import_node_fs28 = require("fs");
20653
20688
  import_node_os15 = __toESM(require("os"), 1);
20654
- import_node_path33 = __toESM(require("path"), 1);
20689
+ import_node_path31 = __toESM(require("path"), 1);
20655
20690
  init_claude();
20656
20691
  init_stdout_splitter();
20657
20692
  init_permission_stdio();
@@ -20883,8 +20918,8 @@ Env (advanced):
20883
20918
  `;
20884
20919
 
20885
20920
  // src/index.ts
20886
- var import_node_path31 = __toESM(require("path"), 1);
20887
- var import_node_fs28 = __toESM(require("fs"), 1);
20921
+ var import_node_path29 = __toESM(require("path"), 1);
20922
+ var import_node_fs26 = __toESM(require("fs"), 1);
20888
20923
 
20889
20924
  // src/logger.ts
20890
20925
  var import_node_fs2 = __toESM(require("fs"), 1);
@@ -22619,18 +22654,6 @@ var SessionManager = class {
22619
22654
  (a, b2) => a.updatedAt > b2.updatedAt ? -1 : a.updatedAt < b2.updatedAt ? 1 : 0
22620
22655
  );
22621
22656
  }
22622
- /**
22623
- * 跨 scope 列出所有 owner 视角的 session(default + persona owner + persona guest),
22624
- * 附带派生的 SessionScope。attachment GC tick 用 —— 需要遍历所有活跃 session 的
22625
- * .attachments 目录 + 群文件清单求 liveSet。
22626
- */
22627
- listOwnedSessionScopes() {
22628
- return this.listAllOwned().map((file) => ({
22629
- scope: this.scopeForFile(file),
22630
- sessionId: file.sessionId,
22631
- cwd: file.cwd
22632
- }));
22633
- }
22634
22657
  // 写回 SessionFile,自动根据 ownerPersonaId / creatorPrincipalId 派生写入 scope.
22635
22658
  // 调用方原先都是 `deps.store.write(updated)`,全部走这里收口路由.
22636
22659
  writeOwned(file) {
@@ -22884,6 +22907,32 @@ var SessionManager = class {
22884
22907
  const file = this.getFile(args.sessionId);
22885
22908
  return { response: file, broadcast: [] };
22886
22909
  }
22910
+ // mirror peer sessions (2026-05-26):把来自朋友 daemon 的 SessionFile(originOwnerPrincipalId
22911
+ // 非空)持久化到本机 default store。dumb write,**不广播 push 帧**——UI 是写入唯一源,
22912
+ // 双写完成后通过 cache.setData 同步 sidebar 本地 state。
22913
+ //
22914
+ // 强制走 deps.store(即 default scope),不走 scopeForFile:mirror 物理落 default 即可。
22915
+ // 远端 creator / origin 字段原值保留(不被本机 ctx 派生覆写)。
22916
+ //
22917
+ // strip 远端 personaId:mirror SessionFile 上的 ownerPersonaId 是朋友 daemon 的 persona
22918
+ // 目录 id,本机不持有对应 persona。留着会让按 ownerPersonaId 反查的本机消费者
22919
+ // (MyPersonasSection 等)把 mirror 误聚到同 id persona 下。写入前清掉。
22920
+ //
22921
+ // 与 manager.create / manager.update 区别:
22922
+ // - 不走 newRunner / attachObserver / cwd 校验(mirror 是纯 metadata 层)
22923
+ // - 不发 session:info 帧(manager.create / .update 通过 reducer/wireCollector 发 push)
22924
+ mirrorUpsert(file) {
22925
+ const { ownerPersonaId: _stripped, ...rest } = file;
22926
+ return this.deps.store.write(rest);
22927
+ }
22928
+ // mirror peer sessions:删本机 mirror SessionFile。**跟 listAllOwned 对称扫所有 scope**
22929
+ // (default + persona/owner + persona/guest)。光删 deps.store 会有 store.delete 返回
22930
+ // false 但 handler 仍 ack `peerSession:remove:ok` 的假象(文件落到 persona scope 时
22931
+ // listAllOwned 还能扫出来 → sidebar 刷新页面 mirror 复活)。deleteOwned 跟 listAllOwned
22932
+ // 一对,对账齐全。idempotent:找不到不 throw。**不广播 session:deleted**。
22933
+ mirrorRemove(sessionId) {
22934
+ this.deleteOwned(sessionId);
22935
+ }
22887
22936
  // observer 上报"jsonl 写入了一条 user 行"时调用:在 buffer 里找到与之对应的、
22888
22937
  // 还没建立映射的最早一条 synth user_text,记下 synth → real 映射。
22889
22938
  // 找不到匹配的 synth(runner 不存在 / buffer 已被 /clear 清空 / 真 uuid 来自纯 history
@@ -27121,8 +27170,8 @@ function rmdirIfEmpty(p2) {
27121
27170
  }
27122
27171
 
27123
27172
  // src/transport/http-router.ts
27124
- var import_node_fs16 = __toESM(require("fs"), 1);
27125
- var import_node_path18 = __toESM(require("path"), 1);
27173
+ var import_node_fs15 = __toESM(require("fs"), 1);
27174
+ var import_node_path17 = __toESM(require("path"), 1);
27126
27175
 
27127
27176
  // src/attachment/mime.ts
27128
27177
  var import_node_path16 = __toESM(require("path"), 1);
@@ -27300,137 +27349,7 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
27300
27349
  return { ok: true, absPath };
27301
27350
  }
27302
27351
 
27303
- // src/attachment/upload.ts
27304
- var import_node_fs15 = __toESM(require("fs"), 1);
27305
- var import_node_path17 = __toESM(require("path"), 1);
27306
- var import_node_crypto6 = __toESM(require("crypto"), 1);
27307
- var import_promises = require("stream/promises");
27308
- var UploadError = class extends Error {
27309
- constructor(code, message) {
27310
- super(message);
27311
- this.code = code;
27312
- this.name = "UploadError";
27313
- }
27314
- code;
27315
- };
27316
- function assertValidFileName(fileName) {
27317
- if (fileName.length === 0 || fileName === "." || fileName === ".." || fileName.startsWith(".") || fileName.includes("/") || fileName.includes("\\") || fileName !== import_node_path17.default.basename(fileName)) {
27318
- throw new UploadError("INVALID_FILENAME", `fileName must be a plain basename, got: ${fileName}`);
27319
- }
27320
- }
27321
- var HASH_PREFIX_LEN = 16;
27322
- async function writeUploadedAttachment(args) {
27323
- assertValidFileName(args.fileName);
27324
- const attachmentsRoot = import_node_path17.default.join(args.sessionDir, ".attachments");
27325
- try {
27326
- import_node_fs15.default.mkdirSync(attachmentsRoot, { recursive: true });
27327
- } catch (err) {
27328
- throw new UploadError("STORAGE_ERROR", `mkdir failed: ${err.message}`);
27329
- }
27330
- const hasher = import_node_crypto6.default.createHash("sha256");
27331
- let actualSize = 0;
27332
- const tmpPath = import_node_path17.default.join(
27333
- attachmentsRoot,
27334
- `.upload-${process.pid}-${Date.now()}-${import_node_crypto6.default.randomBytes(4).toString("hex")}`
27335
- );
27336
- try {
27337
- await (0, import_promises.pipeline)(
27338
- args.body,
27339
- async function* (source) {
27340
- for await (const chunk of source) {
27341
- const buf = chunk;
27342
- actualSize += buf.length;
27343
- hasher.update(buf);
27344
- yield buf;
27345
- }
27346
- },
27347
- import_node_fs15.default.createWriteStream(tmpPath, { mode: 384 })
27348
- );
27349
- } catch (err) {
27350
- try {
27351
- import_node_fs15.default.unlinkSync(tmpPath);
27352
- } catch {
27353
- }
27354
- throw new UploadError("STORAGE_ERROR", `write failed: ${err.message}`);
27355
- }
27356
- if (actualSize !== args.contentLength) {
27357
- try {
27358
- import_node_fs15.default.unlinkSync(tmpPath);
27359
- } catch {
27360
- }
27361
- throw new UploadError(
27362
- "SIZE_MISMATCH",
27363
- `expected ${args.contentLength} bytes, got ${actualSize}`
27364
- );
27365
- }
27366
- const attachmentId = hasher.digest("hex").slice(0, HASH_PREFIX_LEN);
27367
- const hashDir = import_node_path17.default.join(attachmentsRoot, attachmentId);
27368
- let finalFileName;
27369
- let hashDirExists = false;
27370
- try {
27371
- hashDirExists = import_node_fs15.default.statSync(hashDir).isDirectory();
27372
- } catch {
27373
- }
27374
- if (hashDirExists) {
27375
- const existing = import_node_fs15.default.readdirSync(hashDir).filter((n) => !n.startsWith("."));
27376
- finalFileName = existing[0] ?? args.fileName;
27377
- try {
27378
- import_node_fs15.default.unlinkSync(tmpPath);
27379
- } catch {
27380
- }
27381
- } else {
27382
- try {
27383
- import_node_fs15.default.mkdirSync(hashDir, { recursive: true });
27384
- finalFileName = args.fileName;
27385
- import_node_fs15.default.renameSync(tmpPath, import_node_path17.default.join(hashDir, finalFileName));
27386
- } catch (err) {
27387
- try {
27388
- import_node_fs15.default.unlinkSync(tmpPath);
27389
- } catch {
27390
- }
27391
- throw new UploadError("STORAGE_ERROR", `rename failed: ${err.message}`);
27392
- }
27393
- }
27394
- const absPath = import_node_path17.default.join(hashDir, finalFileName);
27395
- const relPath = import_node_path17.default.relative(args.sessionDir, absPath);
27396
- args.groupFileStore.upsert(args.scope, args.sessionId, {
27397
- relPath: absPath,
27398
- // 存绝对路径,与现有 agent 入清单的形态一致(attachment.ts:144 双形态兼容)
27399
- from: "user-upload",
27400
- label: args.fileName,
27401
- size: actualSize,
27402
- mime: args.mimeType
27403
- });
27404
- return { attachmentId, absPath, relPath, sizeBytes: actualSize, mimeType: args.mimeType };
27405
- }
27406
-
27407
27352
  // src/transport/http-router.ts
27408
- var UPLOAD_MIME_ALLOW = /* @__PURE__ */ new Set([
27409
- "image/png",
27410
- "image/jpeg",
27411
- "image/gif",
27412
- "image/webp",
27413
- "application/pdf"
27414
- ]);
27415
- var UPLOAD_MIME_ALLOW_PREFIX = ["text/"];
27416
- var UPLOAD_MAX_BYTES = 25 * 1024 * 1024;
27417
- var UPLOAD_CORS_HEADERS = {
27418
- "Access-Control-Allow-Origin": "*",
27419
- "Access-Control-Allow-Methods": "POST, OPTIONS",
27420
- "Access-Control-Allow-Headers": "Authorization, Content-Type",
27421
- "Access-Control-Max-Age": "86400"
27422
- };
27423
- function isUploadMimeAllowed(mime) {
27424
- if (UPLOAD_MIME_ALLOW.has(mime)) return true;
27425
- return UPLOAD_MIME_ALLOW_PREFIX.some((p2) => mime.startsWith(p2));
27426
- }
27427
- function isValidUploadFileName(fileName) {
27428
- if (fileName.length === 0) return false;
27429
- if (fileName === "." || fileName === "..") return false;
27430
- if (fileName.startsWith(".")) return false;
27431
- if (fileName.includes("/") || fileName.includes("\\")) return false;
27432
- return fileName === import_node_path18.default.basename(fileName);
27433
- }
27434
27353
  function createHttpRouter(deps) {
27435
27354
  return async (req, res) => {
27436
27355
  const url = parseUrl(req.url);
@@ -27444,14 +27363,9 @@ function createHttpRouter(deps) {
27444
27363
  });
27445
27364
  return true;
27446
27365
  }
27447
- if (!url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/") && url.pathname !== "/attachments/upload") {
27366
+ if (!url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/")) {
27448
27367
  return false;
27449
27368
  }
27450
- if (url.pathname === "/attachments/upload" && req.method === "OPTIONS") {
27451
- res.writeHead(204, UPLOAD_CORS_HEADERS);
27452
- res.end();
27453
- return true;
27454
- }
27455
27369
  if (url.pathname.startsWith("/files/") && req.method === "GET") {
27456
27370
  const secret = deps.getSignSecret?.();
27457
27371
  if (!secret) {
@@ -27489,107 +27403,6 @@ function createHttpRouter(deps) {
27489
27403
  sendJson(res, 401, { code: "UNAUTHORIZED", message: "missing or invalid bearer token" });
27490
27404
  return true;
27491
27405
  }
27492
- if (url.pathname === "/attachments/upload" && req.method === "POST") {
27493
- const sendUploadJson = (status, body) => sendJson(res, status, body, UPLOAD_CORS_HEADERS);
27494
- if (ctx.role !== "owner") {
27495
- sendUploadJson(403, { code: "FORBIDDEN", message: "owner only" });
27496
- return true;
27497
- }
27498
- const up = deps.attachmentUpload;
27499
- if (!up) {
27500
- sendUploadJson(501, { code: "NOT_IMPLEMENTED", message: "attachment upload not wired" });
27501
- return true;
27502
- }
27503
- const sessionId = url.searchParams.get("sessionId");
27504
- const fileName = url.searchParams.get("fileName");
27505
- const mimeType = url.searchParams.get("mimeType");
27506
- const contentLengthRaw = req.headers["content-length"];
27507
- if (!sessionId || !fileName || !mimeType || !contentLengthRaw) {
27508
- sendUploadJson(400, {
27509
- code: "INVALID_PARAM",
27510
- message: "missing sessionId/fileName/mimeType/Content-Length"
27511
- });
27512
- return true;
27513
- }
27514
- if (!isValidUploadFileName(fileName)) {
27515
- sendUploadJson(400, {
27516
- code: "INVALID_FILENAME",
27517
- message: `fileName must be a plain basename (no path separators, no dotfiles): ${fileName}`
27518
- });
27519
- return true;
27520
- }
27521
- const contentLength = Number.parseInt(contentLengthRaw, 10);
27522
- if (!Number.isFinite(contentLength) || contentLength < 0) {
27523
- sendUploadJson(400, { code: "INVALID_PARAM", message: "malformed Content-Length" });
27524
- return true;
27525
- }
27526
- if (contentLength > UPLOAD_MAX_BYTES) {
27527
- sendUploadJson(413, {
27528
- code: "FILE_TOO_LARGE",
27529
- message: `max ${UPLOAD_MAX_BYTES} bytes`
27530
- });
27531
- return true;
27532
- }
27533
- if (!isUploadMimeAllowed(mimeType)) {
27534
- sendUploadJson(400, {
27535
- code: "UNSUPPORTED_MIME",
27536
- message: `mime ${mimeType} not allowed`
27537
- });
27538
- return true;
27539
- }
27540
- const sessionDir = up.getSessionDir(sessionId);
27541
- const scope = up.getSessionScope(sessionId);
27542
- if (!sessionDir || !scope) {
27543
- sendUploadJson(404, { code: "NOT_FOUND", message: `session ${sessionId} not found` });
27544
- return true;
27545
- }
27546
- const httpBaseUrl = up.getHttpBaseUrl();
27547
- if (!httpBaseUrl) {
27548
- sendUploadJson(503, {
27549
- code: "TUNNEL_NOT_READY",
27550
- message: "httpBaseUrl unavailable"
27551
- });
27552
- return true;
27553
- }
27554
- const secret = deps.getSignSecret?.();
27555
- if (!secret) {
27556
- sendUploadJson(501, {
27557
- code: "NOT_IMPLEMENTED",
27558
- message: "sign secret unavailable (noAuth?)"
27559
- });
27560
- return true;
27561
- }
27562
- try {
27563
- const result = await writeUploadedAttachment({
27564
- sessionDir,
27565
- sessionId,
27566
- scope,
27567
- fileName,
27568
- mimeType,
27569
- contentLength,
27570
- body: req,
27571
- groupFileStore: up.groupFileStore
27572
- });
27573
- const parts = signUrlParts(secret, result.absPath, null);
27574
- const fullUrl = buildSignedFileUrl(httpBaseUrl, parts);
27575
- sendUploadJson(200, {
27576
- attachmentId: result.attachmentId,
27577
- url: fullUrl,
27578
- relPath: result.relPath,
27579
- sizeBytes: result.sizeBytes,
27580
- mimeType: result.mimeType
27581
- });
27582
- } catch (err) {
27583
- if (err instanceof UploadError) {
27584
- const status = err.code === "SIZE_MISMATCH" ? 400 : 500;
27585
- sendUploadJson(status, { code: err.code, message: err.message });
27586
- } else {
27587
- deps.logger?.warn("attachment upload failed", { err: err.message });
27588
- sendUploadJson(500, { code: "STORAGE_ERROR", message: err.message });
27589
- }
27590
- }
27591
- return true;
27592
- }
27593
27406
  const sessionFilesMatch = url.pathname.match(/^\/session\/([^/]+)\/files$/);
27594
27407
  if (sessionFilesMatch && req.method === "GET") {
27595
27408
  const sid = sessionFilesMatch[1];
@@ -27599,7 +27412,7 @@ function createHttpRouter(deps) {
27599
27412
  return true;
27600
27413
  }
27601
27414
  let absPath;
27602
- if (import_node_path18.default.isAbsolute(pathParam)) {
27415
+ if (import_node_path17.default.isAbsolute(pathParam)) {
27603
27416
  absPath = pathParam;
27604
27417
  } else if (deps.sessionStore) {
27605
27418
  const file = deps.sessionStore.read(sid);
@@ -27607,7 +27420,7 @@ function createHttpRouter(deps) {
27607
27420
  sendJson(res, 404, { code: "NOT_FOUND", message: `session ${sid} not found` });
27608
27421
  return true;
27609
27422
  }
27610
- absPath = import_node_path18.default.join(file.cwd, pathParam);
27423
+ absPath = import_node_path17.default.join(file.cwd, pathParam);
27611
27424
  } else {
27612
27425
  sendJson(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "sessionStore not wired" }));
27613
27426
  return true;
@@ -27640,7 +27453,7 @@ function withCtx(ctx, body) {
27640
27453
  function streamFile(res, absPath, logger) {
27641
27454
  let stat;
27642
27455
  try {
27643
- stat = import_node_fs16.default.statSync(absPath);
27456
+ stat = import_node_fs15.default.statSync(absPath);
27644
27457
  } catch (err) {
27645
27458
  const code = err?.code;
27646
27459
  if (code === "ENOENT") {
@@ -27655,7 +27468,7 @@ function streamFile(res, absPath, logger) {
27655
27468
  return;
27656
27469
  }
27657
27470
  const mime = lookupMime(absPath);
27658
- const basename = import_node_path18.default.basename(absPath);
27471
+ const basename = import_node_path17.default.basename(absPath);
27659
27472
  res.writeHead(200, {
27660
27473
  "Content-Type": mime,
27661
27474
  "Content-Length": String(stat.size),
@@ -27663,7 +27476,7 @@ function streamFile(res, absPath, logger) {
27663
27476
  // 防止浏览器把任意 mime 当 html 渲染
27664
27477
  "X-Content-Type-Options": "nosniff"
27665
27478
  });
27666
- const stream = import_node_fs16.default.createReadStream(absPath);
27479
+ const stream = import_node_fs15.default.createReadStream(absPath);
27667
27480
  stream.on("error", (err) => {
27668
27481
  logger?.warn("streamFile read error", { absPath, err: err.message });
27669
27482
  res.destroy();
@@ -27671,78 +27484,10 @@ function streamFile(res, absPath, logger) {
27671
27484
  stream.pipe(res);
27672
27485
  }
27673
27486
 
27674
- // src/attachment/gc.ts
27675
- var import_node_fs17 = __toESM(require("fs"), 1);
27676
- var import_node_path19 = __toESM(require("path"), 1);
27677
- var DEFAULT_TTL_MS = 30 * 24 * 3600 * 1e3;
27678
- function runAttachmentGc(args) {
27679
- const now = (args.now ?? Date.now)();
27680
- const ttlMs = args.ttlMs ?? DEFAULT_TTL_MS;
27681
- const liveAbs = /* @__PURE__ */ new Set();
27682
- for (const { scope, sessionId } of args.sessionScopes) {
27683
- for (const entry of args.groupFileStore.list(scope, sessionId)) {
27684
- if (entry.stale) continue;
27685
- if (import_node_path19.default.isAbsolute(entry.relPath)) liveAbs.add(entry.relPath);
27686
- }
27687
- }
27688
- for (const { scope, sessionId } of args.sessionScopes) {
27689
- const sessionDir = args.getSessionCwd?.(sessionId) ?? import_node_path19.default.join(
27690
- args.dataDir,
27691
- "sessions",
27692
- ...scopeSubPath(scope).map(safeFileName),
27693
- safeFileName(sessionId)
27694
- );
27695
- const attRoot = import_node_path19.default.join(sessionDir, ".attachments");
27696
- let hashDirs;
27697
- try {
27698
- hashDirs = import_node_fs17.default.readdirSync(attRoot);
27699
- } catch (err) {
27700
- if (err.code === "ENOENT") continue;
27701
- args.logger?.warn("attachment gc: readdir failed", { attRoot, err: err.message });
27702
- continue;
27703
- }
27704
- for (const hashDir of hashDirs) {
27705
- const hashDirAbs = import_node_path19.default.join(attRoot, hashDir);
27706
- let files;
27707
- try {
27708
- files = import_node_fs17.default.readdirSync(hashDirAbs);
27709
- } catch {
27710
- continue;
27711
- }
27712
- for (const name of files) {
27713
- const file = import_node_path19.default.join(hashDirAbs, name);
27714
- let stat;
27715
- try {
27716
- stat = import_node_fs17.default.statSync(file);
27717
- } catch {
27718
- continue;
27719
- }
27720
- if (!stat.isFile()) continue;
27721
- const age = now - stat.mtimeMs;
27722
- if (age < ttlMs) continue;
27723
- if (liveAbs.has(file)) continue;
27724
- try {
27725
- import_node_fs17.default.unlinkSync(file);
27726
- } catch (err) {
27727
- args.logger?.warn("attachment gc: unlink failed", { file, err: err.message });
27728
- }
27729
- }
27730
- try {
27731
- if (import_node_fs17.default.readdirSync(hashDirAbs).length === 0) import_node_fs17.default.rmdirSync(hashDirAbs);
27732
- } catch {
27733
- }
27734
- }
27735
- try {
27736
- if (import_node_fs17.default.readdirSync(attRoot).length === 0) import_node_fs17.default.rmdirSync(attRoot);
27737
- } catch {
27738
- }
27739
- }
27740
- }
27741
-
27742
27487
  // src/attachment/group.ts
27743
- var import_node_fs18 = __toESM(require("fs"), 1);
27744
- var import_node_path20 = __toESM(require("path"), 1);
27745
- var import_node_crypto7 = __toESM(require("crypto"), 1);
27488
+ var import_node_fs16 = __toESM(require("fs"), 1);
27489
+ var import_node_path18 = __toESM(require("path"), 1);
27490
+ var import_node_crypto6 = __toESM(require("crypto"), 1);
27746
27491
  init_protocol();
27747
27492
  var GroupFileStore = class {
27748
27493
  dataDir;
@@ -27753,11 +27498,11 @@ var GroupFileStore = class {
27753
27498
  this.logger = opts.logger;
27754
27499
  }
27755
27500
  rootForScope(scope) {
27756
- return import_node_path20.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
27501
+ return import_node_path18.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
27757
27502
  }
27758
27503
  /** 与 SessionStore.filePath 平级,扩展名 .group-files.json */
27759
27504
  filePath(scope, sessionId) {
27760
- return import_node_path20.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
27505
+ return import_node_path18.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
27761
27506
  }
27762
27507
  cacheKey(scope, sessionId) {
27763
27508
  return scope.kind === "default" ? `default::${sessionId}` : `persona:${scope.personaId}:${scope.mode}::${sessionId}`;
@@ -27766,7 +27511,7 @@ var GroupFileStore = class {
27766
27511
  readFile(scope, sessionId) {
27767
27512
  const file = this.filePath(scope, sessionId);
27768
27513
  try {
27769
- const raw = import_node_fs18.default.readFileSync(file, "utf8");
27514
+ const raw = import_node_fs16.default.readFileSync(file, "utf8");
27770
27515
  const parsed = JSON.parse(raw);
27771
27516
  if (!Array.isArray(parsed)) {
27772
27517
  this.logger?.warn("GroupFileStore.readFile: not an array; resetting session entries", {
@@ -27792,10 +27537,10 @@ var GroupFileStore = class {
27792
27537
  }
27793
27538
  writeFile(scope, sessionId, entries) {
27794
27539
  const file = this.filePath(scope, sessionId);
27795
- import_node_fs18.default.mkdirSync(import_node_path20.default.dirname(file), { recursive: true });
27540
+ import_node_fs16.default.mkdirSync(import_node_path18.default.dirname(file), { recursive: true });
27796
27541
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
27797
- import_node_fs18.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
27798
- import_node_fs18.default.renameSync(tmp, file);
27542
+ import_node_fs16.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
27543
+ import_node_fs16.default.renameSync(tmp, file);
27799
27544
  }
27800
27545
  /** 拉一份当前 session 的清单。读盘 → cache;之后调用复用 cache */
27801
27546
  list(scope, sessionId) {
@@ -27831,7 +27576,7 @@ var GroupFileStore = class {
27831
27576
  entries[idx] = next;
27832
27577
  } else {
27833
27578
  next = {
27834
- id: `gf-${import_node_crypto7.default.randomBytes(6).toString("base64url")}`,
27579
+ id: `gf-${import_node_crypto6.default.randomBytes(6).toString("base64url")}`,
27835
27580
  relPath: input.relPath,
27836
27581
  from: input.from,
27837
27582
  label: input.label,
@@ -27881,10 +27626,10 @@ var GroupFileStore = class {
27881
27626
  };
27882
27627
 
27883
27628
  // src/discovery/state-file.ts
27884
- var import_node_fs19 = __toESM(require("fs"), 1);
27885
- var import_node_path21 = __toESM(require("path"), 1);
27629
+ var import_node_fs17 = __toESM(require("fs"), 1);
27630
+ var import_node_path19 = __toESM(require("path"), 1);
27886
27631
  function defaultStateFilePath(dataDir) {
27887
- return import_node_path21.default.join(dataDir, "state.json");
27632
+ return import_node_path19.default.join(dataDir, "state.json");
27888
27633
  }
27889
27634
  function isPidAlive(pid) {
27890
27635
  if (!Number.isFinite(pid) || pid <= 0) return false;
@@ -27906,7 +27651,7 @@ var StateFileManager = class {
27906
27651
  }
27907
27652
  read() {
27908
27653
  try {
27909
- const raw = import_node_fs19.default.readFileSync(this.file, "utf8");
27654
+ const raw = import_node_fs17.default.readFileSync(this.file, "utf8");
27910
27655
  const parsed = JSON.parse(raw);
27911
27656
  return parsed;
27912
27657
  } catch {
@@ -27920,34 +27665,34 @@ var StateFileManager = class {
27920
27665
  return { status: "stale", existing };
27921
27666
  }
27922
27667
  write(state) {
27923
- import_node_fs19.default.mkdirSync(import_node_path21.default.dirname(this.file), { recursive: true });
27668
+ import_node_fs17.default.mkdirSync(import_node_path19.default.dirname(this.file), { recursive: true });
27924
27669
  const tmp = `${this.file}.tmp.${process.pid}.${Date.now()}`;
27925
- import_node_fs19.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
27926
- import_node_fs19.default.renameSync(tmp, this.file);
27670
+ import_node_fs17.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
27671
+ import_node_fs17.default.renameSync(tmp, this.file);
27927
27672
  if (process.platform !== "win32") {
27928
27673
  try {
27929
- import_node_fs19.default.chmodSync(this.file, 384);
27674
+ import_node_fs17.default.chmodSync(this.file, 384);
27930
27675
  } catch {
27931
27676
  }
27932
27677
  }
27933
27678
  }
27934
27679
  delete() {
27935
27680
  try {
27936
- import_node_fs19.default.unlinkSync(this.file);
27681
+ import_node_fs17.default.unlinkSync(this.file);
27937
27682
  } catch {
27938
27683
  }
27939
27684
  }
27940
27685
  };
27941
27686
 
27942
27687
  // src/tunnel/tunnel-manager.ts
27943
- var import_node_fs23 = __toESM(require("fs"), 1);
27944
- var import_node_path25 = __toESM(require("path"), 1);
27945
- var import_node_crypto8 = __toESM(require("crypto"), 1);
27688
+ var import_node_fs21 = __toESM(require("fs"), 1);
27689
+ var import_node_path23 = __toESM(require("path"), 1);
27690
+ var import_node_crypto7 = __toESM(require("crypto"), 1);
27946
27691
  var import_node_child_process5 = require("child_process");
27947
27692
 
27948
27693
  // src/tunnel/tunnel-store.ts
27949
- var import_node_fs20 = __toESM(require("fs"), 1);
27950
- var import_node_path22 = __toESM(require("path"), 1);
27694
+ var import_node_fs18 = __toESM(require("fs"), 1);
27695
+ var import_node_path20 = __toESM(require("path"), 1);
27951
27696
  var TunnelStore = class {
27952
27697
  constructor(filePath) {
27953
27698
  this.filePath = filePath;
@@ -27955,7 +27700,7 @@ var TunnelStore = class {
27955
27700
  filePath;
27956
27701
  async get() {
27957
27702
  try {
27958
- const raw = await import_node_fs20.default.promises.readFile(this.filePath, "utf8");
27703
+ const raw = await import_node_fs18.default.promises.readFile(this.filePath, "utf8");
27959
27704
  const obj = JSON.parse(raw);
27960
27705
  if (!isPersistedTunnel(obj)) return null;
27961
27706
  return obj;
@@ -27966,22 +27711,22 @@ var TunnelStore = class {
27966
27711
  }
27967
27712
  }
27968
27713
  async set(v2) {
27969
- const dir = import_node_path22.default.dirname(this.filePath);
27970
- await import_node_fs20.default.promises.mkdir(dir, { recursive: true });
27714
+ const dir = import_node_path20.default.dirname(this.filePath);
27715
+ await import_node_fs18.default.promises.mkdir(dir, { recursive: true });
27971
27716
  const data = JSON.stringify(v2, null, 2);
27972
27717
  const tmp = `${this.filePath}.tmp.${process.pid}.${Date.now()}`;
27973
- await import_node_fs20.default.promises.writeFile(tmp, data, { mode: 384 });
27718
+ await import_node_fs18.default.promises.writeFile(tmp, data, { mode: 384 });
27974
27719
  if (process.platform !== "win32") {
27975
27720
  try {
27976
- await import_node_fs20.default.promises.chmod(tmp, 384);
27721
+ await import_node_fs18.default.promises.chmod(tmp, 384);
27977
27722
  } catch {
27978
27723
  }
27979
27724
  }
27980
- await import_node_fs20.default.promises.rename(tmp, this.filePath);
27725
+ await import_node_fs18.default.promises.rename(tmp, this.filePath);
27981
27726
  }
27982
27727
  async clear() {
27983
27728
  try {
27984
- await import_node_fs20.default.promises.unlink(this.filePath);
27729
+ await import_node_fs18.default.promises.unlink(this.filePath);
27985
27730
  } catch (err) {
27986
27731
  const code = err?.code;
27987
27732
  if (code !== "ENOENT") throw err;
@@ -28076,12 +27821,12 @@ function escape(v2) {
28076
27821
  }
28077
27822
 
28078
27823
  // src/tunnel/frpc-binary.ts
28079
- var import_node_fs21 = __toESM(require("fs"), 1);
27824
+ var import_node_fs19 = __toESM(require("fs"), 1);
28080
27825
  var import_node_os10 = __toESM(require("os"), 1);
28081
- var import_node_path23 = __toESM(require("path"), 1);
27826
+ var import_node_path21 = __toESM(require("path"), 1);
28082
27827
  var import_node_child_process3 = require("child_process");
28083
27828
  var import_node_stream2 = require("stream");
28084
- var import_promises2 = require("stream/promises");
27829
+ var import_promises = require("stream/promises");
28085
27830
  var FRPC_VERSION = "0.68.0";
28086
27831
  var UnsupportedPlatformError = class extends Error {
28087
27832
  constructor(platform, arch) {
@@ -28110,20 +27855,20 @@ function frpcDownloadUrl(version2, p2) {
28110
27855
  }
28111
27856
  async function ensureFrpcBinary(opts) {
28112
27857
  if (opts.override) {
28113
- if (!import_node_fs21.default.existsSync(opts.override)) {
27858
+ if (!import_node_fs19.default.existsSync(opts.override)) {
28114
27859
  throw new Error(`frpc binary not found at override path: ${opts.override}`);
28115
27860
  }
28116
27861
  return opts.override;
28117
27862
  }
28118
27863
  const version2 = opts.version ?? FRPC_VERSION;
28119
27864
  const platform = opts.platform ?? detectPlatform();
28120
- const binDir = import_node_path23.default.join(opts.dataDir, "bin");
28121
- import_node_fs21.default.mkdirSync(binDir, { recursive: true });
27865
+ const binDir = import_node_path21.default.join(opts.dataDir, "bin");
27866
+ import_node_fs19.default.mkdirSync(binDir, { recursive: true });
28122
27867
  cleanupStaleArtifacts(binDir);
28123
- const stableBin = import_node_path23.default.join(binDir, "frpc");
28124
- if (import_node_fs21.default.existsSync(stableBin)) return stableBin;
27868
+ const stableBin = import_node_path21.default.join(binDir, "frpc");
27869
+ if (import_node_fs19.default.existsSync(stableBin)) return stableBin;
28125
27870
  const partialBin = `${stableBin}.partial`;
28126
- const tarballPath = import_node_path23.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
27871
+ const tarballPath = import_node_path21.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
28127
27872
  try {
28128
27873
  const url = frpcDownloadUrl(version2, platform);
28129
27874
  await downloadToFile(url, tarballPath, opts.fetchImpl);
@@ -28132,8 +27877,8 @@ async function ensureFrpcBinary(opts) {
28132
27877
  } else {
28133
27878
  await extractFrpcFromTarball(tarballPath, binDir, version2, platform, partialBin);
28134
27879
  }
28135
- import_node_fs21.default.chmodSync(partialBin, 493);
28136
- import_node_fs21.default.renameSync(partialBin, stableBin);
27880
+ import_node_fs19.default.chmodSync(partialBin, 493);
27881
+ import_node_fs19.default.renameSync(partialBin, stableBin);
28137
27882
  } finally {
28138
27883
  safeUnlink(tarballPath);
28139
27884
  safeUnlink(partialBin);
@@ -28143,15 +27888,15 @@ async function ensureFrpcBinary(opts) {
28143
27888
  function cleanupStaleArtifacts(binDir) {
28144
27889
  let entries;
28145
27890
  try {
28146
- entries = import_node_fs21.default.readdirSync(binDir);
27891
+ entries = import_node_fs19.default.readdirSync(binDir);
28147
27892
  } catch {
28148
27893
  return;
28149
27894
  }
28150
27895
  for (const name of entries) {
28151
27896
  if (name.endsWith(".partial") || name.startsWith("extract-")) {
28152
- const full = import_node_path23.default.join(binDir, name);
27897
+ const full = import_node_path21.default.join(binDir, name);
28153
27898
  try {
28154
- import_node_fs21.default.rmSync(full, { recursive: true, force: true });
27899
+ import_node_fs19.default.rmSync(full, { recursive: true, force: true });
28155
27900
  } catch {
28156
27901
  }
28157
27902
  }
@@ -28159,7 +27904,7 @@ function cleanupStaleArtifacts(binDir) {
28159
27904
  }
28160
27905
  function safeUnlink(p2) {
28161
27906
  try {
28162
- import_node_fs21.default.unlinkSync(p2);
27907
+ import_node_fs19.default.unlinkSync(p2);
28163
27908
  } catch {
28164
27909
  }
28165
27910
  }
@@ -28170,13 +27915,13 @@ async function downloadToFile(url, dest, fetchImpl) {
28170
27915
  if (!res.ok || !res.body) {
28171
27916
  throw new Error(`download failed: ${res.status} ${res.statusText}`);
28172
27917
  }
28173
- const out = import_node_fs21.default.createWriteStream(dest);
27918
+ const out = import_node_fs19.default.createWriteStream(dest);
28174
27919
  const nodeStream = import_node_stream2.Readable.fromWeb(res.body);
28175
- await (0, import_promises2.pipeline)(nodeStream, out);
27920
+ await (0, import_promises.pipeline)(nodeStream, out);
28176
27921
  }
28177
27922
  async function extractFrpcFromTarball(tarball, binDir, version2, platform, destBin) {
28178
- const work = import_node_path23.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
28179
- import_node_fs21.default.mkdirSync(work, { recursive: true });
27923
+ const work = import_node_path21.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
27924
+ import_node_fs19.default.mkdirSync(work, { recursive: true });
28180
27925
  try {
28181
27926
  await new Promise((resolve6, reject) => {
28182
27927
  const proc = (0, import_node_child_process3.spawn)("tar", ["xzf", tarball, "-C", work], { stdio: "pipe" });
@@ -28184,32 +27929,32 @@ async function extractFrpcFromTarball(tarball, binDir, version2, platform, destB
28184
27929
  proc.on("exit", (code) => code === 0 ? resolve6() : reject(new Error(`tar exited ${code}`)));
28185
27930
  });
28186
27931
  const dirName = `frp_${version2}_${platform.os}_${platform.arch}`;
28187
- const src = import_node_path23.default.join(work, dirName, "frpc");
28188
- if (!import_node_fs21.default.existsSync(src)) {
27932
+ const src = import_node_path21.default.join(work, dirName, "frpc");
27933
+ if (!import_node_fs19.default.existsSync(src)) {
28189
27934
  throw new Error(`frpc not found inside tarball at ${src}`);
28190
27935
  }
28191
- import_node_fs21.default.copyFileSync(src, destBin);
27936
+ import_node_fs19.default.copyFileSync(src, destBin);
28192
27937
  } finally {
28193
- import_node_fs21.default.rmSync(work, { recursive: true, force: true });
27938
+ import_node_fs19.default.rmSync(work, { recursive: true, force: true });
28194
27939
  }
28195
27940
  }
28196
27941
 
28197
27942
  // src/tunnel/frpc-process.ts
28198
- var import_node_fs22 = __toESM(require("fs"), 1);
28199
- var import_node_path24 = __toESM(require("path"), 1);
27943
+ var import_node_fs20 = __toESM(require("fs"), 1);
27944
+ var import_node_path22 = __toESM(require("path"), 1);
28200
27945
  var import_node_child_process4 = require("child_process");
28201
27946
  function frpcPidFilePath(dataDir) {
28202
- return import_node_path24.default.join(dataDir, "frpc.pid");
27947
+ return import_node_path22.default.join(dataDir, "frpc.pid");
28203
27948
  }
28204
27949
  function writeFrpcPid(dataDir, pid) {
28205
27950
  try {
28206
- import_node_fs22.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
27951
+ import_node_fs20.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
28207
27952
  } catch {
28208
27953
  }
28209
27954
  }
28210
27955
  function clearFrpcPid(dataDir) {
28211
27956
  try {
28212
- import_node_fs22.default.unlinkSync(frpcPidFilePath(dataDir));
27957
+ import_node_fs20.default.unlinkSync(frpcPidFilePath(dataDir));
28213
27958
  } catch {
28214
27959
  }
28215
27960
  }
@@ -28225,7 +27970,7 @@ function defaultIsPidAlive(pid) {
28225
27970
  }
28226
27971
  function defaultReadPidFile(file) {
28227
27972
  try {
28228
- return import_node_fs22.default.readFileSync(file, "utf8");
27973
+ return import_node_fs20.default.readFileSync(file, "utf8");
28229
27974
  } catch {
28230
27975
  return null;
28231
27976
  }
@@ -28241,7 +27986,7 @@ function defaultSleep(ms) {
28241
27986
  }
28242
27987
  async function killStaleFrpc(deps) {
28243
27988
  const pidFile = frpcPidFilePath(deps.dataDir);
28244
- const tomlPath = import_node_path24.default.join(deps.dataDir, "frpc.toml");
27989
+ const tomlPath = import_node_path22.default.join(deps.dataDir, "frpc.toml");
28245
27990
  const readPidFile = deps.readPidFileImpl ?? defaultReadPidFile;
28246
27991
  const isAlive = deps.isPidAliveImpl ?? defaultIsPidAlive;
28247
27992
  const killPid = deps.killPidImpl ?? defaultKillPid;
@@ -28265,7 +28010,7 @@ async function killStaleFrpc(deps) {
28265
28010
  }
28266
28011
  if (victims.size === 0) {
28267
28012
  try {
28268
- import_node_fs22.default.unlinkSync(pidFile);
28013
+ import_node_fs20.default.unlinkSync(pidFile);
28269
28014
  } catch {
28270
28015
  }
28271
28016
  return;
@@ -28276,7 +28021,7 @@ async function killStaleFrpc(deps) {
28276
28021
  }
28277
28022
  await sleep(deps.reapWaitMs ?? 300);
28278
28023
  try {
28279
- import_node_fs22.default.unlinkSync(pidFile);
28024
+ import_node_fs20.default.unlinkSync(pidFile);
28280
28025
  } catch {
28281
28026
  }
28282
28027
  }
@@ -28313,7 +28058,7 @@ var DEFAULT_TUNNEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
28313
28058
  var TunnelManager = class {
28314
28059
  constructor(deps) {
28315
28060
  this.deps = deps;
28316
- this.store = deps.store ?? new TunnelStore(import_node_path25.default.join(deps.dataDir, "tunnel.json"));
28061
+ this.store = deps.store ?? new TunnelStore(import_node_path23.default.join(deps.dataDir, "tunnel.json"));
28317
28062
  this.ttlMs = deps.ttlMs ?? DEFAULT_TUNNEL_TTL_MS;
28318
28063
  this.startupTimeoutMs = deps.startupTimeoutMs ?? 15e3;
28319
28064
  }
@@ -28440,8 +28185,8 @@ var TunnelManager = class {
28440
28185
  dataDir: this.deps.dataDir,
28441
28186
  override: this.deps.frpcBinaryOverride ?? void 0
28442
28187
  });
28443
- const tomlPath = import_node_path25.default.join(this.deps.dataDir, "frpc.toml");
28444
- const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto8.default.randomBytes(3).toString("hex")}`;
28188
+ const tomlPath = import_node_path23.default.join(this.deps.dataDir, "frpc.toml");
28189
+ const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto7.default.randomBytes(3).toString("hex")}`;
28445
28190
  const toml = buildFrpcToml({
28446
28191
  serverAddr: t.frpsHost,
28447
28192
  serverPort: t.frpsPort,
@@ -28451,12 +28196,12 @@ var TunnelManager = class {
28451
28196
  localPort,
28452
28197
  logLevel: "info"
28453
28198
  });
28454
- await import_node_fs23.default.promises.writeFile(tomlPath, toml, { mode: 384 });
28199
+ await import_node_fs21.default.promises.writeFile(tomlPath, toml, { mode: 384 });
28455
28200
  const proc = (this.deps.spawnImpl ?? import_node_child_process5.spawn)(frpcBin, ["-c", tomlPath], {
28456
28201
  stdio: ["ignore", "pipe", "pipe"]
28457
28202
  });
28458
- const logFilePath = import_node_path25.default.join(this.deps.dataDir, "frpc.log");
28459
- const logStream = import_node_fs23.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
28203
+ const logFilePath = import_node_path23.default.join(this.deps.dataDir, "frpc.log");
28204
+ const logStream = import_node_fs21.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
28460
28205
  logStream.on("error", () => {
28461
28206
  });
28462
28207
  const tee = (chunk) => {
@@ -28539,22 +28284,22 @@ async function waitForFrpcReady(proc, timeoutMs) {
28539
28284
 
28540
28285
  // src/tunnel/device-key.ts
28541
28286
  var import_node_os11 = __toESM(require("os"), 1);
28542
- var import_node_crypto9 = __toESM(require("crypto"), 1);
28287
+ var import_node_crypto8 = __toESM(require("crypto"), 1);
28543
28288
  var DERIVE_SALT = "clawd-tunnel-device-v1";
28544
28289
  function deriveStableDeviceKey(opts = {}) {
28545
28290
  const hostname = opts.hostname ?? import_node_os11.default.hostname();
28546
28291
  const uid = opts.uid ?? (typeof import_node_os11.default.userInfo === "function" ? import_node_os11.default.userInfo().uid : 0);
28547
28292
  const input = `${hostname}::${uid}`;
28548
- return import_node_crypto9.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
28293
+ return import_node_crypto8.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
28549
28294
  }
28550
28295
 
28551
28296
  // src/auth-store.ts
28552
- var import_node_fs24 = __toESM(require("fs"), 1);
28553
- var import_node_path26 = __toESM(require("path"), 1);
28554
- var import_node_crypto10 = __toESM(require("crypto"), 1);
28297
+ var import_node_fs22 = __toESM(require("fs"), 1);
28298
+ var import_node_path24 = __toESM(require("path"), 1);
28299
+ var import_node_crypto9 = __toESM(require("crypto"), 1);
28555
28300
  var AUTH_FILE_NAME = "auth.json";
28556
28301
  function authFilePath(dataDir) {
28557
- return import_node_path26.default.join(dataDir, AUTH_FILE_NAME);
28302
+ return import_node_path24.default.join(dataDir, AUTH_FILE_NAME);
28558
28303
  }
28559
28304
  function loadOrCreateAuthFile(opts) {
28560
28305
  const file = authFilePath(opts.dataDir);
@@ -28579,14 +28324,14 @@ function loadOrCreateAuthFile(opts) {
28579
28324
  return next;
28580
28325
  }
28581
28326
  function defaultGenerateToken2() {
28582
- return import_node_crypto10.default.randomBytes(32).toString("base64url");
28327
+ return import_node_crypto9.default.randomBytes(32).toString("base64url");
28583
28328
  }
28584
28329
  function defaultGenerateOwnerPrincipalId() {
28585
- return `owner-${import_node_crypto10.default.randomUUID()}`;
28330
+ return `owner-${import_node_crypto9.default.randomUUID()}`;
28586
28331
  }
28587
28332
  function readAuthFile(file) {
28588
28333
  try {
28589
- const raw = import_node_fs24.default.readFileSync(file, "utf8");
28334
+ const raw = import_node_fs22.default.readFileSync(file, "utf8");
28590
28335
  const parsed = JSON.parse(raw);
28591
28336
  if (typeof parsed?.token !== "string" || parsed.token.length === 0) {
28592
28337
  return null;
@@ -28604,25 +28349,25 @@ function readAuthFile(file) {
28604
28349
  }
28605
28350
  }
28606
28351
  function writeAuthFile(file, content) {
28607
- import_node_fs24.default.mkdirSync(import_node_path26.default.dirname(file), { recursive: true });
28608
- import_node_fs24.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
28352
+ import_node_fs22.default.mkdirSync(import_node_path24.default.dirname(file), { recursive: true });
28353
+ import_node_fs22.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
28609
28354
  try {
28610
- import_node_fs24.default.chmodSync(file, 384);
28355
+ import_node_fs22.default.chmodSync(file, 384);
28611
28356
  } catch {
28612
28357
  }
28613
28358
  }
28614
28359
 
28615
28360
  // src/owner-profile.ts
28616
- var import_node_fs25 = __toESM(require("fs"), 1);
28361
+ var import_node_fs23 = __toESM(require("fs"), 1);
28617
28362
  var import_node_os12 = __toESM(require("os"), 1);
28618
- var import_node_path27 = __toESM(require("path"), 1);
28363
+ var import_node_path25 = __toESM(require("path"), 1);
28619
28364
  var PROFILE_FILENAME = "profile.json";
28620
28365
  function loadOwnerDisplayName(dataDir) {
28621
28366
  const fallback = import_node_os12.default.userInfo().username;
28622
- const profilePath = import_node_path27.default.join(dataDir, PROFILE_FILENAME);
28367
+ const profilePath = import_node_path25.default.join(dataDir, PROFILE_FILENAME);
28623
28368
  let raw;
28624
28369
  try {
28625
- raw = import_node_fs25.default.readFileSync(profilePath, "utf8");
28370
+ raw = import_node_fs23.default.readFileSync(profilePath, "utf8");
28626
28371
  } catch {
28627
28372
  return fallback;
28628
28373
  }
@@ -28651,12 +28396,12 @@ init_protocol();
28651
28396
  init_protocol();
28652
28397
 
28653
28398
  // src/session/fork.ts
28654
- var import_node_fs26 = __toESM(require("fs"), 1);
28399
+ var import_node_fs24 = __toESM(require("fs"), 1);
28655
28400
  var import_node_os13 = __toESM(require("os"), 1);
28656
- var import_node_path28 = __toESM(require("path"), 1);
28401
+ var import_node_path26 = __toESM(require("path"), 1);
28657
28402
  init_claude_history();
28658
28403
  function readJsonlEntries(file) {
28659
- const raw = import_node_fs26.default.readFileSync(file, "utf8");
28404
+ const raw = import_node_fs24.default.readFileSync(file, "utf8");
28660
28405
  const out = [];
28661
28406
  for (const line of raw.split("\n")) {
28662
28407
  const t = line.trim();
@@ -28669,10 +28414,10 @@ function readJsonlEntries(file) {
28669
28414
  return out;
28670
28415
  }
28671
28416
  function forkSession(input) {
28672
- const baseDir = input.baseDir ?? import_node_path28.default.join(import_node_os13.default.homedir(), ".claude");
28673
- const projectDir = import_node_path28.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
28674
- const sourceFile = import_node_path28.default.join(projectDir, `${input.toolSessionId}.jsonl`);
28675
- if (!import_node_fs26.default.existsSync(sourceFile)) {
28417
+ const baseDir = input.baseDir ?? import_node_path26.default.join(import_node_os13.default.homedir(), ".claude");
28418
+ const projectDir = import_node_path26.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
28419
+ const sourceFile = import_node_path26.default.join(projectDir, `${input.toolSessionId}.jsonl`);
28420
+ if (!import_node_fs24.default.existsSync(sourceFile)) {
28676
28421
  throw new Error(`fork: source transcript not found: ${sourceFile}`);
28677
28422
  }
28678
28423
  const entries = readJsonlEntries(sourceFile);
@@ -28702,9 +28447,9 @@ function forkSession(input) {
28702
28447
  }
28703
28448
  forkedLines.push(JSON.stringify(forked));
28704
28449
  }
28705
- const forkedFilePath = import_node_path28.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
28706
- import_node_fs26.default.mkdirSync(projectDir, { recursive: true });
28707
- import_node_fs26.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
28450
+ const forkedFilePath = import_node_path26.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
28451
+ import_node_fs24.default.mkdirSync(projectDir, { recursive: true });
28452
+ import_node_fs24.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
28708
28453
  return { forkedToolSessionId, forkedFilePath };
28709
28454
  }
28710
28455
 
@@ -28992,6 +28737,28 @@ function buildSessionHandlers(deps) {
28992
28737
  }
28993
28738
  };
28994
28739
  };
28740
+ const peerSessionUpsert = async (frame, _client, ctx) => {
28741
+ if (ctx && ctx.principal.kind !== "owner") {
28742
+ throw new ClawdError(
28743
+ ERROR_CODES.UNAUTHORIZED,
28744
+ `principal ${ctx.principal.kind}:${ctx.principal.id} cannot peerSession:upsert`
28745
+ );
28746
+ }
28747
+ const file = PeerSessionUpsertArgs.parse(frame);
28748
+ manager.mirrorUpsert(file);
28749
+ return { response: { type: "peerSession:upsert:ok" }, broadcast: [] };
28750
+ };
28751
+ const peerSessionRemove = async (frame, _client, ctx) => {
28752
+ if (ctx && ctx.principal.kind !== "owner") {
28753
+ throw new ClawdError(
28754
+ ERROR_CODES.UNAUTHORIZED,
28755
+ `principal ${ctx.principal.kind}:${ctx.principal.id} cannot peerSession:remove`
28756
+ );
28757
+ }
28758
+ const args = PeerSessionRemoveArgs.parse(frame);
28759
+ manager.mirrorRemove(args.sessionId);
28760
+ return { response: { type: "peerSession:remove:ok" }, broadcast: [] };
28761
+ };
28995
28762
  return {
28996
28763
  "session:create": create,
28997
28764
  "session:list": list,
@@ -29017,7 +28784,9 @@ function buildSessionHandlers(deps) {
29017
28784
  "session:cancelQuestion": cancelQuestion,
29018
28785
  "session:pty:input": ptyInput,
29019
28786
  "session:pty:resize": ptyResize,
29020
- "session:pty:snapshot": ptySnapshot
28787
+ "session:pty:snapshot": ptySnapshot,
28788
+ "peerSession:upsert": peerSessionUpsert,
28789
+ "peerSession:remove": peerSessionRemove
29021
28790
  };
29022
28791
  }
29023
28792
 
@@ -29036,7 +28805,7 @@ function buildPermissionHandlers(deps) {
29036
28805
  }
29037
28806
 
29038
28807
  // src/handlers/history.ts
29039
- var path38 = __toESM(require("path"), 1);
28808
+ var path36 = __toESM(require("path"), 1);
29040
28809
  init_protocol();
29041
28810
 
29042
28811
  // src/session/recent-dirs.ts
@@ -29054,7 +28823,7 @@ function listRecentDirs(store, limit = 50) {
29054
28823
  }
29055
28824
 
29056
28825
  // src/permission/persona-paths.ts
29057
- var path37 = __toESM(require("path"), 1);
28826
+ var path35 = __toESM(require("path"), 1);
29058
28827
  function getAllowedPersonaIds(grants, action) {
29059
28828
  const ids = /* @__PURE__ */ new Set();
29060
28829
  for (const g2 of grants) {
@@ -29067,26 +28836,26 @@ function getAllowedPersonaIds(grants, action) {
29067
28836
  return ids;
29068
28837
  }
29069
28838
  function isGuestPathAllowed(grants, absPath, personaRoot, action = "read") {
29070
- const root = path37.resolve(personaRoot);
29071
- const target = path37.resolve(absPath);
29072
- const sep3 = root.endsWith(path37.sep) ? "" : path37.sep;
28839
+ const root = path35.resolve(personaRoot);
28840
+ const target = path35.resolve(absPath);
28841
+ const sep3 = root.endsWith(path35.sep) ? "" : path35.sep;
29073
28842
  if (!target.startsWith(root + sep3)) return false;
29074
- const rel = path37.relative(root, target);
28843
+ const rel = path35.relative(root, target);
29075
28844
  if (!rel || rel.startsWith("..")) return false;
29076
- const personaId = rel.split(path37.sep)[0];
28845
+ const personaId = rel.split(path35.sep)[0];
29077
28846
  if (!personaId) return false;
29078
28847
  const allowed = getAllowedPersonaIds(grants, action);
29079
28848
  if (allowed === "*") return true;
29080
28849
  return allowed.has(personaId);
29081
28850
  }
29082
28851
  function personaIdFromPath(absPath, personaRoot) {
29083
- const root = path37.resolve(personaRoot);
29084
- const target = path37.resolve(absPath);
29085
- const sep3 = root.endsWith(path37.sep) ? "" : path37.sep;
28852
+ const root = path35.resolve(personaRoot);
28853
+ const target = path35.resolve(absPath);
28854
+ const sep3 = root.endsWith(path35.sep) ? "" : path35.sep;
29086
28855
  if (!target.startsWith(root + sep3)) return null;
29087
- const rel = path37.relative(root, target);
28856
+ const rel = path35.relative(root, target);
29088
28857
  if (!rel || rel.startsWith("..")) return null;
29089
- const id = rel.split(path37.sep)[0];
28858
+ const id = rel.split(path35.sep)[0];
29090
28859
  return id || null;
29091
28860
  }
29092
28861
 
@@ -29111,7 +28880,7 @@ function buildHistoryHandlers(deps) {
29111
28880
  if (!pid) return false;
29112
28881
  return isGuestPathAllowed(
29113
28882
  ctx.grants,
29114
- path38.join(personaRoot, pid),
28883
+ path36.join(personaRoot, pid),
29115
28884
  personaRoot,
29116
28885
  "read"
29117
28886
  );
@@ -29122,7 +28891,7 @@ function buildHistoryHandlers(deps) {
29122
28891
  };
29123
28892
  const list = async (frame, _client, ctx) => {
29124
28893
  const args = HistoryListArgs.parse(frame);
29125
- assertGuestPath(ctx, path38.resolve(args.projectPath), personaRoot, "history:list");
28894
+ assertGuestPath(ctx, path36.resolve(args.projectPath), personaRoot, "history:list");
29126
28895
  const sessions = await history.listSessions(args);
29127
28896
  return { response: { type: "history:list", sessions } };
29128
28897
  };
@@ -29154,13 +28923,13 @@ function buildHistoryHandlers(deps) {
29154
28923
  };
29155
28924
  const subagents = async (frame, _client, ctx) => {
29156
28925
  const args = HistorySubagentsArgs.parse(frame);
29157
- assertGuestPath(ctx, path38.resolve(args.cwd), personaRoot, "history:subagents");
28926
+ assertGuestPath(ctx, path36.resolve(args.cwd), personaRoot, "history:subagents");
29158
28927
  const subs = await history.listSubagents(args);
29159
28928
  return { response: { type: "history:subagents", subagents: subs } };
29160
28929
  };
29161
28930
  const subagentRead = async (frame, _client, ctx) => {
29162
28931
  const args = HistorySubagentReadArgs.parse(frame);
29163
- assertGuestPath(ctx, path38.resolve(args.cwd), personaRoot, "history:subagent-read");
28932
+ assertGuestPath(ctx, path36.resolve(args.cwd), personaRoot, "history:subagent-read");
29164
28933
  const res = await history.readSubagent(args);
29165
28934
  return { response: { type: "history:subagent-read", ...res } };
29166
28935
  };
@@ -29168,7 +28937,7 @@ function buildHistoryHandlers(deps) {
29168
28937
  const dirs = listRecentDirs(store);
29169
28938
  if (ctx?.principal.kind === "guest" && personaRoot) {
29170
28939
  const filtered = dirs.filter(
29171
- (d) => isGuestPathAllowed(ctx.grants, path38.resolve(d.cwd), personaRoot, "read")
28940
+ (d) => isGuestPathAllowed(ctx.grants, path36.resolve(d.cwd), personaRoot, "read")
29172
28941
  );
29173
28942
  return { response: { type: "history:recentDirs", dirs: filtered } };
29174
28943
  }
@@ -29185,7 +28954,7 @@ function buildHistoryHandlers(deps) {
29185
28954
  }
29186
28955
 
29187
28956
  // src/handlers/workspace.ts
29188
- var path39 = __toESM(require("path"), 1);
28957
+ var path37 = __toESM(require("path"), 1);
29189
28958
  var os14 = __toESM(require("os"), 1);
29190
28959
  init_protocol();
29191
28960
  init_protocol();
@@ -29226,22 +28995,22 @@ function buildWorkspaceHandlers(deps) {
29226
28995
  const args = WorkspaceListArgs.parse(frame);
29227
28996
  const isGuest = ctx?.principal.kind === "guest";
29228
28997
  const fallbackCwd = isGuest && personaRoot ? personaRoot : os14.homedir();
29229
- const resolvedCwd = path39.resolve(args.cwd ?? fallbackCwd);
29230
- const target = args.path ? path39.resolve(resolvedCwd, args.path) : resolvedCwd;
28998
+ const resolvedCwd = path37.resolve(args.cwd ?? fallbackCwd);
28999
+ const target = args.path ? path37.resolve(resolvedCwd, args.path) : resolvedCwd;
29231
29000
  assertGuestPath2(ctx, target, personaRoot, "workspace:list");
29232
29001
  const res = workspace.list({ ...args, cwd: resolvedCwd });
29233
29002
  return { response: { type: "workspace:list", ...res } };
29234
29003
  };
29235
29004
  const read = async (frame, _client, ctx) => {
29236
29005
  const args = WorkspaceReadArgs.parse(frame);
29237
- const target = path39.isAbsolute(args.path) ? path39.resolve(args.path) : path39.resolve(args.cwd, args.path);
29006
+ const target = path37.isAbsolute(args.path) ? path37.resolve(args.path) : path37.resolve(args.cwd, args.path);
29238
29007
  assertGuestPath2(ctx, target, personaRoot, "workspace:read");
29239
29008
  const res = workspace.read(args);
29240
29009
  return { response: { type: "workspace:read", ...res } };
29241
29010
  };
29242
29011
  const skillsList = async (frame, _client, ctx) => {
29243
29012
  const args = SkillsListArgs.parse(frame);
29244
- const cwdAbs = path39.resolve(args.cwd);
29013
+ const cwdAbs = path37.resolve(args.cwd);
29245
29014
  assertGuestPath2(ctx, cwdAbs, personaRoot, "skills:list");
29246
29015
  const list2 = skills.list(args);
29247
29016
  if (ctx?.principal.kind === "guest" && personaRoot) {
@@ -29253,7 +29022,7 @@ function buildWorkspaceHandlers(deps) {
29253
29022
  };
29254
29023
  const agentsList = async (frame, _client, ctx) => {
29255
29024
  const args = AgentsListArgs.parse(frame);
29256
- const cwdAbs = path39.resolve(args.cwd);
29025
+ const cwdAbs = path37.resolve(args.cwd);
29257
29026
  assertGuestPath2(ctx, cwdAbs, personaRoot, "agents:list");
29258
29027
  const list2 = agents.list(args);
29259
29028
  if (ctx?.principal.kind === "guest" && personaRoot) {
@@ -29272,20 +29041,20 @@ function buildWorkspaceHandlers(deps) {
29272
29041
  }
29273
29042
 
29274
29043
  // src/handlers/git.ts
29275
- var path41 = __toESM(require("path"), 1);
29044
+ var path39 = __toESM(require("path"), 1);
29276
29045
  init_protocol();
29277
29046
  init_protocol();
29278
29047
 
29279
29048
  // src/workspace/git.ts
29280
29049
  var import_node_child_process6 = require("child_process");
29281
- var import_node_fs27 = __toESM(require("fs"), 1);
29282
- var import_node_path29 = __toESM(require("path"), 1);
29050
+ var import_node_fs25 = __toESM(require("fs"), 1);
29051
+ var import_node_path27 = __toESM(require("path"), 1);
29283
29052
  var import_node_util = require("util");
29284
29053
  var pexec = (0, import_node_util.promisify)(import_node_child_process6.execFile);
29285
29054
  function normalizePath(p2) {
29286
- const resolved = import_node_path29.default.resolve(p2);
29055
+ const resolved = import_node_path27.default.resolve(p2);
29287
29056
  try {
29288
- return import_node_fs27.default.realpathSync(resolved);
29057
+ return import_node_fs25.default.realpathSync(resolved);
29289
29058
  } catch {
29290
29059
  return resolved;
29291
29060
  }
@@ -29358,7 +29127,7 @@ async function listGitBranches(cwd) {
29358
29127
  // src/handlers/git.ts
29359
29128
  function assertGuestCwd(ctx, cwd, personaRoot, method) {
29360
29129
  if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
29361
- if (!isGuestPathAllowed(ctx.grants, path41.resolve(cwd), personaRoot, "read")) {
29130
+ if (!isGuestPathAllowed(ctx.grants, path39.resolve(cwd), personaRoot, "read")) {
29362
29131
  throw new ClawdError(
29363
29132
  ERROR_CODES.UNAUTHORIZED,
29364
29133
  `guest ${ctx.principal.id} cannot ${method} cwd ${cwd}`
@@ -29845,7 +29614,7 @@ function buildPersonaHandlers(deps) {
29845
29614
  }
29846
29615
 
29847
29616
  // src/handlers/attachment.ts
29848
- var import_node_path30 = __toESM(require("path"), 1);
29617
+ var import_node_path28 = __toESM(require("path"), 1);
29849
29618
  init_protocol();
29850
29619
  init_protocol();
29851
29620
  var DEFAULT_TTL_SECONDS = 24 * 3600;
@@ -29899,12 +29668,12 @@ function buildAttachmentHandlers(deps) {
29899
29668
  `session ${args.sessionId} scope unresolved`
29900
29669
  );
29901
29670
  }
29902
- const cwdAbs = import_node_path30.default.resolve(sessionFile.cwd);
29903
- const candidateAbs = import_node_path30.default.isAbsolute(args.relPath) ? import_node_path30.default.resolve(args.relPath) : import_node_path30.default.resolve(cwdAbs, args.relPath);
29671
+ const cwdAbs = import_node_path28.default.resolve(sessionFile.cwd);
29672
+ const candidateAbs = import_node_path28.default.isAbsolute(args.relPath) ? import_node_path28.default.resolve(args.relPath) : import_node_path28.default.resolve(cwdAbs, args.relPath);
29904
29673
  assertGuestAttachmentPath(ctx, candidateAbs, deps.personaRoot, "attachment.signUrl");
29905
29674
  const entries = deps.groupFileStore.list(scope, args.sessionId);
29906
29675
  const entry = entries.find((e) => {
29907
- const storedAbs = import_node_path30.default.isAbsolute(e.relPath) ? import_node_path30.default.resolve(e.relPath) : import_node_path30.default.resolve(cwdAbs, e.relPath);
29676
+ const storedAbs = import_node_path28.default.isAbsolute(e.relPath) ? import_node_path28.default.resolve(e.relPath) : import_node_path28.default.resolve(cwdAbs, e.relPath);
29908
29677
  return storedAbs === candidateAbs && !e.stale;
29909
29678
  });
29910
29679
  if (!entry) {
@@ -29928,7 +29697,7 @@ function buildAttachmentHandlers(deps) {
29928
29697
  if (!ctx || ctx.principal.kind !== "guest" || !deps.personaRoot || !deps.sessionStore) return;
29929
29698
  const f = deps.sessionStore.read(sessionId);
29930
29699
  if (!f) return;
29931
- assertGuestAttachmentPath(ctx, import_node_path30.default.resolve(f.cwd), deps.personaRoot, method);
29700
+ assertGuestAttachmentPath(ctx, import_node_path28.default.resolve(f.cwd), deps.personaRoot, method);
29932
29701
  }
29933
29702
  const groupAdd = async (frame, _client, ctx) => {
29934
29703
  if (!deps.groupFileStore || !deps.getSessionScope) {
@@ -29969,7 +29738,7 @@ function buildAttachmentHandlers(deps) {
29969
29738
  }
29970
29739
  const entries = deps.groupFileStore.list(scope, parsed.data.sessionId);
29971
29740
  const target = entries.find((e) => e.relPath === parsed.data.relPath);
29972
- if (target?.from === "owner" || target?.from === "user-upload") {
29741
+ if (target?.from === "owner") {
29973
29742
  deps.groupFileStore.remove(scope, parsed.data.sessionId, parsed.data.relPath);
29974
29743
  } else {
29975
29744
  deps.groupFileStore.markStale(scope, parsed.data.sessionId, parsed.data.relPath);
@@ -30095,6 +29864,11 @@ var METHOD_GRANT_MAP = {
30095
29864
  "session:pin": CAPABILITY_SCOPED,
30096
29865
  "session:reorderPins": ADMIN_ANY,
30097
29866
  // owner 全局操作,无 personaId 维度
29867
+ // mirror peer sessions (2026-05-26):owner-only — guest 不应能写/删本机 mirror SessionFile
29868
+ // (会绕过 #739 隔离规则把伪造身份的 SessionFile 写进 default store)。handler 端内部 ctx
29869
+ // 校验,dispatcher 层一刀切 admin 也兜底。
29870
+ "peerSession:upsert": ADMIN_ANY,
29871
+ "peerSession:remove": ADMIN_ANY,
30098
29872
  "permission:respond": CAPABILITY_SCOPED,
30099
29873
  "session:answerQuestion": CAPABILITY_SCOPED,
30100
29874
  "session:cancelQuestion": CAPABILITY_SCOPED,
@@ -30165,7 +29939,7 @@ function computeGrantForFrame(method, frame) {
30165
29939
  async function startDaemon(config) {
30166
29940
  const logger = createLogger({
30167
29941
  level: config.logLevel,
30168
- file: import_node_path31.default.join(config.dataDir, "clawd.log")
29942
+ file: import_node_path29.default.join(config.dataDir, "clawd.log")
30169
29943
  });
30170
29944
  logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
30171
29945
  const stateMgr = new StateFileManager({ dataDir: config.dataDir });
@@ -30252,7 +30026,7 @@ async function startDaemon(config) {
30252
30026
  const agents = new AgentsScanner();
30253
30027
  const history = new ClaudeHistoryReader();
30254
30028
  let transport = null;
30255
- const personaStore = new PersonaStore(import_node_path31.default.join(config.dataDir, "personas"));
30029
+ const personaStore = new PersonaStore(import_node_path29.default.join(config.dataDir, "personas"));
30256
30030
  const defaultsRoot = findDefaultsRoot();
30257
30031
  if (defaultsRoot) {
30258
30032
  seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
@@ -30269,7 +30043,7 @@ async function startDaemon(config) {
30269
30043
  getAdapter,
30270
30044
  historyReader: history,
30271
30045
  dataDir: config.dataDir,
30272
- personaRoot: import_node_path31.default.join(config.dataDir, "personas"),
30046
+ personaRoot: import_node_path29.default.join(config.dataDir, "personas"),
30273
30047
  personaStore,
30274
30048
  ownerDisplayName,
30275
30049
  ownerPrincipalId,
@@ -30293,10 +30067,10 @@ async function startDaemon(config) {
30293
30067
  // 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
30294
30068
  attachmentGroup: {
30295
30069
  onFileEdit: (input) => {
30296
- const absPath = import_node_path31.default.isAbsolute(input.relPath) ? input.relPath : import_node_path31.default.join(input.cwd, input.relPath);
30070
+ const absPath = import_node_path29.default.isAbsolute(input.relPath) ? input.relPath : import_node_path29.default.join(input.cwd, input.relPath);
30297
30071
  let size = 0;
30298
30072
  try {
30299
- size = import_node_fs28.default.statSync(absPath).size;
30073
+ size = import_node_fs26.default.statSync(absPath).size;
30300
30074
  } catch (err) {
30301
30075
  logger.warn("attachment.onFileEdit stat failed", {
30302
30076
  sessionId: input.sessionId,
@@ -30414,10 +30188,10 @@ async function startDaemon(config) {
30414
30188
  // 'persona/<pid>/owner',default 走 'default'。
30415
30189
  getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
30416
30190
  // guest path guard:candidate 必须在 personaRoot 子树下
30417
- personaRoot: import_node_path31.default.join(config.dataDir, "personas")
30191
+ personaRoot: import_node_path29.default.join(config.dataDir, "personas")
30418
30192
  },
30419
30193
  // workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
30420
- personaRoot: import_node_path31.default.join(config.dataDir, "personas"),
30194
+ personaRoot: import_node_path29.default.join(config.dataDir, "personas"),
30421
30195
  // capability:list / delete handler 依赖
30422
30196
  capabilityManager,
30423
30197
  // personal-cap:get 返回的 shareBaseUrl 用此 base URL:
@@ -30453,19 +30227,7 @@ async function startDaemon(config) {
30453
30227
  sessionStore: store,
30454
30228
  // /files HMAC verify 用 auth.json 的 signSecret 字段(与 attachment.signUrl 同源)。
30455
30229
  // --auth-token CLI 模式没 signSecret → 路由返 501,sign URL 功能整体禁用。
30456
- getSignSecret: () => authFile?.signSecret ?? null,
30457
- // chatinput 附件上传(POST /attachments/upload,spec §6.1)。upload pipeline
30458
- // 在 session.cwd 下的 .attachments/ 落盘;signSecret 与 /files HMAC 同源;
30459
- // tunnel 未启时返 503 TUNNEL_NOT_READY 让前端显式提示。
30460
- attachmentUpload: {
30461
- groupFileStore,
30462
- getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
30463
- getSessionDir: (sid) => {
30464
- const f = manager.findOwnedSession(sid);
30465
- return f ? f.cwd : null;
30466
- },
30467
- getHttpBaseUrl: () => currentTunnelUrl ? getHttpBaseUrl() : null
30468
- }
30230
+ getSignSecret: () => authFile?.signSecret ?? null
30469
30231
  });
30470
30232
  wsServer = new LocalWsServer({
30471
30233
  host: config.host,
@@ -30630,8 +30392,8 @@ async function startDaemon(config) {
30630
30392
  const lines = [
30631
30393
  `Tunnel: ${r.url}`,
30632
30394
  ...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
30633
- `Frpc config: ${import_node_path31.default.join(config.dataDir, "frpc.toml")}`,
30634
- `Frpc log: ${import_node_path31.default.join(config.dataDir, "frpc.log")}`
30395
+ `Frpc config: ${import_node_path29.default.join(config.dataDir, "frpc.toml")}`,
30396
+ `Frpc log: ${import_node_path29.default.join(config.dataDir, "frpc.log")}`
30635
30397
  ];
30636
30398
  const width = Math.max(...lines.map((l) => l.length));
30637
30399
  const bar = "\u2550".repeat(width + 4);
@@ -30644,8 +30406,8 @@ ${bar}
30644
30406
 
30645
30407
  `);
30646
30408
  try {
30647
- const connectPath = import_node_path31.default.join(config.dataDir, "connect.txt");
30648
- import_node_fs28.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
30409
+ const connectPath = import_node_path29.default.join(config.dataDir, "connect.txt");
30410
+ import_node_fs26.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
30649
30411
  } catch {
30650
30412
  }
30651
30413
  } catch (err) {
@@ -30670,29 +30432,8 @@ ${bar}
30670
30432
  logger.warn("tunnel unavailable, degraded to local mode", { reason: tunnelError });
30671
30433
  }
30672
30434
  }
30673
- const tickAttachmentGc = () => {
30674
- try {
30675
- runAttachmentGc({
30676
- dataDir: config.dataDir,
30677
- groupFileStore,
30678
- sessionScopes: manager.listOwnedSessionScopes().map(({ scope, sessionId }) => ({
30679
- scope,
30680
- sessionId
30681
- })),
30682
- // 注入 cwd 让 GC 直接定位 .attachments 目录(与 upload 落盘点一致)
30683
- getSessionCwd: (sid) => manager.findOwnedSession(sid)?.cwd ?? null,
30684
- logger
30685
- });
30686
- } catch (err) {
30687
- logger.warn("attachment gc tick failed", { err: err.message });
30688
- }
30689
- };
30690
- setImmediate(tickAttachmentGc);
30691
- const attachmentGcInterval = setInterval(tickAttachmentGc, 24 * 3600 * 1e3);
30692
- attachmentGcInterval.unref();
30693
30435
  const shutdown = async () => {
30694
30436
  logger.info("stopping clawd");
30695
- clearInterval(attachmentGcInterval);
30696
30437
  observer.stopAll();
30697
30438
  manager.stopAll();
30698
30439
  if (tunnelMgr) {
@@ -30710,9 +30451,9 @@ ${bar}
30710
30451
  };
30711
30452
  }
30712
30453
  function migrateDropPersonsDir(dataDir) {
30713
- const dir = import_node_path31.default.join(dataDir, "persons");
30454
+ const dir = import_node_path29.default.join(dataDir, "persons");
30714
30455
  try {
30715
- import_node_fs28.default.rmSync(dir, { recursive: true, force: true });
30456
+ import_node_fs26.default.rmSync(dir, { recursive: true, force: true });
30716
30457
  } catch {
30717
30458
  }
30718
30459
  }