@clawos-dev/clawd 0.2.86-beta.161.3250922 → 0.2.86-beta.162.77b3c16

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 +546 -665
  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: path45, errorMaps, issueData } = params;
647
- const fullPath = [...path45, ...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, path45, key) {
966
+ constructor(parent, value, path44, key) {
959
967
  this._cachedPath = [];
960
968
  this.parent = parent;
961
969
  this.data = value;
962
- this._path = path45;
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 path45 = req.path;
5594
- _req.url = typeof path45 === "string" ? path45 : 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(path45) {
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 < path45.length; i++) {
5766
- const char = path45[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 path45 of paths) {
5898
- const parts = parsePath(path45);
5932
+ for (const path44 of paths) {
5933
+ const parts = parsePath(path44);
5899
5934
  if (parts.includes("*")) {
5900
- redactWildcardPath(obj, parts, censor, path45, 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, path45) => {
5986
- const fullPath = [...pathArray.slice(0, pathLength), ...path45];
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 path45 of pathsToClone) {
6022
- const parts = parsePath(path45);
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(path45) {
6075
- if (typeof path45 !== "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 (path45 === "") {
6113
+ if (path44 === "") {
6079
6114
  throw new Error("Invalid redaction path ()");
6080
6115
  }
6081
- if (path45.includes("..")) {
6082
- throw new Error(`Invalid redaction path (${path45})`);
6116
+ if (path44.includes("..")) {
6117
+ throw new Error(`Invalid redaction path (${path44})`);
6083
6118
  }
6084
- if (path45.includes(",")) {
6085
- throw new Error(`Invalid redaction path (${path45})`);
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 < path45.length; i++) {
6091
- const char = path45[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 (${path45})`);
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 (${path45})`);
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 path45 of paths) {
6118
- validatePath(path45);
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, path45) => {
6287
- return censor(value, [k2, ...path45]);
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 fs36 = require("fs");
6540
+ var fs35 = require("fs");
6506
6541
  var EventEmitter2 = require("events");
6507
6542
  var inherits = require("util").inherits;
6508
- var path45 = 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) fs36.mkdirSync(path45.dirname(file), { recursive: true });
6563
- const fd = fs36.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
- fs36.mkdir(path45.dirname(file), { recursive: true }, (err) => {
6605
+ fs35.mkdir(path44.dirname(file), { recursive: true }, (err) => {
6571
6606
  if (err) return fileOpened(err);
6572
- fs36.open(file, flags, mode, fileOpened);
6607
+ fs35.open(file, flags, mode, fileOpened);
6573
6608
  });
6574
6609
  } else {
6575
- fs36.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 = () => fs36.writeSync(this.fd, this._writingBuf);
6617
- fsWrite = () => fs36.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 fs36.writeSync(this.fd, this._writingBuf);
6661
+ return fs35.writeSync(this.fd, this._writingBuf);
6627
6662
  }
6628
- return fs36.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 fs36.write(this.fd, this._writingBuf, this.release);
6667
+ return fs35.write(this.fd, this._writingBuf, this.release);
6633
6668
  }
6634
- return fs36.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
- fs36.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
- fs36.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
- fs36.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) ? fs36.writeSync(this.fd, buf) : fs36.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
- fs36.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 = fs36.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) ? fs36.writeSync(this.fd, this._writingBuf) : fs36.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
- fs36.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 = fs36.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
- fs36.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
- fs36.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
- fs36.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)
@@ -9427,7 +9462,7 @@ var require_multistream = __commonJS({
9427
9462
  var require_pino = __commonJS({
9428
9463
  "../node_modules/.pnpm/pino@9.14.0/node_modules/pino/pino.js"(exports2, module2) {
9429
9464
  "use strict";
9430
- var os16 = require("os");
9465
+ var os17 = require("os");
9431
9466
  var stdSerializers = require_pino_std_serializers();
9432
9467
  var caller = require_caller();
9433
9468
  var redaction = require_redaction();
@@ -9474,7 +9509,7 @@ var require_pino = __commonJS({
9474
9509
  } = symbols;
9475
9510
  var { epochTime, nullTime } = time;
9476
9511
  var { pid } = process;
9477
- var hostname = os16.hostname();
9512
+ var hostname = os17.hostname();
9478
9513
  var defaultErrorSerializer = stdSerializers.err;
9479
9514
  var defaultOptions = {
9480
9515
  level: "info",
@@ -10198,11 +10233,11 @@ var init_lib = __esm({
10198
10233
  }
10199
10234
  }
10200
10235
  },
10201
- addToPath: function addToPath(path45, added, removed, oldPosInc, options) {
10202
- var last = path45.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: path45.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: path45.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 path45 = 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 (!path45 || content == null) return null;
10649
- const entry = { path: path45, 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 path45 = 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 (!path45 || content == null) return null;
11478
- const out = { path: path45, 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_path31.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_fs28.default.mkdirSync(dir, { recursive: true });
20408
- stream = import_node_fs28.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_fs28, import_node_path31;
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_fs28 = __toESM(require("fs"), 1);
20438
- import_node_path31 = __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_fs29.mkdtempSync)(import_node_path32.default.join(import_node_os14.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_fs29.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_fs29, import_node_os14, import_node_path32;
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_fs29 = require("fs");
20653
- import_node_os14 = __toESM(require("os"), 1);
20654
- import_node_path32 = __toESM(require("path"), 1);
20687
+ import_node_fs28 = require("fs");
20688
+ import_node_os15 = __toESM(require("os"), 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_path30 = __toESM(require("path"), 1);
20887
- var import_node_fs27 = __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,25 @@ 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 SessionFile 带
22915
+ // 远端 ownerPersonaId(远端 persona session),本机也落 default,不创建本机 persona 目录。
22916
+ // 远端 creator / origin 字段原值保留(不被本机 ctx 派生覆写)。
22917
+ //
22918
+ // 与 manager.create / manager.update 区别:
22919
+ // - 不走 newRunner / attachObserver / cwd 校验(mirror 是纯 metadata 层)
22920
+ // - 不发 session:info 帧(manager.create / .update 通过 reducer/wireCollector 发 push)
22921
+ mirrorUpsert(file) {
22922
+ return this.deps.store.write(file);
22923
+ }
22924
+ // mirror peer sessions:删本机 mirror SessionFile。强制 default store。
22925
+ // idempotent:找不到不 throw(UI 反复点删容错)。**不广播 session:deleted**。
22926
+ mirrorRemove(sessionId) {
22927
+ this.deps.store.delete(sessionId);
22928
+ }
22887
22929
  // observer 上报"jsonl 写入了一条 user 行"时调用:在 buffer 里找到与之对应的、
22888
22930
  // 还没建立映射的最早一条 synth user_text,记下 synth → real 映射。
22889
22931
  // 找不到匹配的 synth(runner 不存在 / buffer 已被 /clear 清空 / 真 uuid 来自纯 history
@@ -25531,11 +25573,18 @@ var AgentsScanner = class {
25531
25573
  };
25532
25574
 
25533
25575
  // src/observer/session-observer.ts
25576
+ var import_node_fs13 = __toESM(require("fs"), 1);
25577
+ var import_node_os9 = __toESM(require("os"), 1);
25578
+ var import_node_path14 = __toESM(require("path"), 1);
25579
+ init_claude_history();
25580
+
25581
+ // src/observer/subagent-meta-observer.ts
25534
25582
  var import_node_fs12 = __toESM(require("fs"), 1);
25535
25583
  var import_node_os8 = __toESM(require("os"), 1);
25536
25584
  var import_node_path13 = __toESM(require("path"), 1);
25537
25585
  init_claude_history();
25538
- var SessionObserver = class {
25586
+ var META_RE = /^agent-([A-Za-z0-9_-]+)\.meta\.json$/;
25587
+ var SubagentMetaObserver = class {
25539
25588
  constructor(opts) {
25540
25589
  this.opts = opts;
25541
25590
  this.home = opts.home ?? import_node_os8.default.homedir();
@@ -25543,16 +25592,132 @@ var SessionObserver = class {
25543
25592
  opts;
25544
25593
  home;
25545
25594
  watches = /* @__PURE__ */ new Map();
25595
+ // public for spec only:测试直接拼路径写假 meta.json;生产 start() 内部自己解析
25596
+ resolveSubagentDir(cwd, toolSessionId) {
25597
+ return import_node_path13.default.join(
25598
+ this.home,
25599
+ ".claude",
25600
+ "projects",
25601
+ cwdToHashDir(cwd),
25602
+ toolSessionId,
25603
+ "subagents"
25604
+ );
25605
+ }
25606
+ start(args) {
25607
+ this.stop(args.sessionId);
25608
+ const dirPath = this.resolveSubagentDir(args.cwd, args.toolSessionId);
25609
+ const w2 = {
25610
+ sessionId: args.sessionId,
25611
+ dirPath,
25612
+ watcher: null,
25613
+ pollTimer: null,
25614
+ emitted: /* @__PURE__ */ new Set()
25615
+ };
25616
+ this.watches.set(args.sessionId, w2);
25617
+ this.scan(w2);
25618
+ this.attachWatcher(w2);
25619
+ w2.pollTimer = setInterval(() => this.scan(w2), 200);
25620
+ return { dirPath };
25621
+ }
25622
+ // fs.watch 必须在目录存在后调用;首次启动时 subagents/ 通常不存在 —— attachWatcher
25623
+ // 失败就交给 pollTimer 200ms 兜底,目录一出现就被下次 poll 捡起来,那时也尝试 attach
25624
+ attachWatcher(w2) {
25625
+ if (w2.watcher) return;
25626
+ try {
25627
+ w2.watcher = import_node_fs12.default.watch(w2.dirPath, { persistent: false }, (_evt, name) => {
25628
+ if (!name) return;
25629
+ const m2 = META_RE.exec(String(name));
25630
+ if (!m2) return;
25631
+ this.scanOne(w2, String(name));
25632
+ });
25633
+ } catch {
25634
+ }
25635
+ }
25636
+ scan(w2) {
25637
+ let entries;
25638
+ try {
25639
+ entries = import_node_fs12.default.readdirSync(w2.dirPath);
25640
+ } catch {
25641
+ return;
25642
+ }
25643
+ if (!w2.watcher) this.attachWatcher(w2);
25644
+ for (const name of entries) {
25645
+ this.scanOne(w2, name);
25646
+ }
25647
+ }
25648
+ scanOne(w2, name) {
25649
+ const m2 = META_RE.exec(name);
25650
+ if (!m2) return;
25651
+ const agentId = m2[1];
25652
+ if (w2.emitted.has(agentId)) return;
25653
+ const file = import_node_path13.default.join(w2.dirPath, name);
25654
+ let raw;
25655
+ try {
25656
+ raw = import_node_fs12.default.readFileSync(file, "utf8");
25657
+ } catch {
25658
+ return;
25659
+ }
25660
+ let obj;
25661
+ try {
25662
+ obj = JSON.parse(raw);
25663
+ } catch {
25664
+ return;
25665
+ }
25666
+ const toolUseId = typeof obj.toolUseId === "string" ? obj.toolUseId : "";
25667
+ if (!toolUseId) return;
25668
+ w2.emitted.add(agentId);
25669
+ const ev = {
25670
+ kind: "subagent_progress",
25671
+ toolUseId,
25672
+ agentId,
25673
+ status: "started"
25674
+ };
25675
+ if (typeof obj.description === "string" && obj.description) ev.description = obj.description;
25676
+ this.opts.onEvent({ sessionId: w2.sessionId, events: [ev] });
25677
+ }
25678
+ isObserving(sessionId) {
25679
+ return this.watches.has(sessionId);
25680
+ }
25681
+ stop(sessionId) {
25682
+ const w2 = this.watches.get(sessionId);
25683
+ if (!w2) return false;
25684
+ try {
25685
+ w2.watcher?.close();
25686
+ } catch {
25687
+ }
25688
+ if (w2.pollTimer) clearInterval(w2.pollTimer);
25689
+ this.watches.delete(sessionId);
25690
+ return true;
25691
+ }
25692
+ stopAll() {
25693
+ for (const id of [...this.watches.keys()]) this.stop(id);
25694
+ }
25695
+ };
25696
+
25697
+ // src/observer/session-observer.ts
25698
+ var SessionObserver = class {
25699
+ constructor(opts) {
25700
+ this.opts = opts;
25701
+ this.home = opts.home ?? import_node_os9.default.homedir();
25702
+ this.metaObserver = opts.enableSubagentMetaObserver ? new SubagentMetaObserver({ home: this.home, onEvent: opts.onEvent }) : null;
25703
+ }
25704
+ opts;
25705
+ home;
25706
+ watches = /* @__PURE__ */ new Map();
25707
+ // 子 observer:监听 <projectsRoot>/<hashDir>/<toolSessionId>/subagents/ 目录的 meta.json,
25708
+ // 仅 TUI 模式启用——SDK 模式靠 `system.task_started` 帧(claude.ts:466)emit subagent_progress
25709
+ // 已经能解锁 AgentCallLine 抽屉入口,再跑 meta watcher 会让 ring buffer 留双帧
25710
+ metaObserver;
25546
25711
  resolveJsonlPath(cwd, toolSessionId, override) {
25547
25712
  if (override) return override;
25548
- return import_node_path13.default.join(this.home, ".claude", "projects", cwdToHashDir(cwd), `${toolSessionId}.jsonl`);
25713
+ return import_node_path14.default.join(this.home, ".claude", "projects", cwdToHashDir(cwd), `${toolSessionId}.jsonl`);
25549
25714
  }
25550
25715
  start(args) {
25551
25716
  this.stop(args.sessionId);
25552
25717
  const filePath = this.resolveJsonlPath(args.cwd, args.toolSessionId, args.jsonlPath);
25553
25718
  let size = 0;
25554
25719
  try {
25555
- size = import_node_fs12.default.statSync(filePath).size;
25720
+ size = import_node_fs13.default.statSync(filePath).size;
25556
25721
  } catch {
25557
25722
  }
25558
25723
  const w2 = {
@@ -25565,10 +25730,10 @@ var SessionObserver = class {
25565
25730
  adapter: args.adapter
25566
25731
  };
25567
25732
  try {
25568
- import_node_fs12.default.mkdirSync(import_node_path13.default.dirname(filePath), { recursive: true });
25733
+ import_node_fs13.default.mkdirSync(import_node_path14.default.dirname(filePath), { recursive: true });
25569
25734
  } catch {
25570
25735
  }
25571
- w2.watcher = import_node_fs12.default.watch(import_node_path13.default.dirname(filePath), { persistent: false }, (_event, changedName) => {
25736
+ w2.watcher = import_node_fs13.default.watch(import_node_path14.default.dirname(filePath), { persistent: false }, (_event, changedName) => {
25572
25737
  if (!changedName || !filePath.endsWith(changedName)) return;
25573
25738
  this.poll(w2);
25574
25739
  });
@@ -25577,6 +25742,11 @@ var SessionObserver = class {
25577
25742
  this.opts.onStatus?.(args.sessionId, "observing");
25578
25743
  this.hydrateMetaTail(w2);
25579
25744
  this.poll(w2);
25745
+ this.metaObserver?.start({
25746
+ sessionId: args.sessionId,
25747
+ cwd: args.cwd,
25748
+ toolSessionId: args.toolSessionId
25749
+ });
25580
25750
  return { filePath };
25581
25751
  }
25582
25752
  // 读 JSONL 文件尾部 N 行,把每行的 meta_update 字段**合并成一个 patch** 喂给 reducer。
@@ -25586,7 +25756,7 @@ var SessionObserver = class {
25586
25756
  // 异常静默吞,不阻塞 watcher 启动
25587
25757
  hydrateMetaTail(w2, maxLines = 200) {
25588
25758
  try {
25589
- const raw = import_node_fs12.default.readFileSync(w2.filePath, "utf8");
25759
+ const raw = import_node_fs13.default.readFileSync(w2.filePath, "utf8");
25590
25760
  if (!raw) return;
25591
25761
  const allLines = raw.split("\n").filter((l) => l.trim().length > 0);
25592
25762
  if (allLines.length === 0) return;
@@ -25610,7 +25780,7 @@ var SessionObserver = class {
25610
25780
  poll(w2) {
25611
25781
  let size = 0;
25612
25782
  try {
25613
- size = import_node_fs12.default.statSync(w2.filePath).size;
25783
+ size = import_node_fs13.default.statSync(w2.filePath).size;
25614
25784
  } catch {
25615
25785
  return;
25616
25786
  }
@@ -25619,11 +25789,11 @@ var SessionObserver = class {
25619
25789
  w2.buf = "";
25620
25790
  }
25621
25791
  if (size === w2.lastSize) return;
25622
- const fd = import_node_fs12.default.openSync(w2.filePath, "r");
25792
+ const fd = import_node_fs13.default.openSync(w2.filePath, "r");
25623
25793
  try {
25624
25794
  const len = size - w2.lastSize;
25625
25795
  const buf = Buffer.alloc(len);
25626
- import_node_fs12.default.readSync(fd, buf, 0, len, w2.lastSize);
25796
+ import_node_fs13.default.readSync(fd, buf, 0, len, w2.lastSize);
25627
25797
  w2.lastSize = size;
25628
25798
  w2.buf += buf.toString("utf8");
25629
25799
  let newlineIndex;
@@ -25637,7 +25807,7 @@ var SessionObserver = class {
25637
25807
  }
25638
25808
  }
25639
25809
  } finally {
25640
- import_node_fs12.default.closeSync(fd);
25810
+ import_node_fs13.default.closeSync(fd);
25641
25811
  }
25642
25812
  }
25643
25813
  // 解析 JSONL 单行:仅当是主链 user 文本行(非 sidechain / 非 sub-agent / message.role='user'
@@ -25690,11 +25860,13 @@ var SessionObserver = class {
25690
25860
  }
25691
25861
  if (w2.pollTimer) clearInterval(w2.pollTimer);
25692
25862
  this.watches.delete(sessionId);
25863
+ this.metaObserver?.stop(sessionId);
25693
25864
  this.opts.onStatus?.(sessionId, "stopped");
25694
25865
  return true;
25695
25866
  }
25696
25867
  stopAll() {
25697
25868
  for (const id of [...this.watches.keys()]) this.stop(id);
25869
+ this.metaObserver?.stopAll();
25698
25870
  }
25699
25871
  };
25700
25872
 
@@ -26295,14 +26467,14 @@ function authenticate(token, deps) {
26295
26467
  }
26296
26468
 
26297
26469
  // src/permission/capability-store.ts
26298
- var fs15 = __toESM(require("fs"), 1);
26299
- var path18 = __toESM(require("path"), 1);
26470
+ var fs16 = __toESM(require("fs"), 1);
26471
+ var path19 = __toESM(require("path"), 1);
26300
26472
  var CAPABILITIES_FILE_NAME = "capabilities.json";
26301
26473
  var FILE_VERSION = 1;
26302
26474
  var CapabilityStore = class {
26303
26475
  constructor(dataDir) {
26304
26476
  this.dataDir = dataDir;
26305
- fs15.mkdirSync(dataDir, { recursive: true });
26477
+ fs16.mkdirSync(dataDir, { recursive: true });
26306
26478
  this.cache = this.readFromDisk();
26307
26479
  }
26308
26480
  dataDir;
@@ -26326,13 +26498,13 @@ var CapabilityStore = class {
26326
26498
  this.flush();
26327
26499
  }
26328
26500
  filePath() {
26329
- return path18.join(this.dataDir, CAPABILITIES_FILE_NAME);
26501
+ return path19.join(this.dataDir, CAPABILITIES_FILE_NAME);
26330
26502
  }
26331
26503
  readFromDisk() {
26332
26504
  const file = this.filePath();
26333
26505
  let raw;
26334
26506
  try {
26335
- raw = fs15.readFileSync(file, "utf8");
26507
+ raw = fs16.readFileSync(file, "utf8");
26336
26508
  } catch (err) {
26337
26509
  if (err?.code === "ENOENT") return [];
26338
26510
  return [];
@@ -26360,10 +26532,10 @@ var CapabilityStore = class {
26360
26532
  }
26361
26533
  atomicWrite(file, content) {
26362
26534
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
26363
- fs15.writeFileSync(tmp, content, { mode: 384 });
26364
- fs15.renameSync(tmp, file);
26535
+ fs16.writeFileSync(tmp, content, { mode: 384 });
26536
+ fs16.renameSync(tmp, file);
26365
26537
  try {
26366
- fs15.chmodSync(file, 384);
26538
+ fs16.chmodSync(file, 384);
26367
26539
  } catch {
26368
26540
  }
26369
26541
  }
@@ -26456,14 +26628,14 @@ var CapabilityManager = class {
26456
26628
  };
26457
26629
 
26458
26630
  // src/permission/cleanup.ts
26459
- var fs16 = __toESM(require("fs"), 1);
26631
+ var fs17 = __toESM(require("fs"), 1);
26460
26632
  function cleanupGuestSessionsForCapability(cap, factory) {
26461
26633
  const removed = [];
26462
26634
  for (const g2 of cap.grants) {
26463
26635
  if (g2.resource.type !== "persona") continue;
26464
26636
  const dir = factory.vmGuestRoot(g2.resource.id, cap.id);
26465
26637
  try {
26466
- fs16.rmSync(dir, { recursive: true, force: true });
26638
+ fs17.rmSync(dir, { recursive: true, force: true });
26467
26639
  removed.push(dir);
26468
26640
  } catch {
26469
26641
  }
@@ -26472,13 +26644,13 @@ function cleanupGuestSessionsForCapability(cap, factory) {
26472
26644
  }
26473
26645
 
26474
26646
  // src/permission/personal-capability.ts
26475
- var import_node_fs13 = __toESM(require("fs"), 1);
26476
- var import_node_path14 = __toESM(require("path"), 1);
26647
+ var import_node_fs14 = __toESM(require("fs"), 1);
26648
+ var import_node_path15 = __toESM(require("path"), 1);
26477
26649
  var import_node_crypto4 = __toESM(require("crypto"), 1);
26478
26650
  var PERSONAL_CAP_FILE_NAME = "personal-capability.json";
26479
26651
  var PERSONAL_CAP_DISPLAY_NAME = "personal";
26480
26652
  function personalCapFilePath(dataDir) {
26481
- return import_node_path14.default.join(dataDir, PERSONAL_CAP_FILE_NAME);
26653
+ return import_node_path15.default.join(dataDir, PERSONAL_CAP_FILE_NAME);
26482
26654
  }
26483
26655
  function loadOrCreatePersonalCapability(opts) {
26484
26656
  const file = personalCapFilePath(opts.dataDir);
@@ -26508,7 +26680,7 @@ function loadOrCreatePersonalCapability(opts) {
26508
26680
  function readFile(file) {
26509
26681
  let raw;
26510
26682
  try {
26511
- raw = import_node_fs13.default.readFileSync(file, "utf8");
26683
+ raw = import_node_fs14.default.readFileSync(file, "utf8");
26512
26684
  } catch (err) {
26513
26685
  const code = err?.code;
26514
26686
  if (code === "ENOENT") return null;
@@ -26529,10 +26701,10 @@ function readFile(file) {
26529
26701
  return { token: obj.token, capability: capParsed.data };
26530
26702
  }
26531
26703
  function writeFile(file, content) {
26532
- import_node_fs13.default.mkdirSync(import_node_path14.default.dirname(file), { recursive: true });
26533
- import_node_fs13.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
26704
+ import_node_fs14.default.mkdirSync(import_node_path15.default.dirname(file), { recursive: true });
26705
+ import_node_fs14.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
26534
26706
  try {
26535
- import_node_fs13.default.chmodSync(file, 384);
26707
+ import_node_fs14.default.chmodSync(file, 384);
26536
26708
  } catch {
26537
26709
  }
26538
26710
  }
@@ -26547,13 +26719,13 @@ function sha256Hex2(s) {
26547
26719
  }
26548
26720
 
26549
26721
  // src/inbox/inbox-store.ts
26550
- var fs18 = __toESM(require("fs"), 1);
26551
- var path20 = __toESM(require("path"), 1);
26722
+ var fs19 = __toESM(require("fs"), 1);
26723
+ var path21 = __toESM(require("path"), 1);
26552
26724
  var INBOX_SUBDIR = "inbox";
26553
26725
  var InboxStore = class {
26554
26726
  constructor(dataDir) {
26555
26727
  this.dataDir = dataDir;
26556
- fs18.mkdirSync(this.dirPath(), { recursive: true });
26728
+ fs19.mkdirSync(this.dirPath(), { recursive: true });
26557
26729
  }
26558
26730
  dataDir;
26559
26731
  /**
@@ -26565,7 +26737,7 @@ var InboxStore = class {
26565
26737
  const file = this.filePath(peerOwnerId);
26566
26738
  let raw;
26567
26739
  try {
26568
- raw = fs18.readFileSync(file, "utf8");
26740
+ raw = fs19.readFileSync(file, "utf8");
26569
26741
  } catch (err) {
26570
26742
  if (err?.code === "ENOENT") return [];
26571
26743
  return [];
@@ -26581,7 +26753,7 @@ var InboxStore = class {
26581
26753
  const dir = this.dirPath();
26582
26754
  let entries;
26583
26755
  try {
26584
- entries = fs18.readdirSync(dir);
26756
+ entries = fs19.readdirSync(dir);
26585
26757
  } catch (err) {
26586
26758
  if (err?.code === "ENOENT") return [];
26587
26759
  return [];
@@ -26597,9 +26769,9 @@ var InboxStore = class {
26597
26769
  if (existing.some((m2) => m2.id === message.id)) return;
26598
26770
  const file = this.filePath(message.peerOwnerId);
26599
26771
  const line = JSON.stringify(message) + "\n";
26600
- fs18.appendFileSync(file, line, { mode: 384 });
26772
+ fs19.appendFileSync(file, line, { mode: 384 });
26601
26773
  try {
26602
- fs18.chmodSync(file, 384);
26774
+ fs19.chmodSync(file, 384);
26603
26775
  } catch {
26604
26776
  }
26605
26777
  }
@@ -26629,7 +26801,7 @@ var InboxStore = class {
26629
26801
  removeByPeerOwnerId(peerOwnerId) {
26630
26802
  const file = this.filePath(peerOwnerId);
26631
26803
  try {
26632
- fs18.unlinkSync(file);
26804
+ fs19.unlinkSync(file);
26633
26805
  } catch (err) {
26634
26806
  if (err?.code === "ENOENT") return;
26635
26807
  }
@@ -26638,18 +26810,18 @@ var InboxStore = class {
26638
26810
  const file = this.filePath(peerOwnerId);
26639
26811
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
26640
26812
  const content = messages.map((m2) => JSON.stringify(m2)).join("\n") + (messages.length > 0 ? "\n" : "");
26641
- fs18.writeFileSync(tmp, content, { mode: 384 });
26642
- fs18.renameSync(tmp, file);
26813
+ fs19.writeFileSync(tmp, content, { mode: 384 });
26814
+ fs19.renameSync(tmp, file);
26643
26815
  try {
26644
- fs18.chmodSync(file, 384);
26816
+ fs19.chmodSync(file, 384);
26645
26817
  } catch {
26646
26818
  }
26647
26819
  }
26648
26820
  dirPath() {
26649
- return path20.join(this.dataDir, INBOX_SUBDIR);
26821
+ return path21.join(this.dataDir, INBOX_SUBDIR);
26650
26822
  }
26651
26823
  filePath(peerOwnerId) {
26652
- return path20.join(this.dirPath(), `${peerOwnerId}.jsonl`);
26824
+ return path21.join(this.dirPath(), `${peerOwnerId}.jsonl`);
26653
26825
  }
26654
26826
  };
26655
26827
  function parseAllLines(raw) {
@@ -26736,8 +26908,8 @@ var InboxManager = class {
26736
26908
  };
26737
26909
 
26738
26910
  // src/state/received-capability-store.ts
26739
- var fs19 = __toESM(require("fs"), 1);
26740
- var path21 = __toESM(require("path"), 1);
26911
+ var fs20 = __toESM(require("fs"), 1);
26912
+ var path22 = __toESM(require("path"), 1);
26741
26913
  var FILE_NAME = "received-capabilities.json";
26742
26914
  var ReceivedCapabilityStore = class {
26743
26915
  constructor(dataDir) {
@@ -26747,10 +26919,10 @@ var ReceivedCapabilityStore = class {
26747
26919
  caps = /* @__PURE__ */ new Map();
26748
26920
  load() {
26749
26921
  this.caps.clear();
26750
- const file = path21.join(this.dataDir, FILE_NAME);
26922
+ const file = path22.join(this.dataDir, FILE_NAME);
26751
26923
  let raw;
26752
26924
  try {
26753
- raw = fs19.readFileSync(file, "utf8");
26925
+ raw = fs20.readFileSync(file, "utf8");
26754
26926
  } catch (err) {
26755
26927
  if (err?.code !== "ENOENT") this.renameBak(file);
26756
26928
  return;
@@ -26787,20 +26959,20 @@ var ReceivedCapabilityStore = class {
26787
26959
  this.flush();
26788
26960
  }
26789
26961
  flush() {
26790
- const file = path21.join(this.dataDir, FILE_NAME);
26962
+ const file = path22.join(this.dataDir, FILE_NAME);
26791
26963
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
26792
26964
  const content = JSON.stringify(
26793
26965
  { receivedCaps: Array.from(this.caps.values()) },
26794
26966
  null,
26795
26967
  2
26796
26968
  );
26797
- fs19.mkdirSync(this.dataDir, { recursive: true });
26798
- fs19.writeFileSync(tmp, content, { mode: 384 });
26799
- fs19.renameSync(tmp, file);
26969
+ fs20.mkdirSync(this.dataDir, { recursive: true });
26970
+ fs20.writeFileSync(tmp, content, { mode: 384 });
26971
+ fs20.renameSync(tmp, file);
26800
26972
  }
26801
26973
  renameBak(file) {
26802
26974
  try {
26803
- fs19.renameSync(file, `${file}.bak`);
26975
+ fs20.renameSync(file, `${file}.bak`);
26804
26976
  } catch {
26805
26977
  }
26806
26978
  }
@@ -26898,61 +27070,61 @@ async function connectRemote(args) {
26898
27070
  }
26899
27071
 
26900
27072
  // src/migrations/2026-05-20-flatten-sessions.ts
26901
- var fs20 = __toESM(require("fs"), 1);
26902
- var path22 = __toESM(require("path"), 1);
27073
+ var fs21 = __toESM(require("fs"), 1);
27074
+ var path23 = __toESM(require("path"), 1);
26903
27075
  var MIGRATION_FLAG_NAME = ".migration.v1.done";
26904
27076
  function migrateFlattenSessions(opts) {
26905
27077
  const dataDir = opts.dataDir;
26906
27078
  const now = opts.now ?? Date.now;
26907
- const sessionsDir = path22.join(dataDir, "sessions");
26908
- const flagPath = path22.join(sessionsDir, MIGRATION_FLAG_NAME);
27079
+ const sessionsDir = path23.join(dataDir, "sessions");
27080
+ const flagPath = path23.join(sessionsDir, MIGRATION_FLAG_NAME);
26909
27081
  if (existsSync3(flagPath)) {
26910
27082
  return { skipped: true, flagWritten: false, movedBare: 0, movedVmOwner: 0, archivedListener: 0 };
26911
27083
  }
26912
27084
  let movedBare = 0;
26913
27085
  let movedVmOwner = 0;
26914
27086
  let archivedListener = 0;
26915
- const defaultDir = path22.join(sessionsDir, "default");
27087
+ const defaultDir = path23.join(sessionsDir, "default");
26916
27088
  if (existsSync3(defaultDir)) {
26917
27089
  for (const entry of readdirSafe(defaultDir)) {
26918
27090
  if (!entry.endsWith(".json")) continue;
26919
- const src = path22.join(defaultDir, entry);
26920
- const dst = path22.join(sessionsDir, entry);
26921
- fs20.renameSync(src, dst);
27091
+ const src = path23.join(defaultDir, entry);
27092
+ const dst = path23.join(sessionsDir, entry);
27093
+ fs21.renameSync(src, dst);
26922
27094
  movedBare += 1;
26923
27095
  }
26924
27096
  rmdirIfEmpty(defaultDir);
26925
27097
  }
26926
27098
  for (const pid of readdirSafe(sessionsDir)) {
26927
- const personaDir = path22.join(sessionsDir, pid);
27099
+ const personaDir = path23.join(sessionsDir, pid);
26928
27100
  if (!isDir(personaDir)) continue;
26929
27101
  if (pid === "default") continue;
26930
- const ownerSrc = path22.join(personaDir, "owner");
27102
+ const ownerSrc = path23.join(personaDir, "owner");
26931
27103
  if (existsSync3(ownerSrc) && isDir(ownerSrc)) {
26932
- const ownerDst = path22.join(dataDir, "personas", pid, ".clawd", "sessions", "owner");
26933
- fs20.mkdirSync(ownerDst, { recursive: true });
27104
+ const ownerDst = path23.join(dataDir, "personas", pid, ".clawd", "sessions", "owner");
27105
+ fs21.mkdirSync(ownerDst, { recursive: true });
26934
27106
  for (const file of readdirSafe(ownerSrc)) {
26935
27107
  if (!file.endsWith(".json")) continue;
26936
- fs20.renameSync(path22.join(ownerSrc, file), path22.join(ownerDst, file));
27108
+ fs21.renameSync(path23.join(ownerSrc, file), path23.join(ownerDst, file));
26937
27109
  movedVmOwner += 1;
26938
27110
  }
26939
27111
  rmdirIfEmpty(ownerSrc);
26940
27112
  }
26941
- const listenerSrc = path22.join(personaDir, "listener");
27113
+ const listenerSrc = path23.join(personaDir, "listener");
26942
27114
  if (existsSync3(listenerSrc) && isDir(listenerSrc)) {
26943
- const archiveDst = path22.join(dataDir, ".legacy", `listener-${pid}`);
26944
- fs20.mkdirSync(archiveDst, { recursive: true });
27115
+ const archiveDst = path23.join(dataDir, ".legacy", `listener-${pid}`);
27116
+ fs21.mkdirSync(archiveDst, { recursive: true });
26945
27117
  for (const file of readdirSafe(listenerSrc)) {
26946
27118
  if (!file.endsWith(".json")) continue;
26947
- fs20.renameSync(path22.join(listenerSrc, file), path22.join(archiveDst, file));
27119
+ fs21.renameSync(path23.join(listenerSrc, file), path23.join(archiveDst, file));
26948
27120
  archivedListener += 1;
26949
27121
  }
26950
27122
  rmdirIfEmpty(listenerSrc);
26951
27123
  }
26952
27124
  rmdirIfEmpty(personaDir);
26953
27125
  }
26954
- fs20.mkdirSync(sessionsDir, { recursive: true });
26955
- fs20.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
27126
+ fs21.mkdirSync(sessionsDir, { recursive: true });
27127
+ fs21.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
26956
27128
  return {
26957
27129
  skipped: false,
26958
27130
  flagWritten: true,
@@ -26963,7 +27135,7 @@ function migrateFlattenSessions(opts) {
26963
27135
  }
26964
27136
  function existsSync3(p2) {
26965
27137
  try {
26966
- fs20.statSync(p2);
27138
+ fs21.statSync(p2);
26967
27139
  return true;
26968
27140
  } catch {
26969
27141
  return false;
@@ -26971,21 +27143,21 @@ function existsSync3(p2) {
26971
27143
  }
26972
27144
  function isDir(p2) {
26973
27145
  try {
26974
- return fs20.statSync(p2).isDirectory();
27146
+ return fs21.statSync(p2).isDirectory();
26975
27147
  } catch {
26976
27148
  return false;
26977
27149
  }
26978
27150
  }
26979
27151
  function readdirSafe(p2) {
26980
27152
  try {
26981
- return fs20.readdirSync(p2);
27153
+ return fs21.readdirSync(p2);
26982
27154
  } catch {
26983
27155
  return [];
26984
27156
  }
26985
27157
  }
26986
27158
  function rmdirIfEmpty(p2) {
26987
27159
  try {
26988
- fs20.rmdirSync(p2);
27160
+ fs21.rmdirSync(p2);
26989
27161
  } catch {
26990
27162
  }
26991
27163
  }
@@ -26995,7 +27167,7 @@ var import_node_fs15 = __toESM(require("fs"), 1);
26995
27167
  var import_node_path17 = __toESM(require("path"), 1);
26996
27168
 
26997
27169
  // src/attachment/mime.ts
26998
- var import_node_path15 = __toESM(require("path"), 1);
27170
+ var import_node_path16 = __toESM(require("path"), 1);
26999
27171
  var TEXT_PLAIN = "text/plain; charset=utf-8";
27000
27172
  var EXT_TO_NATIVE_MIME = {
27001
27173
  // 图片
@@ -27102,7 +27274,7 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
27102
27274
  ".mk"
27103
27275
  ]);
27104
27276
  function lookupMime(filePathOrName) {
27105
- const ext = import_node_path15.default.extname(filePathOrName).toLowerCase();
27277
+ const ext = import_node_path16.default.extname(filePathOrName).toLowerCase();
27106
27278
  if (EXT_TO_NATIVE_MIME[ext]) return EXT_TO_NATIVE_MIME[ext];
27107
27279
  if (TEXT_EXTENSIONS.has(ext)) return TEXT_PLAIN;
27108
27280
  return "application/octet-stream";
@@ -27170,131 +27342,7 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
27170
27342
  return { ok: true, absPath };
27171
27343
  }
27172
27344
 
27173
- // src/attachment/upload.ts
27174
- var import_node_fs14 = __toESM(require("fs"), 1);
27175
- var import_node_path16 = __toESM(require("path"), 1);
27176
- var import_node_crypto6 = __toESM(require("crypto"), 1);
27177
- var import_promises = require("stream/promises");
27178
- var UploadError = class extends Error {
27179
- constructor(code, message) {
27180
- super(message);
27181
- this.code = code;
27182
- this.name = "UploadError";
27183
- }
27184
- code;
27185
- };
27186
- function assertValidFileName(fileName) {
27187
- if (fileName.length === 0 || fileName === "." || fileName === ".." || fileName.startsWith(".") || fileName.includes("/") || fileName.includes("\\") || fileName !== import_node_path16.default.basename(fileName)) {
27188
- throw new UploadError("INVALID_FILENAME", `fileName must be a plain basename, got: ${fileName}`);
27189
- }
27190
- }
27191
- var HASH_PREFIX_LEN = 16;
27192
- async function writeUploadedAttachment(args) {
27193
- assertValidFileName(args.fileName);
27194
- const attachmentsRoot = import_node_path16.default.join(args.sessionDir, ".attachments");
27195
- try {
27196
- import_node_fs14.default.mkdirSync(attachmentsRoot, { recursive: true });
27197
- } catch (err) {
27198
- throw new UploadError("STORAGE_ERROR", `mkdir failed: ${err.message}`);
27199
- }
27200
- const hasher = import_node_crypto6.default.createHash("sha256");
27201
- let actualSize = 0;
27202
- const tmpPath = import_node_path16.default.join(
27203
- attachmentsRoot,
27204
- `.upload-${process.pid}-${Date.now()}-${import_node_crypto6.default.randomBytes(4).toString("hex")}`
27205
- );
27206
- try {
27207
- await (0, import_promises.pipeline)(
27208
- args.body,
27209
- async function* (source) {
27210
- for await (const chunk of source) {
27211
- const buf = chunk;
27212
- actualSize += buf.length;
27213
- hasher.update(buf);
27214
- yield buf;
27215
- }
27216
- },
27217
- import_node_fs14.default.createWriteStream(tmpPath, { mode: 384 })
27218
- );
27219
- } catch (err) {
27220
- try {
27221
- import_node_fs14.default.unlinkSync(tmpPath);
27222
- } catch {
27223
- }
27224
- throw new UploadError("STORAGE_ERROR", `write failed: ${err.message}`);
27225
- }
27226
- if (actualSize !== args.contentLength) {
27227
- try {
27228
- import_node_fs14.default.unlinkSync(tmpPath);
27229
- } catch {
27230
- }
27231
- throw new UploadError(
27232
- "SIZE_MISMATCH",
27233
- `expected ${args.contentLength} bytes, got ${actualSize}`
27234
- );
27235
- }
27236
- const attachmentId = hasher.digest("hex").slice(0, HASH_PREFIX_LEN);
27237
- const hashDir = import_node_path16.default.join(attachmentsRoot, attachmentId);
27238
- let finalFileName;
27239
- let hashDirExists = false;
27240
- try {
27241
- hashDirExists = import_node_fs14.default.statSync(hashDir).isDirectory();
27242
- } catch {
27243
- }
27244
- if (hashDirExists) {
27245
- const existing = import_node_fs14.default.readdirSync(hashDir).filter((n) => !n.startsWith("."));
27246
- finalFileName = existing[0] ?? args.fileName;
27247
- try {
27248
- import_node_fs14.default.unlinkSync(tmpPath);
27249
- } catch {
27250
- }
27251
- } else {
27252
- try {
27253
- import_node_fs14.default.mkdirSync(hashDir, { recursive: true });
27254
- finalFileName = args.fileName;
27255
- import_node_fs14.default.renameSync(tmpPath, import_node_path16.default.join(hashDir, finalFileName));
27256
- } catch (err) {
27257
- try {
27258
- import_node_fs14.default.unlinkSync(tmpPath);
27259
- } catch {
27260
- }
27261
- throw new UploadError("STORAGE_ERROR", `rename failed: ${err.message}`);
27262
- }
27263
- }
27264
- const absPath = import_node_path16.default.join(hashDir, finalFileName);
27265
- const relPath = import_node_path16.default.relative(args.sessionDir, absPath);
27266
- args.groupFileStore.upsert(args.scope, args.sessionId, {
27267
- relPath: absPath,
27268
- // 存绝对路径,与现有 agent 入清单的形态一致(attachment.ts:144 双形态兼容)
27269
- from: "user-upload",
27270
- label: args.fileName,
27271
- size: actualSize,
27272
- mime: args.mimeType
27273
- });
27274
- return { attachmentId, absPath, relPath, sizeBytes: actualSize, mimeType: args.mimeType };
27275
- }
27276
-
27277
27345
  // src/transport/http-router.ts
27278
- var UPLOAD_MIME_ALLOW = /* @__PURE__ */ new Set([
27279
- "image/png",
27280
- "image/jpeg",
27281
- "image/gif",
27282
- "image/webp",
27283
- "application/pdf"
27284
- ]);
27285
- var UPLOAD_MIME_ALLOW_PREFIX = ["text/"];
27286
- var UPLOAD_MAX_BYTES = 25 * 1024 * 1024;
27287
- function isUploadMimeAllowed(mime) {
27288
- if (UPLOAD_MIME_ALLOW.has(mime)) return true;
27289
- return UPLOAD_MIME_ALLOW_PREFIX.some((p2) => mime.startsWith(p2));
27290
- }
27291
- function isValidUploadFileName(fileName) {
27292
- if (fileName.length === 0) return false;
27293
- if (fileName === "." || fileName === "..") return false;
27294
- if (fileName.startsWith(".")) return false;
27295
- if (fileName.includes("/") || fileName.includes("\\")) return false;
27296
- return fileName === import_node_path17.default.basename(fileName);
27297
- }
27298
27346
  function createHttpRouter(deps) {
27299
27347
  return async (req, res) => {
27300
27348
  const url = parseUrl(req.url);
@@ -27308,7 +27356,7 @@ function createHttpRouter(deps) {
27308
27356
  });
27309
27357
  return true;
27310
27358
  }
27311
- if (!url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/") && url.pathname !== "/attachments/upload") {
27359
+ if (!url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/")) {
27312
27360
  return false;
27313
27361
  }
27314
27362
  if (url.pathname.startsWith("/files/") && req.method === "GET") {
@@ -27348,106 +27396,6 @@ function createHttpRouter(deps) {
27348
27396
  sendJson(res, 401, { code: "UNAUTHORIZED", message: "missing or invalid bearer token" });
27349
27397
  return true;
27350
27398
  }
27351
- if (url.pathname === "/attachments/upload" && req.method === "POST") {
27352
- if (ctx.role !== "owner") {
27353
- sendJson(res, 403, { code: "FORBIDDEN", message: "owner only" });
27354
- return true;
27355
- }
27356
- const up = deps.attachmentUpload;
27357
- if (!up) {
27358
- sendJson(res, 501, { code: "NOT_IMPLEMENTED", message: "attachment upload not wired" });
27359
- return true;
27360
- }
27361
- const sessionId = url.searchParams.get("sessionId");
27362
- const fileName = url.searchParams.get("fileName");
27363
- const mimeType = url.searchParams.get("mimeType");
27364
- const contentLengthRaw = req.headers["content-length"];
27365
- if (!sessionId || !fileName || !mimeType || !contentLengthRaw) {
27366
- sendJson(res, 400, {
27367
- code: "INVALID_PARAM",
27368
- message: "missing sessionId/fileName/mimeType/Content-Length"
27369
- });
27370
- return true;
27371
- }
27372
- if (!isValidUploadFileName(fileName)) {
27373
- sendJson(res, 400, {
27374
- code: "INVALID_FILENAME",
27375
- message: `fileName must be a plain basename (no path separators, no dotfiles): ${fileName}`
27376
- });
27377
- return true;
27378
- }
27379
- const contentLength = Number.parseInt(contentLengthRaw, 10);
27380
- if (!Number.isFinite(contentLength) || contentLength < 0) {
27381
- sendJson(res, 400, { code: "INVALID_PARAM", message: "malformed Content-Length" });
27382
- return true;
27383
- }
27384
- if (contentLength > UPLOAD_MAX_BYTES) {
27385
- sendJson(res, 413, {
27386
- code: "FILE_TOO_LARGE",
27387
- message: `max ${UPLOAD_MAX_BYTES} bytes`
27388
- });
27389
- return true;
27390
- }
27391
- if (!isUploadMimeAllowed(mimeType)) {
27392
- sendJson(res, 400, {
27393
- code: "UNSUPPORTED_MIME",
27394
- message: `mime ${mimeType} not allowed`
27395
- });
27396
- return true;
27397
- }
27398
- const sessionDir = up.getSessionDir(sessionId);
27399
- const scope = up.getSessionScope(sessionId);
27400
- if (!sessionDir || !scope) {
27401
- sendJson(res, 404, { code: "NOT_FOUND", message: `session ${sessionId} not found` });
27402
- return true;
27403
- }
27404
- const httpBaseUrl = up.getHttpBaseUrl();
27405
- if (!httpBaseUrl) {
27406
- sendJson(res, 503, {
27407
- code: "TUNNEL_NOT_READY",
27408
- message: "httpBaseUrl unavailable"
27409
- });
27410
- return true;
27411
- }
27412
- const secret = deps.getSignSecret?.();
27413
- if (!secret) {
27414
- sendJson(res, 501, {
27415
- code: "NOT_IMPLEMENTED",
27416
- message: "sign secret unavailable (noAuth?)"
27417
- });
27418
- return true;
27419
- }
27420
- try {
27421
- const result = await writeUploadedAttachment({
27422
- sessionDir,
27423
- sessionId,
27424
- scope,
27425
- fileName,
27426
- mimeType,
27427
- contentLength,
27428
- body: req,
27429
- groupFileStore: up.groupFileStore
27430
- });
27431
- const parts = signUrlParts(secret, result.absPath, null);
27432
- const fullUrl = buildSignedFileUrl(httpBaseUrl, parts);
27433
- sendJson(res, 200, {
27434
- attachmentId: result.attachmentId,
27435
- url: fullUrl,
27436
- relPath: result.relPath,
27437
- sizeBytes: result.sizeBytes,
27438
- mimeType: result.mimeType
27439
- });
27440
- } catch (err) {
27441
- if (err instanceof UploadError) {
27442
- const status = err.code === "SIZE_MISMATCH" ? 400 : 500;
27443
- sendJson(res, status, { code: err.code, message: err.message });
27444
- } else {
27445
- deps.logger?.warn("attachment upload failed", { err: err.message });
27446
- sendJson(res, 500, { code: "STORAGE_ERROR", message: err.message });
27447
- }
27448
- }
27449
- return true;
27450
- }
27451
27399
  const sessionFilesMatch = url.pathname.match(/^\/session\/([^/]+)\/files$/);
27452
27400
  if (sessionFilesMatch && req.method === "GET") {
27453
27401
  const sid = sessionFilesMatch[1];
@@ -27529,78 +27477,10 @@ function streamFile(res, absPath, logger) {
27529
27477
  stream.pipe(res);
27530
27478
  }
27531
27479
 
27532
- // src/attachment/gc.ts
27480
+ // src/attachment/group.ts
27533
27481
  var import_node_fs16 = __toESM(require("fs"), 1);
27534
27482
  var import_node_path18 = __toESM(require("path"), 1);
27535
- var DEFAULT_TTL_MS = 30 * 24 * 3600 * 1e3;
27536
- function runAttachmentGc(args) {
27537
- const now = (args.now ?? Date.now)();
27538
- const ttlMs = args.ttlMs ?? DEFAULT_TTL_MS;
27539
- const liveAbs = /* @__PURE__ */ new Set();
27540
- for (const { scope, sessionId } of args.sessionScopes) {
27541
- for (const entry of args.groupFileStore.list(scope, sessionId)) {
27542
- if (entry.stale) continue;
27543
- if (import_node_path18.default.isAbsolute(entry.relPath)) liveAbs.add(entry.relPath);
27544
- }
27545
- }
27546
- for (const { scope, sessionId } of args.sessionScopes) {
27547
- const sessionDir = args.getSessionCwd?.(sessionId) ?? import_node_path18.default.join(
27548
- args.dataDir,
27549
- "sessions",
27550
- ...scopeSubPath(scope).map(safeFileName),
27551
- safeFileName(sessionId)
27552
- );
27553
- const attRoot = import_node_path18.default.join(sessionDir, ".attachments");
27554
- let hashDirs;
27555
- try {
27556
- hashDirs = import_node_fs16.default.readdirSync(attRoot);
27557
- } catch (err) {
27558
- if (err.code === "ENOENT") continue;
27559
- args.logger?.warn("attachment gc: readdir failed", { attRoot, err: err.message });
27560
- continue;
27561
- }
27562
- for (const hashDir of hashDirs) {
27563
- const hashDirAbs = import_node_path18.default.join(attRoot, hashDir);
27564
- let files;
27565
- try {
27566
- files = import_node_fs16.default.readdirSync(hashDirAbs);
27567
- } catch {
27568
- continue;
27569
- }
27570
- for (const name of files) {
27571
- const file = import_node_path18.default.join(hashDirAbs, name);
27572
- let stat;
27573
- try {
27574
- stat = import_node_fs16.default.statSync(file);
27575
- } catch {
27576
- continue;
27577
- }
27578
- if (!stat.isFile()) continue;
27579
- const age = now - stat.mtimeMs;
27580
- if (age < ttlMs) continue;
27581
- if (liveAbs.has(file)) continue;
27582
- try {
27583
- import_node_fs16.default.unlinkSync(file);
27584
- } catch (err) {
27585
- args.logger?.warn("attachment gc: unlink failed", { file, err: err.message });
27586
- }
27587
- }
27588
- try {
27589
- if (import_node_fs16.default.readdirSync(hashDirAbs).length === 0) import_node_fs16.default.rmdirSync(hashDirAbs);
27590
- } catch {
27591
- }
27592
- }
27593
- try {
27594
- if (import_node_fs16.default.readdirSync(attRoot).length === 0) import_node_fs16.default.rmdirSync(attRoot);
27595
- } catch {
27596
- }
27597
- }
27598
- }
27599
-
27600
- // src/attachment/group.ts
27601
- var import_node_fs17 = __toESM(require("fs"), 1);
27602
- var import_node_path19 = __toESM(require("path"), 1);
27603
- var import_node_crypto7 = __toESM(require("crypto"), 1);
27483
+ var import_node_crypto6 = __toESM(require("crypto"), 1);
27604
27484
  init_protocol();
27605
27485
  var GroupFileStore = class {
27606
27486
  dataDir;
@@ -27611,11 +27491,11 @@ var GroupFileStore = class {
27611
27491
  this.logger = opts.logger;
27612
27492
  }
27613
27493
  rootForScope(scope) {
27614
- return import_node_path19.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
27494
+ return import_node_path18.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
27615
27495
  }
27616
27496
  /** 与 SessionStore.filePath 平级,扩展名 .group-files.json */
27617
27497
  filePath(scope, sessionId) {
27618
- return import_node_path19.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
27498
+ return import_node_path18.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
27619
27499
  }
27620
27500
  cacheKey(scope, sessionId) {
27621
27501
  return scope.kind === "default" ? `default::${sessionId}` : `persona:${scope.personaId}:${scope.mode}::${sessionId}`;
@@ -27624,7 +27504,7 @@ var GroupFileStore = class {
27624
27504
  readFile(scope, sessionId) {
27625
27505
  const file = this.filePath(scope, sessionId);
27626
27506
  try {
27627
- const raw = import_node_fs17.default.readFileSync(file, "utf8");
27507
+ const raw = import_node_fs16.default.readFileSync(file, "utf8");
27628
27508
  const parsed = JSON.parse(raw);
27629
27509
  if (!Array.isArray(parsed)) {
27630
27510
  this.logger?.warn("GroupFileStore.readFile: not an array; resetting session entries", {
@@ -27650,10 +27530,10 @@ var GroupFileStore = class {
27650
27530
  }
27651
27531
  writeFile(scope, sessionId, entries) {
27652
27532
  const file = this.filePath(scope, sessionId);
27653
- import_node_fs17.default.mkdirSync(import_node_path19.default.dirname(file), { recursive: true });
27533
+ import_node_fs16.default.mkdirSync(import_node_path18.default.dirname(file), { recursive: true });
27654
27534
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
27655
- import_node_fs17.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
27656
- import_node_fs17.default.renameSync(tmp, file);
27535
+ import_node_fs16.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
27536
+ import_node_fs16.default.renameSync(tmp, file);
27657
27537
  }
27658
27538
  /** 拉一份当前 session 的清单。读盘 → cache;之后调用复用 cache */
27659
27539
  list(scope, sessionId) {
@@ -27689,7 +27569,7 @@ var GroupFileStore = class {
27689
27569
  entries[idx] = next;
27690
27570
  } else {
27691
27571
  next = {
27692
- id: `gf-${import_node_crypto7.default.randomBytes(6).toString("base64url")}`,
27572
+ id: `gf-${import_node_crypto6.default.randomBytes(6).toString("base64url")}`,
27693
27573
  relPath: input.relPath,
27694
27574
  from: input.from,
27695
27575
  label: input.label,
@@ -27739,10 +27619,10 @@ var GroupFileStore = class {
27739
27619
  };
27740
27620
 
27741
27621
  // src/discovery/state-file.ts
27742
- var import_node_fs18 = __toESM(require("fs"), 1);
27743
- var import_node_path20 = __toESM(require("path"), 1);
27622
+ var import_node_fs17 = __toESM(require("fs"), 1);
27623
+ var import_node_path19 = __toESM(require("path"), 1);
27744
27624
  function defaultStateFilePath(dataDir) {
27745
- return import_node_path20.default.join(dataDir, "state.json");
27625
+ return import_node_path19.default.join(dataDir, "state.json");
27746
27626
  }
27747
27627
  function isPidAlive(pid) {
27748
27628
  if (!Number.isFinite(pid) || pid <= 0) return false;
@@ -27764,7 +27644,7 @@ var StateFileManager = class {
27764
27644
  }
27765
27645
  read() {
27766
27646
  try {
27767
- const raw = import_node_fs18.default.readFileSync(this.file, "utf8");
27647
+ const raw = import_node_fs17.default.readFileSync(this.file, "utf8");
27768
27648
  const parsed = JSON.parse(raw);
27769
27649
  return parsed;
27770
27650
  } catch {
@@ -27778,34 +27658,34 @@ var StateFileManager = class {
27778
27658
  return { status: "stale", existing };
27779
27659
  }
27780
27660
  write(state) {
27781
- import_node_fs18.default.mkdirSync(import_node_path20.default.dirname(this.file), { recursive: true });
27661
+ import_node_fs17.default.mkdirSync(import_node_path19.default.dirname(this.file), { recursive: true });
27782
27662
  const tmp = `${this.file}.tmp.${process.pid}.${Date.now()}`;
27783
- import_node_fs18.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
27784
- import_node_fs18.default.renameSync(tmp, this.file);
27663
+ import_node_fs17.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
27664
+ import_node_fs17.default.renameSync(tmp, this.file);
27785
27665
  if (process.platform !== "win32") {
27786
27666
  try {
27787
- import_node_fs18.default.chmodSync(this.file, 384);
27667
+ import_node_fs17.default.chmodSync(this.file, 384);
27788
27668
  } catch {
27789
27669
  }
27790
27670
  }
27791
27671
  }
27792
27672
  delete() {
27793
27673
  try {
27794
- import_node_fs18.default.unlinkSync(this.file);
27674
+ import_node_fs17.default.unlinkSync(this.file);
27795
27675
  } catch {
27796
27676
  }
27797
27677
  }
27798
27678
  };
27799
27679
 
27800
27680
  // src/tunnel/tunnel-manager.ts
27801
- var import_node_fs22 = __toESM(require("fs"), 1);
27802
- var import_node_path24 = __toESM(require("path"), 1);
27803
- var import_node_crypto8 = __toESM(require("crypto"), 1);
27681
+ var import_node_fs21 = __toESM(require("fs"), 1);
27682
+ var import_node_path23 = __toESM(require("path"), 1);
27683
+ var import_node_crypto7 = __toESM(require("crypto"), 1);
27804
27684
  var import_node_child_process5 = require("child_process");
27805
27685
 
27806
27686
  // src/tunnel/tunnel-store.ts
27807
- var import_node_fs19 = __toESM(require("fs"), 1);
27808
- var import_node_path21 = __toESM(require("path"), 1);
27687
+ var import_node_fs18 = __toESM(require("fs"), 1);
27688
+ var import_node_path20 = __toESM(require("path"), 1);
27809
27689
  var TunnelStore = class {
27810
27690
  constructor(filePath) {
27811
27691
  this.filePath = filePath;
@@ -27813,7 +27693,7 @@ var TunnelStore = class {
27813
27693
  filePath;
27814
27694
  async get() {
27815
27695
  try {
27816
- const raw = await import_node_fs19.default.promises.readFile(this.filePath, "utf8");
27696
+ const raw = await import_node_fs18.default.promises.readFile(this.filePath, "utf8");
27817
27697
  const obj = JSON.parse(raw);
27818
27698
  if (!isPersistedTunnel(obj)) return null;
27819
27699
  return obj;
@@ -27824,22 +27704,22 @@ var TunnelStore = class {
27824
27704
  }
27825
27705
  }
27826
27706
  async set(v2) {
27827
- const dir = import_node_path21.default.dirname(this.filePath);
27828
- await import_node_fs19.default.promises.mkdir(dir, { recursive: true });
27707
+ const dir = import_node_path20.default.dirname(this.filePath);
27708
+ await import_node_fs18.default.promises.mkdir(dir, { recursive: true });
27829
27709
  const data = JSON.stringify(v2, null, 2);
27830
27710
  const tmp = `${this.filePath}.tmp.${process.pid}.${Date.now()}`;
27831
- await import_node_fs19.default.promises.writeFile(tmp, data, { mode: 384 });
27711
+ await import_node_fs18.default.promises.writeFile(tmp, data, { mode: 384 });
27832
27712
  if (process.platform !== "win32") {
27833
27713
  try {
27834
- await import_node_fs19.default.promises.chmod(tmp, 384);
27714
+ await import_node_fs18.default.promises.chmod(tmp, 384);
27835
27715
  } catch {
27836
27716
  }
27837
27717
  }
27838
- await import_node_fs19.default.promises.rename(tmp, this.filePath);
27718
+ await import_node_fs18.default.promises.rename(tmp, this.filePath);
27839
27719
  }
27840
27720
  async clear() {
27841
27721
  try {
27842
- await import_node_fs19.default.promises.unlink(this.filePath);
27722
+ await import_node_fs18.default.promises.unlink(this.filePath);
27843
27723
  } catch (err) {
27844
27724
  const code = err?.code;
27845
27725
  if (code !== "ENOENT") throw err;
@@ -27934,12 +27814,12 @@ function escape(v2) {
27934
27814
  }
27935
27815
 
27936
27816
  // src/tunnel/frpc-binary.ts
27937
- var import_node_fs20 = __toESM(require("fs"), 1);
27938
- var import_node_os9 = __toESM(require("os"), 1);
27939
- var import_node_path22 = __toESM(require("path"), 1);
27817
+ var import_node_fs19 = __toESM(require("fs"), 1);
27818
+ var import_node_os10 = __toESM(require("os"), 1);
27819
+ var import_node_path21 = __toESM(require("path"), 1);
27940
27820
  var import_node_child_process3 = require("child_process");
27941
27821
  var import_node_stream2 = require("stream");
27942
- var import_promises2 = require("stream/promises");
27822
+ var import_promises = require("stream/promises");
27943
27823
  var FRPC_VERSION = "0.68.0";
27944
27824
  var UnsupportedPlatformError = class extends Error {
27945
27825
  constructor(platform, arch) {
@@ -27968,20 +27848,20 @@ function frpcDownloadUrl(version2, p2) {
27968
27848
  }
27969
27849
  async function ensureFrpcBinary(opts) {
27970
27850
  if (opts.override) {
27971
- if (!import_node_fs20.default.existsSync(opts.override)) {
27851
+ if (!import_node_fs19.default.existsSync(opts.override)) {
27972
27852
  throw new Error(`frpc binary not found at override path: ${opts.override}`);
27973
27853
  }
27974
27854
  return opts.override;
27975
27855
  }
27976
27856
  const version2 = opts.version ?? FRPC_VERSION;
27977
27857
  const platform = opts.platform ?? detectPlatform();
27978
- const binDir = import_node_path22.default.join(opts.dataDir, "bin");
27979
- import_node_fs20.default.mkdirSync(binDir, { recursive: true });
27858
+ const binDir = import_node_path21.default.join(opts.dataDir, "bin");
27859
+ import_node_fs19.default.mkdirSync(binDir, { recursive: true });
27980
27860
  cleanupStaleArtifacts(binDir);
27981
- const stableBin = import_node_path22.default.join(binDir, "frpc");
27982
- if (import_node_fs20.default.existsSync(stableBin)) return stableBin;
27861
+ const stableBin = import_node_path21.default.join(binDir, "frpc");
27862
+ if (import_node_fs19.default.existsSync(stableBin)) return stableBin;
27983
27863
  const partialBin = `${stableBin}.partial`;
27984
- const tarballPath = import_node_path22.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
27864
+ const tarballPath = import_node_path21.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
27985
27865
  try {
27986
27866
  const url = frpcDownloadUrl(version2, platform);
27987
27867
  await downloadToFile(url, tarballPath, opts.fetchImpl);
@@ -27990,8 +27870,8 @@ async function ensureFrpcBinary(opts) {
27990
27870
  } else {
27991
27871
  await extractFrpcFromTarball(tarballPath, binDir, version2, platform, partialBin);
27992
27872
  }
27993
- import_node_fs20.default.chmodSync(partialBin, 493);
27994
- import_node_fs20.default.renameSync(partialBin, stableBin);
27873
+ import_node_fs19.default.chmodSync(partialBin, 493);
27874
+ import_node_fs19.default.renameSync(partialBin, stableBin);
27995
27875
  } finally {
27996
27876
  safeUnlink(tarballPath);
27997
27877
  safeUnlink(partialBin);
@@ -28001,15 +27881,15 @@ async function ensureFrpcBinary(opts) {
28001
27881
  function cleanupStaleArtifacts(binDir) {
28002
27882
  let entries;
28003
27883
  try {
28004
- entries = import_node_fs20.default.readdirSync(binDir);
27884
+ entries = import_node_fs19.default.readdirSync(binDir);
28005
27885
  } catch {
28006
27886
  return;
28007
27887
  }
28008
27888
  for (const name of entries) {
28009
27889
  if (name.endsWith(".partial") || name.startsWith("extract-")) {
28010
- const full = import_node_path22.default.join(binDir, name);
27890
+ const full = import_node_path21.default.join(binDir, name);
28011
27891
  try {
28012
- import_node_fs20.default.rmSync(full, { recursive: true, force: true });
27892
+ import_node_fs19.default.rmSync(full, { recursive: true, force: true });
28013
27893
  } catch {
28014
27894
  }
28015
27895
  }
@@ -28017,7 +27897,7 @@ function cleanupStaleArtifacts(binDir) {
28017
27897
  }
28018
27898
  function safeUnlink(p2) {
28019
27899
  try {
28020
- import_node_fs20.default.unlinkSync(p2);
27900
+ import_node_fs19.default.unlinkSync(p2);
28021
27901
  } catch {
28022
27902
  }
28023
27903
  }
@@ -28028,13 +27908,13 @@ async function downloadToFile(url, dest, fetchImpl) {
28028
27908
  if (!res.ok || !res.body) {
28029
27909
  throw new Error(`download failed: ${res.status} ${res.statusText}`);
28030
27910
  }
28031
- const out = import_node_fs20.default.createWriteStream(dest);
27911
+ const out = import_node_fs19.default.createWriteStream(dest);
28032
27912
  const nodeStream = import_node_stream2.Readable.fromWeb(res.body);
28033
- await (0, import_promises2.pipeline)(nodeStream, out);
27913
+ await (0, import_promises.pipeline)(nodeStream, out);
28034
27914
  }
28035
27915
  async function extractFrpcFromTarball(tarball, binDir, version2, platform, destBin) {
28036
- const work = import_node_path22.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
28037
- import_node_fs20.default.mkdirSync(work, { recursive: true });
27916
+ const work = import_node_path21.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
27917
+ import_node_fs19.default.mkdirSync(work, { recursive: true });
28038
27918
  try {
28039
27919
  await new Promise((resolve6, reject) => {
28040
27920
  const proc = (0, import_node_child_process3.spawn)("tar", ["xzf", tarball, "-C", work], { stdio: "pipe" });
@@ -28042,32 +27922,32 @@ async function extractFrpcFromTarball(tarball, binDir, version2, platform, destB
28042
27922
  proc.on("exit", (code) => code === 0 ? resolve6() : reject(new Error(`tar exited ${code}`)));
28043
27923
  });
28044
27924
  const dirName = `frp_${version2}_${platform.os}_${platform.arch}`;
28045
- const src = import_node_path22.default.join(work, dirName, "frpc");
28046
- if (!import_node_fs20.default.existsSync(src)) {
27925
+ const src = import_node_path21.default.join(work, dirName, "frpc");
27926
+ if (!import_node_fs19.default.existsSync(src)) {
28047
27927
  throw new Error(`frpc not found inside tarball at ${src}`);
28048
27928
  }
28049
- import_node_fs20.default.copyFileSync(src, destBin);
27929
+ import_node_fs19.default.copyFileSync(src, destBin);
28050
27930
  } finally {
28051
- import_node_fs20.default.rmSync(work, { recursive: true, force: true });
27931
+ import_node_fs19.default.rmSync(work, { recursive: true, force: true });
28052
27932
  }
28053
27933
  }
28054
27934
 
28055
27935
  // src/tunnel/frpc-process.ts
28056
- var import_node_fs21 = __toESM(require("fs"), 1);
28057
- var import_node_path23 = __toESM(require("path"), 1);
27936
+ var import_node_fs20 = __toESM(require("fs"), 1);
27937
+ var import_node_path22 = __toESM(require("path"), 1);
28058
27938
  var import_node_child_process4 = require("child_process");
28059
27939
  function frpcPidFilePath(dataDir) {
28060
- return import_node_path23.default.join(dataDir, "frpc.pid");
27940
+ return import_node_path22.default.join(dataDir, "frpc.pid");
28061
27941
  }
28062
27942
  function writeFrpcPid(dataDir, pid) {
28063
27943
  try {
28064
- import_node_fs21.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
27944
+ import_node_fs20.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
28065
27945
  } catch {
28066
27946
  }
28067
27947
  }
28068
27948
  function clearFrpcPid(dataDir) {
28069
27949
  try {
28070
- import_node_fs21.default.unlinkSync(frpcPidFilePath(dataDir));
27950
+ import_node_fs20.default.unlinkSync(frpcPidFilePath(dataDir));
28071
27951
  } catch {
28072
27952
  }
28073
27953
  }
@@ -28083,7 +27963,7 @@ function defaultIsPidAlive(pid) {
28083
27963
  }
28084
27964
  function defaultReadPidFile(file) {
28085
27965
  try {
28086
- return import_node_fs21.default.readFileSync(file, "utf8");
27966
+ return import_node_fs20.default.readFileSync(file, "utf8");
28087
27967
  } catch {
28088
27968
  return null;
28089
27969
  }
@@ -28099,7 +27979,7 @@ function defaultSleep(ms) {
28099
27979
  }
28100
27980
  async function killStaleFrpc(deps) {
28101
27981
  const pidFile = frpcPidFilePath(deps.dataDir);
28102
- const tomlPath = import_node_path23.default.join(deps.dataDir, "frpc.toml");
27982
+ const tomlPath = import_node_path22.default.join(deps.dataDir, "frpc.toml");
28103
27983
  const readPidFile = deps.readPidFileImpl ?? defaultReadPidFile;
28104
27984
  const isAlive = deps.isPidAliveImpl ?? defaultIsPidAlive;
28105
27985
  const killPid = deps.killPidImpl ?? defaultKillPid;
@@ -28123,7 +28003,7 @@ async function killStaleFrpc(deps) {
28123
28003
  }
28124
28004
  if (victims.size === 0) {
28125
28005
  try {
28126
- import_node_fs21.default.unlinkSync(pidFile);
28006
+ import_node_fs20.default.unlinkSync(pidFile);
28127
28007
  } catch {
28128
28008
  }
28129
28009
  return;
@@ -28134,7 +28014,7 @@ async function killStaleFrpc(deps) {
28134
28014
  }
28135
28015
  await sleep(deps.reapWaitMs ?? 300);
28136
28016
  try {
28137
- import_node_fs21.default.unlinkSync(pidFile);
28017
+ import_node_fs20.default.unlinkSync(pidFile);
28138
28018
  } catch {
28139
28019
  }
28140
28020
  }
@@ -28171,7 +28051,7 @@ var DEFAULT_TUNNEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
28171
28051
  var TunnelManager = class {
28172
28052
  constructor(deps) {
28173
28053
  this.deps = deps;
28174
- this.store = deps.store ?? new TunnelStore(import_node_path24.default.join(deps.dataDir, "tunnel.json"));
28054
+ this.store = deps.store ?? new TunnelStore(import_node_path23.default.join(deps.dataDir, "tunnel.json"));
28175
28055
  this.ttlMs = deps.ttlMs ?? DEFAULT_TUNNEL_TTL_MS;
28176
28056
  this.startupTimeoutMs = deps.startupTimeoutMs ?? 15e3;
28177
28057
  }
@@ -28298,8 +28178,8 @@ var TunnelManager = class {
28298
28178
  dataDir: this.deps.dataDir,
28299
28179
  override: this.deps.frpcBinaryOverride ?? void 0
28300
28180
  });
28301
- const tomlPath = import_node_path24.default.join(this.deps.dataDir, "frpc.toml");
28302
- const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto8.default.randomBytes(3).toString("hex")}`;
28181
+ const tomlPath = import_node_path23.default.join(this.deps.dataDir, "frpc.toml");
28182
+ const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto7.default.randomBytes(3).toString("hex")}`;
28303
28183
  const toml = buildFrpcToml({
28304
28184
  serverAddr: t.frpsHost,
28305
28185
  serverPort: t.frpsPort,
@@ -28309,12 +28189,12 @@ var TunnelManager = class {
28309
28189
  localPort,
28310
28190
  logLevel: "info"
28311
28191
  });
28312
- await import_node_fs22.default.promises.writeFile(tomlPath, toml, { mode: 384 });
28192
+ await import_node_fs21.default.promises.writeFile(tomlPath, toml, { mode: 384 });
28313
28193
  const proc = (this.deps.spawnImpl ?? import_node_child_process5.spawn)(frpcBin, ["-c", tomlPath], {
28314
28194
  stdio: ["ignore", "pipe", "pipe"]
28315
28195
  });
28316
- const logFilePath = import_node_path24.default.join(this.deps.dataDir, "frpc.log");
28317
- const logStream = import_node_fs22.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
28196
+ const logFilePath = import_node_path23.default.join(this.deps.dataDir, "frpc.log");
28197
+ const logStream = import_node_fs21.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
28318
28198
  logStream.on("error", () => {
28319
28199
  });
28320
28200
  const tee = (chunk) => {
@@ -28396,23 +28276,23 @@ async function waitForFrpcReady(proc, timeoutMs) {
28396
28276
  }
28397
28277
 
28398
28278
  // src/tunnel/device-key.ts
28399
- var import_node_os10 = __toESM(require("os"), 1);
28400
- var import_node_crypto9 = __toESM(require("crypto"), 1);
28279
+ var import_node_os11 = __toESM(require("os"), 1);
28280
+ var import_node_crypto8 = __toESM(require("crypto"), 1);
28401
28281
  var DERIVE_SALT = "clawd-tunnel-device-v1";
28402
28282
  function deriveStableDeviceKey(opts = {}) {
28403
- const hostname = opts.hostname ?? import_node_os10.default.hostname();
28404
- const uid = opts.uid ?? (typeof import_node_os10.default.userInfo === "function" ? import_node_os10.default.userInfo().uid : 0);
28283
+ const hostname = opts.hostname ?? import_node_os11.default.hostname();
28284
+ const uid = opts.uid ?? (typeof import_node_os11.default.userInfo === "function" ? import_node_os11.default.userInfo().uid : 0);
28405
28285
  const input = `${hostname}::${uid}`;
28406
- return import_node_crypto9.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
28286
+ return import_node_crypto8.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
28407
28287
  }
28408
28288
 
28409
28289
  // src/auth-store.ts
28410
- var import_node_fs23 = __toESM(require("fs"), 1);
28411
- var import_node_path25 = __toESM(require("path"), 1);
28412
- var import_node_crypto10 = __toESM(require("crypto"), 1);
28290
+ var import_node_fs22 = __toESM(require("fs"), 1);
28291
+ var import_node_path24 = __toESM(require("path"), 1);
28292
+ var import_node_crypto9 = __toESM(require("crypto"), 1);
28413
28293
  var AUTH_FILE_NAME = "auth.json";
28414
28294
  function authFilePath(dataDir) {
28415
- return import_node_path25.default.join(dataDir, AUTH_FILE_NAME);
28295
+ return import_node_path24.default.join(dataDir, AUTH_FILE_NAME);
28416
28296
  }
28417
28297
  function loadOrCreateAuthFile(opts) {
28418
28298
  const file = authFilePath(opts.dataDir);
@@ -28437,14 +28317,14 @@ function loadOrCreateAuthFile(opts) {
28437
28317
  return next;
28438
28318
  }
28439
28319
  function defaultGenerateToken2() {
28440
- return import_node_crypto10.default.randomBytes(32).toString("base64url");
28320
+ return import_node_crypto9.default.randomBytes(32).toString("base64url");
28441
28321
  }
28442
28322
  function defaultGenerateOwnerPrincipalId() {
28443
- return `owner-${import_node_crypto10.default.randomUUID()}`;
28323
+ return `owner-${import_node_crypto9.default.randomUUID()}`;
28444
28324
  }
28445
28325
  function readAuthFile(file) {
28446
28326
  try {
28447
- const raw = import_node_fs23.default.readFileSync(file, "utf8");
28327
+ const raw = import_node_fs22.default.readFileSync(file, "utf8");
28448
28328
  const parsed = JSON.parse(raw);
28449
28329
  if (typeof parsed?.token !== "string" || parsed.token.length === 0) {
28450
28330
  return null;
@@ -28462,25 +28342,25 @@ function readAuthFile(file) {
28462
28342
  }
28463
28343
  }
28464
28344
  function writeAuthFile(file, content) {
28465
- import_node_fs23.default.mkdirSync(import_node_path25.default.dirname(file), { recursive: true });
28466
- import_node_fs23.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
28345
+ import_node_fs22.default.mkdirSync(import_node_path24.default.dirname(file), { recursive: true });
28346
+ import_node_fs22.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
28467
28347
  try {
28468
- import_node_fs23.default.chmodSync(file, 384);
28348
+ import_node_fs22.default.chmodSync(file, 384);
28469
28349
  } catch {
28470
28350
  }
28471
28351
  }
28472
28352
 
28473
28353
  // src/owner-profile.ts
28474
- var import_node_fs24 = __toESM(require("fs"), 1);
28475
- var import_node_os11 = __toESM(require("os"), 1);
28476
- var import_node_path26 = __toESM(require("path"), 1);
28354
+ var import_node_fs23 = __toESM(require("fs"), 1);
28355
+ var import_node_os12 = __toESM(require("os"), 1);
28356
+ var import_node_path25 = __toESM(require("path"), 1);
28477
28357
  var PROFILE_FILENAME = "profile.json";
28478
28358
  function loadOwnerDisplayName(dataDir) {
28479
- const fallback = import_node_os11.default.userInfo().username;
28480
- const profilePath = import_node_path26.default.join(dataDir, PROFILE_FILENAME);
28359
+ const fallback = import_node_os12.default.userInfo().username;
28360
+ const profilePath = import_node_path25.default.join(dataDir, PROFILE_FILENAME);
28481
28361
  let raw;
28482
28362
  try {
28483
- raw = import_node_fs24.default.readFileSync(profilePath, "utf8");
28363
+ raw = import_node_fs23.default.readFileSync(profilePath, "utf8");
28484
28364
  } catch {
28485
28365
  return fallback;
28486
28366
  }
@@ -28509,12 +28389,12 @@ init_protocol();
28509
28389
  init_protocol();
28510
28390
 
28511
28391
  // src/session/fork.ts
28512
- var import_node_fs25 = __toESM(require("fs"), 1);
28513
- var import_node_os12 = __toESM(require("os"), 1);
28514
- var import_node_path27 = __toESM(require("path"), 1);
28392
+ var import_node_fs24 = __toESM(require("fs"), 1);
28393
+ var import_node_os13 = __toESM(require("os"), 1);
28394
+ var import_node_path26 = __toESM(require("path"), 1);
28515
28395
  init_claude_history();
28516
28396
  function readJsonlEntries(file) {
28517
- const raw = import_node_fs25.default.readFileSync(file, "utf8");
28397
+ const raw = import_node_fs24.default.readFileSync(file, "utf8");
28518
28398
  const out = [];
28519
28399
  for (const line of raw.split("\n")) {
28520
28400
  const t = line.trim();
@@ -28527,10 +28407,10 @@ function readJsonlEntries(file) {
28527
28407
  return out;
28528
28408
  }
28529
28409
  function forkSession(input) {
28530
- const baseDir = input.baseDir ?? import_node_path27.default.join(import_node_os12.default.homedir(), ".claude");
28531
- const projectDir = import_node_path27.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
28532
- const sourceFile = import_node_path27.default.join(projectDir, `${input.toolSessionId}.jsonl`);
28533
- if (!import_node_fs25.default.existsSync(sourceFile)) {
28410
+ const baseDir = input.baseDir ?? import_node_path26.default.join(import_node_os13.default.homedir(), ".claude");
28411
+ const projectDir = import_node_path26.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
28412
+ const sourceFile = import_node_path26.default.join(projectDir, `${input.toolSessionId}.jsonl`);
28413
+ if (!import_node_fs24.default.existsSync(sourceFile)) {
28534
28414
  throw new Error(`fork: source transcript not found: ${sourceFile}`);
28535
28415
  }
28536
28416
  const entries = readJsonlEntries(sourceFile);
@@ -28560,9 +28440,9 @@ function forkSession(input) {
28560
28440
  }
28561
28441
  forkedLines.push(JSON.stringify(forked));
28562
28442
  }
28563
- const forkedFilePath = import_node_path27.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
28564
- import_node_fs25.default.mkdirSync(projectDir, { recursive: true });
28565
- import_node_fs25.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
28443
+ const forkedFilePath = import_node_path26.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
28444
+ import_node_fs24.default.mkdirSync(projectDir, { recursive: true });
28445
+ import_node_fs24.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
28566
28446
  return { forkedToolSessionId, forkedFilePath };
28567
28447
  }
28568
28448
 
@@ -28850,6 +28730,28 @@ function buildSessionHandlers(deps) {
28850
28730
  }
28851
28731
  };
28852
28732
  };
28733
+ const peerSessionUpsert = async (frame, _client, ctx) => {
28734
+ if (ctx && ctx.principal.kind !== "owner") {
28735
+ throw new ClawdError(
28736
+ ERROR_CODES.UNAUTHORIZED,
28737
+ `principal ${ctx.principal.kind}:${ctx.principal.id} cannot peerSession:upsert`
28738
+ );
28739
+ }
28740
+ const file = PeerSessionUpsertArgs.parse(frame);
28741
+ manager.mirrorUpsert(file);
28742
+ return { response: { type: "peerSession:upsert:ok" }, broadcast: [] };
28743
+ };
28744
+ const peerSessionRemove = async (frame, _client, ctx) => {
28745
+ if (ctx && ctx.principal.kind !== "owner") {
28746
+ throw new ClawdError(
28747
+ ERROR_CODES.UNAUTHORIZED,
28748
+ `principal ${ctx.principal.kind}:${ctx.principal.id} cannot peerSession:remove`
28749
+ );
28750
+ }
28751
+ const args = PeerSessionRemoveArgs.parse(frame);
28752
+ manager.mirrorRemove(args.sessionId);
28753
+ return { response: { type: "peerSession:remove:ok" }, broadcast: [] };
28754
+ };
28853
28755
  return {
28854
28756
  "session:create": create,
28855
28757
  "session:list": list,
@@ -28875,7 +28777,9 @@ function buildSessionHandlers(deps) {
28875
28777
  "session:cancelQuestion": cancelQuestion,
28876
28778
  "session:pty:input": ptyInput,
28877
28779
  "session:pty:resize": ptyResize,
28878
- "session:pty:snapshot": ptySnapshot
28780
+ "session:pty:snapshot": ptySnapshot,
28781
+ "peerSession:upsert": peerSessionUpsert,
28782
+ "peerSession:remove": peerSessionRemove
28879
28783
  };
28880
28784
  }
28881
28785
 
@@ -28894,7 +28798,7 @@ function buildPermissionHandlers(deps) {
28894
28798
  }
28895
28799
 
28896
28800
  // src/handlers/history.ts
28897
- var path37 = __toESM(require("path"), 1);
28801
+ var path36 = __toESM(require("path"), 1);
28898
28802
  init_protocol();
28899
28803
 
28900
28804
  // src/session/recent-dirs.ts
@@ -28912,7 +28816,7 @@ function listRecentDirs(store, limit = 50) {
28912
28816
  }
28913
28817
 
28914
28818
  // src/permission/persona-paths.ts
28915
- var path36 = __toESM(require("path"), 1);
28819
+ var path35 = __toESM(require("path"), 1);
28916
28820
  function getAllowedPersonaIds(grants, action) {
28917
28821
  const ids = /* @__PURE__ */ new Set();
28918
28822
  for (const g2 of grants) {
@@ -28925,26 +28829,26 @@ function getAllowedPersonaIds(grants, action) {
28925
28829
  return ids;
28926
28830
  }
28927
28831
  function isGuestPathAllowed(grants, absPath, personaRoot, action = "read") {
28928
- const root = path36.resolve(personaRoot);
28929
- const target = path36.resolve(absPath);
28930
- const sep3 = root.endsWith(path36.sep) ? "" : path36.sep;
28832
+ const root = path35.resolve(personaRoot);
28833
+ const target = path35.resolve(absPath);
28834
+ const sep3 = root.endsWith(path35.sep) ? "" : path35.sep;
28931
28835
  if (!target.startsWith(root + sep3)) return false;
28932
- const rel = path36.relative(root, target);
28836
+ const rel = path35.relative(root, target);
28933
28837
  if (!rel || rel.startsWith("..")) return false;
28934
- const personaId = rel.split(path36.sep)[0];
28838
+ const personaId = rel.split(path35.sep)[0];
28935
28839
  if (!personaId) return false;
28936
28840
  const allowed = getAllowedPersonaIds(grants, action);
28937
28841
  if (allowed === "*") return true;
28938
28842
  return allowed.has(personaId);
28939
28843
  }
28940
28844
  function personaIdFromPath(absPath, personaRoot) {
28941
- const root = path36.resolve(personaRoot);
28942
- const target = path36.resolve(absPath);
28943
- const sep3 = root.endsWith(path36.sep) ? "" : path36.sep;
28845
+ const root = path35.resolve(personaRoot);
28846
+ const target = path35.resolve(absPath);
28847
+ const sep3 = root.endsWith(path35.sep) ? "" : path35.sep;
28944
28848
  if (!target.startsWith(root + sep3)) return null;
28945
- const rel = path36.relative(root, target);
28849
+ const rel = path35.relative(root, target);
28946
28850
  if (!rel || rel.startsWith("..")) return null;
28947
- const id = rel.split(path36.sep)[0];
28851
+ const id = rel.split(path35.sep)[0];
28948
28852
  return id || null;
28949
28853
  }
28950
28854
 
@@ -28969,7 +28873,7 @@ function buildHistoryHandlers(deps) {
28969
28873
  if (!pid) return false;
28970
28874
  return isGuestPathAllowed(
28971
28875
  ctx.grants,
28972
- path37.join(personaRoot, pid),
28876
+ path36.join(personaRoot, pid),
28973
28877
  personaRoot,
28974
28878
  "read"
28975
28879
  );
@@ -28980,7 +28884,7 @@ function buildHistoryHandlers(deps) {
28980
28884
  };
28981
28885
  const list = async (frame, _client, ctx) => {
28982
28886
  const args = HistoryListArgs.parse(frame);
28983
- assertGuestPath(ctx, path37.resolve(args.projectPath), personaRoot, "history:list");
28887
+ assertGuestPath(ctx, path36.resolve(args.projectPath), personaRoot, "history:list");
28984
28888
  const sessions = await history.listSessions(args);
28985
28889
  return { response: { type: "history:list", sessions } };
28986
28890
  };
@@ -29012,13 +28916,13 @@ function buildHistoryHandlers(deps) {
29012
28916
  };
29013
28917
  const subagents = async (frame, _client, ctx) => {
29014
28918
  const args = HistorySubagentsArgs.parse(frame);
29015
- assertGuestPath(ctx, path37.resolve(args.cwd), personaRoot, "history:subagents");
28919
+ assertGuestPath(ctx, path36.resolve(args.cwd), personaRoot, "history:subagents");
29016
28920
  const subs = await history.listSubagents(args);
29017
28921
  return { response: { type: "history:subagents", subagents: subs } };
29018
28922
  };
29019
28923
  const subagentRead = async (frame, _client, ctx) => {
29020
28924
  const args = HistorySubagentReadArgs.parse(frame);
29021
- assertGuestPath(ctx, path37.resolve(args.cwd), personaRoot, "history:subagent-read");
28925
+ assertGuestPath(ctx, path36.resolve(args.cwd), personaRoot, "history:subagent-read");
29022
28926
  const res = await history.readSubagent(args);
29023
28927
  return { response: { type: "history:subagent-read", ...res } };
29024
28928
  };
@@ -29026,7 +28930,7 @@ function buildHistoryHandlers(deps) {
29026
28930
  const dirs = listRecentDirs(store);
29027
28931
  if (ctx?.principal.kind === "guest" && personaRoot) {
29028
28932
  const filtered = dirs.filter(
29029
- (d) => isGuestPathAllowed(ctx.grants, path37.resolve(d.cwd), personaRoot, "read")
28933
+ (d) => isGuestPathAllowed(ctx.grants, path36.resolve(d.cwd), personaRoot, "read")
29030
28934
  );
29031
28935
  return { response: { type: "history:recentDirs", dirs: filtered } };
29032
28936
  }
@@ -29043,8 +28947,8 @@ function buildHistoryHandlers(deps) {
29043
28947
  }
29044
28948
 
29045
28949
  // src/handlers/workspace.ts
29046
- var path38 = __toESM(require("path"), 1);
29047
- var os13 = __toESM(require("os"), 1);
28950
+ var path37 = __toESM(require("path"), 1);
28951
+ var os14 = __toESM(require("os"), 1);
29048
28952
  init_protocol();
29049
28953
  init_protocol();
29050
28954
  function buildEnabledPluginNames(personaManager, personaId) {
@@ -29083,23 +28987,23 @@ function buildWorkspaceHandlers(deps) {
29083
28987
  const list = async (frame, _client, ctx) => {
29084
28988
  const args = WorkspaceListArgs.parse(frame);
29085
28989
  const isGuest = ctx?.principal.kind === "guest";
29086
- const fallbackCwd = isGuest && personaRoot ? personaRoot : os13.homedir();
29087
- const resolvedCwd = path38.resolve(args.cwd ?? fallbackCwd);
29088
- const target = args.path ? path38.resolve(resolvedCwd, args.path) : resolvedCwd;
28990
+ const fallbackCwd = isGuest && personaRoot ? personaRoot : os14.homedir();
28991
+ const resolvedCwd = path37.resolve(args.cwd ?? fallbackCwd);
28992
+ const target = args.path ? path37.resolve(resolvedCwd, args.path) : resolvedCwd;
29089
28993
  assertGuestPath2(ctx, target, personaRoot, "workspace:list");
29090
28994
  const res = workspace.list({ ...args, cwd: resolvedCwd });
29091
28995
  return { response: { type: "workspace:list", ...res } };
29092
28996
  };
29093
28997
  const read = async (frame, _client, ctx) => {
29094
28998
  const args = WorkspaceReadArgs.parse(frame);
29095
- const target = path38.isAbsolute(args.path) ? path38.resolve(args.path) : path38.resolve(args.cwd, args.path);
28999
+ const target = path37.isAbsolute(args.path) ? path37.resolve(args.path) : path37.resolve(args.cwd, args.path);
29096
29000
  assertGuestPath2(ctx, target, personaRoot, "workspace:read");
29097
29001
  const res = workspace.read(args);
29098
29002
  return { response: { type: "workspace:read", ...res } };
29099
29003
  };
29100
29004
  const skillsList = async (frame, _client, ctx) => {
29101
29005
  const args = SkillsListArgs.parse(frame);
29102
- const cwdAbs = path38.resolve(args.cwd);
29006
+ const cwdAbs = path37.resolve(args.cwd);
29103
29007
  assertGuestPath2(ctx, cwdAbs, personaRoot, "skills:list");
29104
29008
  const list2 = skills.list(args);
29105
29009
  if (ctx?.principal.kind === "guest" && personaRoot) {
@@ -29111,7 +29015,7 @@ function buildWorkspaceHandlers(deps) {
29111
29015
  };
29112
29016
  const agentsList = async (frame, _client, ctx) => {
29113
29017
  const args = AgentsListArgs.parse(frame);
29114
- const cwdAbs = path38.resolve(args.cwd);
29018
+ const cwdAbs = path37.resolve(args.cwd);
29115
29019
  assertGuestPath2(ctx, cwdAbs, personaRoot, "agents:list");
29116
29020
  const list2 = agents.list(args);
29117
29021
  if (ctx?.principal.kind === "guest" && personaRoot) {
@@ -29130,20 +29034,20 @@ function buildWorkspaceHandlers(deps) {
29130
29034
  }
29131
29035
 
29132
29036
  // src/handlers/git.ts
29133
- var path40 = __toESM(require("path"), 1);
29037
+ var path39 = __toESM(require("path"), 1);
29134
29038
  init_protocol();
29135
29039
  init_protocol();
29136
29040
 
29137
29041
  // src/workspace/git.ts
29138
29042
  var import_node_child_process6 = require("child_process");
29139
- var import_node_fs26 = __toESM(require("fs"), 1);
29140
- var import_node_path28 = __toESM(require("path"), 1);
29043
+ var import_node_fs25 = __toESM(require("fs"), 1);
29044
+ var import_node_path27 = __toESM(require("path"), 1);
29141
29045
  var import_node_util = require("util");
29142
29046
  var pexec = (0, import_node_util.promisify)(import_node_child_process6.execFile);
29143
29047
  function normalizePath(p2) {
29144
- const resolved = import_node_path28.default.resolve(p2);
29048
+ const resolved = import_node_path27.default.resolve(p2);
29145
29049
  try {
29146
- return import_node_fs26.default.realpathSync(resolved);
29050
+ return import_node_fs25.default.realpathSync(resolved);
29147
29051
  } catch {
29148
29052
  return resolved;
29149
29053
  }
@@ -29216,7 +29120,7 @@ async function listGitBranches(cwd) {
29216
29120
  // src/handlers/git.ts
29217
29121
  function assertGuestCwd(ctx, cwd, personaRoot, method) {
29218
29122
  if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
29219
- if (!isGuestPathAllowed(ctx.grants, path40.resolve(cwd), personaRoot, "read")) {
29123
+ if (!isGuestPathAllowed(ctx.grants, path39.resolve(cwd), personaRoot, "read")) {
29220
29124
  throw new ClawdError(
29221
29125
  ERROR_CODES.UNAUTHORIZED,
29222
29126
  `guest ${ctx.principal.id} cannot ${method} cwd ${cwd}`
@@ -29579,7 +29483,7 @@ function buildWhoamiHandler(deps) {
29579
29483
  }
29580
29484
 
29581
29485
  // src/handlers/meta.ts
29582
- var import_node_os13 = __toESM(require("os"), 1);
29486
+ var import_node_os14 = __toESM(require("os"), 1);
29583
29487
  init_protocol();
29584
29488
 
29585
29489
  // src/version.ts
@@ -29609,7 +29513,7 @@ function buildReadyFrame(deps, client) {
29609
29513
  return {
29610
29514
  version,
29611
29515
  protocolVersion: PROTOCOL_VERSION,
29612
- hostname: import_node_os13.default.hostname(),
29516
+ hostname: import_node_os14.default.hostname(),
29613
29517
  os: process.platform,
29614
29518
  tools,
29615
29519
  runningSessions: info.runningSessions,
@@ -29703,7 +29607,7 @@ function buildPersonaHandlers(deps) {
29703
29607
  }
29704
29608
 
29705
29609
  // src/handlers/attachment.ts
29706
- var import_node_path29 = __toESM(require("path"), 1);
29610
+ var import_node_path28 = __toESM(require("path"), 1);
29707
29611
  init_protocol();
29708
29612
  init_protocol();
29709
29613
  var DEFAULT_TTL_SECONDS = 24 * 3600;
@@ -29757,12 +29661,12 @@ function buildAttachmentHandlers(deps) {
29757
29661
  `session ${args.sessionId} scope unresolved`
29758
29662
  );
29759
29663
  }
29760
- const cwdAbs = import_node_path29.default.resolve(sessionFile.cwd);
29761
- const candidateAbs = import_node_path29.default.isAbsolute(args.relPath) ? import_node_path29.default.resolve(args.relPath) : import_node_path29.default.resolve(cwdAbs, args.relPath);
29664
+ const cwdAbs = import_node_path28.default.resolve(sessionFile.cwd);
29665
+ const candidateAbs = import_node_path28.default.isAbsolute(args.relPath) ? import_node_path28.default.resolve(args.relPath) : import_node_path28.default.resolve(cwdAbs, args.relPath);
29762
29666
  assertGuestAttachmentPath(ctx, candidateAbs, deps.personaRoot, "attachment.signUrl");
29763
29667
  const entries = deps.groupFileStore.list(scope, args.sessionId);
29764
29668
  const entry = entries.find((e) => {
29765
- const storedAbs = import_node_path29.default.isAbsolute(e.relPath) ? import_node_path29.default.resolve(e.relPath) : import_node_path29.default.resolve(cwdAbs, e.relPath);
29669
+ const storedAbs = import_node_path28.default.isAbsolute(e.relPath) ? import_node_path28.default.resolve(e.relPath) : import_node_path28.default.resolve(cwdAbs, e.relPath);
29766
29670
  return storedAbs === candidateAbs && !e.stale;
29767
29671
  });
29768
29672
  if (!entry) {
@@ -29786,7 +29690,7 @@ function buildAttachmentHandlers(deps) {
29786
29690
  if (!ctx || ctx.principal.kind !== "guest" || !deps.personaRoot || !deps.sessionStore) return;
29787
29691
  const f = deps.sessionStore.read(sessionId);
29788
29692
  if (!f) return;
29789
- assertGuestAttachmentPath(ctx, import_node_path29.default.resolve(f.cwd), deps.personaRoot, method);
29693
+ assertGuestAttachmentPath(ctx, import_node_path28.default.resolve(f.cwd), deps.personaRoot, method);
29790
29694
  }
29791
29695
  const groupAdd = async (frame, _client, ctx) => {
29792
29696
  if (!deps.groupFileStore || !deps.getSessionScope) {
@@ -29827,7 +29731,7 @@ function buildAttachmentHandlers(deps) {
29827
29731
  }
29828
29732
  const entries = deps.groupFileStore.list(scope, parsed.data.sessionId);
29829
29733
  const target = entries.find((e) => e.relPath === parsed.data.relPath);
29830
- if (target?.from === "owner" || target?.from === "user-upload") {
29734
+ if (target?.from === "owner") {
29831
29735
  deps.groupFileStore.remove(scope, parsed.data.sessionId, parsed.data.relPath);
29832
29736
  } else {
29833
29737
  deps.groupFileStore.markStale(scope, parsed.data.sessionId, parsed.data.relPath);
@@ -29953,6 +29857,11 @@ var METHOD_GRANT_MAP = {
29953
29857
  "session:pin": CAPABILITY_SCOPED,
29954
29858
  "session:reorderPins": ADMIN_ANY,
29955
29859
  // owner 全局操作,无 personaId 维度
29860
+ // mirror peer sessions (2026-05-26):owner-only — guest 不应能写/删本机 mirror SessionFile
29861
+ // (会绕过 #739 隔离规则把伪造身份的 SessionFile 写进 default store)。handler 端内部 ctx
29862
+ // 校验,dispatcher 层一刀切 admin 也兜底。
29863
+ "peerSession:upsert": ADMIN_ANY,
29864
+ "peerSession:remove": ADMIN_ANY,
29956
29865
  "permission:respond": CAPABILITY_SCOPED,
29957
29866
  "session:answerQuestion": CAPABILITY_SCOPED,
29958
29867
  "session:cancelQuestion": CAPABILITY_SCOPED,
@@ -30023,7 +29932,7 @@ function computeGrantForFrame(method, frame) {
30023
29932
  async function startDaemon(config) {
30024
29933
  const logger = createLogger({
30025
29934
  level: config.logLevel,
30026
- file: import_node_path30.default.join(config.dataDir, "clawd.log")
29935
+ file: import_node_path29.default.join(config.dataDir, "clawd.log")
30027
29936
  });
30028
29937
  logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
30029
29938
  const stateMgr = new StateFileManager({ dataDir: config.dataDir });
@@ -30110,7 +30019,7 @@ async function startDaemon(config) {
30110
30019
  const agents = new AgentsScanner();
30111
30020
  const history = new ClaudeHistoryReader();
30112
30021
  let transport = null;
30113
- const personaStore = new PersonaStore(import_node_path30.default.join(config.dataDir, "personas"));
30022
+ const personaStore = new PersonaStore(import_node_path29.default.join(config.dataDir, "personas"));
30114
30023
  const defaultsRoot = findDefaultsRoot();
30115
30024
  if (defaultsRoot) {
30116
30025
  seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
@@ -30127,7 +30036,7 @@ async function startDaemon(config) {
30127
30036
  getAdapter,
30128
30037
  historyReader: history,
30129
30038
  dataDir: config.dataDir,
30130
- personaRoot: import_node_path30.default.join(config.dataDir, "personas"),
30039
+ personaRoot: import_node_path29.default.join(config.dataDir, "personas"),
30131
30040
  personaStore,
30132
30041
  ownerDisplayName,
30133
30042
  ownerPrincipalId,
@@ -30151,10 +30060,10 @@ async function startDaemon(config) {
30151
30060
  // 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
30152
30061
  attachmentGroup: {
30153
30062
  onFileEdit: (input) => {
30154
- const absPath = import_node_path30.default.isAbsolute(input.relPath) ? input.relPath : import_node_path30.default.join(input.cwd, input.relPath);
30063
+ const absPath = import_node_path29.default.isAbsolute(input.relPath) ? input.relPath : import_node_path29.default.join(input.cwd, input.relPath);
30155
30064
  let size = 0;
30156
30065
  try {
30157
- size = import_node_fs27.default.statSync(absPath).size;
30066
+ size = import_node_fs26.default.statSync(absPath).size;
30158
30067
  } catch (err) {
30159
30068
  logger.warn("attachment.onFileEdit stat failed", {
30160
30069
  sessionId: input.sessionId,
@@ -30180,6 +30089,11 @@ async function startDaemon(config) {
30180
30089
  }
30181
30090
  });
30182
30091
  const observer = new SessionObserver({
30092
+ // TUI 模式 pty 跑 CC 不发 SDK system.task_started 帧 → UI pending 期 progress.agentId
30093
+ // 永远 undefined,子 agent 调用过程没有抽屉入口。开 meta observer 后从 subagents 目录
30094
+ // 新出现的 agent-*.meta.json 派生 subagent_progress(started) 补这块缺口。SDK 模式仍走
30095
+ // task_started 路径,避免双 emit 让 ring buffer 双帧。
30096
+ enableSubagentMetaObserver: config.mode === "tui",
30183
30097
  onEvent: ({ sessionId, events }) => {
30184
30098
  manager.feedObserverEvents(sessionId, events);
30185
30099
  },
@@ -30267,10 +30181,10 @@ async function startDaemon(config) {
30267
30181
  // 'persona/<pid>/owner',default 走 'default'。
30268
30182
  getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
30269
30183
  // guest path guard:candidate 必须在 personaRoot 子树下
30270
- personaRoot: import_node_path30.default.join(config.dataDir, "personas")
30184
+ personaRoot: import_node_path29.default.join(config.dataDir, "personas")
30271
30185
  },
30272
30186
  // workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
30273
- personaRoot: import_node_path30.default.join(config.dataDir, "personas"),
30187
+ personaRoot: import_node_path29.default.join(config.dataDir, "personas"),
30274
30188
  // capability:list / delete handler 依赖
30275
30189
  capabilityManager,
30276
30190
  // personal-cap:get 返回的 shareBaseUrl 用此 base URL:
@@ -30306,19 +30220,7 @@ async function startDaemon(config) {
30306
30220
  sessionStore: store,
30307
30221
  // /files HMAC verify 用 auth.json 的 signSecret 字段(与 attachment.signUrl 同源)。
30308
30222
  // --auth-token CLI 模式没 signSecret → 路由返 501,sign URL 功能整体禁用。
30309
- getSignSecret: () => authFile?.signSecret ?? null,
30310
- // chatinput 附件上传(POST /attachments/upload,spec §6.1)。upload pipeline
30311
- // 在 session.cwd 下的 .attachments/ 落盘;signSecret 与 /files HMAC 同源;
30312
- // tunnel 未启时返 503 TUNNEL_NOT_READY 让前端显式提示。
30313
- attachmentUpload: {
30314
- groupFileStore,
30315
- getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
30316
- getSessionDir: (sid) => {
30317
- const f = manager.findOwnedSession(sid);
30318
- return f ? f.cwd : null;
30319
- },
30320
- getHttpBaseUrl: () => currentTunnelUrl ? getHttpBaseUrl() : null
30321
- }
30223
+ getSignSecret: () => authFile?.signSecret ?? null
30322
30224
  });
30323
30225
  wsServer = new LocalWsServer({
30324
30226
  host: config.host,
@@ -30483,8 +30385,8 @@ async function startDaemon(config) {
30483
30385
  const lines = [
30484
30386
  `Tunnel: ${r.url}`,
30485
30387
  ...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
30486
- `Frpc config: ${import_node_path30.default.join(config.dataDir, "frpc.toml")}`,
30487
- `Frpc log: ${import_node_path30.default.join(config.dataDir, "frpc.log")}`
30388
+ `Frpc config: ${import_node_path29.default.join(config.dataDir, "frpc.toml")}`,
30389
+ `Frpc log: ${import_node_path29.default.join(config.dataDir, "frpc.log")}`
30488
30390
  ];
30489
30391
  const width = Math.max(...lines.map((l) => l.length));
30490
30392
  const bar = "\u2550".repeat(width + 4);
@@ -30497,8 +30399,8 @@ ${bar}
30497
30399
 
30498
30400
  `);
30499
30401
  try {
30500
- const connectPath = import_node_path30.default.join(config.dataDir, "connect.txt");
30501
- import_node_fs27.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
30402
+ const connectPath = import_node_path29.default.join(config.dataDir, "connect.txt");
30403
+ import_node_fs26.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
30502
30404
  } catch {
30503
30405
  }
30504
30406
  } catch (err) {
@@ -30523,29 +30425,8 @@ ${bar}
30523
30425
  logger.warn("tunnel unavailable, degraded to local mode", { reason: tunnelError });
30524
30426
  }
30525
30427
  }
30526
- const tickAttachmentGc = () => {
30527
- try {
30528
- runAttachmentGc({
30529
- dataDir: config.dataDir,
30530
- groupFileStore,
30531
- sessionScopes: manager.listOwnedSessionScopes().map(({ scope, sessionId }) => ({
30532
- scope,
30533
- sessionId
30534
- })),
30535
- // 注入 cwd 让 GC 直接定位 .attachments 目录(与 upload 落盘点一致)
30536
- getSessionCwd: (sid) => manager.findOwnedSession(sid)?.cwd ?? null,
30537
- logger
30538
- });
30539
- } catch (err) {
30540
- logger.warn("attachment gc tick failed", { err: err.message });
30541
- }
30542
- };
30543
- setImmediate(tickAttachmentGc);
30544
- const attachmentGcInterval = setInterval(tickAttachmentGc, 24 * 3600 * 1e3);
30545
- attachmentGcInterval.unref();
30546
30428
  const shutdown = async () => {
30547
30429
  logger.info("stopping clawd");
30548
- clearInterval(attachmentGcInterval);
30549
30430
  observer.stopAll();
30550
30431
  manager.stopAll();
30551
30432
  if (tunnelMgr) {
@@ -30563,9 +30444,9 @@ ${bar}
30563
30444
  };
30564
30445
  }
30565
30446
  function migrateDropPersonsDir(dataDir) {
30566
- const dir = import_node_path30.default.join(dataDir, "persons");
30447
+ const dir = import_node_path29.default.join(dataDir, "persons");
30567
30448
  try {
30568
- import_node_fs27.default.rmSync(dir, { recursive: true, force: true });
30449
+ import_node_fs26.default.rmSync(dir, { recursive: true, force: true });
30569
30450
  } catch {
30570
30451
  }
30571
30452
  }