@clawos-dev/clawd 0.2.85-beta.158.934b6ca → 0.2.85-beta.159.97c16b3

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 +565 -331
  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: path43, errorMaps, issueData } = params;
655
- const fullPath = [...path43, ...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, path43, key) {
958
+ constructor(parent, value, path45, key) {
967
959
  this._cachedPath = [];
968
960
  this.parent = parent;
969
961
  this.data = value;
970
- this._path = path43;
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 path43 = req.path;
5629
- _req.url = typeof path43 === "string" ? path43 : 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(path43) {
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 < path43.length; i++) {
5801
- const char = path43[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 path43 of paths) {
5933
- const parts = parsePath(path43);
5897
+ for (const path45 of paths) {
5898
+ const parts = parsePath(path45);
5934
5899
  if (parts.includes("*")) {
5935
- redactWildcardPath(obj, parts, censor, path43, 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, path43) => {
6021
- const fullPath = [...pathArray.slice(0, pathLength), ...path43];
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 path43 of pathsToClone) {
6057
- const parts = parsePath(path43);
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(path43) {
6110
- if (typeof path43 !== "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 (path43 === "") {
6078
+ if (path45 === "") {
6114
6079
  throw new Error("Invalid redaction path ()");
6115
6080
  }
6116
- if (path43.includes("..")) {
6117
- throw new Error(`Invalid redaction path (${path43})`);
6081
+ if (path45.includes("..")) {
6082
+ throw new Error(`Invalid redaction path (${path45})`);
6118
6083
  }
6119
- if (path43.includes(",")) {
6120
- throw new Error(`Invalid redaction path (${path43})`);
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 < path43.length; i++) {
6126
- const char = path43[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 (${path43})`);
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 (${path43})`);
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 path43 of paths) {
6153
- validatePath(path43);
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, path43) => {
6322
- return censor(value, [k2, ...path43]);
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 fs34 = require("fs");
6505
+ var fs36 = require("fs");
6541
6506
  var EventEmitter2 = require("events");
6542
6507
  var inherits = require("util").inherits;
6543
- var path43 = 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) fs34.mkdirSync(path43.dirname(file), { recursive: true });
6598
- const fd = fs34.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
- fs34.mkdir(path43.dirname(file), { recursive: true }, (err) => {
6570
+ fs36.mkdir(path45.dirname(file), { recursive: true }, (err) => {
6606
6571
  if (err) return fileOpened(err);
6607
- fs34.open(file, flags, mode, fileOpened);
6572
+ fs36.open(file, flags, mode, fileOpened);
6608
6573
  });
6609
6574
  } else {
6610
- fs34.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 = () => fs34.writeSync(this.fd, this._writingBuf);
6652
- fsWrite = () => fs34.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 fs34.writeSync(this.fd, this._writingBuf);
6626
+ return fs36.writeSync(this.fd, this._writingBuf);
6662
6627
  }
6663
- return fs34.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 fs34.write(this.fd, this._writingBuf, this.release);
6632
+ return fs36.write(this.fd, this._writingBuf, this.release);
6668
6633
  }
6669
- return fs34.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
- fs34.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
- fs34.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
- fs34.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) ? fs34.writeSync(this.fd, buf) : fs34.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
- fs34.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 = fs34.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) ? fs34.writeSync(this.fd, this._writingBuf) : fs34.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
- fs34.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 = fs34.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
- fs34.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
- fs34.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
- fs34.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)
@@ -10233,11 +10198,11 @@ var init_lib = __esm({
10233
10198
  }
10234
10199
  }
10235
10200
  },
10236
- addToPath: function addToPath(path43, added, removed, oldPosInc, options) {
10237
- var last = path43.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: path43.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: path43.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 path43 = 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 (!path43 || content == null) return null;
10684
- const entry = { path: path43, 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 path43 = 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 (!path43 || content == null) return null;
11513
- const out = { path: path43, 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_path29.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_fs26.default.mkdirSync(dir, { recursive: true });
20443
- stream = import_node_fs26.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_fs26, import_node_path29;
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_fs26 = __toESM(require("fs"), 1);
20473
- import_node_path29 = __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_fs27.mkdtempSync)(import_node_path30.default.join(import_node_os14.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_fs27.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_fs27, import_node_os14, import_node_path30;
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_fs27 = require("fs");
20652
+ import_node_fs29 = require("fs");
20688
20653
  import_node_os14 = __toESM(require("os"), 1);
20689
- import_node_path30 = __toESM(require("path"), 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_path28 = __toESM(require("path"), 1);
20922
- var import_node_fs25 = __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
@@ -27033,8 +26991,8 @@ function rmdirIfEmpty(p2) {
27033
26991
  }
27034
26992
 
27035
26993
  // src/transport/http-router.ts
27036
- var import_node_fs14 = __toESM(require("fs"), 1);
27037
- var import_node_path16 = __toESM(require("path"), 1);
26994
+ var import_node_fs15 = __toESM(require("fs"), 1);
26995
+ var import_node_path17 = __toESM(require("path"), 1);
27038
26996
 
27039
26997
  // src/attachment/mime.ts
27040
26998
  var import_node_path15 = __toESM(require("path"), 1);
@@ -27212,7 +27170,118 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
27212
27170
  return { ok: true, absPath };
27213
27171
  }
27214
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
+ var HASH_PREFIX_LEN = 16;
27187
+ async function writeUploadedAttachment(args) {
27188
+ const attachmentsRoot = import_node_path16.default.join(args.sessionDir, ".attachments");
27189
+ try {
27190
+ import_node_fs14.default.mkdirSync(attachmentsRoot, { recursive: true });
27191
+ } catch (err) {
27192
+ throw new UploadError("STORAGE_ERROR", `mkdir failed: ${err.message}`);
27193
+ }
27194
+ const hasher = import_node_crypto6.default.createHash("sha256");
27195
+ let actualSize = 0;
27196
+ const tmpPath = import_node_path16.default.join(
27197
+ attachmentsRoot,
27198
+ `.upload-${process.pid}-${Date.now()}-${import_node_crypto6.default.randomBytes(4).toString("hex")}`
27199
+ );
27200
+ try {
27201
+ await (0, import_promises.pipeline)(
27202
+ args.body,
27203
+ async function* (source) {
27204
+ for await (const chunk of source) {
27205
+ const buf = chunk;
27206
+ actualSize += buf.length;
27207
+ hasher.update(buf);
27208
+ yield buf;
27209
+ }
27210
+ },
27211
+ import_node_fs14.default.createWriteStream(tmpPath, { mode: 384 })
27212
+ );
27213
+ } catch (err) {
27214
+ try {
27215
+ import_node_fs14.default.unlinkSync(tmpPath);
27216
+ } catch {
27217
+ }
27218
+ throw new UploadError("STORAGE_ERROR", `write failed: ${err.message}`);
27219
+ }
27220
+ if (actualSize !== args.contentLength) {
27221
+ try {
27222
+ import_node_fs14.default.unlinkSync(tmpPath);
27223
+ } catch {
27224
+ }
27225
+ throw new UploadError(
27226
+ "SIZE_MISMATCH",
27227
+ `expected ${args.contentLength} bytes, got ${actualSize}`
27228
+ );
27229
+ }
27230
+ const attachmentId = hasher.digest("hex").slice(0, HASH_PREFIX_LEN);
27231
+ const hashDir = import_node_path16.default.join(attachmentsRoot, attachmentId);
27232
+ let finalFileName;
27233
+ let hashDirExists = false;
27234
+ try {
27235
+ hashDirExists = import_node_fs14.default.statSync(hashDir).isDirectory();
27236
+ } catch {
27237
+ }
27238
+ if (hashDirExists) {
27239
+ const existing = import_node_fs14.default.readdirSync(hashDir).filter((n) => !n.startsWith("."));
27240
+ finalFileName = existing[0] ?? args.fileName;
27241
+ try {
27242
+ import_node_fs14.default.unlinkSync(tmpPath);
27243
+ } catch {
27244
+ }
27245
+ } else {
27246
+ try {
27247
+ import_node_fs14.default.mkdirSync(hashDir, { recursive: true });
27248
+ finalFileName = args.fileName;
27249
+ import_node_fs14.default.renameSync(tmpPath, import_node_path16.default.join(hashDir, finalFileName));
27250
+ } catch (err) {
27251
+ try {
27252
+ import_node_fs14.default.unlinkSync(tmpPath);
27253
+ } catch {
27254
+ }
27255
+ throw new UploadError("STORAGE_ERROR", `rename failed: ${err.message}`);
27256
+ }
27257
+ }
27258
+ const absPath = import_node_path16.default.join(hashDir, finalFileName);
27259
+ const relPath = import_node_path16.default.relative(args.sessionDir, absPath);
27260
+ args.groupFileStore.upsert(args.scope, args.sessionId, {
27261
+ relPath: absPath,
27262
+ // 存绝对路径,与现有 agent 入清单的形态一致(attachment.ts:144 双形态兼容)
27263
+ from: "user-upload",
27264
+ label: args.fileName,
27265
+ size: actualSize,
27266
+ mime: args.mimeType
27267
+ });
27268
+ return { attachmentId, absPath, relPath, sizeBytes: actualSize, mimeType: args.mimeType };
27269
+ }
27270
+
27215
27271
  // src/transport/http-router.ts
27272
+ var UPLOAD_MIME_ALLOW = /* @__PURE__ */ new Set([
27273
+ "image/png",
27274
+ "image/jpeg",
27275
+ "image/gif",
27276
+ "image/webp",
27277
+ "application/pdf"
27278
+ ]);
27279
+ var UPLOAD_MIME_ALLOW_PREFIX = ["text/"];
27280
+ var UPLOAD_MAX_BYTES = 25 * 1024 * 1024;
27281
+ function isUploadMimeAllowed(mime) {
27282
+ if (UPLOAD_MIME_ALLOW.has(mime)) return true;
27283
+ return UPLOAD_MIME_ALLOW_PREFIX.some((p2) => mime.startsWith(p2));
27284
+ }
27216
27285
  function createHttpRouter(deps) {
27217
27286
  return async (req, res) => {
27218
27287
  const url = parseUrl(req.url);
@@ -27226,7 +27295,7 @@ function createHttpRouter(deps) {
27226
27295
  });
27227
27296
  return true;
27228
27297
  }
27229
- if (!url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/")) {
27298
+ if (!url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/") && url.pathname !== "/attachments/upload") {
27230
27299
  return false;
27231
27300
  }
27232
27301
  if (url.pathname.startsWith("/files/") && req.method === "GET") {
@@ -27266,6 +27335,99 @@ function createHttpRouter(deps) {
27266
27335
  sendJson(res, 401, { code: "UNAUTHORIZED", message: "missing or invalid bearer token" });
27267
27336
  return true;
27268
27337
  }
27338
+ if (url.pathname === "/attachments/upload" && req.method === "POST") {
27339
+ if (ctx.role !== "owner") {
27340
+ sendJson(res, 403, { code: "FORBIDDEN", message: "owner only" });
27341
+ return true;
27342
+ }
27343
+ const up = deps.attachmentUpload;
27344
+ if (!up) {
27345
+ sendJson(res, 501, { code: "NOT_IMPLEMENTED", message: "attachment upload not wired" });
27346
+ return true;
27347
+ }
27348
+ const sessionId = url.searchParams.get("sessionId");
27349
+ const fileName = url.searchParams.get("fileName");
27350
+ const mimeType = url.searchParams.get("mimeType");
27351
+ const contentLengthRaw = req.headers["content-length"];
27352
+ if (!sessionId || !fileName || !mimeType || !contentLengthRaw) {
27353
+ sendJson(res, 400, {
27354
+ code: "INVALID_PARAM",
27355
+ message: "missing sessionId/fileName/mimeType/Content-Length"
27356
+ });
27357
+ return true;
27358
+ }
27359
+ const contentLength = Number.parseInt(contentLengthRaw, 10);
27360
+ if (!Number.isFinite(contentLength) || contentLength < 0) {
27361
+ sendJson(res, 400, { code: "INVALID_PARAM", message: "malformed Content-Length" });
27362
+ return true;
27363
+ }
27364
+ if (contentLength > UPLOAD_MAX_BYTES) {
27365
+ sendJson(res, 413, {
27366
+ code: "FILE_TOO_LARGE",
27367
+ message: `max ${UPLOAD_MAX_BYTES} bytes`
27368
+ });
27369
+ return true;
27370
+ }
27371
+ if (!isUploadMimeAllowed(mimeType)) {
27372
+ sendJson(res, 400, {
27373
+ code: "UNSUPPORTED_MIME",
27374
+ message: `mime ${mimeType} not allowed`
27375
+ });
27376
+ return true;
27377
+ }
27378
+ const sessionDir = up.getSessionDir(sessionId);
27379
+ const scope = up.getSessionScope(sessionId);
27380
+ if (!sessionDir || !scope) {
27381
+ sendJson(res, 404, { code: "NOT_FOUND", message: `session ${sessionId} not found` });
27382
+ return true;
27383
+ }
27384
+ const httpBaseUrl = up.getHttpBaseUrl();
27385
+ if (!httpBaseUrl) {
27386
+ sendJson(res, 503, {
27387
+ code: "TUNNEL_NOT_READY",
27388
+ message: "httpBaseUrl unavailable"
27389
+ });
27390
+ return true;
27391
+ }
27392
+ const secret = deps.getSignSecret?.();
27393
+ if (!secret) {
27394
+ sendJson(res, 501, {
27395
+ code: "NOT_IMPLEMENTED",
27396
+ message: "sign secret unavailable (noAuth?)"
27397
+ });
27398
+ return true;
27399
+ }
27400
+ try {
27401
+ const result = await writeUploadedAttachment({
27402
+ sessionDir,
27403
+ sessionId,
27404
+ scope,
27405
+ fileName,
27406
+ mimeType,
27407
+ contentLength,
27408
+ body: req,
27409
+ groupFileStore: up.groupFileStore
27410
+ });
27411
+ const parts = signUrlParts(secret, result.absPath, null);
27412
+ const fullUrl = buildSignedFileUrl(httpBaseUrl, parts);
27413
+ sendJson(res, 200, {
27414
+ attachmentId: result.attachmentId,
27415
+ url: fullUrl,
27416
+ relPath: result.relPath,
27417
+ sizeBytes: result.sizeBytes,
27418
+ mimeType: result.mimeType
27419
+ });
27420
+ } catch (err) {
27421
+ if (err instanceof UploadError) {
27422
+ const status = err.code === "SIZE_MISMATCH" ? 400 : 500;
27423
+ sendJson(res, status, { code: err.code, message: err.message });
27424
+ } else {
27425
+ deps.logger?.warn("attachment upload failed", { err: err.message });
27426
+ sendJson(res, 500, { code: "STORAGE_ERROR", message: err.message });
27427
+ }
27428
+ }
27429
+ return true;
27430
+ }
27269
27431
  const sessionFilesMatch = url.pathname.match(/^\/session\/([^/]+)\/files$/);
27270
27432
  if (sessionFilesMatch && req.method === "GET") {
27271
27433
  const sid = sessionFilesMatch[1];
@@ -27275,7 +27437,7 @@ function createHttpRouter(deps) {
27275
27437
  return true;
27276
27438
  }
27277
27439
  let absPath;
27278
- if (import_node_path16.default.isAbsolute(pathParam)) {
27440
+ if (import_node_path17.default.isAbsolute(pathParam)) {
27279
27441
  absPath = pathParam;
27280
27442
  } else if (deps.sessionStore) {
27281
27443
  const file = deps.sessionStore.read(sid);
@@ -27283,7 +27445,7 @@ function createHttpRouter(deps) {
27283
27445
  sendJson(res, 404, { code: "NOT_FOUND", message: `session ${sid} not found` });
27284
27446
  return true;
27285
27447
  }
27286
- absPath = import_node_path16.default.join(file.cwd, pathParam);
27448
+ absPath = import_node_path17.default.join(file.cwd, pathParam);
27287
27449
  } else {
27288
27450
  sendJson(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "sessionStore not wired" }));
27289
27451
  return true;
@@ -27316,7 +27478,7 @@ function withCtx(ctx, body) {
27316
27478
  function streamFile(res, absPath, logger) {
27317
27479
  let stat;
27318
27480
  try {
27319
- stat = import_node_fs14.default.statSync(absPath);
27481
+ stat = import_node_fs15.default.statSync(absPath);
27320
27482
  } catch (err) {
27321
27483
  const code = err?.code;
27322
27484
  if (code === "ENOENT") {
@@ -27331,7 +27493,7 @@ function streamFile(res, absPath, logger) {
27331
27493
  return;
27332
27494
  }
27333
27495
  const mime = lookupMime(absPath);
27334
- const basename = import_node_path16.default.basename(absPath);
27496
+ const basename = import_node_path17.default.basename(absPath);
27335
27497
  res.writeHead(200, {
27336
27498
  "Content-Type": mime,
27337
27499
  "Content-Length": String(stat.size),
@@ -27339,7 +27501,7 @@ function streamFile(res, absPath, logger) {
27339
27501
  // 防止浏览器把任意 mime 当 html 渲染
27340
27502
  "X-Content-Type-Options": "nosniff"
27341
27503
  });
27342
- const stream = import_node_fs14.default.createReadStream(absPath);
27504
+ const stream = import_node_fs15.default.createReadStream(absPath);
27343
27505
  stream.on("error", (err) => {
27344
27506
  logger?.warn("streamFile read error", { absPath, err: err.message });
27345
27507
  res.destroy();
@@ -27347,10 +27509,78 @@ function streamFile(res, absPath, logger) {
27347
27509
  stream.pipe(res);
27348
27510
  }
27349
27511
 
27512
+ // src/attachment/gc.ts
27513
+ var import_node_fs16 = __toESM(require("fs"), 1);
27514
+ var import_node_path18 = __toESM(require("path"), 1);
27515
+ var DEFAULT_TTL_MS = 30 * 24 * 3600 * 1e3;
27516
+ function runAttachmentGc(args) {
27517
+ const now = (args.now ?? Date.now)();
27518
+ const ttlMs = args.ttlMs ?? DEFAULT_TTL_MS;
27519
+ const liveAbs = /* @__PURE__ */ new Set();
27520
+ for (const { scope, sessionId } of args.sessionScopes) {
27521
+ for (const entry of args.groupFileStore.list(scope, sessionId)) {
27522
+ if (entry.stale) continue;
27523
+ if (import_node_path18.default.isAbsolute(entry.relPath)) liveAbs.add(entry.relPath);
27524
+ }
27525
+ }
27526
+ for (const { scope, sessionId } of args.sessionScopes) {
27527
+ const sessionDir = args.getSessionCwd?.(sessionId) ?? import_node_path18.default.join(
27528
+ args.dataDir,
27529
+ "sessions",
27530
+ ...scopeSubPath(scope).map(safeFileName),
27531
+ safeFileName(sessionId)
27532
+ );
27533
+ const attRoot = import_node_path18.default.join(sessionDir, ".attachments");
27534
+ let hashDirs;
27535
+ try {
27536
+ hashDirs = import_node_fs16.default.readdirSync(attRoot);
27537
+ } catch (err) {
27538
+ if (err.code === "ENOENT") continue;
27539
+ args.logger?.warn("attachment gc: readdir failed", { attRoot, err: err.message });
27540
+ continue;
27541
+ }
27542
+ for (const hashDir of hashDirs) {
27543
+ const hashDirAbs = import_node_path18.default.join(attRoot, hashDir);
27544
+ let files;
27545
+ try {
27546
+ files = import_node_fs16.default.readdirSync(hashDirAbs);
27547
+ } catch {
27548
+ continue;
27549
+ }
27550
+ for (const name of files) {
27551
+ const file = import_node_path18.default.join(hashDirAbs, name);
27552
+ let stat;
27553
+ try {
27554
+ stat = import_node_fs16.default.statSync(file);
27555
+ } catch {
27556
+ continue;
27557
+ }
27558
+ if (!stat.isFile()) continue;
27559
+ const age = now - stat.mtimeMs;
27560
+ if (age < ttlMs) continue;
27561
+ if (liveAbs.has(file)) continue;
27562
+ try {
27563
+ import_node_fs16.default.unlinkSync(file);
27564
+ } catch (err) {
27565
+ args.logger?.warn("attachment gc: unlink failed", { file, err: err.message });
27566
+ }
27567
+ }
27568
+ try {
27569
+ if (import_node_fs16.default.readdirSync(hashDirAbs).length === 0) import_node_fs16.default.rmdirSync(hashDirAbs);
27570
+ } catch {
27571
+ }
27572
+ }
27573
+ try {
27574
+ if (import_node_fs16.default.readdirSync(attRoot).length === 0) import_node_fs16.default.rmdirSync(attRoot);
27575
+ } catch {
27576
+ }
27577
+ }
27578
+ }
27579
+
27350
27580
  // src/attachment/group.ts
27351
- var import_node_fs15 = __toESM(require("fs"), 1);
27352
- var import_node_path17 = __toESM(require("path"), 1);
27353
- var import_node_crypto6 = __toESM(require("crypto"), 1);
27581
+ var import_node_fs17 = __toESM(require("fs"), 1);
27582
+ var import_node_path19 = __toESM(require("path"), 1);
27583
+ var import_node_crypto7 = __toESM(require("crypto"), 1);
27354
27584
  init_protocol();
27355
27585
  var GroupFileStore = class {
27356
27586
  dataDir;
@@ -27361,11 +27591,11 @@ var GroupFileStore = class {
27361
27591
  this.logger = opts.logger;
27362
27592
  }
27363
27593
  rootForScope(scope) {
27364
- return import_node_path17.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
27594
+ return import_node_path19.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
27365
27595
  }
27366
27596
  /** 与 SessionStore.filePath 平级,扩展名 .group-files.json */
27367
27597
  filePath(scope, sessionId) {
27368
- return import_node_path17.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
27598
+ return import_node_path19.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
27369
27599
  }
27370
27600
  cacheKey(scope, sessionId) {
27371
27601
  return scope.kind === "default" ? `default::${sessionId}` : `persona:${scope.personaId}:${scope.mode}::${sessionId}`;
@@ -27374,7 +27604,7 @@ var GroupFileStore = class {
27374
27604
  readFile(scope, sessionId) {
27375
27605
  const file = this.filePath(scope, sessionId);
27376
27606
  try {
27377
- const raw = import_node_fs15.default.readFileSync(file, "utf8");
27607
+ const raw = import_node_fs17.default.readFileSync(file, "utf8");
27378
27608
  const parsed = JSON.parse(raw);
27379
27609
  if (!Array.isArray(parsed)) {
27380
27610
  this.logger?.warn("GroupFileStore.readFile: not an array; resetting session entries", {
@@ -27400,10 +27630,10 @@ var GroupFileStore = class {
27400
27630
  }
27401
27631
  writeFile(scope, sessionId, entries) {
27402
27632
  const file = this.filePath(scope, sessionId);
27403
- import_node_fs15.default.mkdirSync(import_node_path17.default.dirname(file), { recursive: true });
27633
+ import_node_fs17.default.mkdirSync(import_node_path19.default.dirname(file), { recursive: true });
27404
27634
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
27405
- import_node_fs15.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
27406
- import_node_fs15.default.renameSync(tmp, file);
27635
+ import_node_fs17.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
27636
+ import_node_fs17.default.renameSync(tmp, file);
27407
27637
  }
27408
27638
  /** 拉一份当前 session 的清单。读盘 → cache;之后调用复用 cache */
27409
27639
  list(scope, sessionId) {
@@ -27439,7 +27669,7 @@ var GroupFileStore = class {
27439
27669
  entries[idx] = next;
27440
27670
  } else {
27441
27671
  next = {
27442
- id: `gf-${import_node_crypto6.default.randomBytes(6).toString("base64url")}`,
27672
+ id: `gf-${import_node_crypto7.default.randomBytes(6).toString("base64url")}`,
27443
27673
  relPath: input.relPath,
27444
27674
  from: input.from,
27445
27675
  label: input.label,
@@ -27489,10 +27719,10 @@ var GroupFileStore = class {
27489
27719
  };
27490
27720
 
27491
27721
  // src/discovery/state-file.ts
27492
- var import_node_fs16 = __toESM(require("fs"), 1);
27493
- var import_node_path18 = __toESM(require("path"), 1);
27722
+ var import_node_fs18 = __toESM(require("fs"), 1);
27723
+ var import_node_path20 = __toESM(require("path"), 1);
27494
27724
  function defaultStateFilePath(dataDir) {
27495
- return import_node_path18.default.join(dataDir, "state.json");
27725
+ return import_node_path20.default.join(dataDir, "state.json");
27496
27726
  }
27497
27727
  function isPidAlive(pid) {
27498
27728
  if (!Number.isFinite(pid) || pid <= 0) return false;
@@ -27514,7 +27744,7 @@ var StateFileManager = class {
27514
27744
  }
27515
27745
  read() {
27516
27746
  try {
27517
- const raw = import_node_fs16.default.readFileSync(this.file, "utf8");
27747
+ const raw = import_node_fs18.default.readFileSync(this.file, "utf8");
27518
27748
  const parsed = JSON.parse(raw);
27519
27749
  return parsed;
27520
27750
  } catch {
@@ -27528,34 +27758,34 @@ var StateFileManager = class {
27528
27758
  return { status: "stale", existing };
27529
27759
  }
27530
27760
  write(state) {
27531
- import_node_fs16.default.mkdirSync(import_node_path18.default.dirname(this.file), { recursive: true });
27761
+ import_node_fs18.default.mkdirSync(import_node_path20.default.dirname(this.file), { recursive: true });
27532
27762
  const tmp = `${this.file}.tmp.${process.pid}.${Date.now()}`;
27533
- import_node_fs16.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
27534
- import_node_fs16.default.renameSync(tmp, this.file);
27763
+ import_node_fs18.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
27764
+ import_node_fs18.default.renameSync(tmp, this.file);
27535
27765
  if (process.platform !== "win32") {
27536
27766
  try {
27537
- import_node_fs16.default.chmodSync(this.file, 384);
27767
+ import_node_fs18.default.chmodSync(this.file, 384);
27538
27768
  } catch {
27539
27769
  }
27540
27770
  }
27541
27771
  }
27542
27772
  delete() {
27543
27773
  try {
27544
- import_node_fs16.default.unlinkSync(this.file);
27774
+ import_node_fs18.default.unlinkSync(this.file);
27545
27775
  } catch {
27546
27776
  }
27547
27777
  }
27548
27778
  };
27549
27779
 
27550
27780
  // src/tunnel/tunnel-manager.ts
27551
- var import_node_fs20 = __toESM(require("fs"), 1);
27552
- var import_node_path22 = __toESM(require("path"), 1);
27553
- var import_node_crypto7 = __toESM(require("crypto"), 1);
27781
+ var import_node_fs22 = __toESM(require("fs"), 1);
27782
+ var import_node_path24 = __toESM(require("path"), 1);
27783
+ var import_node_crypto8 = __toESM(require("crypto"), 1);
27554
27784
  var import_node_child_process5 = require("child_process");
27555
27785
 
27556
27786
  // src/tunnel/tunnel-store.ts
27557
- var import_node_fs17 = __toESM(require("fs"), 1);
27558
- var import_node_path19 = __toESM(require("path"), 1);
27787
+ var import_node_fs19 = __toESM(require("fs"), 1);
27788
+ var import_node_path21 = __toESM(require("path"), 1);
27559
27789
  var TunnelStore = class {
27560
27790
  constructor(filePath) {
27561
27791
  this.filePath = filePath;
@@ -27563,7 +27793,7 @@ var TunnelStore = class {
27563
27793
  filePath;
27564
27794
  async get() {
27565
27795
  try {
27566
- const raw = await import_node_fs17.default.promises.readFile(this.filePath, "utf8");
27796
+ const raw = await import_node_fs19.default.promises.readFile(this.filePath, "utf8");
27567
27797
  const obj = JSON.parse(raw);
27568
27798
  if (!isPersistedTunnel(obj)) return null;
27569
27799
  return obj;
@@ -27574,22 +27804,22 @@ var TunnelStore = class {
27574
27804
  }
27575
27805
  }
27576
27806
  async set(v2) {
27577
- const dir = import_node_path19.default.dirname(this.filePath);
27578
- await import_node_fs17.default.promises.mkdir(dir, { recursive: true });
27807
+ const dir = import_node_path21.default.dirname(this.filePath);
27808
+ await import_node_fs19.default.promises.mkdir(dir, { recursive: true });
27579
27809
  const data = JSON.stringify(v2, null, 2);
27580
27810
  const tmp = `${this.filePath}.tmp.${process.pid}.${Date.now()}`;
27581
- await import_node_fs17.default.promises.writeFile(tmp, data, { mode: 384 });
27811
+ await import_node_fs19.default.promises.writeFile(tmp, data, { mode: 384 });
27582
27812
  if (process.platform !== "win32") {
27583
27813
  try {
27584
- await import_node_fs17.default.promises.chmod(tmp, 384);
27814
+ await import_node_fs19.default.promises.chmod(tmp, 384);
27585
27815
  } catch {
27586
27816
  }
27587
27817
  }
27588
- await import_node_fs17.default.promises.rename(tmp, this.filePath);
27818
+ await import_node_fs19.default.promises.rename(tmp, this.filePath);
27589
27819
  }
27590
27820
  async clear() {
27591
27821
  try {
27592
- await import_node_fs17.default.promises.unlink(this.filePath);
27822
+ await import_node_fs19.default.promises.unlink(this.filePath);
27593
27823
  } catch (err) {
27594
27824
  const code = err?.code;
27595
27825
  if (code !== "ENOENT") throw err;
@@ -27684,12 +27914,12 @@ function escape(v2) {
27684
27914
  }
27685
27915
 
27686
27916
  // src/tunnel/frpc-binary.ts
27687
- var import_node_fs18 = __toESM(require("fs"), 1);
27917
+ var import_node_fs20 = __toESM(require("fs"), 1);
27688
27918
  var import_node_os9 = __toESM(require("os"), 1);
27689
- var import_node_path20 = __toESM(require("path"), 1);
27919
+ var import_node_path22 = __toESM(require("path"), 1);
27690
27920
  var import_node_child_process3 = require("child_process");
27691
27921
  var import_node_stream2 = require("stream");
27692
- var import_promises = require("stream/promises");
27922
+ var import_promises2 = require("stream/promises");
27693
27923
  var FRPC_VERSION = "0.68.0";
27694
27924
  var UnsupportedPlatformError = class extends Error {
27695
27925
  constructor(platform, arch) {
@@ -27718,20 +27948,20 @@ function frpcDownloadUrl(version2, p2) {
27718
27948
  }
27719
27949
  async function ensureFrpcBinary(opts) {
27720
27950
  if (opts.override) {
27721
- if (!import_node_fs18.default.existsSync(opts.override)) {
27951
+ if (!import_node_fs20.default.existsSync(opts.override)) {
27722
27952
  throw new Error(`frpc binary not found at override path: ${opts.override}`);
27723
27953
  }
27724
27954
  return opts.override;
27725
27955
  }
27726
27956
  const version2 = opts.version ?? FRPC_VERSION;
27727
27957
  const platform = opts.platform ?? detectPlatform();
27728
- const binDir = import_node_path20.default.join(opts.dataDir, "bin");
27729
- import_node_fs18.default.mkdirSync(binDir, { recursive: true });
27958
+ const binDir = import_node_path22.default.join(opts.dataDir, "bin");
27959
+ import_node_fs20.default.mkdirSync(binDir, { recursive: true });
27730
27960
  cleanupStaleArtifacts(binDir);
27731
- const stableBin = import_node_path20.default.join(binDir, "frpc");
27732
- if (import_node_fs18.default.existsSync(stableBin)) return stableBin;
27961
+ const stableBin = import_node_path22.default.join(binDir, "frpc");
27962
+ if (import_node_fs20.default.existsSync(stableBin)) return stableBin;
27733
27963
  const partialBin = `${stableBin}.partial`;
27734
- const tarballPath = import_node_path20.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
27964
+ const tarballPath = import_node_path22.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
27735
27965
  try {
27736
27966
  const url = frpcDownloadUrl(version2, platform);
27737
27967
  await downloadToFile(url, tarballPath, opts.fetchImpl);
@@ -27740,8 +27970,8 @@ async function ensureFrpcBinary(opts) {
27740
27970
  } else {
27741
27971
  await extractFrpcFromTarball(tarballPath, binDir, version2, platform, partialBin);
27742
27972
  }
27743
- import_node_fs18.default.chmodSync(partialBin, 493);
27744
- import_node_fs18.default.renameSync(partialBin, stableBin);
27973
+ import_node_fs20.default.chmodSync(partialBin, 493);
27974
+ import_node_fs20.default.renameSync(partialBin, stableBin);
27745
27975
  } finally {
27746
27976
  safeUnlink(tarballPath);
27747
27977
  safeUnlink(partialBin);
@@ -27751,15 +27981,15 @@ async function ensureFrpcBinary(opts) {
27751
27981
  function cleanupStaleArtifacts(binDir) {
27752
27982
  let entries;
27753
27983
  try {
27754
- entries = import_node_fs18.default.readdirSync(binDir);
27984
+ entries = import_node_fs20.default.readdirSync(binDir);
27755
27985
  } catch {
27756
27986
  return;
27757
27987
  }
27758
27988
  for (const name of entries) {
27759
27989
  if (name.endsWith(".partial") || name.startsWith("extract-")) {
27760
- const full = import_node_path20.default.join(binDir, name);
27990
+ const full = import_node_path22.default.join(binDir, name);
27761
27991
  try {
27762
- import_node_fs18.default.rmSync(full, { recursive: true, force: true });
27992
+ import_node_fs20.default.rmSync(full, { recursive: true, force: true });
27763
27993
  } catch {
27764
27994
  }
27765
27995
  }
@@ -27767,7 +27997,7 @@ function cleanupStaleArtifacts(binDir) {
27767
27997
  }
27768
27998
  function safeUnlink(p2) {
27769
27999
  try {
27770
- import_node_fs18.default.unlinkSync(p2);
28000
+ import_node_fs20.default.unlinkSync(p2);
27771
28001
  } catch {
27772
28002
  }
27773
28003
  }
@@ -27778,13 +28008,13 @@ async function downloadToFile(url, dest, fetchImpl) {
27778
28008
  if (!res.ok || !res.body) {
27779
28009
  throw new Error(`download failed: ${res.status} ${res.statusText}`);
27780
28010
  }
27781
- const out = import_node_fs18.default.createWriteStream(dest);
28011
+ const out = import_node_fs20.default.createWriteStream(dest);
27782
28012
  const nodeStream = import_node_stream2.Readable.fromWeb(res.body);
27783
- await (0, import_promises.pipeline)(nodeStream, out);
28013
+ await (0, import_promises2.pipeline)(nodeStream, out);
27784
28014
  }
27785
28015
  async function extractFrpcFromTarball(tarball, binDir, version2, platform, destBin) {
27786
- const work = import_node_path20.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
27787
- import_node_fs18.default.mkdirSync(work, { recursive: true });
28016
+ const work = import_node_path22.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
28017
+ import_node_fs20.default.mkdirSync(work, { recursive: true });
27788
28018
  try {
27789
28019
  await new Promise((resolve6, reject) => {
27790
28020
  const proc = (0, import_node_child_process3.spawn)("tar", ["xzf", tarball, "-C", work], { stdio: "pipe" });
@@ -27792,32 +28022,32 @@ async function extractFrpcFromTarball(tarball, binDir, version2, platform, destB
27792
28022
  proc.on("exit", (code) => code === 0 ? resolve6() : reject(new Error(`tar exited ${code}`)));
27793
28023
  });
27794
28024
  const dirName = `frp_${version2}_${platform.os}_${platform.arch}`;
27795
- const src = import_node_path20.default.join(work, dirName, "frpc");
27796
- if (!import_node_fs18.default.existsSync(src)) {
28025
+ const src = import_node_path22.default.join(work, dirName, "frpc");
28026
+ if (!import_node_fs20.default.existsSync(src)) {
27797
28027
  throw new Error(`frpc not found inside tarball at ${src}`);
27798
28028
  }
27799
- import_node_fs18.default.copyFileSync(src, destBin);
28029
+ import_node_fs20.default.copyFileSync(src, destBin);
27800
28030
  } finally {
27801
- import_node_fs18.default.rmSync(work, { recursive: true, force: true });
28031
+ import_node_fs20.default.rmSync(work, { recursive: true, force: true });
27802
28032
  }
27803
28033
  }
27804
28034
 
27805
28035
  // src/tunnel/frpc-process.ts
27806
- var import_node_fs19 = __toESM(require("fs"), 1);
27807
- var import_node_path21 = __toESM(require("path"), 1);
28036
+ var import_node_fs21 = __toESM(require("fs"), 1);
28037
+ var import_node_path23 = __toESM(require("path"), 1);
27808
28038
  var import_node_child_process4 = require("child_process");
27809
28039
  function frpcPidFilePath(dataDir) {
27810
- return import_node_path21.default.join(dataDir, "frpc.pid");
28040
+ return import_node_path23.default.join(dataDir, "frpc.pid");
27811
28041
  }
27812
28042
  function writeFrpcPid(dataDir, pid) {
27813
28043
  try {
27814
- import_node_fs19.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
28044
+ import_node_fs21.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
27815
28045
  } catch {
27816
28046
  }
27817
28047
  }
27818
28048
  function clearFrpcPid(dataDir) {
27819
28049
  try {
27820
- import_node_fs19.default.unlinkSync(frpcPidFilePath(dataDir));
28050
+ import_node_fs21.default.unlinkSync(frpcPidFilePath(dataDir));
27821
28051
  } catch {
27822
28052
  }
27823
28053
  }
@@ -27833,7 +28063,7 @@ function defaultIsPidAlive(pid) {
27833
28063
  }
27834
28064
  function defaultReadPidFile(file) {
27835
28065
  try {
27836
- return import_node_fs19.default.readFileSync(file, "utf8");
28066
+ return import_node_fs21.default.readFileSync(file, "utf8");
27837
28067
  } catch {
27838
28068
  return null;
27839
28069
  }
@@ -27849,7 +28079,7 @@ function defaultSleep(ms) {
27849
28079
  }
27850
28080
  async function killStaleFrpc(deps) {
27851
28081
  const pidFile = frpcPidFilePath(deps.dataDir);
27852
- const tomlPath = import_node_path21.default.join(deps.dataDir, "frpc.toml");
28082
+ const tomlPath = import_node_path23.default.join(deps.dataDir, "frpc.toml");
27853
28083
  const readPidFile = deps.readPidFileImpl ?? defaultReadPidFile;
27854
28084
  const isAlive = deps.isPidAliveImpl ?? defaultIsPidAlive;
27855
28085
  const killPid = deps.killPidImpl ?? defaultKillPid;
@@ -27873,7 +28103,7 @@ async function killStaleFrpc(deps) {
27873
28103
  }
27874
28104
  if (victims.size === 0) {
27875
28105
  try {
27876
- import_node_fs19.default.unlinkSync(pidFile);
28106
+ import_node_fs21.default.unlinkSync(pidFile);
27877
28107
  } catch {
27878
28108
  }
27879
28109
  return;
@@ -27884,7 +28114,7 @@ async function killStaleFrpc(deps) {
27884
28114
  }
27885
28115
  await sleep(deps.reapWaitMs ?? 300);
27886
28116
  try {
27887
- import_node_fs19.default.unlinkSync(pidFile);
28117
+ import_node_fs21.default.unlinkSync(pidFile);
27888
28118
  } catch {
27889
28119
  }
27890
28120
  }
@@ -27921,7 +28151,7 @@ var DEFAULT_TUNNEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
27921
28151
  var TunnelManager = class {
27922
28152
  constructor(deps) {
27923
28153
  this.deps = deps;
27924
- this.store = deps.store ?? new TunnelStore(import_node_path22.default.join(deps.dataDir, "tunnel.json"));
28154
+ this.store = deps.store ?? new TunnelStore(import_node_path24.default.join(deps.dataDir, "tunnel.json"));
27925
28155
  this.ttlMs = deps.ttlMs ?? DEFAULT_TUNNEL_TTL_MS;
27926
28156
  this.startupTimeoutMs = deps.startupTimeoutMs ?? 15e3;
27927
28157
  }
@@ -28048,8 +28278,8 @@ var TunnelManager = class {
28048
28278
  dataDir: this.deps.dataDir,
28049
28279
  override: this.deps.frpcBinaryOverride ?? void 0
28050
28280
  });
28051
- const tomlPath = import_node_path22.default.join(this.deps.dataDir, "frpc.toml");
28052
- const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto7.default.randomBytes(3).toString("hex")}`;
28281
+ const tomlPath = import_node_path24.default.join(this.deps.dataDir, "frpc.toml");
28282
+ const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto8.default.randomBytes(3).toString("hex")}`;
28053
28283
  const toml = buildFrpcToml({
28054
28284
  serverAddr: t.frpsHost,
28055
28285
  serverPort: t.frpsPort,
@@ -28059,12 +28289,12 @@ var TunnelManager = class {
28059
28289
  localPort,
28060
28290
  logLevel: "info"
28061
28291
  });
28062
- await import_node_fs20.default.promises.writeFile(tomlPath, toml, { mode: 384 });
28292
+ await import_node_fs22.default.promises.writeFile(tomlPath, toml, { mode: 384 });
28063
28293
  const proc = (this.deps.spawnImpl ?? import_node_child_process5.spawn)(frpcBin, ["-c", tomlPath], {
28064
28294
  stdio: ["ignore", "pipe", "pipe"]
28065
28295
  });
28066
- const logFilePath = import_node_path22.default.join(this.deps.dataDir, "frpc.log");
28067
- const logStream = import_node_fs20.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
28296
+ const logFilePath = import_node_path24.default.join(this.deps.dataDir, "frpc.log");
28297
+ const logStream = import_node_fs22.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
28068
28298
  logStream.on("error", () => {
28069
28299
  });
28070
28300
  const tee = (chunk) => {
@@ -28147,22 +28377,22 @@ async function waitForFrpcReady(proc, timeoutMs) {
28147
28377
 
28148
28378
  // src/tunnel/device-key.ts
28149
28379
  var import_node_os10 = __toESM(require("os"), 1);
28150
- var import_node_crypto8 = __toESM(require("crypto"), 1);
28380
+ var import_node_crypto9 = __toESM(require("crypto"), 1);
28151
28381
  var DERIVE_SALT = "clawd-tunnel-device-v1";
28152
28382
  function deriveStableDeviceKey(opts = {}) {
28153
28383
  const hostname = opts.hostname ?? import_node_os10.default.hostname();
28154
28384
  const uid = opts.uid ?? (typeof import_node_os10.default.userInfo === "function" ? import_node_os10.default.userInfo().uid : 0);
28155
28385
  const input = `${hostname}::${uid}`;
28156
- return import_node_crypto8.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
28386
+ return import_node_crypto9.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
28157
28387
  }
28158
28388
 
28159
28389
  // src/auth-store.ts
28160
- var import_node_fs21 = __toESM(require("fs"), 1);
28161
- var import_node_path23 = __toESM(require("path"), 1);
28162
- var import_node_crypto9 = __toESM(require("crypto"), 1);
28390
+ var import_node_fs23 = __toESM(require("fs"), 1);
28391
+ var import_node_path25 = __toESM(require("path"), 1);
28392
+ var import_node_crypto10 = __toESM(require("crypto"), 1);
28163
28393
  var AUTH_FILE_NAME = "auth.json";
28164
28394
  function authFilePath(dataDir) {
28165
- return import_node_path23.default.join(dataDir, AUTH_FILE_NAME);
28395
+ return import_node_path25.default.join(dataDir, AUTH_FILE_NAME);
28166
28396
  }
28167
28397
  function loadOrCreateAuthFile(opts) {
28168
28398
  const file = authFilePath(opts.dataDir);
@@ -28187,14 +28417,14 @@ function loadOrCreateAuthFile(opts) {
28187
28417
  return next;
28188
28418
  }
28189
28419
  function defaultGenerateToken2() {
28190
- return import_node_crypto9.default.randomBytes(32).toString("base64url");
28420
+ return import_node_crypto10.default.randomBytes(32).toString("base64url");
28191
28421
  }
28192
28422
  function defaultGenerateOwnerPrincipalId() {
28193
- return `owner-${import_node_crypto9.default.randomUUID()}`;
28423
+ return `owner-${import_node_crypto10.default.randomUUID()}`;
28194
28424
  }
28195
28425
  function readAuthFile(file) {
28196
28426
  try {
28197
- const raw = import_node_fs21.default.readFileSync(file, "utf8");
28427
+ const raw = import_node_fs23.default.readFileSync(file, "utf8");
28198
28428
  const parsed = JSON.parse(raw);
28199
28429
  if (typeof parsed?.token !== "string" || parsed.token.length === 0) {
28200
28430
  return null;
@@ -28212,25 +28442,25 @@ function readAuthFile(file) {
28212
28442
  }
28213
28443
  }
28214
28444
  function writeAuthFile(file, content) {
28215
- import_node_fs21.default.mkdirSync(import_node_path23.default.dirname(file), { recursive: true });
28216
- import_node_fs21.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
28445
+ import_node_fs23.default.mkdirSync(import_node_path25.default.dirname(file), { recursive: true });
28446
+ import_node_fs23.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
28217
28447
  try {
28218
- import_node_fs21.default.chmodSync(file, 384);
28448
+ import_node_fs23.default.chmodSync(file, 384);
28219
28449
  } catch {
28220
28450
  }
28221
28451
  }
28222
28452
 
28223
28453
  // src/owner-profile.ts
28224
- var import_node_fs22 = __toESM(require("fs"), 1);
28454
+ var import_node_fs24 = __toESM(require("fs"), 1);
28225
28455
  var import_node_os11 = __toESM(require("os"), 1);
28226
- var import_node_path24 = __toESM(require("path"), 1);
28456
+ var import_node_path26 = __toESM(require("path"), 1);
28227
28457
  var PROFILE_FILENAME = "profile.json";
28228
28458
  function loadOwnerDisplayName(dataDir) {
28229
28459
  const fallback = import_node_os11.default.userInfo().username;
28230
- const profilePath = import_node_path24.default.join(dataDir, PROFILE_FILENAME);
28460
+ const profilePath = import_node_path26.default.join(dataDir, PROFILE_FILENAME);
28231
28461
  let raw;
28232
28462
  try {
28233
- raw = import_node_fs22.default.readFileSync(profilePath, "utf8");
28463
+ raw = import_node_fs24.default.readFileSync(profilePath, "utf8");
28234
28464
  } catch {
28235
28465
  return fallback;
28236
28466
  }
@@ -28259,12 +28489,12 @@ init_protocol();
28259
28489
  init_protocol();
28260
28490
 
28261
28491
  // src/session/fork.ts
28262
- var import_node_fs23 = __toESM(require("fs"), 1);
28492
+ var import_node_fs25 = __toESM(require("fs"), 1);
28263
28493
  var import_node_os12 = __toESM(require("os"), 1);
28264
- var import_node_path25 = __toESM(require("path"), 1);
28494
+ var import_node_path27 = __toESM(require("path"), 1);
28265
28495
  init_claude_history();
28266
28496
  function readJsonlEntries(file) {
28267
- const raw = import_node_fs23.default.readFileSync(file, "utf8");
28497
+ const raw = import_node_fs25.default.readFileSync(file, "utf8");
28268
28498
  const out = [];
28269
28499
  for (const line of raw.split("\n")) {
28270
28500
  const t = line.trim();
@@ -28277,10 +28507,10 @@ function readJsonlEntries(file) {
28277
28507
  return out;
28278
28508
  }
28279
28509
  function forkSession(input) {
28280
- const baseDir = input.baseDir ?? import_node_path25.default.join(import_node_os12.default.homedir(), ".claude");
28281
- const projectDir = import_node_path25.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
28282
- const sourceFile = import_node_path25.default.join(projectDir, `${input.toolSessionId}.jsonl`);
28283
- if (!import_node_fs23.default.existsSync(sourceFile)) {
28510
+ const baseDir = input.baseDir ?? import_node_path27.default.join(import_node_os12.default.homedir(), ".claude");
28511
+ const projectDir = import_node_path27.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
28512
+ const sourceFile = import_node_path27.default.join(projectDir, `${input.toolSessionId}.jsonl`);
28513
+ if (!import_node_fs25.default.existsSync(sourceFile)) {
28284
28514
  throw new Error(`fork: source transcript not found: ${sourceFile}`);
28285
28515
  }
28286
28516
  const entries = readJsonlEntries(sourceFile);
@@ -28310,9 +28540,9 @@ function forkSession(input) {
28310
28540
  }
28311
28541
  forkedLines.push(JSON.stringify(forked));
28312
28542
  }
28313
- const forkedFilePath = import_node_path25.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
28314
- import_node_fs23.default.mkdirSync(projectDir, { recursive: true });
28315
- import_node_fs23.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
28543
+ const forkedFilePath = import_node_path27.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
28544
+ import_node_fs25.default.mkdirSync(projectDir, { recursive: true });
28545
+ import_node_fs25.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
28316
28546
  return { forkedToolSessionId, forkedFilePath };
28317
28547
  }
28318
28548
 
@@ -28600,28 +28830,6 @@ function buildSessionHandlers(deps) {
28600
28830
  }
28601
28831
  };
28602
28832
  };
28603
- const peerSessionUpsert = async (frame, _client, ctx) => {
28604
- if (ctx && ctx.principal.kind !== "owner") {
28605
- throw new ClawdError(
28606
- ERROR_CODES.UNAUTHORIZED,
28607
- `principal ${ctx.principal.kind}:${ctx.principal.id} cannot peerSession:upsert`
28608
- );
28609
- }
28610
- const file = PeerSessionUpsertArgs.parse(frame);
28611
- manager.mirrorUpsert(file);
28612
- return { response: { type: "peerSession:upsert:ok" }, broadcast: [] };
28613
- };
28614
- const peerSessionRemove = async (frame, _client, ctx) => {
28615
- if (ctx && ctx.principal.kind !== "owner") {
28616
- throw new ClawdError(
28617
- ERROR_CODES.UNAUTHORIZED,
28618
- `principal ${ctx.principal.kind}:${ctx.principal.id} cannot peerSession:remove`
28619
- );
28620
- }
28621
- const args = PeerSessionRemoveArgs.parse(frame);
28622
- manager.mirrorRemove(args.sessionId);
28623
- return { response: { type: "peerSession:remove:ok" }, broadcast: [] };
28624
- };
28625
28833
  return {
28626
28834
  "session:create": create,
28627
28835
  "session:list": list,
@@ -28647,9 +28855,7 @@ function buildSessionHandlers(deps) {
28647
28855
  "session:cancelQuestion": cancelQuestion,
28648
28856
  "session:pty:input": ptyInput,
28649
28857
  "session:pty:resize": ptyResize,
28650
- "session:pty:snapshot": ptySnapshot,
28651
- "peerSession:upsert": peerSessionUpsert,
28652
- "peerSession:remove": peerSessionRemove
28858
+ "session:pty:snapshot": ptySnapshot
28653
28859
  };
28654
28860
  }
28655
28861
 
@@ -28668,7 +28874,7 @@ function buildPermissionHandlers(deps) {
28668
28874
  }
28669
28875
 
28670
28876
  // src/handlers/history.ts
28671
- var path35 = __toESM(require("path"), 1);
28877
+ var path37 = __toESM(require("path"), 1);
28672
28878
  init_protocol();
28673
28879
 
28674
28880
  // src/session/recent-dirs.ts
@@ -28686,7 +28892,7 @@ function listRecentDirs(store, limit = 50) {
28686
28892
  }
28687
28893
 
28688
28894
  // src/permission/persona-paths.ts
28689
- var path34 = __toESM(require("path"), 1);
28895
+ var path36 = __toESM(require("path"), 1);
28690
28896
  function getAllowedPersonaIds(grants, action) {
28691
28897
  const ids = /* @__PURE__ */ new Set();
28692
28898
  for (const g2 of grants) {
@@ -28699,26 +28905,26 @@ function getAllowedPersonaIds(grants, action) {
28699
28905
  return ids;
28700
28906
  }
28701
28907
  function isGuestPathAllowed(grants, absPath, personaRoot, action = "read") {
28702
- const root = path34.resolve(personaRoot);
28703
- const target = path34.resolve(absPath);
28704
- const sep3 = root.endsWith(path34.sep) ? "" : path34.sep;
28908
+ const root = path36.resolve(personaRoot);
28909
+ const target = path36.resolve(absPath);
28910
+ const sep3 = root.endsWith(path36.sep) ? "" : path36.sep;
28705
28911
  if (!target.startsWith(root + sep3)) return false;
28706
- const rel = path34.relative(root, target);
28912
+ const rel = path36.relative(root, target);
28707
28913
  if (!rel || rel.startsWith("..")) return false;
28708
- const personaId = rel.split(path34.sep)[0];
28914
+ const personaId = rel.split(path36.sep)[0];
28709
28915
  if (!personaId) return false;
28710
28916
  const allowed = getAllowedPersonaIds(grants, action);
28711
28917
  if (allowed === "*") return true;
28712
28918
  return allowed.has(personaId);
28713
28919
  }
28714
28920
  function personaIdFromPath(absPath, personaRoot) {
28715
- const root = path34.resolve(personaRoot);
28716
- const target = path34.resolve(absPath);
28717
- const sep3 = root.endsWith(path34.sep) ? "" : path34.sep;
28921
+ const root = path36.resolve(personaRoot);
28922
+ const target = path36.resolve(absPath);
28923
+ const sep3 = root.endsWith(path36.sep) ? "" : path36.sep;
28718
28924
  if (!target.startsWith(root + sep3)) return null;
28719
- const rel = path34.relative(root, target);
28925
+ const rel = path36.relative(root, target);
28720
28926
  if (!rel || rel.startsWith("..")) return null;
28721
- const id = rel.split(path34.sep)[0];
28927
+ const id = rel.split(path36.sep)[0];
28722
28928
  return id || null;
28723
28929
  }
28724
28930
 
@@ -28743,7 +28949,7 @@ function buildHistoryHandlers(deps) {
28743
28949
  if (!pid) return false;
28744
28950
  return isGuestPathAllowed(
28745
28951
  ctx.grants,
28746
- path35.join(personaRoot, pid),
28952
+ path37.join(personaRoot, pid),
28747
28953
  personaRoot,
28748
28954
  "read"
28749
28955
  );
@@ -28754,7 +28960,7 @@ function buildHistoryHandlers(deps) {
28754
28960
  };
28755
28961
  const list = async (frame, _client, ctx) => {
28756
28962
  const args = HistoryListArgs.parse(frame);
28757
- assertGuestPath(ctx, path35.resolve(args.projectPath), personaRoot, "history:list");
28963
+ assertGuestPath(ctx, path37.resolve(args.projectPath), personaRoot, "history:list");
28758
28964
  const sessions = await history.listSessions(args);
28759
28965
  return { response: { type: "history:list", sessions } };
28760
28966
  };
@@ -28786,13 +28992,13 @@ function buildHistoryHandlers(deps) {
28786
28992
  };
28787
28993
  const subagents = async (frame, _client, ctx) => {
28788
28994
  const args = HistorySubagentsArgs.parse(frame);
28789
- assertGuestPath(ctx, path35.resolve(args.cwd), personaRoot, "history:subagents");
28995
+ assertGuestPath(ctx, path37.resolve(args.cwd), personaRoot, "history:subagents");
28790
28996
  const subs = await history.listSubagents(args);
28791
28997
  return { response: { type: "history:subagents", subagents: subs } };
28792
28998
  };
28793
28999
  const subagentRead = async (frame, _client, ctx) => {
28794
29000
  const args = HistorySubagentReadArgs.parse(frame);
28795
- assertGuestPath(ctx, path35.resolve(args.cwd), personaRoot, "history:subagent-read");
29001
+ assertGuestPath(ctx, path37.resolve(args.cwd), personaRoot, "history:subagent-read");
28796
29002
  const res = await history.readSubagent(args);
28797
29003
  return { response: { type: "history:subagent-read", ...res } };
28798
29004
  };
@@ -28800,7 +29006,7 @@ function buildHistoryHandlers(deps) {
28800
29006
  const dirs = listRecentDirs(store);
28801
29007
  if (ctx?.principal.kind === "guest" && personaRoot) {
28802
29008
  const filtered = dirs.filter(
28803
- (d) => isGuestPathAllowed(ctx.grants, path35.resolve(d.cwd), personaRoot, "read")
29009
+ (d) => isGuestPathAllowed(ctx.grants, path37.resolve(d.cwd), personaRoot, "read")
28804
29010
  );
28805
29011
  return { response: { type: "history:recentDirs", dirs: filtered } };
28806
29012
  }
@@ -28817,7 +29023,7 @@ function buildHistoryHandlers(deps) {
28817
29023
  }
28818
29024
 
28819
29025
  // src/handlers/workspace.ts
28820
- var path36 = __toESM(require("path"), 1);
29026
+ var path38 = __toESM(require("path"), 1);
28821
29027
  var os13 = __toESM(require("os"), 1);
28822
29028
  init_protocol();
28823
29029
  init_protocol();
@@ -28858,22 +29064,22 @@ function buildWorkspaceHandlers(deps) {
28858
29064
  const args = WorkspaceListArgs.parse(frame);
28859
29065
  const isGuest = ctx?.principal.kind === "guest";
28860
29066
  const fallbackCwd = isGuest && personaRoot ? personaRoot : os13.homedir();
28861
- const resolvedCwd = path36.resolve(args.cwd ?? fallbackCwd);
28862
- const target = args.path ? path36.resolve(resolvedCwd, args.path) : resolvedCwd;
29067
+ const resolvedCwd = path38.resolve(args.cwd ?? fallbackCwd);
29068
+ const target = args.path ? path38.resolve(resolvedCwd, args.path) : resolvedCwd;
28863
29069
  assertGuestPath2(ctx, target, personaRoot, "workspace:list");
28864
29070
  const res = workspace.list({ ...args, cwd: resolvedCwd });
28865
29071
  return { response: { type: "workspace:list", ...res } };
28866
29072
  };
28867
29073
  const read = async (frame, _client, ctx) => {
28868
29074
  const args = WorkspaceReadArgs.parse(frame);
28869
- const target = path36.isAbsolute(args.path) ? path36.resolve(args.path) : path36.resolve(args.cwd, args.path);
29075
+ const target = path38.isAbsolute(args.path) ? path38.resolve(args.path) : path38.resolve(args.cwd, args.path);
28870
29076
  assertGuestPath2(ctx, target, personaRoot, "workspace:read");
28871
29077
  const res = workspace.read(args);
28872
29078
  return { response: { type: "workspace:read", ...res } };
28873
29079
  };
28874
29080
  const skillsList = async (frame, _client, ctx) => {
28875
29081
  const args = SkillsListArgs.parse(frame);
28876
- const cwdAbs = path36.resolve(args.cwd);
29082
+ const cwdAbs = path38.resolve(args.cwd);
28877
29083
  assertGuestPath2(ctx, cwdAbs, personaRoot, "skills:list");
28878
29084
  const list2 = skills.list(args);
28879
29085
  if (ctx?.principal.kind === "guest" && personaRoot) {
@@ -28885,7 +29091,7 @@ function buildWorkspaceHandlers(deps) {
28885
29091
  };
28886
29092
  const agentsList = async (frame, _client, ctx) => {
28887
29093
  const args = AgentsListArgs.parse(frame);
28888
- const cwdAbs = path36.resolve(args.cwd);
29094
+ const cwdAbs = path38.resolve(args.cwd);
28889
29095
  assertGuestPath2(ctx, cwdAbs, personaRoot, "agents:list");
28890
29096
  const list2 = agents.list(args);
28891
29097
  if (ctx?.principal.kind === "guest" && personaRoot) {
@@ -28904,20 +29110,20 @@ function buildWorkspaceHandlers(deps) {
28904
29110
  }
28905
29111
 
28906
29112
  // src/handlers/git.ts
28907
- var path38 = __toESM(require("path"), 1);
29113
+ var path40 = __toESM(require("path"), 1);
28908
29114
  init_protocol();
28909
29115
  init_protocol();
28910
29116
 
28911
29117
  // src/workspace/git.ts
28912
29118
  var import_node_child_process6 = require("child_process");
28913
- var import_node_fs24 = __toESM(require("fs"), 1);
28914
- var import_node_path26 = __toESM(require("path"), 1);
29119
+ var import_node_fs26 = __toESM(require("fs"), 1);
29120
+ var import_node_path28 = __toESM(require("path"), 1);
28915
29121
  var import_node_util = require("util");
28916
29122
  var pexec = (0, import_node_util.promisify)(import_node_child_process6.execFile);
28917
29123
  function normalizePath(p2) {
28918
- const resolved = import_node_path26.default.resolve(p2);
29124
+ const resolved = import_node_path28.default.resolve(p2);
28919
29125
  try {
28920
- return import_node_fs24.default.realpathSync(resolved);
29126
+ return import_node_fs26.default.realpathSync(resolved);
28921
29127
  } catch {
28922
29128
  return resolved;
28923
29129
  }
@@ -28990,7 +29196,7 @@ async function listGitBranches(cwd) {
28990
29196
  // src/handlers/git.ts
28991
29197
  function assertGuestCwd(ctx, cwd, personaRoot, method) {
28992
29198
  if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
28993
- if (!isGuestPathAllowed(ctx.grants, path38.resolve(cwd), personaRoot, "read")) {
29199
+ if (!isGuestPathAllowed(ctx.grants, path40.resolve(cwd), personaRoot, "read")) {
28994
29200
  throw new ClawdError(
28995
29201
  ERROR_CODES.UNAUTHORIZED,
28996
29202
  `guest ${ctx.principal.id} cannot ${method} cwd ${cwd}`
@@ -29477,7 +29683,7 @@ function buildPersonaHandlers(deps) {
29477
29683
  }
29478
29684
 
29479
29685
  // src/handlers/attachment.ts
29480
- var import_node_path27 = __toESM(require("path"), 1);
29686
+ var import_node_path29 = __toESM(require("path"), 1);
29481
29687
  init_protocol();
29482
29688
  init_protocol();
29483
29689
  var DEFAULT_TTL_SECONDS = 24 * 3600;
@@ -29531,12 +29737,12 @@ function buildAttachmentHandlers(deps) {
29531
29737
  `session ${args.sessionId} scope unresolved`
29532
29738
  );
29533
29739
  }
29534
- const cwdAbs = import_node_path27.default.resolve(sessionFile.cwd);
29535
- const candidateAbs = import_node_path27.default.isAbsolute(args.relPath) ? import_node_path27.default.resolve(args.relPath) : import_node_path27.default.resolve(cwdAbs, args.relPath);
29740
+ const cwdAbs = import_node_path29.default.resolve(sessionFile.cwd);
29741
+ const candidateAbs = import_node_path29.default.isAbsolute(args.relPath) ? import_node_path29.default.resolve(args.relPath) : import_node_path29.default.resolve(cwdAbs, args.relPath);
29536
29742
  assertGuestAttachmentPath(ctx, candidateAbs, deps.personaRoot, "attachment.signUrl");
29537
29743
  const entries = deps.groupFileStore.list(scope, args.sessionId);
29538
29744
  const entry = entries.find((e) => {
29539
- const storedAbs = import_node_path27.default.isAbsolute(e.relPath) ? import_node_path27.default.resolve(e.relPath) : import_node_path27.default.resolve(cwdAbs, e.relPath);
29745
+ const storedAbs = import_node_path29.default.isAbsolute(e.relPath) ? import_node_path29.default.resolve(e.relPath) : import_node_path29.default.resolve(cwdAbs, e.relPath);
29540
29746
  return storedAbs === candidateAbs && !e.stale;
29541
29747
  });
29542
29748
  if (!entry) {
@@ -29560,7 +29766,7 @@ function buildAttachmentHandlers(deps) {
29560
29766
  if (!ctx || ctx.principal.kind !== "guest" || !deps.personaRoot || !deps.sessionStore) return;
29561
29767
  const f = deps.sessionStore.read(sessionId);
29562
29768
  if (!f) return;
29563
- assertGuestAttachmentPath(ctx, import_node_path27.default.resolve(f.cwd), deps.personaRoot, method);
29769
+ assertGuestAttachmentPath(ctx, import_node_path29.default.resolve(f.cwd), deps.personaRoot, method);
29564
29770
  }
29565
29771
  const groupAdd = async (frame, _client, ctx) => {
29566
29772
  if (!deps.groupFileStore || !deps.getSessionScope) {
@@ -29601,7 +29807,7 @@ function buildAttachmentHandlers(deps) {
29601
29807
  }
29602
29808
  const entries = deps.groupFileStore.list(scope, parsed.data.sessionId);
29603
29809
  const target = entries.find((e) => e.relPath === parsed.data.relPath);
29604
- if (target?.from === "owner") {
29810
+ if (target?.from === "owner" || target?.from === "user-upload") {
29605
29811
  deps.groupFileStore.remove(scope, parsed.data.sessionId, parsed.data.relPath);
29606
29812
  } else {
29607
29813
  deps.groupFileStore.markStale(scope, parsed.data.sessionId, parsed.data.relPath);
@@ -29727,11 +29933,6 @@ var METHOD_GRANT_MAP = {
29727
29933
  "session:pin": CAPABILITY_SCOPED,
29728
29934
  "session:reorderPins": ADMIN_ANY,
29729
29935
  // owner 全局操作,无 personaId 维度
29730
- // mirror peer sessions (2026-05-26):owner-only — guest 不应能写/删本机 mirror SessionFile
29731
- // (会绕过 #739 隔离规则把伪造身份的 SessionFile 写进 default store)。handler 端内部 ctx
29732
- // 校验,dispatcher 层一刀切 admin 也兜底。
29733
- "peerSession:upsert": ADMIN_ANY,
29734
- "peerSession:remove": ADMIN_ANY,
29735
29936
  "permission:respond": CAPABILITY_SCOPED,
29736
29937
  "session:answerQuestion": CAPABILITY_SCOPED,
29737
29938
  "session:cancelQuestion": CAPABILITY_SCOPED,
@@ -29802,7 +30003,7 @@ function computeGrantForFrame(method, frame) {
29802
30003
  async function startDaemon(config) {
29803
30004
  const logger = createLogger({
29804
30005
  level: config.logLevel,
29805
- file: import_node_path28.default.join(config.dataDir, "clawd.log")
30006
+ file: import_node_path30.default.join(config.dataDir, "clawd.log")
29806
30007
  });
29807
30008
  logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
29808
30009
  const stateMgr = new StateFileManager({ dataDir: config.dataDir });
@@ -29889,7 +30090,7 @@ async function startDaemon(config) {
29889
30090
  const agents = new AgentsScanner();
29890
30091
  const history = new ClaudeHistoryReader();
29891
30092
  let transport = null;
29892
- const personaStore = new PersonaStore(import_node_path28.default.join(config.dataDir, "personas"));
30093
+ const personaStore = new PersonaStore(import_node_path30.default.join(config.dataDir, "personas"));
29893
30094
  const defaultsRoot = findDefaultsRoot();
29894
30095
  if (defaultsRoot) {
29895
30096
  seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
@@ -29906,7 +30107,7 @@ async function startDaemon(config) {
29906
30107
  getAdapter,
29907
30108
  historyReader: history,
29908
30109
  dataDir: config.dataDir,
29909
- personaRoot: import_node_path28.default.join(config.dataDir, "personas"),
30110
+ personaRoot: import_node_path30.default.join(config.dataDir, "personas"),
29910
30111
  personaStore,
29911
30112
  ownerDisplayName,
29912
30113
  ownerPrincipalId,
@@ -29930,10 +30131,10 @@ async function startDaemon(config) {
29930
30131
  // 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
29931
30132
  attachmentGroup: {
29932
30133
  onFileEdit: (input) => {
29933
- const absPath = import_node_path28.default.isAbsolute(input.relPath) ? input.relPath : import_node_path28.default.join(input.cwd, input.relPath);
30134
+ const absPath = import_node_path30.default.isAbsolute(input.relPath) ? input.relPath : import_node_path30.default.join(input.cwd, input.relPath);
29934
30135
  let size = 0;
29935
30136
  try {
29936
- size = import_node_fs25.default.statSync(absPath).size;
30137
+ size = import_node_fs27.default.statSync(absPath).size;
29937
30138
  } catch (err) {
29938
30139
  logger.warn("attachment.onFileEdit stat failed", {
29939
30140
  sessionId: input.sessionId,
@@ -30046,10 +30247,10 @@ async function startDaemon(config) {
30046
30247
  // 'persona/<pid>/owner',default 走 'default'。
30047
30248
  getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
30048
30249
  // guest path guard:candidate 必须在 personaRoot 子树下
30049
- personaRoot: import_node_path28.default.join(config.dataDir, "personas")
30250
+ personaRoot: import_node_path30.default.join(config.dataDir, "personas")
30050
30251
  },
30051
30252
  // workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
30052
- personaRoot: import_node_path28.default.join(config.dataDir, "personas"),
30253
+ personaRoot: import_node_path30.default.join(config.dataDir, "personas"),
30053
30254
  // capability:list / delete handler 依赖
30054
30255
  capabilityManager,
30055
30256
  // personal-cap:get 返回的 shareBaseUrl 用此 base URL:
@@ -30085,7 +30286,19 @@ async function startDaemon(config) {
30085
30286
  sessionStore: store,
30086
30287
  // /files HMAC verify 用 auth.json 的 signSecret 字段(与 attachment.signUrl 同源)。
30087
30288
  // --auth-token CLI 模式没 signSecret → 路由返 501,sign URL 功能整体禁用。
30088
- getSignSecret: () => authFile?.signSecret ?? null
30289
+ getSignSecret: () => authFile?.signSecret ?? null,
30290
+ // chatinput 附件上传(POST /attachments/upload,spec §6.1)。upload pipeline
30291
+ // 在 session.cwd 下的 .attachments/ 落盘;signSecret 与 /files HMAC 同源;
30292
+ // tunnel 未启时返 503 TUNNEL_NOT_READY 让前端显式提示。
30293
+ attachmentUpload: {
30294
+ groupFileStore,
30295
+ getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
30296
+ getSessionDir: (sid) => {
30297
+ const f = manager.findOwnedSession(sid);
30298
+ return f ? f.cwd : null;
30299
+ },
30300
+ getHttpBaseUrl: () => currentTunnelUrl ? getHttpBaseUrl() : null
30301
+ }
30089
30302
  });
30090
30303
  wsServer = new LocalWsServer({
30091
30304
  host: config.host,
@@ -30250,8 +30463,8 @@ async function startDaemon(config) {
30250
30463
  const lines = [
30251
30464
  `Tunnel: ${r.url}`,
30252
30465
  ...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
30253
- `Frpc config: ${import_node_path28.default.join(config.dataDir, "frpc.toml")}`,
30254
- `Frpc log: ${import_node_path28.default.join(config.dataDir, "frpc.log")}`
30466
+ `Frpc config: ${import_node_path30.default.join(config.dataDir, "frpc.toml")}`,
30467
+ `Frpc log: ${import_node_path30.default.join(config.dataDir, "frpc.log")}`
30255
30468
  ];
30256
30469
  const width = Math.max(...lines.map((l) => l.length));
30257
30470
  const bar = "\u2550".repeat(width + 4);
@@ -30264,8 +30477,8 @@ ${bar}
30264
30477
 
30265
30478
  `);
30266
30479
  try {
30267
- const connectPath = import_node_path28.default.join(config.dataDir, "connect.txt");
30268
- import_node_fs25.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
30480
+ const connectPath = import_node_path30.default.join(config.dataDir, "connect.txt");
30481
+ import_node_fs27.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
30269
30482
  } catch {
30270
30483
  }
30271
30484
  } catch (err) {
@@ -30290,8 +30503,29 @@ ${bar}
30290
30503
  logger.warn("tunnel unavailable, degraded to local mode", { reason: tunnelError });
30291
30504
  }
30292
30505
  }
30506
+ const tickAttachmentGc = () => {
30507
+ try {
30508
+ runAttachmentGc({
30509
+ dataDir: config.dataDir,
30510
+ groupFileStore,
30511
+ sessionScopes: manager.listOwnedSessionScopes().map(({ scope, sessionId }) => ({
30512
+ scope,
30513
+ sessionId
30514
+ })),
30515
+ // 注入 cwd 让 GC 直接定位 .attachments 目录(与 upload 落盘点一致)
30516
+ getSessionCwd: (sid) => manager.findOwnedSession(sid)?.cwd ?? null,
30517
+ logger
30518
+ });
30519
+ } catch (err) {
30520
+ logger.warn("attachment gc tick failed", { err: err.message });
30521
+ }
30522
+ };
30523
+ setImmediate(tickAttachmentGc);
30524
+ const attachmentGcInterval = setInterval(tickAttachmentGc, 24 * 3600 * 1e3);
30525
+ attachmentGcInterval.unref();
30293
30526
  const shutdown = async () => {
30294
30527
  logger.info("stopping clawd");
30528
+ clearInterval(attachmentGcInterval);
30295
30529
  observer.stopAll();
30296
30530
  manager.stopAll();
30297
30531
  if (tunnelMgr) {
@@ -30309,9 +30543,9 @@ ${bar}
30309
30543
  };
30310
30544
  }
30311
30545
  function migrateDropPersonsDir(dataDir) {
30312
- const dir = import_node_path28.default.join(dataDir, "persons");
30546
+ const dir = import_node_path30.default.join(dataDir, "persons");
30313
30547
  try {
30314
- import_node_fs25.default.rmSync(dir, { recursive: true, force: true });
30548
+ import_node_fs27.default.rmSync(dir, { recursive: true, force: true });
30315
30549
  } catch {
30316
30550
  }
30317
30551
  }