@clawos-dev/clawd 0.2.86-beta.162.77b3c16 → 0.2.87-beta.163.de7e74a

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 +677 -546
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -65,14 +65,6 @@ 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",
76
68
  "permission:respond",
77
69
  // AskUserQuestion 表单回写:UI 答完所有 question 后调用,daemon 把答案合并进 updated_input
78
70
  // 写一条 control_response 到 CC stdin(详见 events.ts session:question JSDoc)
@@ -651,8 +643,8 @@ var init_parseUtil = __esm({
651
643
  init_errors2();
652
644
  init_en();
653
645
  makeIssue = (params) => {
654
- const { data, path: path44, errorMaps, issueData } = params;
655
- const fullPath = [...path44, ...issueData.path || []];
646
+ const { data, path: path45, errorMaps, issueData } = params;
647
+ const fullPath = [...path45, ...issueData.path || []];
656
648
  const fullIssue = {
657
649
  ...issueData,
658
650
  path: fullPath
@@ -963,11 +955,11 @@ var init_types = __esm({
963
955
  init_parseUtil();
964
956
  init_util();
965
957
  ParseInputLazyPath = class {
966
- constructor(parent, value, path44, key) {
958
+ constructor(parent, value, path45, key) {
967
959
  this._cachedPath = [];
968
960
  this.parent = parent;
969
961
  this.data = value;
970
- this._path = path44;
962
+ this._path = path45;
971
963
  this._key = key;
972
964
  }
973
965
  get path() {
@@ -4351,13 +4343,13 @@ var init_zod = __esm({
4351
4343
  });
4352
4344
 
4353
4345
  // ../protocol/src/attachment-schemas.ts
4354
- var TOKEN_ROLES, GROUP_FILE_SOURCES, GroupFileEntrySchema, AttachmentSignUrlArgs, AttachmentSignUrlResponseSchema, AttachmentGroupAddArgs, AttachmentGroupAddResponseSchema, AttachmentGroupRemoveArgs, AttachmentGroupRemoveResponseSchema, AttachmentGroupListArgs, AttachmentGroupListResponseSchema;
4346
+ var TOKEN_ROLES, GROUP_FILE_SOURCES, GroupFileEntrySchema, AttachmentSignUrlArgs, AttachmentSignUrlResponseSchema, AttachmentGroupAddArgs, AttachmentGroupAddResponseSchema, AttachmentGroupRemoveArgs, AttachmentGroupRemoveResponseSchema, AttachmentGroupListArgs, AttachmentGroupListResponseSchema, AttachmentUploadResponseSchema;
4355
4347
  var init_attachment_schemas = __esm({
4356
4348
  "../protocol/src/attachment-schemas.ts"() {
4357
4349
  "use strict";
4358
4350
  init_zod();
4359
4351
  TOKEN_ROLES = ["owner"];
4360
- GROUP_FILE_SOURCES = ["agent", "owner"];
4352
+ GROUP_FILE_SOURCES = ["agent", "owner", "user-upload"];
4361
4353
  GroupFileEntrySchema = external_exports.object({
4362
4354
  /** daemon 派发的稳定 id(用于 RPC remove / UI key) */
4363
4355
  id: external_exports.string().min(1),
@@ -4412,6 +4404,16 @@ var init_attachment_schemas = __esm({
4412
4404
  AttachmentGroupListResponseSchema = external_exports.object({
4413
4405
  entries: external_exports.array(GroupFileEntrySchema)
4414
4406
  });
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
+ });
4415
4417
  }
4416
4418
  });
4417
4419
 
@@ -4491,7 +4493,7 @@ var init_persona_schemas = __esm({
4491
4493
  });
4492
4494
 
4493
4495
  // ../protocol/src/schemas.ts
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;
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;
4495
4497
  var init_schemas = __esm({
4496
4498
  "../protocol/src/schemas.ts"() {
4497
4499
  "use strict";
@@ -4619,35 +4621,6 @@ var init_schemas = __esm({
4619
4621
  * 当前为 caller 自报,daemon 不密码学验证真伪;强身份验签是独立工作(B 方案)。
4620
4622
  */
4621
4623
  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(),
4651
4624
  createdAt: external_exports.string().min(1),
4652
4625
  updatedAt: external_exports.string().min(1)
4653
4626
  });
@@ -5003,14 +4976,6 @@ var init_schemas = __esm({
5003
4976
  sessionId: external_exports.string().min(1),
5004
4977
  pinned: external_exports.boolean()
5005
4978
  });
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
- });
5014
4979
  SessionReorderPinsArgs = external_exports.object({
5015
4980
  orderedIds: external_exports.array(external_exports.string().min(1)).min(1)
5016
4981
  });
@@ -5625,8 +5590,8 @@ var require_req = __commonJS({
5625
5590
  if (req.originalUrl) {
5626
5591
  _req.url = req.originalUrl;
5627
5592
  } else {
5628
- const path44 = req.path;
5629
- _req.url = typeof path44 === "string" ? path44 : req.url ? req.url.path || req.url : void 0;
5593
+ const path45 = req.path;
5594
+ _req.url = typeof path45 === "string" ? path45 : req.url ? req.url.path || req.url : void 0;
5630
5595
  }
5631
5596
  if (req.query) {
5632
5597
  _req.query = req.query;
@@ -5791,14 +5756,14 @@ var require_redact = __commonJS({
5791
5756
  }
5792
5757
  return obj;
5793
5758
  }
5794
- function parsePath(path44) {
5759
+ function parsePath(path45) {
5795
5760
  const parts = [];
5796
5761
  let current = "";
5797
5762
  let inBrackets = false;
5798
5763
  let inQuotes = false;
5799
5764
  let quoteChar = "";
5800
- for (let i = 0; i < path44.length; i++) {
5801
- const char = path44[i];
5765
+ for (let i = 0; i < path45.length; i++) {
5766
+ const char = path45[i];
5802
5767
  if (!inBrackets && char === ".") {
5803
5768
  if (current) {
5804
5769
  parts.push(current);
@@ -5929,10 +5894,10 @@ var require_redact = __commonJS({
5929
5894
  return current;
5930
5895
  }
5931
5896
  function redactPaths(obj, paths, censor, remove = false) {
5932
- for (const path44 of paths) {
5933
- const parts = parsePath(path44);
5897
+ for (const path45 of paths) {
5898
+ const parts = parsePath(path45);
5934
5899
  if (parts.includes("*")) {
5935
- redactWildcardPath(obj, parts, censor, path44, remove);
5900
+ redactWildcardPath(obj, parts, censor, path45, remove);
5936
5901
  } else {
5937
5902
  if (remove) {
5938
5903
  removeKey(obj, parts);
@@ -6017,8 +5982,8 @@ var require_redact = __commonJS({
6017
5982
  }
6018
5983
  } else {
6019
5984
  if (afterWildcard.includes("*")) {
6020
- const wrappedCensor = typeof censor === "function" ? (value, path44) => {
6021
- const fullPath = [...pathArray.slice(0, pathLength), ...path44];
5985
+ const wrappedCensor = typeof censor === "function" ? (value, path45) => {
5986
+ const fullPath = [...pathArray.slice(0, pathLength), ...path45];
6022
5987
  return censor(value, fullPath);
6023
5988
  } : censor;
6024
5989
  redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
@@ -6053,8 +6018,8 @@ var require_redact = __commonJS({
6053
6018
  return null;
6054
6019
  }
6055
6020
  const pathStructure = /* @__PURE__ */ new Map();
6056
- for (const path44 of pathsToClone) {
6057
- const parts = parsePath(path44);
6021
+ for (const path45 of pathsToClone) {
6022
+ const parts = parsePath(path45);
6058
6023
  let current = pathStructure;
6059
6024
  for (let i = 0; i < parts.length; i++) {
6060
6025
  const part = parts[i];
@@ -6106,24 +6071,24 @@ var require_redact = __commonJS({
6106
6071
  }
6107
6072
  return cloneSelectively(obj, pathStructure);
6108
6073
  }
6109
- function validatePath(path44) {
6110
- if (typeof path44 !== "string") {
6074
+ function validatePath(path45) {
6075
+ if (typeof path45 !== "string") {
6111
6076
  throw new Error("Paths must be (non-empty) strings");
6112
6077
  }
6113
- if (path44 === "") {
6078
+ if (path45 === "") {
6114
6079
  throw new Error("Invalid redaction path ()");
6115
6080
  }
6116
- if (path44.includes("..")) {
6117
- throw new Error(`Invalid redaction path (${path44})`);
6081
+ if (path45.includes("..")) {
6082
+ throw new Error(`Invalid redaction path (${path45})`);
6118
6083
  }
6119
- if (path44.includes(",")) {
6120
- throw new Error(`Invalid redaction path (${path44})`);
6084
+ if (path45.includes(",")) {
6085
+ throw new Error(`Invalid redaction path (${path45})`);
6121
6086
  }
6122
6087
  let bracketCount = 0;
6123
6088
  let inQuotes = false;
6124
6089
  let quoteChar = "";
6125
- for (let i = 0; i < path44.length; i++) {
6126
- const char = path44[i];
6090
+ for (let i = 0; i < path45.length; i++) {
6091
+ const char = path45[i];
6127
6092
  if ((char === '"' || char === "'") && bracketCount > 0) {
6128
6093
  if (!inQuotes) {
6129
6094
  inQuotes = true;
@@ -6137,20 +6102,20 @@ var require_redact = __commonJS({
6137
6102
  } else if (char === "]" && !inQuotes) {
6138
6103
  bracketCount--;
6139
6104
  if (bracketCount < 0) {
6140
- throw new Error(`Invalid redaction path (${path44})`);
6105
+ throw new Error(`Invalid redaction path (${path45})`);
6141
6106
  }
6142
6107
  }
6143
6108
  }
6144
6109
  if (bracketCount !== 0) {
6145
- throw new Error(`Invalid redaction path (${path44})`);
6110
+ throw new Error(`Invalid redaction path (${path45})`);
6146
6111
  }
6147
6112
  }
6148
6113
  function validatePaths(paths) {
6149
6114
  if (!Array.isArray(paths)) {
6150
6115
  throw new TypeError("paths must be an array");
6151
6116
  }
6152
- for (const path44 of paths) {
6153
- validatePath(path44);
6117
+ for (const path45 of paths) {
6118
+ validatePath(path45);
6154
6119
  }
6155
6120
  }
6156
6121
  function slowRedact(options = {}) {
@@ -6318,8 +6283,8 @@ var require_redaction = __commonJS({
6318
6283
  if (shape[k2] === null) {
6319
6284
  o[k2] = (value) => topCensor(value, [k2]);
6320
6285
  } else {
6321
- const wrappedCensor = typeof censor === "function" ? (value, path44) => {
6322
- return censor(value, [k2, ...path44]);
6286
+ const wrappedCensor = typeof censor === "function" ? (value, path45) => {
6287
+ return censor(value, [k2, ...path45]);
6323
6288
  } : censor;
6324
6289
  o[k2] = Redact({
6325
6290
  paths: shape[k2],
@@ -6537,10 +6502,10 @@ var require_atomic_sleep = __commonJS({
6537
6502
  var require_sonic_boom = __commonJS({
6538
6503
  "../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
6539
6504
  "use strict";
6540
- var fs35 = require("fs");
6505
+ var fs36 = require("fs");
6541
6506
  var EventEmitter2 = require("events");
6542
6507
  var inherits = require("util").inherits;
6543
- var path44 = require("path");
6508
+ var path45 = require("path");
6544
6509
  var sleep = require_atomic_sleep();
6545
6510
  var assert = require("assert");
6546
6511
  var BUSY_WRITE_TIMEOUT = 100;
@@ -6594,20 +6559,20 @@ var require_sonic_boom = __commonJS({
6594
6559
  const mode = sonic.mode;
6595
6560
  if (sonic.sync) {
6596
6561
  try {
6597
- if (sonic.mkdir) fs35.mkdirSync(path44.dirname(file), { recursive: true });
6598
- const fd = fs35.openSync(file, flags, mode);
6562
+ if (sonic.mkdir) fs36.mkdirSync(path45.dirname(file), { recursive: true });
6563
+ const fd = fs36.openSync(file, flags, mode);
6599
6564
  fileOpened(null, fd);
6600
6565
  } catch (err) {
6601
6566
  fileOpened(err);
6602
6567
  throw err;
6603
6568
  }
6604
6569
  } else if (sonic.mkdir) {
6605
- fs35.mkdir(path44.dirname(file), { recursive: true }, (err) => {
6570
+ fs36.mkdir(path45.dirname(file), { recursive: true }, (err) => {
6606
6571
  if (err) return fileOpened(err);
6607
- fs35.open(file, flags, mode, fileOpened);
6572
+ fs36.open(file, flags, mode, fileOpened);
6608
6573
  });
6609
6574
  } else {
6610
- fs35.open(file, flags, mode, fileOpened);
6575
+ fs36.open(file, flags, mode, fileOpened);
6611
6576
  }
6612
6577
  }
6613
6578
  function SonicBoom(opts) {
@@ -6648,8 +6613,8 @@ var require_sonic_boom = __commonJS({
6648
6613
  this.flush = flushBuffer;
6649
6614
  this.flushSync = flushBufferSync;
6650
6615
  this._actualWrite = actualWriteBuffer;
6651
- fsWriteSync = () => fs35.writeSync(this.fd, this._writingBuf);
6652
- fsWrite = () => fs35.write(this.fd, this._writingBuf, this.release);
6616
+ fsWriteSync = () => fs36.writeSync(this.fd, this._writingBuf);
6617
+ fsWrite = () => fs36.write(this.fd, this._writingBuf, this.release);
6653
6618
  } else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
6654
6619
  this._writingBuf = "";
6655
6620
  this.write = write;
@@ -6658,15 +6623,15 @@ var require_sonic_boom = __commonJS({
6658
6623
  this._actualWrite = actualWrite;
6659
6624
  fsWriteSync = () => {
6660
6625
  if (Buffer.isBuffer(this._writingBuf)) {
6661
- return fs35.writeSync(this.fd, this._writingBuf);
6626
+ return fs36.writeSync(this.fd, this._writingBuf);
6662
6627
  }
6663
- return fs35.writeSync(this.fd, this._writingBuf, "utf8");
6628
+ return fs36.writeSync(this.fd, this._writingBuf, "utf8");
6664
6629
  };
6665
6630
  fsWrite = () => {
6666
6631
  if (Buffer.isBuffer(this._writingBuf)) {
6667
- return fs35.write(this.fd, this._writingBuf, this.release);
6632
+ return fs36.write(this.fd, this._writingBuf, this.release);
6668
6633
  }
6669
- return fs35.write(this.fd, this._writingBuf, "utf8", this.release);
6634
+ return fs36.write(this.fd, this._writingBuf, "utf8", this.release);
6670
6635
  };
6671
6636
  } else {
6672
6637
  throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
@@ -6723,7 +6688,7 @@ var require_sonic_boom = __commonJS({
6723
6688
  }
6724
6689
  }
6725
6690
  if (this._fsync) {
6726
- fs35.fsyncSync(this.fd);
6691
+ fs36.fsyncSync(this.fd);
6727
6692
  }
6728
6693
  const len = this._len;
6729
6694
  if (this._reopening) {
@@ -6837,7 +6802,7 @@ var require_sonic_boom = __commonJS({
6837
6802
  const onDrain = () => {
6838
6803
  if (!this._fsync) {
6839
6804
  try {
6840
- fs35.fsync(this.fd, (err) => {
6805
+ fs36.fsync(this.fd, (err) => {
6841
6806
  this._flushPending = false;
6842
6807
  cb(err);
6843
6808
  });
@@ -6939,7 +6904,7 @@ var require_sonic_boom = __commonJS({
6939
6904
  const fd = this.fd;
6940
6905
  this.once("ready", () => {
6941
6906
  if (fd !== this.fd) {
6942
- fs35.close(fd, (err) => {
6907
+ fs36.close(fd, (err) => {
6943
6908
  if (err) {
6944
6909
  return this.emit("error", err);
6945
6910
  }
@@ -6988,7 +6953,7 @@ var require_sonic_boom = __commonJS({
6988
6953
  buf = this._bufs[0];
6989
6954
  }
6990
6955
  try {
6991
- const n = Buffer.isBuffer(buf) ? fs35.writeSync(this.fd, buf) : fs35.writeSync(this.fd, buf, "utf8");
6956
+ const n = Buffer.isBuffer(buf) ? fs36.writeSync(this.fd, buf) : fs36.writeSync(this.fd, buf, "utf8");
6992
6957
  const releasedBufObj = releaseWritingBuf(buf, this._len, n);
6993
6958
  buf = releasedBufObj.writingBuf;
6994
6959
  this._len = releasedBufObj.len;
@@ -7004,7 +6969,7 @@ var require_sonic_boom = __commonJS({
7004
6969
  }
7005
6970
  }
7006
6971
  try {
7007
- fs35.fsyncSync(this.fd);
6972
+ fs36.fsyncSync(this.fd);
7008
6973
  } catch {
7009
6974
  }
7010
6975
  }
@@ -7025,7 +6990,7 @@ var require_sonic_boom = __commonJS({
7025
6990
  buf = mergeBuf(this._bufs[0], this._lens[0]);
7026
6991
  }
7027
6992
  try {
7028
- const n = fs35.writeSync(this.fd, buf);
6993
+ const n = fs36.writeSync(this.fd, buf);
7029
6994
  buf = buf.subarray(n);
7030
6995
  this._len = Math.max(this._len - n, 0);
7031
6996
  if (buf.length <= 0) {
@@ -7053,13 +7018,13 @@ var require_sonic_boom = __commonJS({
7053
7018
  this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
7054
7019
  if (this.sync) {
7055
7020
  try {
7056
- const written = Buffer.isBuffer(this._writingBuf) ? fs35.writeSync(this.fd, this._writingBuf) : fs35.writeSync(this.fd, this._writingBuf, "utf8");
7021
+ const written = Buffer.isBuffer(this._writingBuf) ? fs36.writeSync(this.fd, this._writingBuf) : fs36.writeSync(this.fd, this._writingBuf, "utf8");
7057
7022
  release(null, written);
7058
7023
  } catch (err) {
7059
7024
  release(err);
7060
7025
  }
7061
7026
  } else {
7062
- fs35.write(this.fd, this._writingBuf, release);
7027
+ fs36.write(this.fd, this._writingBuf, release);
7063
7028
  }
7064
7029
  }
7065
7030
  function actualWriteBuffer() {
@@ -7068,7 +7033,7 @@ var require_sonic_boom = __commonJS({
7068
7033
  this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
7069
7034
  if (this.sync) {
7070
7035
  try {
7071
- const written = fs35.writeSync(this.fd, this._writingBuf);
7036
+ const written = fs36.writeSync(this.fd, this._writingBuf);
7072
7037
  release(null, written);
7073
7038
  } catch (err) {
7074
7039
  release(err);
@@ -7077,7 +7042,7 @@ var require_sonic_boom = __commonJS({
7077
7042
  if (kCopyBuffer) {
7078
7043
  this._writingBuf = Buffer.from(this._writingBuf);
7079
7044
  }
7080
- fs35.write(this.fd, this._writingBuf, release);
7045
+ fs36.write(this.fd, this._writingBuf, release);
7081
7046
  }
7082
7047
  }
7083
7048
  function actualClose(sonic) {
@@ -7093,12 +7058,12 @@ var require_sonic_boom = __commonJS({
7093
7058
  sonic._lens = [];
7094
7059
  assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
7095
7060
  try {
7096
- fs35.fsync(sonic.fd, closeWrapped);
7061
+ fs36.fsync(sonic.fd, closeWrapped);
7097
7062
  } catch {
7098
7063
  }
7099
7064
  function closeWrapped() {
7100
7065
  if (sonic.fd !== 1 && sonic.fd !== 2) {
7101
- fs35.close(sonic.fd, done);
7066
+ fs36.close(sonic.fd, done);
7102
7067
  } else {
7103
7068
  done();
7104
7069
  }
@@ -7829,7 +7794,7 @@ var require_transport = __commonJS({
7829
7794
  stream.flushSync();
7830
7795
  }
7831
7796
  function transport(fullOptions) {
7832
- const { pipeline: pipeline2, targets, levels, dedupe, worker = {}, caller = getCallers(), sync = false } = fullOptions;
7797
+ const { pipeline: pipeline3, targets, levels, dedupe, worker = {}, caller = getCallers(), sync = false } = fullOptions;
7833
7798
  const options = {
7834
7799
  ...fullOptions.options
7835
7800
  };
@@ -7857,9 +7822,9 @@ var require_transport = __commonJS({
7857
7822
  };
7858
7823
  });
7859
7824
  });
7860
- } else if (pipeline2) {
7825
+ } else if (pipeline3) {
7861
7826
  target = bundlerOverrides["pino-worker"] || join11(__dirname, "worker.js");
7862
- options.pipelines = [pipeline2.map((dest) => {
7827
+ options.pipelines = [pipeline3.map((dest) => {
7863
7828
  return {
7864
7829
  ...dest,
7865
7830
  target: fixTarget(dest.target)
@@ -9462,7 +9427,7 @@ var require_multistream = __commonJS({
9462
9427
  var require_pino = __commonJS({
9463
9428
  "../node_modules/.pnpm/pino@9.14.0/node_modules/pino/pino.js"(exports2, module2) {
9464
9429
  "use strict";
9465
- var os17 = require("os");
9430
+ var os16 = require("os");
9466
9431
  var stdSerializers = require_pino_std_serializers();
9467
9432
  var caller = require_caller();
9468
9433
  var redaction = require_redaction();
@@ -9509,7 +9474,7 @@ var require_pino = __commonJS({
9509
9474
  } = symbols;
9510
9475
  var { epochTime, nullTime } = time;
9511
9476
  var { pid } = process;
9512
- var hostname = os17.hostname();
9477
+ var hostname = os16.hostname();
9513
9478
  var defaultErrorSerializer = stdSerializers.err;
9514
9479
  var defaultOptions = {
9515
9480
  level: "info",
@@ -10233,11 +10198,11 @@ var init_lib = __esm({
10233
10198
  }
10234
10199
  }
10235
10200
  },
10236
- addToPath: function addToPath(path44, added, removed, oldPosInc, options) {
10237
- var last = path44.lastComponent;
10201
+ addToPath: function addToPath(path45, added, removed, oldPosInc, options) {
10202
+ var last = path45.lastComponent;
10238
10203
  if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
10239
10204
  return {
10240
- oldPos: path44.oldPos + oldPosInc,
10205
+ oldPos: path45.oldPos + oldPosInc,
10241
10206
  lastComponent: {
10242
10207
  count: last.count + 1,
10243
10208
  added,
@@ -10247,7 +10212,7 @@ var init_lib = __esm({
10247
10212
  };
10248
10213
  } else {
10249
10214
  return {
10250
- oldPos: path44.oldPos + oldPosInc,
10215
+ oldPos: path45.oldPos + oldPosInc,
10251
10216
  lastComponent: {
10252
10217
  count: 1,
10253
10218
  added,
@@ -10678,10 +10643,10 @@ function attachmentToHistoryMessage(o, ts) {
10678
10643
  const memories = raw.map((m2) => {
10679
10644
  if (!m2 || typeof m2 !== "object") return null;
10680
10645
  const rec = m2;
10681
- const path44 = typeof rec.path === "string" ? rec.path : null;
10646
+ const path45 = typeof rec.path === "string" ? rec.path : null;
10682
10647
  const content = typeof rec.content === "string" ? rec.content : null;
10683
- if (!path44 || content == null) return null;
10684
- const entry = { path: path44, content };
10648
+ if (!path45 || content == null) return null;
10649
+ const entry = { path: path45, content };
10685
10650
  if (typeof rec.mtimeMs === "number") entry.mtimeMs = rec.mtimeMs;
10686
10651
  return entry;
10687
10652
  }).filter((m2) => m2 !== null);
@@ -11507,10 +11472,10 @@ function parseAttachment(obj) {
11507
11472
  const memories = raw.map((m2) => {
11508
11473
  if (!m2 || typeof m2 !== "object") return null;
11509
11474
  const rec = m2;
11510
- const path44 = typeof rec.path === "string" ? rec.path : null;
11475
+ const path45 = typeof rec.path === "string" ? rec.path : null;
11511
11476
  const content = typeof rec.content === "string" ? rec.content : null;
11512
- if (!path44 || content == null) return null;
11513
- const out = { path: path44, content };
11477
+ if (!path45 || content == null) return null;
11478
+ const out = { path: path45, content };
11514
11479
  if (typeof rec.mtimeMs === "number") out.mtimeMs = rec.mtimeMs;
11515
11480
  return out;
11516
11481
  }).filter((m2) => m2 !== null);
@@ -20425,7 +20390,7 @@ var require_websocket_server = __commonJS({
20425
20390
  // src/run-case/recorder.ts
20426
20391
  function startRunCaseRecorder(opts) {
20427
20392
  const now = opts.now ?? Date.now;
20428
- const dir = import_node_path30.default.dirname(opts.recordPath);
20393
+ const dir = import_node_path31.default.dirname(opts.recordPath);
20429
20394
  let stream = null;
20430
20395
  let closing = false;
20431
20396
  let closedSettled = false;
@@ -20439,8 +20404,8 @@ function startRunCaseRecorder(opts) {
20439
20404
  });
20440
20405
  const ensureStream = () => {
20441
20406
  if (stream) return stream;
20442
- import_node_fs27.default.mkdirSync(dir, { recursive: true });
20443
- stream = import_node_fs27.default.createWriteStream(opts.recordPath, { flags: "a" });
20407
+ import_node_fs28.default.mkdirSync(dir, { recursive: true });
20408
+ stream = import_node_fs28.default.createWriteStream(opts.recordPath, { flags: "a" });
20444
20409
  stream.on("close", () => closedResolve());
20445
20410
  return stream;
20446
20411
  };
@@ -20465,12 +20430,12 @@ function startRunCaseRecorder(opts) {
20465
20430
  };
20466
20431
  return { tap, close, closed };
20467
20432
  }
20468
- var import_node_fs27, import_node_path30;
20433
+ var import_node_fs28, import_node_path31;
20469
20434
  var init_recorder = __esm({
20470
20435
  "src/run-case/recorder.ts"() {
20471
20436
  "use strict";
20472
- import_node_fs27 = __toESM(require("fs"), 1);
20473
- import_node_path30 = __toESM(require("path"), 1);
20437
+ import_node_fs28 = __toESM(require("fs"), 1);
20438
+ import_node_path31 = __toESM(require("path"), 1);
20474
20439
  }
20475
20440
  });
20476
20441
 
@@ -20513,7 +20478,7 @@ var init_wire = __esm({
20513
20478
  // src/run-case/controller.ts
20514
20479
  async function runController(opts) {
20515
20480
  const now = opts.now ?? Date.now;
20516
- const cwd = opts.cwd ?? (0, import_node_fs28.mkdtempSync)(import_node_path31.default.join(import_node_os15.default.tmpdir(), "clawd-runcase-"));
20481
+ const cwd = opts.cwd ?? (0, import_node_fs29.mkdtempSync)(import_node_path32.default.join(import_node_os14.default.tmpdir(), "clawd-runcase-"));
20517
20482
  const ownsCwd = opts.cwd === void 0;
20518
20483
  const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
20519
20484
  const spawnCtx = { cwd };
@@ -20674,19 +20639,19 @@ async function runController(opts) {
20674
20639
  if (sigintHandler) process.off("SIGINT", sigintHandler);
20675
20640
  if (ownsCwd) {
20676
20641
  try {
20677
- (0, import_node_fs28.rmSync)(cwd, { recursive: true, force: true });
20642
+ (0, import_node_fs29.rmSync)(cwd, { recursive: true, force: true });
20678
20643
  } catch {
20679
20644
  }
20680
20645
  }
20681
20646
  return exitCode ?? 0;
20682
20647
  }
20683
- var import_node_fs28, import_node_os15, import_node_path31;
20648
+ var import_node_fs29, import_node_os14, import_node_path32;
20684
20649
  var init_controller = __esm({
20685
20650
  "src/run-case/controller.ts"() {
20686
20651
  "use strict";
20687
- import_node_fs28 = require("fs");
20688
- import_node_os15 = __toESM(require("os"), 1);
20689
- import_node_path31 = __toESM(require("path"), 1);
20652
+ import_node_fs29 = require("fs");
20653
+ import_node_os14 = __toESM(require("os"), 1);
20654
+ import_node_path32 = __toESM(require("path"), 1);
20690
20655
  init_claude();
20691
20656
  init_stdout_splitter();
20692
20657
  init_permission_stdio();
@@ -20918,8 +20883,8 @@ Env (advanced):
20918
20883
  `;
20919
20884
 
20920
20885
  // src/index.ts
20921
- var import_node_path29 = __toESM(require("path"), 1);
20922
- var import_node_fs26 = __toESM(require("fs"), 1);
20886
+ var import_node_path30 = __toESM(require("path"), 1);
20887
+ var import_node_fs27 = __toESM(require("fs"), 1);
20923
20888
 
20924
20889
  // src/logger.ts
20925
20890
  var import_node_fs2 = __toESM(require("fs"), 1);
@@ -22654,6 +22619,18 @@ var SessionManager = class {
22654
22619
  (a, b2) => a.updatedAt > b2.updatedAt ? -1 : a.updatedAt < b2.updatedAt ? 1 : 0
22655
22620
  );
22656
22621
  }
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
+ }
22657
22634
  // 写回 SessionFile,自动根据 ownerPersonaId / creatorPrincipalId 派生写入 scope.
22658
22635
  // 调用方原先都是 `deps.store.write(updated)`,全部走这里收口路由.
22659
22636
  writeOwned(file) {
@@ -22907,25 +22884,6 @@ var SessionManager = class {
22907
22884
  const file = this.getFile(args.sessionId);
22908
22885
  return { response: file, broadcast: [] };
22909
22886
  }
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
- }
22929
22887
  // observer 上报"jsonl 写入了一条 user 行"时调用:在 buffer 里找到与之对应的、
22930
22888
  // 还没建立映射的最早一条 synth user_text,记下 synth → real 映射。
22931
22889
  // 找不到匹配的 synth(runner 不存在 / buffer 已被 /clear 清空 / 真 uuid 来自纯 history
@@ -25573,151 +25531,28 @@ var AgentsScanner = class {
25573
25531
  };
25574
25532
 
25575
25533
  // 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
25582
25534
  var import_node_fs12 = __toESM(require("fs"), 1);
25583
25535
  var import_node_os8 = __toESM(require("os"), 1);
25584
25536
  var import_node_path13 = __toESM(require("path"), 1);
25585
25537
  init_claude_history();
25586
- var META_RE = /^agent-([A-Za-z0-9_-]+)\.meta\.json$/;
25587
- var SubagentMetaObserver = class {
25588
- constructor(opts) {
25589
- this.opts = opts;
25590
- this.home = opts.home ?? import_node_os8.default.homedir();
25591
- }
25592
- opts;
25593
- home;
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
25538
  var SessionObserver = class {
25699
25539
  constructor(opts) {
25700
25540
  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;
25541
+ this.home = opts.home ?? import_node_os8.default.homedir();
25703
25542
  }
25704
25543
  opts;
25705
25544
  home;
25706
25545
  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;
25711
25546
  resolveJsonlPath(cwd, toolSessionId, override) {
25712
25547
  if (override) return override;
25713
- return import_node_path14.default.join(this.home, ".claude", "projects", cwdToHashDir(cwd), `${toolSessionId}.jsonl`);
25548
+ return import_node_path13.default.join(this.home, ".claude", "projects", cwdToHashDir(cwd), `${toolSessionId}.jsonl`);
25714
25549
  }
25715
25550
  start(args) {
25716
25551
  this.stop(args.sessionId);
25717
25552
  const filePath = this.resolveJsonlPath(args.cwd, args.toolSessionId, args.jsonlPath);
25718
25553
  let size = 0;
25719
25554
  try {
25720
- size = import_node_fs13.default.statSync(filePath).size;
25555
+ size = import_node_fs12.default.statSync(filePath).size;
25721
25556
  } catch {
25722
25557
  }
25723
25558
  const w2 = {
@@ -25730,10 +25565,10 @@ var SessionObserver = class {
25730
25565
  adapter: args.adapter
25731
25566
  };
25732
25567
  try {
25733
- import_node_fs13.default.mkdirSync(import_node_path14.default.dirname(filePath), { recursive: true });
25568
+ import_node_fs12.default.mkdirSync(import_node_path13.default.dirname(filePath), { recursive: true });
25734
25569
  } catch {
25735
25570
  }
25736
- w2.watcher = import_node_fs13.default.watch(import_node_path14.default.dirname(filePath), { persistent: false }, (_event, changedName) => {
25571
+ w2.watcher = import_node_fs12.default.watch(import_node_path13.default.dirname(filePath), { persistent: false }, (_event, changedName) => {
25737
25572
  if (!changedName || !filePath.endsWith(changedName)) return;
25738
25573
  this.poll(w2);
25739
25574
  });
@@ -25742,11 +25577,6 @@ var SessionObserver = class {
25742
25577
  this.opts.onStatus?.(args.sessionId, "observing");
25743
25578
  this.hydrateMetaTail(w2);
25744
25579
  this.poll(w2);
25745
- this.metaObserver?.start({
25746
- sessionId: args.sessionId,
25747
- cwd: args.cwd,
25748
- toolSessionId: args.toolSessionId
25749
- });
25750
25580
  return { filePath };
25751
25581
  }
25752
25582
  // 读 JSONL 文件尾部 N 行,把每行的 meta_update 字段**合并成一个 patch** 喂给 reducer。
@@ -25756,7 +25586,7 @@ var SessionObserver = class {
25756
25586
  // 异常静默吞,不阻塞 watcher 启动
25757
25587
  hydrateMetaTail(w2, maxLines = 200) {
25758
25588
  try {
25759
- const raw = import_node_fs13.default.readFileSync(w2.filePath, "utf8");
25589
+ const raw = import_node_fs12.default.readFileSync(w2.filePath, "utf8");
25760
25590
  if (!raw) return;
25761
25591
  const allLines = raw.split("\n").filter((l) => l.trim().length > 0);
25762
25592
  if (allLines.length === 0) return;
@@ -25780,7 +25610,7 @@ var SessionObserver = class {
25780
25610
  poll(w2) {
25781
25611
  let size = 0;
25782
25612
  try {
25783
- size = import_node_fs13.default.statSync(w2.filePath).size;
25613
+ size = import_node_fs12.default.statSync(w2.filePath).size;
25784
25614
  } catch {
25785
25615
  return;
25786
25616
  }
@@ -25789,11 +25619,11 @@ var SessionObserver = class {
25789
25619
  w2.buf = "";
25790
25620
  }
25791
25621
  if (size === w2.lastSize) return;
25792
- const fd = import_node_fs13.default.openSync(w2.filePath, "r");
25622
+ const fd = import_node_fs12.default.openSync(w2.filePath, "r");
25793
25623
  try {
25794
25624
  const len = size - w2.lastSize;
25795
25625
  const buf = Buffer.alloc(len);
25796
- import_node_fs13.default.readSync(fd, buf, 0, len, w2.lastSize);
25626
+ import_node_fs12.default.readSync(fd, buf, 0, len, w2.lastSize);
25797
25627
  w2.lastSize = size;
25798
25628
  w2.buf += buf.toString("utf8");
25799
25629
  let newlineIndex;
@@ -25807,7 +25637,7 @@ var SessionObserver = class {
25807
25637
  }
25808
25638
  }
25809
25639
  } finally {
25810
- import_node_fs13.default.closeSync(fd);
25640
+ import_node_fs12.default.closeSync(fd);
25811
25641
  }
25812
25642
  }
25813
25643
  // 解析 JSONL 单行:仅当是主链 user 文本行(非 sidechain / 非 sub-agent / message.role='user'
@@ -25860,13 +25690,11 @@ var SessionObserver = class {
25860
25690
  }
25861
25691
  if (w2.pollTimer) clearInterval(w2.pollTimer);
25862
25692
  this.watches.delete(sessionId);
25863
- this.metaObserver?.stop(sessionId);
25864
25693
  this.opts.onStatus?.(sessionId, "stopped");
25865
25694
  return true;
25866
25695
  }
25867
25696
  stopAll() {
25868
25697
  for (const id of [...this.watches.keys()]) this.stop(id);
25869
- this.metaObserver?.stopAll();
25870
25698
  }
25871
25699
  };
25872
25700
 
@@ -26467,14 +26295,14 @@ function authenticate(token, deps) {
26467
26295
  }
26468
26296
 
26469
26297
  // src/permission/capability-store.ts
26470
- var fs16 = __toESM(require("fs"), 1);
26471
- var path19 = __toESM(require("path"), 1);
26298
+ var fs15 = __toESM(require("fs"), 1);
26299
+ var path18 = __toESM(require("path"), 1);
26472
26300
  var CAPABILITIES_FILE_NAME = "capabilities.json";
26473
26301
  var FILE_VERSION = 1;
26474
26302
  var CapabilityStore = class {
26475
26303
  constructor(dataDir) {
26476
26304
  this.dataDir = dataDir;
26477
- fs16.mkdirSync(dataDir, { recursive: true });
26305
+ fs15.mkdirSync(dataDir, { recursive: true });
26478
26306
  this.cache = this.readFromDisk();
26479
26307
  }
26480
26308
  dataDir;
@@ -26498,13 +26326,13 @@ var CapabilityStore = class {
26498
26326
  this.flush();
26499
26327
  }
26500
26328
  filePath() {
26501
- return path19.join(this.dataDir, CAPABILITIES_FILE_NAME);
26329
+ return path18.join(this.dataDir, CAPABILITIES_FILE_NAME);
26502
26330
  }
26503
26331
  readFromDisk() {
26504
26332
  const file = this.filePath();
26505
26333
  let raw;
26506
26334
  try {
26507
- raw = fs16.readFileSync(file, "utf8");
26335
+ raw = fs15.readFileSync(file, "utf8");
26508
26336
  } catch (err) {
26509
26337
  if (err?.code === "ENOENT") return [];
26510
26338
  return [];
@@ -26532,10 +26360,10 @@ var CapabilityStore = class {
26532
26360
  }
26533
26361
  atomicWrite(file, content) {
26534
26362
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
26535
- fs16.writeFileSync(tmp, content, { mode: 384 });
26536
- fs16.renameSync(tmp, file);
26363
+ fs15.writeFileSync(tmp, content, { mode: 384 });
26364
+ fs15.renameSync(tmp, file);
26537
26365
  try {
26538
- fs16.chmodSync(file, 384);
26366
+ fs15.chmodSync(file, 384);
26539
26367
  } catch {
26540
26368
  }
26541
26369
  }
@@ -26628,14 +26456,14 @@ var CapabilityManager = class {
26628
26456
  };
26629
26457
 
26630
26458
  // src/permission/cleanup.ts
26631
- var fs17 = __toESM(require("fs"), 1);
26459
+ var fs16 = __toESM(require("fs"), 1);
26632
26460
  function cleanupGuestSessionsForCapability(cap, factory) {
26633
26461
  const removed = [];
26634
26462
  for (const g2 of cap.grants) {
26635
26463
  if (g2.resource.type !== "persona") continue;
26636
26464
  const dir = factory.vmGuestRoot(g2.resource.id, cap.id);
26637
26465
  try {
26638
- fs17.rmSync(dir, { recursive: true, force: true });
26466
+ fs16.rmSync(dir, { recursive: true, force: true });
26639
26467
  removed.push(dir);
26640
26468
  } catch {
26641
26469
  }
@@ -26644,13 +26472,13 @@ function cleanupGuestSessionsForCapability(cap, factory) {
26644
26472
  }
26645
26473
 
26646
26474
  // src/permission/personal-capability.ts
26647
- var import_node_fs14 = __toESM(require("fs"), 1);
26648
- var import_node_path15 = __toESM(require("path"), 1);
26475
+ var import_node_fs13 = __toESM(require("fs"), 1);
26476
+ var import_node_path14 = __toESM(require("path"), 1);
26649
26477
  var import_node_crypto4 = __toESM(require("crypto"), 1);
26650
26478
  var PERSONAL_CAP_FILE_NAME = "personal-capability.json";
26651
26479
  var PERSONAL_CAP_DISPLAY_NAME = "personal";
26652
26480
  function personalCapFilePath(dataDir) {
26653
- return import_node_path15.default.join(dataDir, PERSONAL_CAP_FILE_NAME);
26481
+ return import_node_path14.default.join(dataDir, PERSONAL_CAP_FILE_NAME);
26654
26482
  }
26655
26483
  function loadOrCreatePersonalCapability(opts) {
26656
26484
  const file = personalCapFilePath(opts.dataDir);
@@ -26680,7 +26508,7 @@ function loadOrCreatePersonalCapability(opts) {
26680
26508
  function readFile(file) {
26681
26509
  let raw;
26682
26510
  try {
26683
- raw = import_node_fs14.default.readFileSync(file, "utf8");
26511
+ raw = import_node_fs13.default.readFileSync(file, "utf8");
26684
26512
  } catch (err) {
26685
26513
  const code = err?.code;
26686
26514
  if (code === "ENOENT") return null;
@@ -26701,10 +26529,10 @@ function readFile(file) {
26701
26529
  return { token: obj.token, capability: capParsed.data };
26702
26530
  }
26703
26531
  function writeFile(file, content) {
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 });
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 });
26706
26534
  try {
26707
- import_node_fs14.default.chmodSync(file, 384);
26535
+ import_node_fs13.default.chmodSync(file, 384);
26708
26536
  } catch {
26709
26537
  }
26710
26538
  }
@@ -26719,13 +26547,13 @@ function sha256Hex2(s) {
26719
26547
  }
26720
26548
 
26721
26549
  // src/inbox/inbox-store.ts
26722
- var fs19 = __toESM(require("fs"), 1);
26723
- var path21 = __toESM(require("path"), 1);
26550
+ var fs18 = __toESM(require("fs"), 1);
26551
+ var path20 = __toESM(require("path"), 1);
26724
26552
  var INBOX_SUBDIR = "inbox";
26725
26553
  var InboxStore = class {
26726
26554
  constructor(dataDir) {
26727
26555
  this.dataDir = dataDir;
26728
- fs19.mkdirSync(this.dirPath(), { recursive: true });
26556
+ fs18.mkdirSync(this.dirPath(), { recursive: true });
26729
26557
  }
26730
26558
  dataDir;
26731
26559
  /**
@@ -26737,7 +26565,7 @@ var InboxStore = class {
26737
26565
  const file = this.filePath(peerOwnerId);
26738
26566
  let raw;
26739
26567
  try {
26740
- raw = fs19.readFileSync(file, "utf8");
26568
+ raw = fs18.readFileSync(file, "utf8");
26741
26569
  } catch (err) {
26742
26570
  if (err?.code === "ENOENT") return [];
26743
26571
  return [];
@@ -26753,7 +26581,7 @@ var InboxStore = class {
26753
26581
  const dir = this.dirPath();
26754
26582
  let entries;
26755
26583
  try {
26756
- entries = fs19.readdirSync(dir);
26584
+ entries = fs18.readdirSync(dir);
26757
26585
  } catch (err) {
26758
26586
  if (err?.code === "ENOENT") return [];
26759
26587
  return [];
@@ -26769,9 +26597,9 @@ var InboxStore = class {
26769
26597
  if (existing.some((m2) => m2.id === message.id)) return;
26770
26598
  const file = this.filePath(message.peerOwnerId);
26771
26599
  const line = JSON.stringify(message) + "\n";
26772
- fs19.appendFileSync(file, line, { mode: 384 });
26600
+ fs18.appendFileSync(file, line, { mode: 384 });
26773
26601
  try {
26774
- fs19.chmodSync(file, 384);
26602
+ fs18.chmodSync(file, 384);
26775
26603
  } catch {
26776
26604
  }
26777
26605
  }
@@ -26801,7 +26629,7 @@ var InboxStore = class {
26801
26629
  removeByPeerOwnerId(peerOwnerId) {
26802
26630
  const file = this.filePath(peerOwnerId);
26803
26631
  try {
26804
- fs19.unlinkSync(file);
26632
+ fs18.unlinkSync(file);
26805
26633
  } catch (err) {
26806
26634
  if (err?.code === "ENOENT") return;
26807
26635
  }
@@ -26810,18 +26638,18 @@ var InboxStore = class {
26810
26638
  const file = this.filePath(peerOwnerId);
26811
26639
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
26812
26640
  const content = messages.map((m2) => JSON.stringify(m2)).join("\n") + (messages.length > 0 ? "\n" : "");
26813
- fs19.writeFileSync(tmp, content, { mode: 384 });
26814
- fs19.renameSync(tmp, file);
26641
+ fs18.writeFileSync(tmp, content, { mode: 384 });
26642
+ fs18.renameSync(tmp, file);
26815
26643
  try {
26816
- fs19.chmodSync(file, 384);
26644
+ fs18.chmodSync(file, 384);
26817
26645
  } catch {
26818
26646
  }
26819
26647
  }
26820
26648
  dirPath() {
26821
- return path21.join(this.dataDir, INBOX_SUBDIR);
26649
+ return path20.join(this.dataDir, INBOX_SUBDIR);
26822
26650
  }
26823
26651
  filePath(peerOwnerId) {
26824
- return path21.join(this.dirPath(), `${peerOwnerId}.jsonl`);
26652
+ return path20.join(this.dirPath(), `${peerOwnerId}.jsonl`);
26825
26653
  }
26826
26654
  };
26827
26655
  function parseAllLines(raw) {
@@ -26908,8 +26736,8 @@ var InboxManager = class {
26908
26736
  };
26909
26737
 
26910
26738
  // src/state/received-capability-store.ts
26911
- var fs20 = __toESM(require("fs"), 1);
26912
- var path22 = __toESM(require("path"), 1);
26739
+ var fs19 = __toESM(require("fs"), 1);
26740
+ var path21 = __toESM(require("path"), 1);
26913
26741
  var FILE_NAME = "received-capabilities.json";
26914
26742
  var ReceivedCapabilityStore = class {
26915
26743
  constructor(dataDir) {
@@ -26919,10 +26747,10 @@ var ReceivedCapabilityStore = class {
26919
26747
  caps = /* @__PURE__ */ new Map();
26920
26748
  load() {
26921
26749
  this.caps.clear();
26922
- const file = path22.join(this.dataDir, FILE_NAME);
26750
+ const file = path21.join(this.dataDir, FILE_NAME);
26923
26751
  let raw;
26924
26752
  try {
26925
- raw = fs20.readFileSync(file, "utf8");
26753
+ raw = fs19.readFileSync(file, "utf8");
26926
26754
  } catch (err) {
26927
26755
  if (err?.code !== "ENOENT") this.renameBak(file);
26928
26756
  return;
@@ -26959,20 +26787,20 @@ var ReceivedCapabilityStore = class {
26959
26787
  this.flush();
26960
26788
  }
26961
26789
  flush() {
26962
- const file = path22.join(this.dataDir, FILE_NAME);
26790
+ const file = path21.join(this.dataDir, FILE_NAME);
26963
26791
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
26964
26792
  const content = JSON.stringify(
26965
26793
  { receivedCaps: Array.from(this.caps.values()) },
26966
26794
  null,
26967
26795
  2
26968
26796
  );
26969
- fs20.mkdirSync(this.dataDir, { recursive: true });
26970
- fs20.writeFileSync(tmp, content, { mode: 384 });
26971
- fs20.renameSync(tmp, file);
26797
+ fs19.mkdirSync(this.dataDir, { recursive: true });
26798
+ fs19.writeFileSync(tmp, content, { mode: 384 });
26799
+ fs19.renameSync(tmp, file);
26972
26800
  }
26973
26801
  renameBak(file) {
26974
26802
  try {
26975
- fs20.renameSync(file, `${file}.bak`);
26803
+ fs19.renameSync(file, `${file}.bak`);
26976
26804
  } catch {
26977
26805
  }
26978
26806
  }
@@ -27070,61 +26898,61 @@ async function connectRemote(args) {
27070
26898
  }
27071
26899
 
27072
26900
  // src/migrations/2026-05-20-flatten-sessions.ts
27073
- var fs21 = __toESM(require("fs"), 1);
27074
- var path23 = __toESM(require("path"), 1);
26901
+ var fs20 = __toESM(require("fs"), 1);
26902
+ var path22 = __toESM(require("path"), 1);
27075
26903
  var MIGRATION_FLAG_NAME = ".migration.v1.done";
27076
26904
  function migrateFlattenSessions(opts) {
27077
26905
  const dataDir = opts.dataDir;
27078
26906
  const now = opts.now ?? Date.now;
27079
- const sessionsDir = path23.join(dataDir, "sessions");
27080
- const flagPath = path23.join(sessionsDir, MIGRATION_FLAG_NAME);
26907
+ const sessionsDir = path22.join(dataDir, "sessions");
26908
+ const flagPath = path22.join(sessionsDir, MIGRATION_FLAG_NAME);
27081
26909
  if (existsSync3(flagPath)) {
27082
26910
  return { skipped: true, flagWritten: false, movedBare: 0, movedVmOwner: 0, archivedListener: 0 };
27083
26911
  }
27084
26912
  let movedBare = 0;
27085
26913
  let movedVmOwner = 0;
27086
26914
  let archivedListener = 0;
27087
- const defaultDir = path23.join(sessionsDir, "default");
26915
+ const defaultDir = path22.join(sessionsDir, "default");
27088
26916
  if (existsSync3(defaultDir)) {
27089
26917
  for (const entry of readdirSafe(defaultDir)) {
27090
26918
  if (!entry.endsWith(".json")) continue;
27091
- const src = path23.join(defaultDir, entry);
27092
- const dst = path23.join(sessionsDir, entry);
27093
- fs21.renameSync(src, dst);
26919
+ const src = path22.join(defaultDir, entry);
26920
+ const dst = path22.join(sessionsDir, entry);
26921
+ fs20.renameSync(src, dst);
27094
26922
  movedBare += 1;
27095
26923
  }
27096
26924
  rmdirIfEmpty(defaultDir);
27097
26925
  }
27098
26926
  for (const pid of readdirSafe(sessionsDir)) {
27099
- const personaDir = path23.join(sessionsDir, pid);
26927
+ const personaDir = path22.join(sessionsDir, pid);
27100
26928
  if (!isDir(personaDir)) continue;
27101
26929
  if (pid === "default") continue;
27102
- const ownerSrc = path23.join(personaDir, "owner");
26930
+ const ownerSrc = path22.join(personaDir, "owner");
27103
26931
  if (existsSync3(ownerSrc) && isDir(ownerSrc)) {
27104
- const ownerDst = path23.join(dataDir, "personas", pid, ".clawd", "sessions", "owner");
27105
- fs21.mkdirSync(ownerDst, { recursive: true });
26932
+ const ownerDst = path22.join(dataDir, "personas", pid, ".clawd", "sessions", "owner");
26933
+ fs20.mkdirSync(ownerDst, { recursive: true });
27106
26934
  for (const file of readdirSafe(ownerSrc)) {
27107
26935
  if (!file.endsWith(".json")) continue;
27108
- fs21.renameSync(path23.join(ownerSrc, file), path23.join(ownerDst, file));
26936
+ fs20.renameSync(path22.join(ownerSrc, file), path22.join(ownerDst, file));
27109
26937
  movedVmOwner += 1;
27110
26938
  }
27111
26939
  rmdirIfEmpty(ownerSrc);
27112
26940
  }
27113
- const listenerSrc = path23.join(personaDir, "listener");
26941
+ const listenerSrc = path22.join(personaDir, "listener");
27114
26942
  if (existsSync3(listenerSrc) && isDir(listenerSrc)) {
27115
- const archiveDst = path23.join(dataDir, ".legacy", `listener-${pid}`);
27116
- fs21.mkdirSync(archiveDst, { recursive: true });
26943
+ const archiveDst = path22.join(dataDir, ".legacy", `listener-${pid}`);
26944
+ fs20.mkdirSync(archiveDst, { recursive: true });
27117
26945
  for (const file of readdirSafe(listenerSrc)) {
27118
26946
  if (!file.endsWith(".json")) continue;
27119
- fs21.renameSync(path23.join(listenerSrc, file), path23.join(archiveDst, file));
26947
+ fs20.renameSync(path22.join(listenerSrc, file), path22.join(archiveDst, file));
27120
26948
  archivedListener += 1;
27121
26949
  }
27122
26950
  rmdirIfEmpty(listenerSrc);
27123
26951
  }
27124
26952
  rmdirIfEmpty(personaDir);
27125
26953
  }
27126
- fs21.mkdirSync(sessionsDir, { recursive: true });
27127
- fs21.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
26954
+ fs20.mkdirSync(sessionsDir, { recursive: true });
26955
+ fs20.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
27128
26956
  return {
27129
26957
  skipped: false,
27130
26958
  flagWritten: true,
@@ -27135,7 +26963,7 @@ function migrateFlattenSessions(opts) {
27135
26963
  }
27136
26964
  function existsSync3(p2) {
27137
26965
  try {
27138
- fs21.statSync(p2);
26966
+ fs20.statSync(p2);
27139
26967
  return true;
27140
26968
  } catch {
27141
26969
  return false;
@@ -27143,21 +26971,21 @@ function existsSync3(p2) {
27143
26971
  }
27144
26972
  function isDir(p2) {
27145
26973
  try {
27146
- return fs21.statSync(p2).isDirectory();
26974
+ return fs20.statSync(p2).isDirectory();
27147
26975
  } catch {
27148
26976
  return false;
27149
26977
  }
27150
26978
  }
27151
26979
  function readdirSafe(p2) {
27152
26980
  try {
27153
- return fs21.readdirSync(p2);
26981
+ return fs20.readdirSync(p2);
27154
26982
  } catch {
27155
26983
  return [];
27156
26984
  }
27157
26985
  }
27158
26986
  function rmdirIfEmpty(p2) {
27159
26987
  try {
27160
- fs21.rmdirSync(p2);
26988
+ fs20.rmdirSync(p2);
27161
26989
  } catch {
27162
26990
  }
27163
26991
  }
@@ -27167,7 +26995,7 @@ var import_node_fs15 = __toESM(require("fs"), 1);
27167
26995
  var import_node_path17 = __toESM(require("path"), 1);
27168
26996
 
27169
26997
  // src/attachment/mime.ts
27170
- var import_node_path16 = __toESM(require("path"), 1);
26998
+ var import_node_path15 = __toESM(require("path"), 1);
27171
26999
  var TEXT_PLAIN = "text/plain; charset=utf-8";
27172
27000
  var EXT_TO_NATIVE_MIME = {
27173
27001
  // 图片
@@ -27274,7 +27102,7 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
27274
27102
  ".mk"
27275
27103
  ]);
27276
27104
  function lookupMime(filePathOrName) {
27277
- const ext = import_node_path16.default.extname(filePathOrName).toLowerCase();
27105
+ const ext = import_node_path15.default.extname(filePathOrName).toLowerCase();
27278
27106
  if (EXT_TO_NATIVE_MIME[ext]) return EXT_TO_NATIVE_MIME[ext];
27279
27107
  if (TEXT_EXTENSIONS.has(ext)) return TEXT_PLAIN;
27280
27108
  return "application/octet-stream";
@@ -27342,7 +27170,137 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
27342
27170
  return { ok: true, absPath };
27343
27171
  }
27344
27172
 
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
+
27345
27277
  // 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
+ var UPLOAD_CORS_HEADERS = {
27288
+ "Access-Control-Allow-Origin": "*",
27289
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
27290
+ "Access-Control-Allow-Headers": "Authorization, Content-Type",
27291
+ "Access-Control-Max-Age": "86400"
27292
+ };
27293
+ function isUploadMimeAllowed(mime) {
27294
+ if (UPLOAD_MIME_ALLOW.has(mime)) return true;
27295
+ return UPLOAD_MIME_ALLOW_PREFIX.some((p2) => mime.startsWith(p2));
27296
+ }
27297
+ function isValidUploadFileName(fileName) {
27298
+ if (fileName.length === 0) return false;
27299
+ if (fileName === "." || fileName === "..") return false;
27300
+ if (fileName.startsWith(".")) return false;
27301
+ if (fileName.includes("/") || fileName.includes("\\")) return false;
27302
+ return fileName === import_node_path17.default.basename(fileName);
27303
+ }
27346
27304
  function createHttpRouter(deps) {
27347
27305
  return async (req, res) => {
27348
27306
  const url = parseUrl(req.url);
@@ -27356,9 +27314,14 @@ function createHttpRouter(deps) {
27356
27314
  });
27357
27315
  return true;
27358
27316
  }
27359
- if (!url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/")) {
27317
+ if (!url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/") && url.pathname !== "/attachments/upload") {
27360
27318
  return false;
27361
27319
  }
27320
+ if (url.pathname === "/attachments/upload" && req.method === "OPTIONS") {
27321
+ res.writeHead(204, UPLOAD_CORS_HEADERS);
27322
+ res.end();
27323
+ return true;
27324
+ }
27362
27325
  if (url.pathname.startsWith("/files/") && req.method === "GET") {
27363
27326
  const secret = deps.getSignSecret?.();
27364
27327
  if (!secret) {
@@ -27396,6 +27359,107 @@ function createHttpRouter(deps) {
27396
27359
  sendJson(res, 401, { code: "UNAUTHORIZED", message: "missing or invalid bearer token" });
27397
27360
  return true;
27398
27361
  }
27362
+ if (url.pathname === "/attachments/upload" && req.method === "POST") {
27363
+ const sendUploadJson = (status, body) => sendJson(res, status, body, UPLOAD_CORS_HEADERS);
27364
+ if (ctx.role !== "owner") {
27365
+ sendUploadJson(403, { code: "FORBIDDEN", message: "owner only" });
27366
+ return true;
27367
+ }
27368
+ const up = deps.attachmentUpload;
27369
+ if (!up) {
27370
+ sendUploadJson(501, { code: "NOT_IMPLEMENTED", message: "attachment upload not wired" });
27371
+ return true;
27372
+ }
27373
+ const sessionId = url.searchParams.get("sessionId");
27374
+ const fileName = url.searchParams.get("fileName");
27375
+ const mimeType = url.searchParams.get("mimeType");
27376
+ const contentLengthRaw = req.headers["content-length"];
27377
+ if (!sessionId || !fileName || !mimeType || !contentLengthRaw) {
27378
+ sendUploadJson(400, {
27379
+ code: "INVALID_PARAM",
27380
+ message: "missing sessionId/fileName/mimeType/Content-Length"
27381
+ });
27382
+ return true;
27383
+ }
27384
+ if (!isValidUploadFileName(fileName)) {
27385
+ sendUploadJson(400, {
27386
+ code: "INVALID_FILENAME",
27387
+ message: `fileName must be a plain basename (no path separators, no dotfiles): ${fileName}`
27388
+ });
27389
+ return true;
27390
+ }
27391
+ const contentLength = Number.parseInt(contentLengthRaw, 10);
27392
+ if (!Number.isFinite(contentLength) || contentLength < 0) {
27393
+ sendUploadJson(400, { code: "INVALID_PARAM", message: "malformed Content-Length" });
27394
+ return true;
27395
+ }
27396
+ if (contentLength > UPLOAD_MAX_BYTES) {
27397
+ sendUploadJson(413, {
27398
+ code: "FILE_TOO_LARGE",
27399
+ message: `max ${UPLOAD_MAX_BYTES} bytes`
27400
+ });
27401
+ return true;
27402
+ }
27403
+ if (!isUploadMimeAllowed(mimeType)) {
27404
+ sendUploadJson(400, {
27405
+ code: "UNSUPPORTED_MIME",
27406
+ message: `mime ${mimeType} not allowed`
27407
+ });
27408
+ return true;
27409
+ }
27410
+ const sessionDir = up.getSessionDir(sessionId);
27411
+ const scope = up.getSessionScope(sessionId);
27412
+ if (!sessionDir || !scope) {
27413
+ sendUploadJson(404, { code: "NOT_FOUND", message: `session ${sessionId} not found` });
27414
+ return true;
27415
+ }
27416
+ const httpBaseUrl = up.getHttpBaseUrl();
27417
+ if (!httpBaseUrl) {
27418
+ sendUploadJson(503, {
27419
+ code: "TUNNEL_NOT_READY",
27420
+ message: "httpBaseUrl unavailable"
27421
+ });
27422
+ return true;
27423
+ }
27424
+ const secret = deps.getSignSecret?.();
27425
+ if (!secret) {
27426
+ sendUploadJson(501, {
27427
+ code: "NOT_IMPLEMENTED",
27428
+ message: "sign secret unavailable (noAuth?)"
27429
+ });
27430
+ return true;
27431
+ }
27432
+ try {
27433
+ const result = await writeUploadedAttachment({
27434
+ sessionDir,
27435
+ sessionId,
27436
+ scope,
27437
+ fileName,
27438
+ mimeType,
27439
+ contentLength,
27440
+ body: req,
27441
+ groupFileStore: up.groupFileStore
27442
+ });
27443
+ const parts = signUrlParts(secret, result.absPath, null);
27444
+ const fullUrl = buildSignedFileUrl(httpBaseUrl, parts);
27445
+ sendUploadJson(200, {
27446
+ attachmentId: result.attachmentId,
27447
+ url: fullUrl,
27448
+ relPath: result.relPath,
27449
+ sizeBytes: result.sizeBytes,
27450
+ mimeType: result.mimeType
27451
+ });
27452
+ } catch (err) {
27453
+ if (err instanceof UploadError) {
27454
+ const status = err.code === "SIZE_MISMATCH" ? 400 : 500;
27455
+ sendUploadJson(status, { code: err.code, message: err.message });
27456
+ } else {
27457
+ deps.logger?.warn("attachment upload failed", { err: err.message });
27458
+ sendUploadJson(500, { code: "STORAGE_ERROR", message: err.message });
27459
+ }
27460
+ }
27461
+ return true;
27462
+ }
27399
27463
  const sessionFilesMatch = url.pathname.match(/^\/session\/([^/]+)\/files$/);
27400
27464
  if (sessionFilesMatch && req.method === "GET") {
27401
27465
  const sid = sessionFilesMatch[1];
@@ -27477,10 +27541,78 @@ function streamFile(res, absPath, logger) {
27477
27541
  stream.pipe(res);
27478
27542
  }
27479
27543
 
27480
- // src/attachment/group.ts
27544
+ // src/attachment/gc.ts
27481
27545
  var import_node_fs16 = __toESM(require("fs"), 1);
27482
27546
  var import_node_path18 = __toESM(require("path"), 1);
27483
- var import_node_crypto6 = __toESM(require("crypto"), 1);
27547
+ var DEFAULT_TTL_MS = 30 * 24 * 3600 * 1e3;
27548
+ function runAttachmentGc(args) {
27549
+ const now = (args.now ?? Date.now)();
27550
+ const ttlMs = args.ttlMs ?? DEFAULT_TTL_MS;
27551
+ const liveAbs = /* @__PURE__ */ new Set();
27552
+ for (const { scope, sessionId } of args.sessionScopes) {
27553
+ for (const entry of args.groupFileStore.list(scope, sessionId)) {
27554
+ if (entry.stale) continue;
27555
+ if (import_node_path18.default.isAbsolute(entry.relPath)) liveAbs.add(entry.relPath);
27556
+ }
27557
+ }
27558
+ for (const { scope, sessionId } of args.sessionScopes) {
27559
+ const sessionDir = args.getSessionCwd?.(sessionId) ?? import_node_path18.default.join(
27560
+ args.dataDir,
27561
+ "sessions",
27562
+ ...scopeSubPath(scope).map(safeFileName),
27563
+ safeFileName(sessionId)
27564
+ );
27565
+ const attRoot = import_node_path18.default.join(sessionDir, ".attachments");
27566
+ let hashDirs;
27567
+ try {
27568
+ hashDirs = import_node_fs16.default.readdirSync(attRoot);
27569
+ } catch (err) {
27570
+ if (err.code === "ENOENT") continue;
27571
+ args.logger?.warn("attachment gc: readdir failed", { attRoot, err: err.message });
27572
+ continue;
27573
+ }
27574
+ for (const hashDir of hashDirs) {
27575
+ const hashDirAbs = import_node_path18.default.join(attRoot, hashDir);
27576
+ let files;
27577
+ try {
27578
+ files = import_node_fs16.default.readdirSync(hashDirAbs);
27579
+ } catch {
27580
+ continue;
27581
+ }
27582
+ for (const name of files) {
27583
+ const file = import_node_path18.default.join(hashDirAbs, name);
27584
+ let stat;
27585
+ try {
27586
+ stat = import_node_fs16.default.statSync(file);
27587
+ } catch {
27588
+ continue;
27589
+ }
27590
+ if (!stat.isFile()) continue;
27591
+ const age = now - stat.mtimeMs;
27592
+ if (age < ttlMs) continue;
27593
+ if (liveAbs.has(file)) continue;
27594
+ try {
27595
+ import_node_fs16.default.unlinkSync(file);
27596
+ } catch (err) {
27597
+ args.logger?.warn("attachment gc: unlink failed", { file, err: err.message });
27598
+ }
27599
+ }
27600
+ try {
27601
+ if (import_node_fs16.default.readdirSync(hashDirAbs).length === 0) import_node_fs16.default.rmdirSync(hashDirAbs);
27602
+ } catch {
27603
+ }
27604
+ }
27605
+ try {
27606
+ if (import_node_fs16.default.readdirSync(attRoot).length === 0) import_node_fs16.default.rmdirSync(attRoot);
27607
+ } catch {
27608
+ }
27609
+ }
27610
+ }
27611
+
27612
+ // src/attachment/group.ts
27613
+ var import_node_fs17 = __toESM(require("fs"), 1);
27614
+ var import_node_path19 = __toESM(require("path"), 1);
27615
+ var import_node_crypto7 = __toESM(require("crypto"), 1);
27484
27616
  init_protocol();
27485
27617
  var GroupFileStore = class {
27486
27618
  dataDir;
@@ -27491,11 +27623,11 @@ var GroupFileStore = class {
27491
27623
  this.logger = opts.logger;
27492
27624
  }
27493
27625
  rootForScope(scope) {
27494
- return import_node_path18.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
27626
+ return import_node_path19.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
27495
27627
  }
27496
27628
  /** 与 SessionStore.filePath 平级,扩展名 .group-files.json */
27497
27629
  filePath(scope, sessionId) {
27498
- return import_node_path18.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
27630
+ return import_node_path19.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
27499
27631
  }
27500
27632
  cacheKey(scope, sessionId) {
27501
27633
  return scope.kind === "default" ? `default::${sessionId}` : `persona:${scope.personaId}:${scope.mode}::${sessionId}`;
@@ -27504,7 +27636,7 @@ var GroupFileStore = class {
27504
27636
  readFile(scope, sessionId) {
27505
27637
  const file = this.filePath(scope, sessionId);
27506
27638
  try {
27507
- const raw = import_node_fs16.default.readFileSync(file, "utf8");
27639
+ const raw = import_node_fs17.default.readFileSync(file, "utf8");
27508
27640
  const parsed = JSON.parse(raw);
27509
27641
  if (!Array.isArray(parsed)) {
27510
27642
  this.logger?.warn("GroupFileStore.readFile: not an array; resetting session entries", {
@@ -27530,10 +27662,10 @@ var GroupFileStore = class {
27530
27662
  }
27531
27663
  writeFile(scope, sessionId, entries) {
27532
27664
  const file = this.filePath(scope, sessionId);
27533
- import_node_fs16.default.mkdirSync(import_node_path18.default.dirname(file), { recursive: true });
27665
+ import_node_fs17.default.mkdirSync(import_node_path19.default.dirname(file), { recursive: true });
27534
27666
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
27535
- import_node_fs16.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
27536
- import_node_fs16.default.renameSync(tmp, file);
27667
+ import_node_fs17.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
27668
+ import_node_fs17.default.renameSync(tmp, file);
27537
27669
  }
27538
27670
  /** 拉一份当前 session 的清单。读盘 → cache;之后调用复用 cache */
27539
27671
  list(scope, sessionId) {
@@ -27569,7 +27701,7 @@ var GroupFileStore = class {
27569
27701
  entries[idx] = next;
27570
27702
  } else {
27571
27703
  next = {
27572
- id: `gf-${import_node_crypto6.default.randomBytes(6).toString("base64url")}`,
27704
+ id: `gf-${import_node_crypto7.default.randomBytes(6).toString("base64url")}`,
27573
27705
  relPath: input.relPath,
27574
27706
  from: input.from,
27575
27707
  label: input.label,
@@ -27619,10 +27751,10 @@ var GroupFileStore = class {
27619
27751
  };
27620
27752
 
27621
27753
  // src/discovery/state-file.ts
27622
- var import_node_fs17 = __toESM(require("fs"), 1);
27623
- var import_node_path19 = __toESM(require("path"), 1);
27754
+ var import_node_fs18 = __toESM(require("fs"), 1);
27755
+ var import_node_path20 = __toESM(require("path"), 1);
27624
27756
  function defaultStateFilePath(dataDir) {
27625
- return import_node_path19.default.join(dataDir, "state.json");
27757
+ return import_node_path20.default.join(dataDir, "state.json");
27626
27758
  }
27627
27759
  function isPidAlive(pid) {
27628
27760
  if (!Number.isFinite(pid) || pid <= 0) return false;
@@ -27644,7 +27776,7 @@ var StateFileManager = class {
27644
27776
  }
27645
27777
  read() {
27646
27778
  try {
27647
- const raw = import_node_fs17.default.readFileSync(this.file, "utf8");
27779
+ const raw = import_node_fs18.default.readFileSync(this.file, "utf8");
27648
27780
  const parsed = JSON.parse(raw);
27649
27781
  return parsed;
27650
27782
  } catch {
@@ -27658,34 +27790,34 @@ var StateFileManager = class {
27658
27790
  return { status: "stale", existing };
27659
27791
  }
27660
27792
  write(state) {
27661
- import_node_fs17.default.mkdirSync(import_node_path19.default.dirname(this.file), { recursive: true });
27793
+ import_node_fs18.default.mkdirSync(import_node_path20.default.dirname(this.file), { recursive: true });
27662
27794
  const tmp = `${this.file}.tmp.${process.pid}.${Date.now()}`;
27663
- import_node_fs17.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
27664
- import_node_fs17.default.renameSync(tmp, this.file);
27795
+ import_node_fs18.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
27796
+ import_node_fs18.default.renameSync(tmp, this.file);
27665
27797
  if (process.platform !== "win32") {
27666
27798
  try {
27667
- import_node_fs17.default.chmodSync(this.file, 384);
27799
+ import_node_fs18.default.chmodSync(this.file, 384);
27668
27800
  } catch {
27669
27801
  }
27670
27802
  }
27671
27803
  }
27672
27804
  delete() {
27673
27805
  try {
27674
- import_node_fs17.default.unlinkSync(this.file);
27806
+ import_node_fs18.default.unlinkSync(this.file);
27675
27807
  } catch {
27676
27808
  }
27677
27809
  }
27678
27810
  };
27679
27811
 
27680
27812
  // src/tunnel/tunnel-manager.ts
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);
27813
+ var import_node_fs22 = __toESM(require("fs"), 1);
27814
+ var import_node_path24 = __toESM(require("path"), 1);
27815
+ var import_node_crypto8 = __toESM(require("crypto"), 1);
27684
27816
  var import_node_child_process5 = require("child_process");
27685
27817
 
27686
27818
  // src/tunnel/tunnel-store.ts
27687
- var import_node_fs18 = __toESM(require("fs"), 1);
27688
- var import_node_path20 = __toESM(require("path"), 1);
27819
+ var import_node_fs19 = __toESM(require("fs"), 1);
27820
+ var import_node_path21 = __toESM(require("path"), 1);
27689
27821
  var TunnelStore = class {
27690
27822
  constructor(filePath) {
27691
27823
  this.filePath = filePath;
@@ -27693,7 +27825,7 @@ var TunnelStore = class {
27693
27825
  filePath;
27694
27826
  async get() {
27695
27827
  try {
27696
- const raw = await import_node_fs18.default.promises.readFile(this.filePath, "utf8");
27828
+ const raw = await import_node_fs19.default.promises.readFile(this.filePath, "utf8");
27697
27829
  const obj = JSON.parse(raw);
27698
27830
  if (!isPersistedTunnel(obj)) return null;
27699
27831
  return obj;
@@ -27704,22 +27836,22 @@ var TunnelStore = class {
27704
27836
  }
27705
27837
  }
27706
27838
  async set(v2) {
27707
- const dir = import_node_path20.default.dirname(this.filePath);
27708
- await import_node_fs18.default.promises.mkdir(dir, { recursive: true });
27839
+ const dir = import_node_path21.default.dirname(this.filePath);
27840
+ await import_node_fs19.default.promises.mkdir(dir, { recursive: true });
27709
27841
  const data = JSON.stringify(v2, null, 2);
27710
27842
  const tmp = `${this.filePath}.tmp.${process.pid}.${Date.now()}`;
27711
- await import_node_fs18.default.promises.writeFile(tmp, data, { mode: 384 });
27843
+ await import_node_fs19.default.promises.writeFile(tmp, data, { mode: 384 });
27712
27844
  if (process.platform !== "win32") {
27713
27845
  try {
27714
- await import_node_fs18.default.promises.chmod(tmp, 384);
27846
+ await import_node_fs19.default.promises.chmod(tmp, 384);
27715
27847
  } catch {
27716
27848
  }
27717
27849
  }
27718
- await import_node_fs18.default.promises.rename(tmp, this.filePath);
27850
+ await import_node_fs19.default.promises.rename(tmp, this.filePath);
27719
27851
  }
27720
27852
  async clear() {
27721
27853
  try {
27722
- await import_node_fs18.default.promises.unlink(this.filePath);
27854
+ await import_node_fs19.default.promises.unlink(this.filePath);
27723
27855
  } catch (err) {
27724
27856
  const code = err?.code;
27725
27857
  if (code !== "ENOENT") throw err;
@@ -27814,12 +27946,12 @@ function escape(v2) {
27814
27946
  }
27815
27947
 
27816
27948
  // src/tunnel/frpc-binary.ts
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);
27949
+ var import_node_fs20 = __toESM(require("fs"), 1);
27950
+ var import_node_os9 = __toESM(require("os"), 1);
27951
+ var import_node_path22 = __toESM(require("path"), 1);
27820
27952
  var import_node_child_process3 = require("child_process");
27821
27953
  var import_node_stream2 = require("stream");
27822
- var import_promises = require("stream/promises");
27954
+ var import_promises2 = require("stream/promises");
27823
27955
  var FRPC_VERSION = "0.68.0";
27824
27956
  var UnsupportedPlatformError = class extends Error {
27825
27957
  constructor(platform, arch) {
@@ -27848,20 +27980,20 @@ function frpcDownloadUrl(version2, p2) {
27848
27980
  }
27849
27981
  async function ensureFrpcBinary(opts) {
27850
27982
  if (opts.override) {
27851
- if (!import_node_fs19.default.existsSync(opts.override)) {
27983
+ if (!import_node_fs20.default.existsSync(opts.override)) {
27852
27984
  throw new Error(`frpc binary not found at override path: ${opts.override}`);
27853
27985
  }
27854
27986
  return opts.override;
27855
27987
  }
27856
27988
  const version2 = opts.version ?? FRPC_VERSION;
27857
27989
  const platform = opts.platform ?? detectPlatform();
27858
- const binDir = import_node_path21.default.join(opts.dataDir, "bin");
27859
- import_node_fs19.default.mkdirSync(binDir, { recursive: true });
27990
+ const binDir = import_node_path22.default.join(opts.dataDir, "bin");
27991
+ import_node_fs20.default.mkdirSync(binDir, { recursive: true });
27860
27992
  cleanupStaleArtifacts(binDir);
27861
- const stableBin = import_node_path21.default.join(binDir, "frpc");
27862
- if (import_node_fs19.default.existsSync(stableBin)) return stableBin;
27993
+ const stableBin = import_node_path22.default.join(binDir, "frpc");
27994
+ if (import_node_fs20.default.existsSync(stableBin)) return stableBin;
27863
27995
  const partialBin = `${stableBin}.partial`;
27864
- const tarballPath = import_node_path21.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
27996
+ const tarballPath = import_node_path22.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
27865
27997
  try {
27866
27998
  const url = frpcDownloadUrl(version2, platform);
27867
27999
  await downloadToFile(url, tarballPath, opts.fetchImpl);
@@ -27870,8 +28002,8 @@ async function ensureFrpcBinary(opts) {
27870
28002
  } else {
27871
28003
  await extractFrpcFromTarball(tarballPath, binDir, version2, platform, partialBin);
27872
28004
  }
27873
- import_node_fs19.default.chmodSync(partialBin, 493);
27874
- import_node_fs19.default.renameSync(partialBin, stableBin);
28005
+ import_node_fs20.default.chmodSync(partialBin, 493);
28006
+ import_node_fs20.default.renameSync(partialBin, stableBin);
27875
28007
  } finally {
27876
28008
  safeUnlink(tarballPath);
27877
28009
  safeUnlink(partialBin);
@@ -27881,15 +28013,15 @@ async function ensureFrpcBinary(opts) {
27881
28013
  function cleanupStaleArtifacts(binDir) {
27882
28014
  let entries;
27883
28015
  try {
27884
- entries = import_node_fs19.default.readdirSync(binDir);
28016
+ entries = import_node_fs20.default.readdirSync(binDir);
27885
28017
  } catch {
27886
28018
  return;
27887
28019
  }
27888
28020
  for (const name of entries) {
27889
28021
  if (name.endsWith(".partial") || name.startsWith("extract-")) {
27890
- const full = import_node_path21.default.join(binDir, name);
28022
+ const full = import_node_path22.default.join(binDir, name);
27891
28023
  try {
27892
- import_node_fs19.default.rmSync(full, { recursive: true, force: true });
28024
+ import_node_fs20.default.rmSync(full, { recursive: true, force: true });
27893
28025
  } catch {
27894
28026
  }
27895
28027
  }
@@ -27897,7 +28029,7 @@ function cleanupStaleArtifacts(binDir) {
27897
28029
  }
27898
28030
  function safeUnlink(p2) {
27899
28031
  try {
27900
- import_node_fs19.default.unlinkSync(p2);
28032
+ import_node_fs20.default.unlinkSync(p2);
27901
28033
  } catch {
27902
28034
  }
27903
28035
  }
@@ -27908,13 +28040,13 @@ async function downloadToFile(url, dest, fetchImpl) {
27908
28040
  if (!res.ok || !res.body) {
27909
28041
  throw new Error(`download failed: ${res.status} ${res.statusText}`);
27910
28042
  }
27911
- const out = import_node_fs19.default.createWriteStream(dest);
28043
+ const out = import_node_fs20.default.createWriteStream(dest);
27912
28044
  const nodeStream = import_node_stream2.Readable.fromWeb(res.body);
27913
- await (0, import_promises.pipeline)(nodeStream, out);
28045
+ await (0, import_promises2.pipeline)(nodeStream, out);
27914
28046
  }
27915
28047
  async function extractFrpcFromTarball(tarball, binDir, version2, platform, destBin) {
27916
- const work = import_node_path21.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
27917
- import_node_fs19.default.mkdirSync(work, { recursive: true });
28048
+ const work = import_node_path22.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
28049
+ import_node_fs20.default.mkdirSync(work, { recursive: true });
27918
28050
  try {
27919
28051
  await new Promise((resolve6, reject) => {
27920
28052
  const proc = (0, import_node_child_process3.spawn)("tar", ["xzf", tarball, "-C", work], { stdio: "pipe" });
@@ -27922,32 +28054,32 @@ async function extractFrpcFromTarball(tarball, binDir, version2, platform, destB
27922
28054
  proc.on("exit", (code) => code === 0 ? resolve6() : reject(new Error(`tar exited ${code}`)));
27923
28055
  });
27924
28056
  const dirName = `frp_${version2}_${platform.os}_${platform.arch}`;
27925
- const src = import_node_path21.default.join(work, dirName, "frpc");
27926
- if (!import_node_fs19.default.existsSync(src)) {
28057
+ const src = import_node_path22.default.join(work, dirName, "frpc");
28058
+ if (!import_node_fs20.default.existsSync(src)) {
27927
28059
  throw new Error(`frpc not found inside tarball at ${src}`);
27928
28060
  }
27929
- import_node_fs19.default.copyFileSync(src, destBin);
28061
+ import_node_fs20.default.copyFileSync(src, destBin);
27930
28062
  } finally {
27931
- import_node_fs19.default.rmSync(work, { recursive: true, force: true });
28063
+ import_node_fs20.default.rmSync(work, { recursive: true, force: true });
27932
28064
  }
27933
28065
  }
27934
28066
 
27935
28067
  // src/tunnel/frpc-process.ts
27936
- var import_node_fs20 = __toESM(require("fs"), 1);
27937
- var import_node_path22 = __toESM(require("path"), 1);
28068
+ var import_node_fs21 = __toESM(require("fs"), 1);
28069
+ var import_node_path23 = __toESM(require("path"), 1);
27938
28070
  var import_node_child_process4 = require("child_process");
27939
28071
  function frpcPidFilePath(dataDir) {
27940
- return import_node_path22.default.join(dataDir, "frpc.pid");
28072
+ return import_node_path23.default.join(dataDir, "frpc.pid");
27941
28073
  }
27942
28074
  function writeFrpcPid(dataDir, pid) {
27943
28075
  try {
27944
- import_node_fs20.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
28076
+ import_node_fs21.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
27945
28077
  } catch {
27946
28078
  }
27947
28079
  }
27948
28080
  function clearFrpcPid(dataDir) {
27949
28081
  try {
27950
- import_node_fs20.default.unlinkSync(frpcPidFilePath(dataDir));
28082
+ import_node_fs21.default.unlinkSync(frpcPidFilePath(dataDir));
27951
28083
  } catch {
27952
28084
  }
27953
28085
  }
@@ -27963,7 +28095,7 @@ function defaultIsPidAlive(pid) {
27963
28095
  }
27964
28096
  function defaultReadPidFile(file) {
27965
28097
  try {
27966
- return import_node_fs20.default.readFileSync(file, "utf8");
28098
+ return import_node_fs21.default.readFileSync(file, "utf8");
27967
28099
  } catch {
27968
28100
  return null;
27969
28101
  }
@@ -27979,7 +28111,7 @@ function defaultSleep(ms) {
27979
28111
  }
27980
28112
  async function killStaleFrpc(deps) {
27981
28113
  const pidFile = frpcPidFilePath(deps.dataDir);
27982
- const tomlPath = import_node_path22.default.join(deps.dataDir, "frpc.toml");
28114
+ const tomlPath = import_node_path23.default.join(deps.dataDir, "frpc.toml");
27983
28115
  const readPidFile = deps.readPidFileImpl ?? defaultReadPidFile;
27984
28116
  const isAlive = deps.isPidAliveImpl ?? defaultIsPidAlive;
27985
28117
  const killPid = deps.killPidImpl ?? defaultKillPid;
@@ -28003,7 +28135,7 @@ async function killStaleFrpc(deps) {
28003
28135
  }
28004
28136
  if (victims.size === 0) {
28005
28137
  try {
28006
- import_node_fs20.default.unlinkSync(pidFile);
28138
+ import_node_fs21.default.unlinkSync(pidFile);
28007
28139
  } catch {
28008
28140
  }
28009
28141
  return;
@@ -28014,7 +28146,7 @@ async function killStaleFrpc(deps) {
28014
28146
  }
28015
28147
  await sleep(deps.reapWaitMs ?? 300);
28016
28148
  try {
28017
- import_node_fs20.default.unlinkSync(pidFile);
28149
+ import_node_fs21.default.unlinkSync(pidFile);
28018
28150
  } catch {
28019
28151
  }
28020
28152
  }
@@ -28051,7 +28183,7 @@ var DEFAULT_TUNNEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
28051
28183
  var TunnelManager = class {
28052
28184
  constructor(deps) {
28053
28185
  this.deps = deps;
28054
- this.store = deps.store ?? new TunnelStore(import_node_path23.default.join(deps.dataDir, "tunnel.json"));
28186
+ this.store = deps.store ?? new TunnelStore(import_node_path24.default.join(deps.dataDir, "tunnel.json"));
28055
28187
  this.ttlMs = deps.ttlMs ?? DEFAULT_TUNNEL_TTL_MS;
28056
28188
  this.startupTimeoutMs = deps.startupTimeoutMs ?? 15e3;
28057
28189
  }
@@ -28178,8 +28310,8 @@ var TunnelManager = class {
28178
28310
  dataDir: this.deps.dataDir,
28179
28311
  override: this.deps.frpcBinaryOverride ?? void 0
28180
28312
  });
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")}`;
28313
+ const tomlPath = import_node_path24.default.join(this.deps.dataDir, "frpc.toml");
28314
+ const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto8.default.randomBytes(3).toString("hex")}`;
28183
28315
  const toml = buildFrpcToml({
28184
28316
  serverAddr: t.frpsHost,
28185
28317
  serverPort: t.frpsPort,
@@ -28189,12 +28321,12 @@ var TunnelManager = class {
28189
28321
  localPort,
28190
28322
  logLevel: "info"
28191
28323
  });
28192
- await import_node_fs21.default.promises.writeFile(tomlPath, toml, { mode: 384 });
28324
+ await import_node_fs22.default.promises.writeFile(tomlPath, toml, { mode: 384 });
28193
28325
  const proc = (this.deps.spawnImpl ?? import_node_child_process5.spawn)(frpcBin, ["-c", tomlPath], {
28194
28326
  stdio: ["ignore", "pipe", "pipe"]
28195
28327
  });
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 });
28328
+ const logFilePath = import_node_path24.default.join(this.deps.dataDir, "frpc.log");
28329
+ const logStream = import_node_fs22.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
28198
28330
  logStream.on("error", () => {
28199
28331
  });
28200
28332
  const tee = (chunk) => {
@@ -28276,23 +28408,23 @@ async function waitForFrpcReady(proc, timeoutMs) {
28276
28408
  }
28277
28409
 
28278
28410
  // src/tunnel/device-key.ts
28279
- var import_node_os11 = __toESM(require("os"), 1);
28280
- var import_node_crypto8 = __toESM(require("crypto"), 1);
28411
+ var import_node_os10 = __toESM(require("os"), 1);
28412
+ var import_node_crypto9 = __toESM(require("crypto"), 1);
28281
28413
  var DERIVE_SALT = "clawd-tunnel-device-v1";
28282
28414
  function deriveStableDeviceKey(opts = {}) {
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);
28415
+ const hostname = opts.hostname ?? import_node_os10.default.hostname();
28416
+ const uid = opts.uid ?? (typeof import_node_os10.default.userInfo === "function" ? import_node_os10.default.userInfo().uid : 0);
28285
28417
  const input = `${hostname}::${uid}`;
28286
- return import_node_crypto8.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
28418
+ return import_node_crypto9.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
28287
28419
  }
28288
28420
 
28289
28421
  // src/auth-store.ts
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);
28422
+ var import_node_fs23 = __toESM(require("fs"), 1);
28423
+ var import_node_path25 = __toESM(require("path"), 1);
28424
+ var import_node_crypto10 = __toESM(require("crypto"), 1);
28293
28425
  var AUTH_FILE_NAME = "auth.json";
28294
28426
  function authFilePath(dataDir) {
28295
- return import_node_path24.default.join(dataDir, AUTH_FILE_NAME);
28427
+ return import_node_path25.default.join(dataDir, AUTH_FILE_NAME);
28296
28428
  }
28297
28429
  function loadOrCreateAuthFile(opts) {
28298
28430
  const file = authFilePath(opts.dataDir);
@@ -28317,14 +28449,14 @@ function loadOrCreateAuthFile(opts) {
28317
28449
  return next;
28318
28450
  }
28319
28451
  function defaultGenerateToken2() {
28320
- return import_node_crypto9.default.randomBytes(32).toString("base64url");
28452
+ return import_node_crypto10.default.randomBytes(32).toString("base64url");
28321
28453
  }
28322
28454
  function defaultGenerateOwnerPrincipalId() {
28323
- return `owner-${import_node_crypto9.default.randomUUID()}`;
28455
+ return `owner-${import_node_crypto10.default.randomUUID()}`;
28324
28456
  }
28325
28457
  function readAuthFile(file) {
28326
28458
  try {
28327
- const raw = import_node_fs22.default.readFileSync(file, "utf8");
28459
+ const raw = import_node_fs23.default.readFileSync(file, "utf8");
28328
28460
  const parsed = JSON.parse(raw);
28329
28461
  if (typeof parsed?.token !== "string" || parsed.token.length === 0) {
28330
28462
  return null;
@@ -28342,25 +28474,25 @@ function readAuthFile(file) {
28342
28474
  }
28343
28475
  }
28344
28476
  function writeAuthFile(file, content) {
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 });
28477
+ import_node_fs23.default.mkdirSync(import_node_path25.default.dirname(file), { recursive: true });
28478
+ import_node_fs23.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
28347
28479
  try {
28348
- import_node_fs22.default.chmodSync(file, 384);
28480
+ import_node_fs23.default.chmodSync(file, 384);
28349
28481
  } catch {
28350
28482
  }
28351
28483
  }
28352
28484
 
28353
28485
  // src/owner-profile.ts
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);
28486
+ var import_node_fs24 = __toESM(require("fs"), 1);
28487
+ var import_node_os11 = __toESM(require("os"), 1);
28488
+ var import_node_path26 = __toESM(require("path"), 1);
28357
28489
  var PROFILE_FILENAME = "profile.json";
28358
28490
  function loadOwnerDisplayName(dataDir) {
28359
- const fallback = import_node_os12.default.userInfo().username;
28360
- const profilePath = import_node_path25.default.join(dataDir, PROFILE_FILENAME);
28491
+ const fallback = import_node_os11.default.userInfo().username;
28492
+ const profilePath = import_node_path26.default.join(dataDir, PROFILE_FILENAME);
28361
28493
  let raw;
28362
28494
  try {
28363
- raw = import_node_fs23.default.readFileSync(profilePath, "utf8");
28495
+ raw = import_node_fs24.default.readFileSync(profilePath, "utf8");
28364
28496
  } catch {
28365
28497
  return fallback;
28366
28498
  }
@@ -28389,12 +28521,12 @@ init_protocol();
28389
28521
  init_protocol();
28390
28522
 
28391
28523
  // src/session/fork.ts
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);
28524
+ var import_node_fs25 = __toESM(require("fs"), 1);
28525
+ var import_node_os12 = __toESM(require("os"), 1);
28526
+ var import_node_path27 = __toESM(require("path"), 1);
28395
28527
  init_claude_history();
28396
28528
  function readJsonlEntries(file) {
28397
- const raw = import_node_fs24.default.readFileSync(file, "utf8");
28529
+ const raw = import_node_fs25.default.readFileSync(file, "utf8");
28398
28530
  const out = [];
28399
28531
  for (const line of raw.split("\n")) {
28400
28532
  const t = line.trim();
@@ -28407,10 +28539,10 @@ function readJsonlEntries(file) {
28407
28539
  return out;
28408
28540
  }
28409
28541
  function forkSession(input) {
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)) {
28542
+ const baseDir = input.baseDir ?? import_node_path27.default.join(import_node_os12.default.homedir(), ".claude");
28543
+ const projectDir = import_node_path27.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
28544
+ const sourceFile = import_node_path27.default.join(projectDir, `${input.toolSessionId}.jsonl`);
28545
+ if (!import_node_fs25.default.existsSync(sourceFile)) {
28414
28546
  throw new Error(`fork: source transcript not found: ${sourceFile}`);
28415
28547
  }
28416
28548
  const entries = readJsonlEntries(sourceFile);
@@ -28440,9 +28572,9 @@ function forkSession(input) {
28440
28572
  }
28441
28573
  forkedLines.push(JSON.stringify(forked));
28442
28574
  }
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 });
28575
+ const forkedFilePath = import_node_path27.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
28576
+ import_node_fs25.default.mkdirSync(projectDir, { recursive: true });
28577
+ import_node_fs25.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
28446
28578
  return { forkedToolSessionId, forkedFilePath };
28447
28579
  }
28448
28580
 
@@ -28730,28 +28862,6 @@ function buildSessionHandlers(deps) {
28730
28862
  }
28731
28863
  };
28732
28864
  };
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
- };
28755
28865
  return {
28756
28866
  "session:create": create,
28757
28867
  "session:list": list,
@@ -28777,9 +28887,7 @@ function buildSessionHandlers(deps) {
28777
28887
  "session:cancelQuestion": cancelQuestion,
28778
28888
  "session:pty:input": ptyInput,
28779
28889
  "session:pty:resize": ptyResize,
28780
- "session:pty:snapshot": ptySnapshot,
28781
- "peerSession:upsert": peerSessionUpsert,
28782
- "peerSession:remove": peerSessionRemove
28890
+ "session:pty:snapshot": ptySnapshot
28783
28891
  };
28784
28892
  }
28785
28893
 
@@ -28798,7 +28906,7 @@ function buildPermissionHandlers(deps) {
28798
28906
  }
28799
28907
 
28800
28908
  // src/handlers/history.ts
28801
- var path36 = __toESM(require("path"), 1);
28909
+ var path37 = __toESM(require("path"), 1);
28802
28910
  init_protocol();
28803
28911
 
28804
28912
  // src/session/recent-dirs.ts
@@ -28816,7 +28924,7 @@ function listRecentDirs(store, limit = 50) {
28816
28924
  }
28817
28925
 
28818
28926
  // src/permission/persona-paths.ts
28819
- var path35 = __toESM(require("path"), 1);
28927
+ var path36 = __toESM(require("path"), 1);
28820
28928
  function getAllowedPersonaIds(grants, action) {
28821
28929
  const ids = /* @__PURE__ */ new Set();
28822
28930
  for (const g2 of grants) {
@@ -28829,26 +28937,26 @@ function getAllowedPersonaIds(grants, action) {
28829
28937
  return ids;
28830
28938
  }
28831
28939
  function isGuestPathAllowed(grants, absPath, personaRoot, action = "read") {
28832
- const root = path35.resolve(personaRoot);
28833
- const target = path35.resolve(absPath);
28834
- const sep3 = root.endsWith(path35.sep) ? "" : path35.sep;
28940
+ const root = path36.resolve(personaRoot);
28941
+ const target = path36.resolve(absPath);
28942
+ const sep3 = root.endsWith(path36.sep) ? "" : path36.sep;
28835
28943
  if (!target.startsWith(root + sep3)) return false;
28836
- const rel = path35.relative(root, target);
28944
+ const rel = path36.relative(root, target);
28837
28945
  if (!rel || rel.startsWith("..")) return false;
28838
- const personaId = rel.split(path35.sep)[0];
28946
+ const personaId = rel.split(path36.sep)[0];
28839
28947
  if (!personaId) return false;
28840
28948
  const allowed = getAllowedPersonaIds(grants, action);
28841
28949
  if (allowed === "*") return true;
28842
28950
  return allowed.has(personaId);
28843
28951
  }
28844
28952
  function personaIdFromPath(absPath, personaRoot) {
28845
- const root = path35.resolve(personaRoot);
28846
- const target = path35.resolve(absPath);
28847
- const sep3 = root.endsWith(path35.sep) ? "" : path35.sep;
28953
+ const root = path36.resolve(personaRoot);
28954
+ const target = path36.resolve(absPath);
28955
+ const sep3 = root.endsWith(path36.sep) ? "" : path36.sep;
28848
28956
  if (!target.startsWith(root + sep3)) return null;
28849
- const rel = path35.relative(root, target);
28957
+ const rel = path36.relative(root, target);
28850
28958
  if (!rel || rel.startsWith("..")) return null;
28851
- const id = rel.split(path35.sep)[0];
28959
+ const id = rel.split(path36.sep)[0];
28852
28960
  return id || null;
28853
28961
  }
28854
28962
 
@@ -28873,7 +28981,7 @@ function buildHistoryHandlers(deps) {
28873
28981
  if (!pid) return false;
28874
28982
  return isGuestPathAllowed(
28875
28983
  ctx.grants,
28876
- path36.join(personaRoot, pid),
28984
+ path37.join(personaRoot, pid),
28877
28985
  personaRoot,
28878
28986
  "read"
28879
28987
  );
@@ -28884,7 +28992,7 @@ function buildHistoryHandlers(deps) {
28884
28992
  };
28885
28993
  const list = async (frame, _client, ctx) => {
28886
28994
  const args = HistoryListArgs.parse(frame);
28887
- assertGuestPath(ctx, path36.resolve(args.projectPath), personaRoot, "history:list");
28995
+ assertGuestPath(ctx, path37.resolve(args.projectPath), personaRoot, "history:list");
28888
28996
  const sessions = await history.listSessions(args);
28889
28997
  return { response: { type: "history:list", sessions } };
28890
28998
  };
@@ -28916,13 +29024,13 @@ function buildHistoryHandlers(deps) {
28916
29024
  };
28917
29025
  const subagents = async (frame, _client, ctx) => {
28918
29026
  const args = HistorySubagentsArgs.parse(frame);
28919
- assertGuestPath(ctx, path36.resolve(args.cwd), personaRoot, "history:subagents");
29027
+ assertGuestPath(ctx, path37.resolve(args.cwd), personaRoot, "history:subagents");
28920
29028
  const subs = await history.listSubagents(args);
28921
29029
  return { response: { type: "history:subagents", subagents: subs } };
28922
29030
  };
28923
29031
  const subagentRead = async (frame, _client, ctx) => {
28924
29032
  const args = HistorySubagentReadArgs.parse(frame);
28925
- assertGuestPath(ctx, path36.resolve(args.cwd), personaRoot, "history:subagent-read");
29033
+ assertGuestPath(ctx, path37.resolve(args.cwd), personaRoot, "history:subagent-read");
28926
29034
  const res = await history.readSubagent(args);
28927
29035
  return { response: { type: "history:subagent-read", ...res } };
28928
29036
  };
@@ -28930,7 +29038,7 @@ function buildHistoryHandlers(deps) {
28930
29038
  const dirs = listRecentDirs(store);
28931
29039
  if (ctx?.principal.kind === "guest" && personaRoot) {
28932
29040
  const filtered = dirs.filter(
28933
- (d) => isGuestPathAllowed(ctx.grants, path36.resolve(d.cwd), personaRoot, "read")
29041
+ (d) => isGuestPathAllowed(ctx.grants, path37.resolve(d.cwd), personaRoot, "read")
28934
29042
  );
28935
29043
  return { response: { type: "history:recentDirs", dirs: filtered } };
28936
29044
  }
@@ -28947,8 +29055,8 @@ function buildHistoryHandlers(deps) {
28947
29055
  }
28948
29056
 
28949
29057
  // src/handlers/workspace.ts
28950
- var path37 = __toESM(require("path"), 1);
28951
- var os14 = __toESM(require("os"), 1);
29058
+ var path38 = __toESM(require("path"), 1);
29059
+ var os13 = __toESM(require("os"), 1);
28952
29060
  init_protocol();
28953
29061
  init_protocol();
28954
29062
  function buildEnabledPluginNames(personaManager, personaId) {
@@ -28987,23 +29095,23 @@ function buildWorkspaceHandlers(deps) {
28987
29095
  const list = async (frame, _client, ctx) => {
28988
29096
  const args = WorkspaceListArgs.parse(frame);
28989
29097
  const isGuest = ctx?.principal.kind === "guest";
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;
29098
+ const fallbackCwd = isGuest && personaRoot ? personaRoot : os13.homedir();
29099
+ const resolvedCwd = path38.resolve(args.cwd ?? fallbackCwd);
29100
+ const target = args.path ? path38.resolve(resolvedCwd, args.path) : resolvedCwd;
28993
29101
  assertGuestPath2(ctx, target, personaRoot, "workspace:list");
28994
29102
  const res = workspace.list({ ...args, cwd: resolvedCwd });
28995
29103
  return { response: { type: "workspace:list", ...res } };
28996
29104
  };
28997
29105
  const read = async (frame, _client, ctx) => {
28998
29106
  const args = WorkspaceReadArgs.parse(frame);
28999
- const target = path37.isAbsolute(args.path) ? path37.resolve(args.path) : path37.resolve(args.cwd, args.path);
29107
+ const target = path38.isAbsolute(args.path) ? path38.resolve(args.path) : path38.resolve(args.cwd, args.path);
29000
29108
  assertGuestPath2(ctx, target, personaRoot, "workspace:read");
29001
29109
  const res = workspace.read(args);
29002
29110
  return { response: { type: "workspace:read", ...res } };
29003
29111
  };
29004
29112
  const skillsList = async (frame, _client, ctx) => {
29005
29113
  const args = SkillsListArgs.parse(frame);
29006
- const cwdAbs = path37.resolve(args.cwd);
29114
+ const cwdAbs = path38.resolve(args.cwd);
29007
29115
  assertGuestPath2(ctx, cwdAbs, personaRoot, "skills:list");
29008
29116
  const list2 = skills.list(args);
29009
29117
  if (ctx?.principal.kind === "guest" && personaRoot) {
@@ -29015,7 +29123,7 @@ function buildWorkspaceHandlers(deps) {
29015
29123
  };
29016
29124
  const agentsList = async (frame, _client, ctx) => {
29017
29125
  const args = AgentsListArgs.parse(frame);
29018
- const cwdAbs = path37.resolve(args.cwd);
29126
+ const cwdAbs = path38.resolve(args.cwd);
29019
29127
  assertGuestPath2(ctx, cwdAbs, personaRoot, "agents:list");
29020
29128
  const list2 = agents.list(args);
29021
29129
  if (ctx?.principal.kind === "guest" && personaRoot) {
@@ -29034,20 +29142,20 @@ function buildWorkspaceHandlers(deps) {
29034
29142
  }
29035
29143
 
29036
29144
  // src/handlers/git.ts
29037
- var path39 = __toESM(require("path"), 1);
29145
+ var path40 = __toESM(require("path"), 1);
29038
29146
  init_protocol();
29039
29147
  init_protocol();
29040
29148
 
29041
29149
  // src/workspace/git.ts
29042
29150
  var import_node_child_process6 = require("child_process");
29043
- var import_node_fs25 = __toESM(require("fs"), 1);
29044
- var import_node_path27 = __toESM(require("path"), 1);
29151
+ var import_node_fs26 = __toESM(require("fs"), 1);
29152
+ var import_node_path28 = __toESM(require("path"), 1);
29045
29153
  var import_node_util = require("util");
29046
29154
  var pexec = (0, import_node_util.promisify)(import_node_child_process6.execFile);
29047
29155
  function normalizePath(p2) {
29048
- const resolved = import_node_path27.default.resolve(p2);
29156
+ const resolved = import_node_path28.default.resolve(p2);
29049
29157
  try {
29050
- return import_node_fs25.default.realpathSync(resolved);
29158
+ return import_node_fs26.default.realpathSync(resolved);
29051
29159
  } catch {
29052
29160
  return resolved;
29053
29161
  }
@@ -29120,7 +29228,7 @@ async function listGitBranches(cwd) {
29120
29228
  // src/handlers/git.ts
29121
29229
  function assertGuestCwd(ctx, cwd, personaRoot, method) {
29122
29230
  if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
29123
- if (!isGuestPathAllowed(ctx.grants, path39.resolve(cwd), personaRoot, "read")) {
29231
+ if (!isGuestPathAllowed(ctx.grants, path40.resolve(cwd), personaRoot, "read")) {
29124
29232
  throw new ClawdError(
29125
29233
  ERROR_CODES.UNAUTHORIZED,
29126
29234
  `guest ${ctx.principal.id} cannot ${method} cwd ${cwd}`
@@ -29483,7 +29591,7 @@ function buildWhoamiHandler(deps) {
29483
29591
  }
29484
29592
 
29485
29593
  // src/handlers/meta.ts
29486
- var import_node_os14 = __toESM(require("os"), 1);
29594
+ var import_node_os13 = __toESM(require("os"), 1);
29487
29595
  init_protocol();
29488
29596
 
29489
29597
  // src/version.ts
@@ -29513,7 +29621,7 @@ function buildReadyFrame(deps, client) {
29513
29621
  return {
29514
29622
  version,
29515
29623
  protocolVersion: PROTOCOL_VERSION,
29516
- hostname: import_node_os14.default.hostname(),
29624
+ hostname: import_node_os13.default.hostname(),
29517
29625
  os: process.platform,
29518
29626
  tools,
29519
29627
  runningSessions: info.runningSessions,
@@ -29607,7 +29715,7 @@ function buildPersonaHandlers(deps) {
29607
29715
  }
29608
29716
 
29609
29717
  // src/handlers/attachment.ts
29610
- var import_node_path28 = __toESM(require("path"), 1);
29718
+ var import_node_path29 = __toESM(require("path"), 1);
29611
29719
  init_protocol();
29612
29720
  init_protocol();
29613
29721
  var DEFAULT_TTL_SECONDS = 24 * 3600;
@@ -29661,12 +29769,12 @@ function buildAttachmentHandlers(deps) {
29661
29769
  `session ${args.sessionId} scope unresolved`
29662
29770
  );
29663
29771
  }
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);
29772
+ const cwdAbs = import_node_path29.default.resolve(sessionFile.cwd);
29773
+ const candidateAbs = import_node_path29.default.isAbsolute(args.relPath) ? import_node_path29.default.resolve(args.relPath) : import_node_path29.default.resolve(cwdAbs, args.relPath);
29666
29774
  assertGuestAttachmentPath(ctx, candidateAbs, deps.personaRoot, "attachment.signUrl");
29667
29775
  const entries = deps.groupFileStore.list(scope, args.sessionId);
29668
29776
  const entry = entries.find((e) => {
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);
29777
+ const storedAbs = import_node_path29.default.isAbsolute(e.relPath) ? import_node_path29.default.resolve(e.relPath) : import_node_path29.default.resolve(cwdAbs, e.relPath);
29670
29778
  return storedAbs === candidateAbs && !e.stale;
29671
29779
  });
29672
29780
  if (!entry) {
@@ -29690,7 +29798,7 @@ function buildAttachmentHandlers(deps) {
29690
29798
  if (!ctx || ctx.principal.kind !== "guest" || !deps.personaRoot || !deps.sessionStore) return;
29691
29799
  const f = deps.sessionStore.read(sessionId);
29692
29800
  if (!f) return;
29693
- assertGuestAttachmentPath(ctx, import_node_path28.default.resolve(f.cwd), deps.personaRoot, method);
29801
+ assertGuestAttachmentPath(ctx, import_node_path29.default.resolve(f.cwd), deps.personaRoot, method);
29694
29802
  }
29695
29803
  const groupAdd = async (frame, _client, ctx) => {
29696
29804
  if (!deps.groupFileStore || !deps.getSessionScope) {
@@ -29731,7 +29839,7 @@ function buildAttachmentHandlers(deps) {
29731
29839
  }
29732
29840
  const entries = deps.groupFileStore.list(scope, parsed.data.sessionId);
29733
29841
  const target = entries.find((e) => e.relPath === parsed.data.relPath);
29734
- if (target?.from === "owner") {
29842
+ if (target?.from === "owner" || target?.from === "user-upload") {
29735
29843
  deps.groupFileStore.remove(scope, parsed.data.sessionId, parsed.data.relPath);
29736
29844
  } else {
29737
29845
  deps.groupFileStore.markStale(scope, parsed.data.sessionId, parsed.data.relPath);
@@ -29857,11 +29965,6 @@ var METHOD_GRANT_MAP = {
29857
29965
  "session:pin": CAPABILITY_SCOPED,
29858
29966
  "session:reorderPins": ADMIN_ANY,
29859
29967
  // 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,
29865
29968
  "permission:respond": CAPABILITY_SCOPED,
29866
29969
  "session:answerQuestion": CAPABILITY_SCOPED,
29867
29970
  "session:cancelQuestion": CAPABILITY_SCOPED,
@@ -29932,7 +30035,7 @@ function computeGrantForFrame(method, frame) {
29932
30035
  async function startDaemon(config) {
29933
30036
  const logger = createLogger({
29934
30037
  level: config.logLevel,
29935
- file: import_node_path29.default.join(config.dataDir, "clawd.log")
30038
+ file: import_node_path30.default.join(config.dataDir, "clawd.log")
29936
30039
  });
29937
30040
  logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
29938
30041
  const stateMgr = new StateFileManager({ dataDir: config.dataDir });
@@ -30019,7 +30122,7 @@ async function startDaemon(config) {
30019
30122
  const agents = new AgentsScanner();
30020
30123
  const history = new ClaudeHistoryReader();
30021
30124
  let transport = null;
30022
- const personaStore = new PersonaStore(import_node_path29.default.join(config.dataDir, "personas"));
30125
+ const personaStore = new PersonaStore(import_node_path30.default.join(config.dataDir, "personas"));
30023
30126
  const defaultsRoot = findDefaultsRoot();
30024
30127
  if (defaultsRoot) {
30025
30128
  seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
@@ -30036,7 +30139,7 @@ async function startDaemon(config) {
30036
30139
  getAdapter,
30037
30140
  historyReader: history,
30038
30141
  dataDir: config.dataDir,
30039
- personaRoot: import_node_path29.default.join(config.dataDir, "personas"),
30142
+ personaRoot: import_node_path30.default.join(config.dataDir, "personas"),
30040
30143
  personaStore,
30041
30144
  ownerDisplayName,
30042
30145
  ownerPrincipalId,
@@ -30060,10 +30163,10 @@ async function startDaemon(config) {
30060
30163
  // 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
30061
30164
  attachmentGroup: {
30062
30165
  onFileEdit: (input) => {
30063
- const absPath = import_node_path29.default.isAbsolute(input.relPath) ? input.relPath : import_node_path29.default.join(input.cwd, input.relPath);
30166
+ const absPath = import_node_path30.default.isAbsolute(input.relPath) ? input.relPath : import_node_path30.default.join(input.cwd, input.relPath);
30064
30167
  let size = 0;
30065
30168
  try {
30066
- size = import_node_fs26.default.statSync(absPath).size;
30169
+ size = import_node_fs27.default.statSync(absPath).size;
30067
30170
  } catch (err) {
30068
30171
  logger.warn("attachment.onFileEdit stat failed", {
30069
30172
  sessionId: input.sessionId,
@@ -30089,11 +30192,6 @@ async function startDaemon(config) {
30089
30192
  }
30090
30193
  });
30091
30194
  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",
30097
30195
  onEvent: ({ sessionId, events }) => {
30098
30196
  manager.feedObserverEvents(sessionId, events);
30099
30197
  },
@@ -30181,10 +30279,10 @@ async function startDaemon(config) {
30181
30279
  // 'persona/<pid>/owner',default 走 'default'。
30182
30280
  getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
30183
30281
  // guest path guard:candidate 必须在 personaRoot 子树下
30184
- personaRoot: import_node_path29.default.join(config.dataDir, "personas")
30282
+ personaRoot: import_node_path30.default.join(config.dataDir, "personas")
30185
30283
  },
30186
30284
  // workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
30187
- personaRoot: import_node_path29.default.join(config.dataDir, "personas"),
30285
+ personaRoot: import_node_path30.default.join(config.dataDir, "personas"),
30188
30286
  // capability:list / delete handler 依赖
30189
30287
  capabilityManager,
30190
30288
  // personal-cap:get 返回的 shareBaseUrl 用此 base URL:
@@ -30220,7 +30318,19 @@ async function startDaemon(config) {
30220
30318
  sessionStore: store,
30221
30319
  // /files HMAC verify 用 auth.json 的 signSecret 字段(与 attachment.signUrl 同源)。
30222
30320
  // --auth-token CLI 模式没 signSecret → 路由返 501,sign URL 功能整体禁用。
30223
- getSignSecret: () => authFile?.signSecret ?? null
30321
+ getSignSecret: () => authFile?.signSecret ?? null,
30322
+ // chatinput 附件上传(POST /attachments/upload,spec §6.1)。upload pipeline
30323
+ // 在 session.cwd 下的 .attachments/ 落盘;signSecret 与 /files HMAC 同源;
30324
+ // tunnel 未启时返 503 TUNNEL_NOT_READY 让前端显式提示。
30325
+ attachmentUpload: {
30326
+ groupFileStore,
30327
+ getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
30328
+ getSessionDir: (sid) => {
30329
+ const f = manager.findOwnedSession(sid);
30330
+ return f ? f.cwd : null;
30331
+ },
30332
+ getHttpBaseUrl: () => currentTunnelUrl ? getHttpBaseUrl() : null
30333
+ }
30224
30334
  });
30225
30335
  wsServer = new LocalWsServer({
30226
30336
  host: config.host,
@@ -30385,8 +30495,8 @@ async function startDaemon(config) {
30385
30495
  const lines = [
30386
30496
  `Tunnel: ${r.url}`,
30387
30497
  ...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
30388
- `Frpc config: ${import_node_path29.default.join(config.dataDir, "frpc.toml")}`,
30389
- `Frpc log: ${import_node_path29.default.join(config.dataDir, "frpc.log")}`
30498
+ `Frpc config: ${import_node_path30.default.join(config.dataDir, "frpc.toml")}`,
30499
+ `Frpc log: ${import_node_path30.default.join(config.dataDir, "frpc.log")}`
30390
30500
  ];
30391
30501
  const width = Math.max(...lines.map((l) => l.length));
30392
30502
  const bar = "\u2550".repeat(width + 4);
@@ -30399,8 +30509,8 @@ ${bar}
30399
30509
 
30400
30510
  `);
30401
30511
  try {
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 });
30512
+ const connectPath = import_node_path30.default.join(config.dataDir, "connect.txt");
30513
+ import_node_fs27.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
30404
30514
  } catch {
30405
30515
  }
30406
30516
  } catch (err) {
@@ -30425,8 +30535,29 @@ ${bar}
30425
30535
  logger.warn("tunnel unavailable, degraded to local mode", { reason: tunnelError });
30426
30536
  }
30427
30537
  }
30538
+ const tickAttachmentGc = () => {
30539
+ try {
30540
+ runAttachmentGc({
30541
+ dataDir: config.dataDir,
30542
+ groupFileStore,
30543
+ sessionScopes: manager.listOwnedSessionScopes().map(({ scope, sessionId }) => ({
30544
+ scope,
30545
+ sessionId
30546
+ })),
30547
+ // 注入 cwd 让 GC 直接定位 .attachments 目录(与 upload 落盘点一致)
30548
+ getSessionCwd: (sid) => manager.findOwnedSession(sid)?.cwd ?? null,
30549
+ logger
30550
+ });
30551
+ } catch (err) {
30552
+ logger.warn("attachment gc tick failed", { err: err.message });
30553
+ }
30554
+ };
30555
+ setImmediate(tickAttachmentGc);
30556
+ const attachmentGcInterval = setInterval(tickAttachmentGc, 24 * 3600 * 1e3);
30557
+ attachmentGcInterval.unref();
30428
30558
  const shutdown = async () => {
30429
30559
  logger.info("stopping clawd");
30560
+ clearInterval(attachmentGcInterval);
30430
30561
  observer.stopAll();
30431
30562
  manager.stopAll();
30432
30563
  if (tunnelMgr) {
@@ -30444,9 +30575,9 @@ ${bar}
30444
30575
  };
30445
30576
  }
30446
30577
  function migrateDropPersonsDir(dataDir) {
30447
- const dir = import_node_path29.default.join(dataDir, "persons");
30578
+ const dir = import_node_path30.default.join(dataDir, "persons");
30448
30579
  try {
30449
- import_node_fs26.default.rmSync(dir, { recursive: true, force: true });
30580
+ import_node_fs27.default.rmSync(dir, { recursive: true, force: true });
30450
30581
  } catch {
30451
30582
  }
30452
30583
  }