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

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 +599 -242
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -651,8 +651,8 @@ var init_parseUtil = __esm({
651
651
  init_errors2();
652
652
  init_en();
653
653
  makeIssue = (params) => {
654
- const { data, path: path44, errorMaps, issueData } = params;
655
- const fullPath = [...path44, ...issueData.path || []];
654
+ const { data, path: path46, errorMaps, issueData } = params;
655
+ const fullPath = [...path46, ...issueData.path || []];
656
656
  const fullIssue = {
657
657
  ...issueData,
658
658
  path: fullPath
@@ -963,11 +963,11 @@ var init_types = __esm({
963
963
  init_parseUtil();
964
964
  init_util();
965
965
  ParseInputLazyPath = class {
966
- constructor(parent, value, path44, key) {
966
+ constructor(parent, value, path46, key) {
967
967
  this._cachedPath = [];
968
968
  this.parent = parent;
969
969
  this.data = value;
970
- this._path = path44;
970
+ this._path = path46;
971
971
  this._key = key;
972
972
  }
973
973
  get path() {
@@ -4351,13 +4351,13 @@ var init_zod = __esm({
4351
4351
  });
4352
4352
 
4353
4353
  // ../protocol/src/attachment-schemas.ts
4354
- var TOKEN_ROLES, GROUP_FILE_SOURCES, GroupFileEntrySchema, AttachmentSignUrlArgs, AttachmentSignUrlResponseSchema, AttachmentGroupAddArgs, AttachmentGroupAddResponseSchema, AttachmentGroupRemoveArgs, AttachmentGroupRemoveResponseSchema, AttachmentGroupListArgs, AttachmentGroupListResponseSchema;
4354
+ var TOKEN_ROLES, GROUP_FILE_SOURCES, GroupFileEntrySchema, AttachmentSignUrlArgs, AttachmentSignUrlResponseSchema, AttachmentGroupAddArgs, AttachmentGroupAddResponseSchema, AttachmentGroupRemoveArgs, AttachmentGroupRemoveResponseSchema, AttachmentGroupListArgs, AttachmentGroupListResponseSchema, AttachmentUploadResponseSchema;
4355
4355
  var init_attachment_schemas = __esm({
4356
4356
  "../protocol/src/attachment-schemas.ts"() {
4357
4357
  "use strict";
4358
4358
  init_zod();
4359
4359
  TOKEN_ROLES = ["owner"];
4360
- GROUP_FILE_SOURCES = ["agent", "owner"];
4360
+ GROUP_FILE_SOURCES = ["agent", "owner", "user-upload"];
4361
4361
  GroupFileEntrySchema = external_exports.object({
4362
4362
  /** daemon 派发的稳定 id(用于 RPC remove / UI key) */
4363
4363
  id: external_exports.string().min(1),
@@ -4412,6 +4412,16 @@ var init_attachment_schemas = __esm({
4412
4412
  AttachmentGroupListResponseSchema = external_exports.object({
4413
4413
  entries: external_exports.array(GroupFileEntrySchema)
4414
4414
  });
4415
+ AttachmentUploadResponseSchema = external_exports.object({
4416
+ /** sha256(bytes) 前 16 位,前端持有的稳定 ref */
4417
+ attachmentId: external_exports.string().min(1),
4418
+ /** 完整 tunnel signed URL,前端拼 marker / 直接送 Anthropic 都用它 */
4419
+ url: external_exports.string().min(1),
4420
+ /** 落在 session 目录下的相对路径(后续 group RPC / drawer 展示用) */
4421
+ relPath: external_exports.string().min(1),
4422
+ sizeBytes: external_exports.number().int().nonnegative(),
4423
+ mimeType: external_exports.string().min(1)
4424
+ });
4415
4425
  }
4416
4426
  });
4417
4427
 
@@ -5625,8 +5635,8 @@ var require_req = __commonJS({
5625
5635
  if (req.originalUrl) {
5626
5636
  _req.url = req.originalUrl;
5627
5637
  } else {
5628
- const path44 = req.path;
5629
- _req.url = typeof path44 === "string" ? path44 : req.url ? req.url.path || req.url : void 0;
5638
+ const path46 = req.path;
5639
+ _req.url = typeof path46 === "string" ? path46 : req.url ? req.url.path || req.url : void 0;
5630
5640
  }
5631
5641
  if (req.query) {
5632
5642
  _req.query = req.query;
@@ -5791,14 +5801,14 @@ var require_redact = __commonJS({
5791
5801
  }
5792
5802
  return obj;
5793
5803
  }
5794
- function parsePath(path44) {
5804
+ function parsePath(path46) {
5795
5805
  const parts = [];
5796
5806
  let current = "";
5797
5807
  let inBrackets = false;
5798
5808
  let inQuotes = false;
5799
5809
  let quoteChar = "";
5800
- for (let i = 0; i < path44.length; i++) {
5801
- const char = path44[i];
5810
+ for (let i = 0; i < path46.length; i++) {
5811
+ const char = path46[i];
5802
5812
  if (!inBrackets && char === ".") {
5803
5813
  if (current) {
5804
5814
  parts.push(current);
@@ -5929,10 +5939,10 @@ var require_redact = __commonJS({
5929
5939
  return current;
5930
5940
  }
5931
5941
  function redactPaths(obj, paths, censor, remove = false) {
5932
- for (const path44 of paths) {
5933
- const parts = parsePath(path44);
5942
+ for (const path46 of paths) {
5943
+ const parts = parsePath(path46);
5934
5944
  if (parts.includes("*")) {
5935
- redactWildcardPath(obj, parts, censor, path44, remove);
5945
+ redactWildcardPath(obj, parts, censor, path46, remove);
5936
5946
  } else {
5937
5947
  if (remove) {
5938
5948
  removeKey(obj, parts);
@@ -6017,8 +6027,8 @@ var require_redact = __commonJS({
6017
6027
  }
6018
6028
  } else {
6019
6029
  if (afterWildcard.includes("*")) {
6020
- const wrappedCensor = typeof censor === "function" ? (value, path44) => {
6021
- const fullPath = [...pathArray.slice(0, pathLength), ...path44];
6030
+ const wrappedCensor = typeof censor === "function" ? (value, path46) => {
6031
+ const fullPath = [...pathArray.slice(0, pathLength), ...path46];
6022
6032
  return censor(value, fullPath);
6023
6033
  } : censor;
6024
6034
  redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
@@ -6053,8 +6063,8 @@ var require_redact = __commonJS({
6053
6063
  return null;
6054
6064
  }
6055
6065
  const pathStructure = /* @__PURE__ */ new Map();
6056
- for (const path44 of pathsToClone) {
6057
- const parts = parsePath(path44);
6066
+ for (const path46 of pathsToClone) {
6067
+ const parts = parsePath(path46);
6058
6068
  let current = pathStructure;
6059
6069
  for (let i = 0; i < parts.length; i++) {
6060
6070
  const part = parts[i];
@@ -6106,24 +6116,24 @@ var require_redact = __commonJS({
6106
6116
  }
6107
6117
  return cloneSelectively(obj, pathStructure);
6108
6118
  }
6109
- function validatePath(path44) {
6110
- if (typeof path44 !== "string") {
6119
+ function validatePath(path46) {
6120
+ if (typeof path46 !== "string") {
6111
6121
  throw new Error("Paths must be (non-empty) strings");
6112
6122
  }
6113
- if (path44 === "") {
6123
+ if (path46 === "") {
6114
6124
  throw new Error("Invalid redaction path ()");
6115
6125
  }
6116
- if (path44.includes("..")) {
6117
- throw new Error(`Invalid redaction path (${path44})`);
6126
+ if (path46.includes("..")) {
6127
+ throw new Error(`Invalid redaction path (${path46})`);
6118
6128
  }
6119
- if (path44.includes(",")) {
6120
- throw new Error(`Invalid redaction path (${path44})`);
6129
+ if (path46.includes(",")) {
6130
+ throw new Error(`Invalid redaction path (${path46})`);
6121
6131
  }
6122
6132
  let bracketCount = 0;
6123
6133
  let inQuotes = false;
6124
6134
  let quoteChar = "";
6125
- for (let i = 0; i < path44.length; i++) {
6126
- const char = path44[i];
6135
+ for (let i = 0; i < path46.length; i++) {
6136
+ const char = path46[i];
6127
6137
  if ((char === '"' || char === "'") && bracketCount > 0) {
6128
6138
  if (!inQuotes) {
6129
6139
  inQuotes = true;
@@ -6137,20 +6147,20 @@ var require_redact = __commonJS({
6137
6147
  } else if (char === "]" && !inQuotes) {
6138
6148
  bracketCount--;
6139
6149
  if (bracketCount < 0) {
6140
- throw new Error(`Invalid redaction path (${path44})`);
6150
+ throw new Error(`Invalid redaction path (${path46})`);
6141
6151
  }
6142
6152
  }
6143
6153
  }
6144
6154
  if (bracketCount !== 0) {
6145
- throw new Error(`Invalid redaction path (${path44})`);
6155
+ throw new Error(`Invalid redaction path (${path46})`);
6146
6156
  }
6147
6157
  }
6148
6158
  function validatePaths(paths) {
6149
6159
  if (!Array.isArray(paths)) {
6150
6160
  throw new TypeError("paths must be an array");
6151
6161
  }
6152
- for (const path44 of paths) {
6153
- validatePath(path44);
6162
+ for (const path46 of paths) {
6163
+ validatePath(path46);
6154
6164
  }
6155
6165
  }
6156
6166
  function slowRedact(options = {}) {
@@ -6318,8 +6328,8 @@ var require_redaction = __commonJS({
6318
6328
  if (shape[k2] === null) {
6319
6329
  o[k2] = (value) => topCensor(value, [k2]);
6320
6330
  } else {
6321
- const wrappedCensor = typeof censor === "function" ? (value, path44) => {
6322
- return censor(value, [k2, ...path44]);
6331
+ const wrappedCensor = typeof censor === "function" ? (value, path46) => {
6332
+ return censor(value, [k2, ...path46]);
6323
6333
  } : censor;
6324
6334
  o[k2] = Redact({
6325
6335
  paths: shape[k2],
@@ -6537,10 +6547,10 @@ var require_atomic_sleep = __commonJS({
6537
6547
  var require_sonic_boom = __commonJS({
6538
6548
  "../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
6539
6549
  "use strict";
6540
- var fs35 = require("fs");
6550
+ var fs37 = require("fs");
6541
6551
  var EventEmitter2 = require("events");
6542
6552
  var inherits = require("util").inherits;
6543
- var path44 = require("path");
6553
+ var path46 = require("path");
6544
6554
  var sleep = require_atomic_sleep();
6545
6555
  var assert = require("assert");
6546
6556
  var BUSY_WRITE_TIMEOUT = 100;
@@ -6594,20 +6604,20 @@ var require_sonic_boom = __commonJS({
6594
6604
  const mode = sonic.mode;
6595
6605
  if (sonic.sync) {
6596
6606
  try {
6597
- if (sonic.mkdir) fs35.mkdirSync(path44.dirname(file), { recursive: true });
6598
- const fd = fs35.openSync(file, flags, mode);
6607
+ if (sonic.mkdir) fs37.mkdirSync(path46.dirname(file), { recursive: true });
6608
+ const fd = fs37.openSync(file, flags, mode);
6599
6609
  fileOpened(null, fd);
6600
6610
  } catch (err) {
6601
6611
  fileOpened(err);
6602
6612
  throw err;
6603
6613
  }
6604
6614
  } else if (sonic.mkdir) {
6605
- fs35.mkdir(path44.dirname(file), { recursive: true }, (err) => {
6615
+ fs37.mkdir(path46.dirname(file), { recursive: true }, (err) => {
6606
6616
  if (err) return fileOpened(err);
6607
- fs35.open(file, flags, mode, fileOpened);
6617
+ fs37.open(file, flags, mode, fileOpened);
6608
6618
  });
6609
6619
  } else {
6610
- fs35.open(file, flags, mode, fileOpened);
6620
+ fs37.open(file, flags, mode, fileOpened);
6611
6621
  }
6612
6622
  }
6613
6623
  function SonicBoom(opts) {
@@ -6648,8 +6658,8 @@ var require_sonic_boom = __commonJS({
6648
6658
  this.flush = flushBuffer;
6649
6659
  this.flushSync = flushBufferSync;
6650
6660
  this._actualWrite = actualWriteBuffer;
6651
- fsWriteSync = () => fs35.writeSync(this.fd, this._writingBuf);
6652
- fsWrite = () => fs35.write(this.fd, this._writingBuf, this.release);
6661
+ fsWriteSync = () => fs37.writeSync(this.fd, this._writingBuf);
6662
+ fsWrite = () => fs37.write(this.fd, this._writingBuf, this.release);
6653
6663
  } else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
6654
6664
  this._writingBuf = "";
6655
6665
  this.write = write;
@@ -6658,15 +6668,15 @@ var require_sonic_boom = __commonJS({
6658
6668
  this._actualWrite = actualWrite;
6659
6669
  fsWriteSync = () => {
6660
6670
  if (Buffer.isBuffer(this._writingBuf)) {
6661
- return fs35.writeSync(this.fd, this._writingBuf);
6671
+ return fs37.writeSync(this.fd, this._writingBuf);
6662
6672
  }
6663
- return fs35.writeSync(this.fd, this._writingBuf, "utf8");
6673
+ return fs37.writeSync(this.fd, this._writingBuf, "utf8");
6664
6674
  };
6665
6675
  fsWrite = () => {
6666
6676
  if (Buffer.isBuffer(this._writingBuf)) {
6667
- return fs35.write(this.fd, this._writingBuf, this.release);
6677
+ return fs37.write(this.fd, this._writingBuf, this.release);
6668
6678
  }
6669
- return fs35.write(this.fd, this._writingBuf, "utf8", this.release);
6679
+ return fs37.write(this.fd, this._writingBuf, "utf8", this.release);
6670
6680
  };
6671
6681
  } else {
6672
6682
  throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
@@ -6723,7 +6733,7 @@ var require_sonic_boom = __commonJS({
6723
6733
  }
6724
6734
  }
6725
6735
  if (this._fsync) {
6726
- fs35.fsyncSync(this.fd);
6736
+ fs37.fsyncSync(this.fd);
6727
6737
  }
6728
6738
  const len = this._len;
6729
6739
  if (this._reopening) {
@@ -6837,7 +6847,7 @@ var require_sonic_boom = __commonJS({
6837
6847
  const onDrain = () => {
6838
6848
  if (!this._fsync) {
6839
6849
  try {
6840
- fs35.fsync(this.fd, (err) => {
6850
+ fs37.fsync(this.fd, (err) => {
6841
6851
  this._flushPending = false;
6842
6852
  cb(err);
6843
6853
  });
@@ -6939,7 +6949,7 @@ var require_sonic_boom = __commonJS({
6939
6949
  const fd = this.fd;
6940
6950
  this.once("ready", () => {
6941
6951
  if (fd !== this.fd) {
6942
- fs35.close(fd, (err) => {
6952
+ fs37.close(fd, (err) => {
6943
6953
  if (err) {
6944
6954
  return this.emit("error", err);
6945
6955
  }
@@ -6988,7 +6998,7 @@ var require_sonic_boom = __commonJS({
6988
6998
  buf = this._bufs[0];
6989
6999
  }
6990
7000
  try {
6991
- const n = Buffer.isBuffer(buf) ? fs35.writeSync(this.fd, buf) : fs35.writeSync(this.fd, buf, "utf8");
7001
+ const n = Buffer.isBuffer(buf) ? fs37.writeSync(this.fd, buf) : fs37.writeSync(this.fd, buf, "utf8");
6992
7002
  const releasedBufObj = releaseWritingBuf(buf, this._len, n);
6993
7003
  buf = releasedBufObj.writingBuf;
6994
7004
  this._len = releasedBufObj.len;
@@ -7004,7 +7014,7 @@ var require_sonic_boom = __commonJS({
7004
7014
  }
7005
7015
  }
7006
7016
  try {
7007
- fs35.fsyncSync(this.fd);
7017
+ fs37.fsyncSync(this.fd);
7008
7018
  } catch {
7009
7019
  }
7010
7020
  }
@@ -7025,7 +7035,7 @@ var require_sonic_boom = __commonJS({
7025
7035
  buf = mergeBuf(this._bufs[0], this._lens[0]);
7026
7036
  }
7027
7037
  try {
7028
- const n = fs35.writeSync(this.fd, buf);
7038
+ const n = fs37.writeSync(this.fd, buf);
7029
7039
  buf = buf.subarray(n);
7030
7040
  this._len = Math.max(this._len - n, 0);
7031
7041
  if (buf.length <= 0) {
@@ -7053,13 +7063,13 @@ var require_sonic_boom = __commonJS({
7053
7063
  this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
7054
7064
  if (this.sync) {
7055
7065
  try {
7056
- const written = Buffer.isBuffer(this._writingBuf) ? fs35.writeSync(this.fd, this._writingBuf) : fs35.writeSync(this.fd, this._writingBuf, "utf8");
7066
+ const written = Buffer.isBuffer(this._writingBuf) ? fs37.writeSync(this.fd, this._writingBuf) : fs37.writeSync(this.fd, this._writingBuf, "utf8");
7057
7067
  release(null, written);
7058
7068
  } catch (err) {
7059
7069
  release(err);
7060
7070
  }
7061
7071
  } else {
7062
- fs35.write(this.fd, this._writingBuf, release);
7072
+ fs37.write(this.fd, this._writingBuf, release);
7063
7073
  }
7064
7074
  }
7065
7075
  function actualWriteBuffer() {
@@ -7068,7 +7078,7 @@ var require_sonic_boom = __commonJS({
7068
7078
  this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
7069
7079
  if (this.sync) {
7070
7080
  try {
7071
- const written = fs35.writeSync(this.fd, this._writingBuf);
7081
+ const written = fs37.writeSync(this.fd, this._writingBuf);
7072
7082
  release(null, written);
7073
7083
  } catch (err) {
7074
7084
  release(err);
@@ -7077,7 +7087,7 @@ var require_sonic_boom = __commonJS({
7077
7087
  if (kCopyBuffer) {
7078
7088
  this._writingBuf = Buffer.from(this._writingBuf);
7079
7089
  }
7080
- fs35.write(this.fd, this._writingBuf, release);
7090
+ fs37.write(this.fd, this._writingBuf, release);
7081
7091
  }
7082
7092
  }
7083
7093
  function actualClose(sonic) {
@@ -7093,12 +7103,12 @@ var require_sonic_boom = __commonJS({
7093
7103
  sonic._lens = [];
7094
7104
  assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
7095
7105
  try {
7096
- fs35.fsync(sonic.fd, closeWrapped);
7106
+ fs37.fsync(sonic.fd, closeWrapped);
7097
7107
  } catch {
7098
7108
  }
7099
7109
  function closeWrapped() {
7100
7110
  if (sonic.fd !== 1 && sonic.fd !== 2) {
7101
- fs35.close(sonic.fd, done);
7111
+ fs37.close(sonic.fd, done);
7102
7112
  } else {
7103
7113
  done();
7104
7114
  }
@@ -7829,7 +7839,7 @@ var require_transport = __commonJS({
7829
7839
  stream.flushSync();
7830
7840
  }
7831
7841
  function transport(fullOptions) {
7832
- const { pipeline: pipeline2, targets, levels, dedupe, worker = {}, caller = getCallers(), sync = false } = fullOptions;
7842
+ const { pipeline: pipeline3, targets, levels, dedupe, worker = {}, caller = getCallers(), sync = false } = fullOptions;
7833
7843
  const options = {
7834
7844
  ...fullOptions.options
7835
7845
  };
@@ -7857,9 +7867,9 @@ var require_transport = __commonJS({
7857
7867
  };
7858
7868
  });
7859
7869
  });
7860
- } else if (pipeline2) {
7870
+ } else if (pipeline3) {
7861
7871
  target = bundlerOverrides["pino-worker"] || join11(__dirname, "worker.js");
7862
- options.pipelines = [pipeline2.map((dest) => {
7872
+ options.pipelines = [pipeline3.map((dest) => {
7863
7873
  return {
7864
7874
  ...dest,
7865
7875
  target: fixTarget(dest.target)
@@ -10233,11 +10243,11 @@ var init_lib = __esm({
10233
10243
  }
10234
10244
  }
10235
10245
  },
10236
- addToPath: function addToPath(path44, added, removed, oldPosInc, options) {
10237
- var last = path44.lastComponent;
10246
+ addToPath: function addToPath(path46, added, removed, oldPosInc, options) {
10247
+ var last = path46.lastComponent;
10238
10248
  if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
10239
10249
  return {
10240
- oldPos: path44.oldPos + oldPosInc,
10250
+ oldPos: path46.oldPos + oldPosInc,
10241
10251
  lastComponent: {
10242
10252
  count: last.count + 1,
10243
10253
  added,
@@ -10247,7 +10257,7 @@ var init_lib = __esm({
10247
10257
  };
10248
10258
  } else {
10249
10259
  return {
10250
- oldPos: path44.oldPos + oldPosInc,
10260
+ oldPos: path46.oldPos + oldPosInc,
10251
10261
  lastComponent: {
10252
10262
  count: 1,
10253
10263
  added,
@@ -10678,10 +10688,10 @@ function attachmentToHistoryMessage(o, ts) {
10678
10688
  const memories = raw.map((m2) => {
10679
10689
  if (!m2 || typeof m2 !== "object") return null;
10680
10690
  const rec = m2;
10681
- const path44 = typeof rec.path === "string" ? rec.path : null;
10691
+ const path46 = typeof rec.path === "string" ? rec.path : null;
10682
10692
  const content = typeof rec.content === "string" ? rec.content : null;
10683
- if (!path44 || content == null) return null;
10684
- const entry = { path: path44, content };
10693
+ if (!path46 || content == null) return null;
10694
+ const entry = { path: path46, content };
10685
10695
  if (typeof rec.mtimeMs === "number") entry.mtimeMs = rec.mtimeMs;
10686
10696
  return entry;
10687
10697
  }).filter((m2) => m2 !== null);
@@ -11507,10 +11517,10 @@ function parseAttachment(obj) {
11507
11517
  const memories = raw.map((m2) => {
11508
11518
  if (!m2 || typeof m2 !== "object") return null;
11509
11519
  const rec = m2;
11510
- const path44 = typeof rec.path === "string" ? rec.path : null;
11520
+ const path46 = typeof rec.path === "string" ? rec.path : null;
11511
11521
  const content = typeof rec.content === "string" ? rec.content : null;
11512
- if (!path44 || content == null) return null;
11513
- const out = { path: path44, content };
11522
+ if (!path46 || content == null) return null;
11523
+ const out = { path: path46, content };
11514
11524
  if (typeof rec.mtimeMs === "number") out.mtimeMs = rec.mtimeMs;
11515
11525
  return out;
11516
11526
  }).filter((m2) => m2 !== null);
@@ -20425,7 +20435,7 @@ var require_websocket_server = __commonJS({
20425
20435
  // src/run-case/recorder.ts
20426
20436
  function startRunCaseRecorder(opts) {
20427
20437
  const now = opts.now ?? Date.now;
20428
- const dir = import_node_path30.default.dirname(opts.recordPath);
20438
+ const dir = import_node_path32.default.dirname(opts.recordPath);
20429
20439
  let stream = null;
20430
20440
  let closing = false;
20431
20441
  let closedSettled = false;
@@ -20439,8 +20449,8 @@ function startRunCaseRecorder(opts) {
20439
20449
  });
20440
20450
  const ensureStream = () => {
20441
20451
  if (stream) return stream;
20442
- import_node_fs27.default.mkdirSync(dir, { recursive: true });
20443
- stream = import_node_fs27.default.createWriteStream(opts.recordPath, { flags: "a" });
20452
+ import_node_fs29.default.mkdirSync(dir, { recursive: true });
20453
+ stream = import_node_fs29.default.createWriteStream(opts.recordPath, { flags: "a" });
20444
20454
  stream.on("close", () => closedResolve());
20445
20455
  return stream;
20446
20456
  };
@@ -20465,12 +20475,12 @@ function startRunCaseRecorder(opts) {
20465
20475
  };
20466
20476
  return { tap, close, closed };
20467
20477
  }
20468
- var import_node_fs27, import_node_path30;
20478
+ var import_node_fs29, import_node_path32;
20469
20479
  var init_recorder = __esm({
20470
20480
  "src/run-case/recorder.ts"() {
20471
20481
  "use strict";
20472
- import_node_fs27 = __toESM(require("fs"), 1);
20473
- import_node_path30 = __toESM(require("path"), 1);
20482
+ import_node_fs29 = __toESM(require("fs"), 1);
20483
+ import_node_path32 = __toESM(require("path"), 1);
20474
20484
  }
20475
20485
  });
20476
20486
 
@@ -20513,7 +20523,7 @@ var init_wire = __esm({
20513
20523
  // src/run-case/controller.ts
20514
20524
  async function runController(opts) {
20515
20525
  const now = opts.now ?? Date.now;
20516
- const cwd = opts.cwd ?? (0, import_node_fs28.mkdtempSync)(import_node_path31.default.join(import_node_os15.default.tmpdir(), "clawd-runcase-"));
20526
+ const cwd = opts.cwd ?? (0, import_node_fs30.mkdtempSync)(import_node_path33.default.join(import_node_os15.default.tmpdir(), "clawd-runcase-"));
20517
20527
  const ownsCwd = opts.cwd === void 0;
20518
20528
  const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
20519
20529
  const spawnCtx = { cwd };
@@ -20674,19 +20684,19 @@ async function runController(opts) {
20674
20684
  if (sigintHandler) process.off("SIGINT", sigintHandler);
20675
20685
  if (ownsCwd) {
20676
20686
  try {
20677
- (0, import_node_fs28.rmSync)(cwd, { recursive: true, force: true });
20687
+ (0, import_node_fs30.rmSync)(cwd, { recursive: true, force: true });
20678
20688
  } catch {
20679
20689
  }
20680
20690
  }
20681
20691
  return exitCode ?? 0;
20682
20692
  }
20683
- var import_node_fs28, import_node_os15, import_node_path31;
20693
+ var import_node_fs30, import_node_os15, import_node_path33;
20684
20694
  var init_controller = __esm({
20685
20695
  "src/run-case/controller.ts"() {
20686
20696
  "use strict";
20687
- import_node_fs28 = require("fs");
20697
+ import_node_fs30 = require("fs");
20688
20698
  import_node_os15 = __toESM(require("os"), 1);
20689
- import_node_path31 = __toESM(require("path"), 1);
20699
+ import_node_path33 = __toESM(require("path"), 1);
20690
20700
  init_claude();
20691
20701
  init_stdout_splitter();
20692
20702
  init_permission_stdio();
@@ -20918,8 +20928,8 @@ Env (advanced):
20918
20928
  `;
20919
20929
 
20920
20930
  // src/index.ts
20921
- var import_node_path29 = __toESM(require("path"), 1);
20922
- var import_node_fs26 = __toESM(require("fs"), 1);
20931
+ var import_node_path31 = __toESM(require("path"), 1);
20932
+ var import_node_fs28 = __toESM(require("fs"), 1);
20923
20933
 
20924
20934
  // src/logger.ts
20925
20935
  var import_node_fs2 = __toESM(require("fs"), 1);
@@ -22654,6 +22664,18 @@ var SessionManager = class {
22654
22664
  (a, b2) => a.updatedAt > b2.updatedAt ? -1 : a.updatedAt < b2.updatedAt ? 1 : 0
22655
22665
  );
22656
22666
  }
22667
+ /**
22668
+ * 跨 scope 列出所有 owner 视角的 session(default + persona owner + persona guest),
22669
+ * 附带派生的 SessionScope。attachment GC tick 用 —— 需要遍历所有活跃 session 的
22670
+ * .attachments 目录 + 群文件清单求 liveSet。
22671
+ */
22672
+ listOwnedSessionScopes() {
22673
+ return this.listAllOwned().map((file) => ({
22674
+ scope: this.scopeForFile(file),
22675
+ sessionId: file.sessionId,
22676
+ cwd: file.cwd
22677
+ }));
22678
+ }
22657
22679
  // 写回 SessionFile,自动根据 ownerPersonaId / creatorPrincipalId 派生写入 scope.
22658
22680
  // 调用方原先都是 `deps.store.write(updated)`,全部走这里收口路由.
22659
22681
  writeOwned(file) {
@@ -22925,13 +22947,11 @@ var SessionManager = class {
22925
22947
  const { ownerPersonaId: _stripped, ...rest } = file;
22926
22948
  return this.deps.store.write(rest);
22927
22949
  }
22928
- // mirror peer sessions:删本机 mirror SessionFile。**跟 listAllOwned 对称扫所有 scope**
22929
- // (default + persona/owner + persona/guest)。光删 deps.store 会有 store.delete 返回
22930
- // false handler ack `peerSession:remove:ok` 的假象(文件落到 persona scope 时
22931
- // listAllOwned 还能扫出来 → sidebar 刷新页面 mirror 复活)。deleteOwned 跟 listAllOwned
22932
- // 一对,对账齐全。idempotent:找不到不 throw。**不广播 session:deleted**。
22950
+ // mirror peer sessions:删本机 mirror SessionFile。mirror 一律落 default(mirrorUpsert
22951
+ // 强制走 deps.store + strip ownerPersonaId),删也只删 default。idempotent:找不到
22952
+ // throw(store.delete 内部 ENOENT 兜底返 false throw)。**不广播 session:deleted**。
22933
22953
  mirrorRemove(sessionId) {
22934
- this.deleteOwned(sessionId);
22954
+ this.deps.store.delete(sessionId);
22935
22955
  }
22936
22956
  // observer 上报"jsonl 写入了一条 user 行"时调用:在 buffer 里找到与之对应的、
22937
22957
  // 还没建立映射的最早一条 synth user_text,记下 synth → real 映射。
@@ -27170,8 +27190,8 @@ function rmdirIfEmpty(p2) {
27170
27190
  }
27171
27191
 
27172
27192
  // src/transport/http-router.ts
27173
- var import_node_fs15 = __toESM(require("fs"), 1);
27174
- var import_node_path17 = __toESM(require("path"), 1);
27193
+ var import_node_fs16 = __toESM(require("fs"), 1);
27194
+ var import_node_path18 = __toESM(require("path"), 1);
27175
27195
 
27176
27196
  // src/attachment/mime.ts
27177
27197
  var import_node_path16 = __toESM(require("path"), 1);
@@ -27349,7 +27369,137 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
27349
27369
  return { ok: true, absPath };
27350
27370
  }
27351
27371
 
27372
+ // src/attachment/upload.ts
27373
+ var import_node_fs15 = __toESM(require("fs"), 1);
27374
+ var import_node_path17 = __toESM(require("path"), 1);
27375
+ var import_node_crypto6 = __toESM(require("crypto"), 1);
27376
+ var import_promises = require("stream/promises");
27377
+ var UploadError = class extends Error {
27378
+ constructor(code, message) {
27379
+ super(message);
27380
+ this.code = code;
27381
+ this.name = "UploadError";
27382
+ }
27383
+ code;
27384
+ };
27385
+ function assertValidFileName(fileName) {
27386
+ if (fileName.length === 0 || fileName === "." || fileName === ".." || fileName.startsWith(".") || fileName.includes("/") || fileName.includes("\\") || fileName !== import_node_path17.default.basename(fileName)) {
27387
+ throw new UploadError("INVALID_FILENAME", `fileName must be a plain basename, got: ${fileName}`);
27388
+ }
27389
+ }
27390
+ var HASH_PREFIX_LEN = 16;
27391
+ async function writeUploadedAttachment(args) {
27392
+ assertValidFileName(args.fileName);
27393
+ const attachmentsRoot = import_node_path17.default.join(args.sessionDir, ".attachments");
27394
+ try {
27395
+ import_node_fs15.default.mkdirSync(attachmentsRoot, { recursive: true });
27396
+ } catch (err) {
27397
+ throw new UploadError("STORAGE_ERROR", `mkdir failed: ${err.message}`);
27398
+ }
27399
+ const hasher = import_node_crypto6.default.createHash("sha256");
27400
+ let actualSize = 0;
27401
+ const tmpPath = import_node_path17.default.join(
27402
+ attachmentsRoot,
27403
+ `.upload-${process.pid}-${Date.now()}-${import_node_crypto6.default.randomBytes(4).toString("hex")}`
27404
+ );
27405
+ try {
27406
+ await (0, import_promises.pipeline)(
27407
+ args.body,
27408
+ async function* (source) {
27409
+ for await (const chunk of source) {
27410
+ const buf = chunk;
27411
+ actualSize += buf.length;
27412
+ hasher.update(buf);
27413
+ yield buf;
27414
+ }
27415
+ },
27416
+ import_node_fs15.default.createWriteStream(tmpPath, { mode: 384 })
27417
+ );
27418
+ } catch (err) {
27419
+ try {
27420
+ import_node_fs15.default.unlinkSync(tmpPath);
27421
+ } catch {
27422
+ }
27423
+ throw new UploadError("STORAGE_ERROR", `write failed: ${err.message}`);
27424
+ }
27425
+ if (actualSize !== args.contentLength) {
27426
+ try {
27427
+ import_node_fs15.default.unlinkSync(tmpPath);
27428
+ } catch {
27429
+ }
27430
+ throw new UploadError(
27431
+ "SIZE_MISMATCH",
27432
+ `expected ${args.contentLength} bytes, got ${actualSize}`
27433
+ );
27434
+ }
27435
+ const attachmentId = hasher.digest("hex").slice(0, HASH_PREFIX_LEN);
27436
+ const hashDir = import_node_path17.default.join(attachmentsRoot, attachmentId);
27437
+ let finalFileName;
27438
+ let hashDirExists = false;
27439
+ try {
27440
+ hashDirExists = import_node_fs15.default.statSync(hashDir).isDirectory();
27441
+ } catch {
27442
+ }
27443
+ if (hashDirExists) {
27444
+ const existing = import_node_fs15.default.readdirSync(hashDir).filter((n) => !n.startsWith("."));
27445
+ finalFileName = existing[0] ?? args.fileName;
27446
+ try {
27447
+ import_node_fs15.default.unlinkSync(tmpPath);
27448
+ } catch {
27449
+ }
27450
+ } else {
27451
+ try {
27452
+ import_node_fs15.default.mkdirSync(hashDir, { recursive: true });
27453
+ finalFileName = args.fileName;
27454
+ import_node_fs15.default.renameSync(tmpPath, import_node_path17.default.join(hashDir, finalFileName));
27455
+ } catch (err) {
27456
+ try {
27457
+ import_node_fs15.default.unlinkSync(tmpPath);
27458
+ } catch {
27459
+ }
27460
+ throw new UploadError("STORAGE_ERROR", `rename failed: ${err.message}`);
27461
+ }
27462
+ }
27463
+ const absPath = import_node_path17.default.join(hashDir, finalFileName);
27464
+ const relPath = import_node_path17.default.relative(args.sessionDir, absPath);
27465
+ args.groupFileStore.upsert(args.scope, args.sessionId, {
27466
+ relPath: absPath,
27467
+ // 存绝对路径,与现有 agent 入清单的形态一致(attachment.ts:144 双形态兼容)
27468
+ from: "user-upload",
27469
+ label: args.fileName,
27470
+ size: actualSize,
27471
+ mime: args.mimeType
27472
+ });
27473
+ return { attachmentId, absPath, relPath, sizeBytes: actualSize, mimeType: args.mimeType };
27474
+ }
27475
+
27352
27476
  // src/transport/http-router.ts
27477
+ var UPLOAD_MIME_ALLOW = /* @__PURE__ */ new Set([
27478
+ "image/png",
27479
+ "image/jpeg",
27480
+ "image/gif",
27481
+ "image/webp",
27482
+ "application/pdf"
27483
+ ]);
27484
+ var UPLOAD_MIME_ALLOW_PREFIX = ["text/"];
27485
+ var UPLOAD_MAX_BYTES = 25 * 1024 * 1024;
27486
+ var UPLOAD_CORS_HEADERS = {
27487
+ "Access-Control-Allow-Origin": "*",
27488
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
27489
+ "Access-Control-Allow-Headers": "Authorization, Content-Type",
27490
+ "Access-Control-Max-Age": "86400"
27491
+ };
27492
+ function isUploadMimeAllowed(mime) {
27493
+ if (UPLOAD_MIME_ALLOW.has(mime)) return true;
27494
+ return UPLOAD_MIME_ALLOW_PREFIX.some((p2) => mime.startsWith(p2));
27495
+ }
27496
+ function isValidUploadFileName(fileName) {
27497
+ if (fileName.length === 0) return false;
27498
+ if (fileName === "." || fileName === "..") return false;
27499
+ if (fileName.startsWith(".")) return false;
27500
+ if (fileName.includes("/") || fileName.includes("\\")) return false;
27501
+ return fileName === import_node_path18.default.basename(fileName);
27502
+ }
27353
27503
  function createHttpRouter(deps) {
27354
27504
  return async (req, res) => {
27355
27505
  const url = parseUrl(req.url);
@@ -27363,9 +27513,14 @@ function createHttpRouter(deps) {
27363
27513
  });
27364
27514
  return true;
27365
27515
  }
27366
- if (!url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/")) {
27516
+ if (!url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/") && url.pathname !== "/attachments/upload") {
27367
27517
  return false;
27368
27518
  }
27519
+ if (url.pathname === "/attachments/upload" && req.method === "OPTIONS") {
27520
+ res.writeHead(204, UPLOAD_CORS_HEADERS);
27521
+ res.end();
27522
+ return true;
27523
+ }
27369
27524
  if (url.pathname.startsWith("/files/") && req.method === "GET") {
27370
27525
  const secret = deps.getSignSecret?.();
27371
27526
  if (!secret) {
@@ -27403,6 +27558,107 @@ function createHttpRouter(deps) {
27403
27558
  sendJson(res, 401, { code: "UNAUTHORIZED", message: "missing or invalid bearer token" });
27404
27559
  return true;
27405
27560
  }
27561
+ if (url.pathname === "/attachments/upload" && req.method === "POST") {
27562
+ const sendUploadJson = (status, body) => sendJson(res, status, body, UPLOAD_CORS_HEADERS);
27563
+ if (ctx.role !== "owner") {
27564
+ sendUploadJson(403, { code: "FORBIDDEN", message: "owner only" });
27565
+ return true;
27566
+ }
27567
+ const up = deps.attachmentUpload;
27568
+ if (!up) {
27569
+ sendUploadJson(501, { code: "NOT_IMPLEMENTED", message: "attachment upload not wired" });
27570
+ return true;
27571
+ }
27572
+ const sessionId = url.searchParams.get("sessionId");
27573
+ const fileName = url.searchParams.get("fileName");
27574
+ const mimeType = url.searchParams.get("mimeType");
27575
+ const contentLengthRaw = req.headers["content-length"];
27576
+ if (!sessionId || !fileName || !mimeType || !contentLengthRaw) {
27577
+ sendUploadJson(400, {
27578
+ code: "INVALID_PARAM",
27579
+ message: "missing sessionId/fileName/mimeType/Content-Length"
27580
+ });
27581
+ return true;
27582
+ }
27583
+ if (!isValidUploadFileName(fileName)) {
27584
+ sendUploadJson(400, {
27585
+ code: "INVALID_FILENAME",
27586
+ message: `fileName must be a plain basename (no path separators, no dotfiles): ${fileName}`
27587
+ });
27588
+ return true;
27589
+ }
27590
+ const contentLength = Number.parseInt(contentLengthRaw, 10);
27591
+ if (!Number.isFinite(contentLength) || contentLength < 0) {
27592
+ sendUploadJson(400, { code: "INVALID_PARAM", message: "malformed Content-Length" });
27593
+ return true;
27594
+ }
27595
+ if (contentLength > UPLOAD_MAX_BYTES) {
27596
+ sendUploadJson(413, {
27597
+ code: "FILE_TOO_LARGE",
27598
+ message: `max ${UPLOAD_MAX_BYTES} bytes`
27599
+ });
27600
+ return true;
27601
+ }
27602
+ if (!isUploadMimeAllowed(mimeType)) {
27603
+ sendUploadJson(400, {
27604
+ code: "UNSUPPORTED_MIME",
27605
+ message: `mime ${mimeType} not allowed`
27606
+ });
27607
+ return true;
27608
+ }
27609
+ const sessionDir = up.getSessionDir(sessionId);
27610
+ const scope = up.getSessionScope(sessionId);
27611
+ if (!sessionDir || !scope) {
27612
+ sendUploadJson(404, { code: "NOT_FOUND", message: `session ${sessionId} not found` });
27613
+ return true;
27614
+ }
27615
+ const httpBaseUrl = up.getHttpBaseUrl();
27616
+ if (!httpBaseUrl) {
27617
+ sendUploadJson(503, {
27618
+ code: "TUNNEL_NOT_READY",
27619
+ message: "httpBaseUrl unavailable"
27620
+ });
27621
+ return true;
27622
+ }
27623
+ const secret = deps.getSignSecret?.();
27624
+ if (!secret) {
27625
+ sendUploadJson(501, {
27626
+ code: "NOT_IMPLEMENTED",
27627
+ message: "sign secret unavailable (noAuth?)"
27628
+ });
27629
+ return true;
27630
+ }
27631
+ try {
27632
+ const result = await writeUploadedAttachment({
27633
+ sessionDir,
27634
+ sessionId,
27635
+ scope,
27636
+ fileName,
27637
+ mimeType,
27638
+ contentLength,
27639
+ body: req,
27640
+ groupFileStore: up.groupFileStore
27641
+ });
27642
+ const parts = signUrlParts(secret, result.absPath, null);
27643
+ const fullUrl = buildSignedFileUrl(httpBaseUrl, parts);
27644
+ sendUploadJson(200, {
27645
+ attachmentId: result.attachmentId,
27646
+ url: fullUrl,
27647
+ relPath: result.relPath,
27648
+ sizeBytes: result.sizeBytes,
27649
+ mimeType: result.mimeType
27650
+ });
27651
+ } catch (err) {
27652
+ if (err instanceof UploadError) {
27653
+ const status = err.code === "SIZE_MISMATCH" ? 400 : 500;
27654
+ sendUploadJson(status, { code: err.code, message: err.message });
27655
+ } else {
27656
+ deps.logger?.warn("attachment upload failed", { err: err.message });
27657
+ sendUploadJson(500, { code: "STORAGE_ERROR", message: err.message });
27658
+ }
27659
+ }
27660
+ return true;
27661
+ }
27406
27662
  const sessionFilesMatch = url.pathname.match(/^\/session\/([^/]+)\/files$/);
27407
27663
  if (sessionFilesMatch && req.method === "GET") {
27408
27664
  const sid = sessionFilesMatch[1];
@@ -27412,7 +27668,7 @@ function createHttpRouter(deps) {
27412
27668
  return true;
27413
27669
  }
27414
27670
  let absPath;
27415
- if (import_node_path17.default.isAbsolute(pathParam)) {
27671
+ if (import_node_path18.default.isAbsolute(pathParam)) {
27416
27672
  absPath = pathParam;
27417
27673
  } else if (deps.sessionStore) {
27418
27674
  const file = deps.sessionStore.read(sid);
@@ -27420,7 +27676,7 @@ function createHttpRouter(deps) {
27420
27676
  sendJson(res, 404, { code: "NOT_FOUND", message: `session ${sid} not found` });
27421
27677
  return true;
27422
27678
  }
27423
- absPath = import_node_path17.default.join(file.cwd, pathParam);
27679
+ absPath = import_node_path18.default.join(file.cwd, pathParam);
27424
27680
  } else {
27425
27681
  sendJson(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "sessionStore not wired" }));
27426
27682
  return true;
@@ -27453,7 +27709,7 @@ function withCtx(ctx, body) {
27453
27709
  function streamFile(res, absPath, logger) {
27454
27710
  let stat;
27455
27711
  try {
27456
- stat = import_node_fs15.default.statSync(absPath);
27712
+ stat = import_node_fs16.default.statSync(absPath);
27457
27713
  } catch (err) {
27458
27714
  const code = err?.code;
27459
27715
  if (code === "ENOENT") {
@@ -27468,7 +27724,7 @@ function streamFile(res, absPath, logger) {
27468
27724
  return;
27469
27725
  }
27470
27726
  const mime = lookupMime(absPath);
27471
- const basename = import_node_path17.default.basename(absPath);
27727
+ const basename = import_node_path18.default.basename(absPath);
27472
27728
  res.writeHead(200, {
27473
27729
  "Content-Type": mime,
27474
27730
  "Content-Length": String(stat.size),
@@ -27476,7 +27732,7 @@ function streamFile(res, absPath, logger) {
27476
27732
  // 防止浏览器把任意 mime 当 html 渲染
27477
27733
  "X-Content-Type-Options": "nosniff"
27478
27734
  });
27479
- const stream = import_node_fs15.default.createReadStream(absPath);
27735
+ const stream = import_node_fs16.default.createReadStream(absPath);
27480
27736
  stream.on("error", (err) => {
27481
27737
  logger?.warn("streamFile read error", { absPath, err: err.message });
27482
27738
  res.destroy();
@@ -27484,10 +27740,78 @@ function streamFile(res, absPath, logger) {
27484
27740
  stream.pipe(res);
27485
27741
  }
27486
27742
 
27743
+ // src/attachment/gc.ts
27744
+ var import_node_fs17 = __toESM(require("fs"), 1);
27745
+ var import_node_path19 = __toESM(require("path"), 1);
27746
+ var DEFAULT_TTL_MS = 30 * 24 * 3600 * 1e3;
27747
+ function runAttachmentGc(args) {
27748
+ const now = (args.now ?? Date.now)();
27749
+ const ttlMs = args.ttlMs ?? DEFAULT_TTL_MS;
27750
+ const liveAbs = /* @__PURE__ */ new Set();
27751
+ for (const { scope, sessionId } of args.sessionScopes) {
27752
+ for (const entry of args.groupFileStore.list(scope, sessionId)) {
27753
+ if (entry.stale) continue;
27754
+ if (import_node_path19.default.isAbsolute(entry.relPath)) liveAbs.add(entry.relPath);
27755
+ }
27756
+ }
27757
+ for (const { scope, sessionId } of args.sessionScopes) {
27758
+ const sessionDir = args.getSessionCwd?.(sessionId) ?? import_node_path19.default.join(
27759
+ args.dataDir,
27760
+ "sessions",
27761
+ ...scopeSubPath(scope).map(safeFileName),
27762
+ safeFileName(sessionId)
27763
+ );
27764
+ const attRoot = import_node_path19.default.join(sessionDir, ".attachments");
27765
+ let hashDirs;
27766
+ try {
27767
+ hashDirs = import_node_fs17.default.readdirSync(attRoot);
27768
+ } catch (err) {
27769
+ if (err.code === "ENOENT") continue;
27770
+ args.logger?.warn("attachment gc: readdir failed", { attRoot, err: err.message });
27771
+ continue;
27772
+ }
27773
+ for (const hashDir of hashDirs) {
27774
+ const hashDirAbs = import_node_path19.default.join(attRoot, hashDir);
27775
+ let files;
27776
+ try {
27777
+ files = import_node_fs17.default.readdirSync(hashDirAbs);
27778
+ } catch {
27779
+ continue;
27780
+ }
27781
+ for (const name of files) {
27782
+ const file = import_node_path19.default.join(hashDirAbs, name);
27783
+ let stat;
27784
+ try {
27785
+ stat = import_node_fs17.default.statSync(file);
27786
+ } catch {
27787
+ continue;
27788
+ }
27789
+ if (!stat.isFile()) continue;
27790
+ const age = now - stat.mtimeMs;
27791
+ if (age < ttlMs) continue;
27792
+ if (liveAbs.has(file)) continue;
27793
+ try {
27794
+ import_node_fs17.default.unlinkSync(file);
27795
+ } catch (err) {
27796
+ args.logger?.warn("attachment gc: unlink failed", { file, err: err.message });
27797
+ }
27798
+ }
27799
+ try {
27800
+ if (import_node_fs17.default.readdirSync(hashDirAbs).length === 0) import_node_fs17.default.rmdirSync(hashDirAbs);
27801
+ } catch {
27802
+ }
27803
+ }
27804
+ try {
27805
+ if (import_node_fs17.default.readdirSync(attRoot).length === 0) import_node_fs17.default.rmdirSync(attRoot);
27806
+ } catch {
27807
+ }
27808
+ }
27809
+ }
27810
+
27487
27811
  // src/attachment/group.ts
27488
- var import_node_fs16 = __toESM(require("fs"), 1);
27489
- var import_node_path18 = __toESM(require("path"), 1);
27490
- var import_node_crypto6 = __toESM(require("crypto"), 1);
27812
+ var import_node_fs18 = __toESM(require("fs"), 1);
27813
+ var import_node_path20 = __toESM(require("path"), 1);
27814
+ var import_node_crypto7 = __toESM(require("crypto"), 1);
27491
27815
  init_protocol();
27492
27816
  var GroupFileStore = class {
27493
27817
  dataDir;
@@ -27498,11 +27822,11 @@ var GroupFileStore = class {
27498
27822
  this.logger = opts.logger;
27499
27823
  }
27500
27824
  rootForScope(scope) {
27501
- return import_node_path18.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
27825
+ return import_node_path20.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
27502
27826
  }
27503
27827
  /** 与 SessionStore.filePath 平级,扩展名 .group-files.json */
27504
27828
  filePath(scope, sessionId) {
27505
- return import_node_path18.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
27829
+ return import_node_path20.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
27506
27830
  }
27507
27831
  cacheKey(scope, sessionId) {
27508
27832
  return scope.kind === "default" ? `default::${sessionId}` : `persona:${scope.personaId}:${scope.mode}::${sessionId}`;
@@ -27511,7 +27835,7 @@ var GroupFileStore = class {
27511
27835
  readFile(scope, sessionId) {
27512
27836
  const file = this.filePath(scope, sessionId);
27513
27837
  try {
27514
- const raw = import_node_fs16.default.readFileSync(file, "utf8");
27838
+ const raw = import_node_fs18.default.readFileSync(file, "utf8");
27515
27839
  const parsed = JSON.parse(raw);
27516
27840
  if (!Array.isArray(parsed)) {
27517
27841
  this.logger?.warn("GroupFileStore.readFile: not an array; resetting session entries", {
@@ -27537,10 +27861,10 @@ var GroupFileStore = class {
27537
27861
  }
27538
27862
  writeFile(scope, sessionId, entries) {
27539
27863
  const file = this.filePath(scope, sessionId);
27540
- import_node_fs16.default.mkdirSync(import_node_path18.default.dirname(file), { recursive: true });
27864
+ import_node_fs18.default.mkdirSync(import_node_path20.default.dirname(file), { recursive: true });
27541
27865
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
27542
- import_node_fs16.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
27543
- import_node_fs16.default.renameSync(tmp, file);
27866
+ import_node_fs18.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
27867
+ import_node_fs18.default.renameSync(tmp, file);
27544
27868
  }
27545
27869
  /** 拉一份当前 session 的清单。读盘 → cache;之后调用复用 cache */
27546
27870
  list(scope, sessionId) {
@@ -27576,7 +27900,7 @@ var GroupFileStore = class {
27576
27900
  entries[idx] = next;
27577
27901
  } else {
27578
27902
  next = {
27579
- id: `gf-${import_node_crypto6.default.randomBytes(6).toString("base64url")}`,
27903
+ id: `gf-${import_node_crypto7.default.randomBytes(6).toString("base64url")}`,
27580
27904
  relPath: input.relPath,
27581
27905
  from: input.from,
27582
27906
  label: input.label,
@@ -27626,10 +27950,10 @@ var GroupFileStore = class {
27626
27950
  };
27627
27951
 
27628
27952
  // src/discovery/state-file.ts
27629
- var import_node_fs17 = __toESM(require("fs"), 1);
27630
- var import_node_path19 = __toESM(require("path"), 1);
27953
+ var import_node_fs19 = __toESM(require("fs"), 1);
27954
+ var import_node_path21 = __toESM(require("path"), 1);
27631
27955
  function defaultStateFilePath(dataDir) {
27632
- return import_node_path19.default.join(dataDir, "state.json");
27956
+ return import_node_path21.default.join(dataDir, "state.json");
27633
27957
  }
27634
27958
  function isPidAlive(pid) {
27635
27959
  if (!Number.isFinite(pid) || pid <= 0) return false;
@@ -27651,7 +27975,7 @@ var StateFileManager = class {
27651
27975
  }
27652
27976
  read() {
27653
27977
  try {
27654
- const raw = import_node_fs17.default.readFileSync(this.file, "utf8");
27978
+ const raw = import_node_fs19.default.readFileSync(this.file, "utf8");
27655
27979
  const parsed = JSON.parse(raw);
27656
27980
  return parsed;
27657
27981
  } catch {
@@ -27665,34 +27989,34 @@ var StateFileManager = class {
27665
27989
  return { status: "stale", existing };
27666
27990
  }
27667
27991
  write(state) {
27668
- import_node_fs17.default.mkdirSync(import_node_path19.default.dirname(this.file), { recursive: true });
27992
+ import_node_fs19.default.mkdirSync(import_node_path21.default.dirname(this.file), { recursive: true });
27669
27993
  const tmp = `${this.file}.tmp.${process.pid}.${Date.now()}`;
27670
- import_node_fs17.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
27671
- import_node_fs17.default.renameSync(tmp, this.file);
27994
+ import_node_fs19.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
27995
+ import_node_fs19.default.renameSync(tmp, this.file);
27672
27996
  if (process.platform !== "win32") {
27673
27997
  try {
27674
- import_node_fs17.default.chmodSync(this.file, 384);
27998
+ import_node_fs19.default.chmodSync(this.file, 384);
27675
27999
  } catch {
27676
28000
  }
27677
28001
  }
27678
28002
  }
27679
28003
  delete() {
27680
28004
  try {
27681
- import_node_fs17.default.unlinkSync(this.file);
28005
+ import_node_fs19.default.unlinkSync(this.file);
27682
28006
  } catch {
27683
28007
  }
27684
28008
  }
27685
28009
  };
27686
28010
 
27687
28011
  // src/tunnel/tunnel-manager.ts
27688
- var import_node_fs21 = __toESM(require("fs"), 1);
27689
- var import_node_path23 = __toESM(require("path"), 1);
27690
- var import_node_crypto7 = __toESM(require("crypto"), 1);
28012
+ var import_node_fs23 = __toESM(require("fs"), 1);
28013
+ var import_node_path25 = __toESM(require("path"), 1);
28014
+ var import_node_crypto8 = __toESM(require("crypto"), 1);
27691
28015
  var import_node_child_process5 = require("child_process");
27692
28016
 
27693
28017
  // src/tunnel/tunnel-store.ts
27694
- var import_node_fs18 = __toESM(require("fs"), 1);
27695
- var import_node_path20 = __toESM(require("path"), 1);
28018
+ var import_node_fs20 = __toESM(require("fs"), 1);
28019
+ var import_node_path22 = __toESM(require("path"), 1);
27696
28020
  var TunnelStore = class {
27697
28021
  constructor(filePath) {
27698
28022
  this.filePath = filePath;
@@ -27700,7 +28024,7 @@ var TunnelStore = class {
27700
28024
  filePath;
27701
28025
  async get() {
27702
28026
  try {
27703
- const raw = await import_node_fs18.default.promises.readFile(this.filePath, "utf8");
28027
+ const raw = await import_node_fs20.default.promises.readFile(this.filePath, "utf8");
27704
28028
  const obj = JSON.parse(raw);
27705
28029
  if (!isPersistedTunnel(obj)) return null;
27706
28030
  return obj;
@@ -27711,22 +28035,22 @@ var TunnelStore = class {
27711
28035
  }
27712
28036
  }
27713
28037
  async set(v2) {
27714
- const dir = import_node_path20.default.dirname(this.filePath);
27715
- await import_node_fs18.default.promises.mkdir(dir, { recursive: true });
28038
+ const dir = import_node_path22.default.dirname(this.filePath);
28039
+ await import_node_fs20.default.promises.mkdir(dir, { recursive: true });
27716
28040
  const data = JSON.stringify(v2, null, 2);
27717
28041
  const tmp = `${this.filePath}.tmp.${process.pid}.${Date.now()}`;
27718
- await import_node_fs18.default.promises.writeFile(tmp, data, { mode: 384 });
28042
+ await import_node_fs20.default.promises.writeFile(tmp, data, { mode: 384 });
27719
28043
  if (process.platform !== "win32") {
27720
28044
  try {
27721
- await import_node_fs18.default.promises.chmod(tmp, 384);
28045
+ await import_node_fs20.default.promises.chmod(tmp, 384);
27722
28046
  } catch {
27723
28047
  }
27724
28048
  }
27725
- await import_node_fs18.default.promises.rename(tmp, this.filePath);
28049
+ await import_node_fs20.default.promises.rename(tmp, this.filePath);
27726
28050
  }
27727
28051
  async clear() {
27728
28052
  try {
27729
- await import_node_fs18.default.promises.unlink(this.filePath);
28053
+ await import_node_fs20.default.promises.unlink(this.filePath);
27730
28054
  } catch (err) {
27731
28055
  const code = err?.code;
27732
28056
  if (code !== "ENOENT") throw err;
@@ -27821,12 +28145,12 @@ function escape(v2) {
27821
28145
  }
27822
28146
 
27823
28147
  // src/tunnel/frpc-binary.ts
27824
- var import_node_fs19 = __toESM(require("fs"), 1);
28148
+ var import_node_fs21 = __toESM(require("fs"), 1);
27825
28149
  var import_node_os10 = __toESM(require("os"), 1);
27826
- var import_node_path21 = __toESM(require("path"), 1);
28150
+ var import_node_path23 = __toESM(require("path"), 1);
27827
28151
  var import_node_child_process3 = require("child_process");
27828
28152
  var import_node_stream2 = require("stream");
27829
- var import_promises = require("stream/promises");
28153
+ var import_promises2 = require("stream/promises");
27830
28154
  var FRPC_VERSION = "0.68.0";
27831
28155
  var UnsupportedPlatformError = class extends Error {
27832
28156
  constructor(platform, arch) {
@@ -27855,20 +28179,20 @@ function frpcDownloadUrl(version2, p2) {
27855
28179
  }
27856
28180
  async function ensureFrpcBinary(opts) {
27857
28181
  if (opts.override) {
27858
- if (!import_node_fs19.default.existsSync(opts.override)) {
28182
+ if (!import_node_fs21.default.existsSync(opts.override)) {
27859
28183
  throw new Error(`frpc binary not found at override path: ${opts.override}`);
27860
28184
  }
27861
28185
  return opts.override;
27862
28186
  }
27863
28187
  const version2 = opts.version ?? FRPC_VERSION;
27864
28188
  const platform = opts.platform ?? detectPlatform();
27865
- const binDir = import_node_path21.default.join(opts.dataDir, "bin");
27866
- import_node_fs19.default.mkdirSync(binDir, { recursive: true });
28189
+ const binDir = import_node_path23.default.join(opts.dataDir, "bin");
28190
+ import_node_fs21.default.mkdirSync(binDir, { recursive: true });
27867
28191
  cleanupStaleArtifacts(binDir);
27868
- const stableBin = import_node_path21.default.join(binDir, "frpc");
27869
- if (import_node_fs19.default.existsSync(stableBin)) return stableBin;
28192
+ const stableBin = import_node_path23.default.join(binDir, "frpc");
28193
+ if (import_node_fs21.default.existsSync(stableBin)) return stableBin;
27870
28194
  const partialBin = `${stableBin}.partial`;
27871
- const tarballPath = import_node_path21.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
28195
+ const tarballPath = import_node_path23.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
27872
28196
  try {
27873
28197
  const url = frpcDownloadUrl(version2, platform);
27874
28198
  await downloadToFile(url, tarballPath, opts.fetchImpl);
@@ -27877,8 +28201,8 @@ async function ensureFrpcBinary(opts) {
27877
28201
  } else {
27878
28202
  await extractFrpcFromTarball(tarballPath, binDir, version2, platform, partialBin);
27879
28203
  }
27880
- import_node_fs19.default.chmodSync(partialBin, 493);
27881
- import_node_fs19.default.renameSync(partialBin, stableBin);
28204
+ import_node_fs21.default.chmodSync(partialBin, 493);
28205
+ import_node_fs21.default.renameSync(partialBin, stableBin);
27882
28206
  } finally {
27883
28207
  safeUnlink(tarballPath);
27884
28208
  safeUnlink(partialBin);
@@ -27888,15 +28212,15 @@ async function ensureFrpcBinary(opts) {
27888
28212
  function cleanupStaleArtifacts(binDir) {
27889
28213
  let entries;
27890
28214
  try {
27891
- entries = import_node_fs19.default.readdirSync(binDir);
28215
+ entries = import_node_fs21.default.readdirSync(binDir);
27892
28216
  } catch {
27893
28217
  return;
27894
28218
  }
27895
28219
  for (const name of entries) {
27896
28220
  if (name.endsWith(".partial") || name.startsWith("extract-")) {
27897
- const full = import_node_path21.default.join(binDir, name);
28221
+ const full = import_node_path23.default.join(binDir, name);
27898
28222
  try {
27899
- import_node_fs19.default.rmSync(full, { recursive: true, force: true });
28223
+ import_node_fs21.default.rmSync(full, { recursive: true, force: true });
27900
28224
  } catch {
27901
28225
  }
27902
28226
  }
@@ -27904,7 +28228,7 @@ function cleanupStaleArtifacts(binDir) {
27904
28228
  }
27905
28229
  function safeUnlink(p2) {
27906
28230
  try {
27907
- import_node_fs19.default.unlinkSync(p2);
28231
+ import_node_fs21.default.unlinkSync(p2);
27908
28232
  } catch {
27909
28233
  }
27910
28234
  }
@@ -27915,13 +28239,13 @@ async function downloadToFile(url, dest, fetchImpl) {
27915
28239
  if (!res.ok || !res.body) {
27916
28240
  throw new Error(`download failed: ${res.status} ${res.statusText}`);
27917
28241
  }
27918
- const out = import_node_fs19.default.createWriteStream(dest);
28242
+ const out = import_node_fs21.default.createWriteStream(dest);
27919
28243
  const nodeStream = import_node_stream2.Readable.fromWeb(res.body);
27920
- await (0, import_promises.pipeline)(nodeStream, out);
28244
+ await (0, import_promises2.pipeline)(nodeStream, out);
27921
28245
  }
27922
28246
  async function extractFrpcFromTarball(tarball, binDir, version2, platform, destBin) {
27923
- const work = import_node_path21.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
27924
- import_node_fs19.default.mkdirSync(work, { recursive: true });
28247
+ const work = import_node_path23.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
28248
+ import_node_fs21.default.mkdirSync(work, { recursive: true });
27925
28249
  try {
27926
28250
  await new Promise((resolve6, reject) => {
27927
28251
  const proc = (0, import_node_child_process3.spawn)("tar", ["xzf", tarball, "-C", work], { stdio: "pipe" });
@@ -27929,32 +28253,32 @@ async function extractFrpcFromTarball(tarball, binDir, version2, platform, destB
27929
28253
  proc.on("exit", (code) => code === 0 ? resolve6() : reject(new Error(`tar exited ${code}`)));
27930
28254
  });
27931
28255
  const dirName = `frp_${version2}_${platform.os}_${platform.arch}`;
27932
- const src = import_node_path21.default.join(work, dirName, "frpc");
27933
- if (!import_node_fs19.default.existsSync(src)) {
28256
+ const src = import_node_path23.default.join(work, dirName, "frpc");
28257
+ if (!import_node_fs21.default.existsSync(src)) {
27934
28258
  throw new Error(`frpc not found inside tarball at ${src}`);
27935
28259
  }
27936
- import_node_fs19.default.copyFileSync(src, destBin);
28260
+ import_node_fs21.default.copyFileSync(src, destBin);
27937
28261
  } finally {
27938
- import_node_fs19.default.rmSync(work, { recursive: true, force: true });
28262
+ import_node_fs21.default.rmSync(work, { recursive: true, force: true });
27939
28263
  }
27940
28264
  }
27941
28265
 
27942
28266
  // src/tunnel/frpc-process.ts
27943
- var import_node_fs20 = __toESM(require("fs"), 1);
27944
- var import_node_path22 = __toESM(require("path"), 1);
28267
+ var import_node_fs22 = __toESM(require("fs"), 1);
28268
+ var import_node_path24 = __toESM(require("path"), 1);
27945
28269
  var import_node_child_process4 = require("child_process");
27946
28270
  function frpcPidFilePath(dataDir) {
27947
- return import_node_path22.default.join(dataDir, "frpc.pid");
28271
+ return import_node_path24.default.join(dataDir, "frpc.pid");
27948
28272
  }
27949
28273
  function writeFrpcPid(dataDir, pid) {
27950
28274
  try {
27951
- import_node_fs20.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
28275
+ import_node_fs22.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
27952
28276
  } catch {
27953
28277
  }
27954
28278
  }
27955
28279
  function clearFrpcPid(dataDir) {
27956
28280
  try {
27957
- import_node_fs20.default.unlinkSync(frpcPidFilePath(dataDir));
28281
+ import_node_fs22.default.unlinkSync(frpcPidFilePath(dataDir));
27958
28282
  } catch {
27959
28283
  }
27960
28284
  }
@@ -27970,7 +28294,7 @@ function defaultIsPidAlive(pid) {
27970
28294
  }
27971
28295
  function defaultReadPidFile(file) {
27972
28296
  try {
27973
- return import_node_fs20.default.readFileSync(file, "utf8");
28297
+ return import_node_fs22.default.readFileSync(file, "utf8");
27974
28298
  } catch {
27975
28299
  return null;
27976
28300
  }
@@ -27986,7 +28310,7 @@ function defaultSleep(ms) {
27986
28310
  }
27987
28311
  async function killStaleFrpc(deps) {
27988
28312
  const pidFile = frpcPidFilePath(deps.dataDir);
27989
- const tomlPath = import_node_path22.default.join(deps.dataDir, "frpc.toml");
28313
+ const tomlPath = import_node_path24.default.join(deps.dataDir, "frpc.toml");
27990
28314
  const readPidFile = deps.readPidFileImpl ?? defaultReadPidFile;
27991
28315
  const isAlive = deps.isPidAliveImpl ?? defaultIsPidAlive;
27992
28316
  const killPid = deps.killPidImpl ?? defaultKillPid;
@@ -28010,7 +28334,7 @@ async function killStaleFrpc(deps) {
28010
28334
  }
28011
28335
  if (victims.size === 0) {
28012
28336
  try {
28013
- import_node_fs20.default.unlinkSync(pidFile);
28337
+ import_node_fs22.default.unlinkSync(pidFile);
28014
28338
  } catch {
28015
28339
  }
28016
28340
  return;
@@ -28021,7 +28345,7 @@ async function killStaleFrpc(deps) {
28021
28345
  }
28022
28346
  await sleep(deps.reapWaitMs ?? 300);
28023
28347
  try {
28024
- import_node_fs20.default.unlinkSync(pidFile);
28348
+ import_node_fs22.default.unlinkSync(pidFile);
28025
28349
  } catch {
28026
28350
  }
28027
28351
  }
@@ -28058,7 +28382,7 @@ var DEFAULT_TUNNEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
28058
28382
  var TunnelManager = class {
28059
28383
  constructor(deps) {
28060
28384
  this.deps = deps;
28061
- this.store = deps.store ?? new TunnelStore(import_node_path23.default.join(deps.dataDir, "tunnel.json"));
28385
+ this.store = deps.store ?? new TunnelStore(import_node_path25.default.join(deps.dataDir, "tunnel.json"));
28062
28386
  this.ttlMs = deps.ttlMs ?? DEFAULT_TUNNEL_TTL_MS;
28063
28387
  this.startupTimeoutMs = deps.startupTimeoutMs ?? 15e3;
28064
28388
  }
@@ -28185,8 +28509,8 @@ var TunnelManager = class {
28185
28509
  dataDir: this.deps.dataDir,
28186
28510
  override: this.deps.frpcBinaryOverride ?? void 0
28187
28511
  });
28188
- const tomlPath = import_node_path23.default.join(this.deps.dataDir, "frpc.toml");
28189
- const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto7.default.randomBytes(3).toString("hex")}`;
28512
+ const tomlPath = import_node_path25.default.join(this.deps.dataDir, "frpc.toml");
28513
+ const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto8.default.randomBytes(3).toString("hex")}`;
28190
28514
  const toml = buildFrpcToml({
28191
28515
  serverAddr: t.frpsHost,
28192
28516
  serverPort: t.frpsPort,
@@ -28196,12 +28520,12 @@ var TunnelManager = class {
28196
28520
  localPort,
28197
28521
  logLevel: "info"
28198
28522
  });
28199
- await import_node_fs21.default.promises.writeFile(tomlPath, toml, { mode: 384 });
28523
+ await import_node_fs23.default.promises.writeFile(tomlPath, toml, { mode: 384 });
28200
28524
  const proc = (this.deps.spawnImpl ?? import_node_child_process5.spawn)(frpcBin, ["-c", tomlPath], {
28201
28525
  stdio: ["ignore", "pipe", "pipe"]
28202
28526
  });
28203
- const logFilePath = import_node_path23.default.join(this.deps.dataDir, "frpc.log");
28204
- const logStream = import_node_fs21.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
28527
+ const logFilePath = import_node_path25.default.join(this.deps.dataDir, "frpc.log");
28528
+ const logStream = import_node_fs23.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
28205
28529
  logStream.on("error", () => {
28206
28530
  });
28207
28531
  const tee = (chunk) => {
@@ -28284,22 +28608,22 @@ async function waitForFrpcReady(proc, timeoutMs) {
28284
28608
 
28285
28609
  // src/tunnel/device-key.ts
28286
28610
  var import_node_os11 = __toESM(require("os"), 1);
28287
- var import_node_crypto8 = __toESM(require("crypto"), 1);
28611
+ var import_node_crypto9 = __toESM(require("crypto"), 1);
28288
28612
  var DERIVE_SALT = "clawd-tunnel-device-v1";
28289
28613
  function deriveStableDeviceKey(opts = {}) {
28290
28614
  const hostname = opts.hostname ?? import_node_os11.default.hostname();
28291
28615
  const uid = opts.uid ?? (typeof import_node_os11.default.userInfo === "function" ? import_node_os11.default.userInfo().uid : 0);
28292
28616
  const input = `${hostname}::${uid}`;
28293
- return import_node_crypto8.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
28617
+ return import_node_crypto9.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
28294
28618
  }
28295
28619
 
28296
28620
  // src/auth-store.ts
28297
- var import_node_fs22 = __toESM(require("fs"), 1);
28298
- var import_node_path24 = __toESM(require("path"), 1);
28299
- var import_node_crypto9 = __toESM(require("crypto"), 1);
28621
+ var import_node_fs24 = __toESM(require("fs"), 1);
28622
+ var import_node_path26 = __toESM(require("path"), 1);
28623
+ var import_node_crypto10 = __toESM(require("crypto"), 1);
28300
28624
  var AUTH_FILE_NAME = "auth.json";
28301
28625
  function authFilePath(dataDir) {
28302
- return import_node_path24.default.join(dataDir, AUTH_FILE_NAME);
28626
+ return import_node_path26.default.join(dataDir, AUTH_FILE_NAME);
28303
28627
  }
28304
28628
  function loadOrCreateAuthFile(opts) {
28305
28629
  const file = authFilePath(opts.dataDir);
@@ -28324,14 +28648,14 @@ function loadOrCreateAuthFile(opts) {
28324
28648
  return next;
28325
28649
  }
28326
28650
  function defaultGenerateToken2() {
28327
- return import_node_crypto9.default.randomBytes(32).toString("base64url");
28651
+ return import_node_crypto10.default.randomBytes(32).toString("base64url");
28328
28652
  }
28329
28653
  function defaultGenerateOwnerPrincipalId() {
28330
- return `owner-${import_node_crypto9.default.randomUUID()}`;
28654
+ return `owner-${import_node_crypto10.default.randomUUID()}`;
28331
28655
  }
28332
28656
  function readAuthFile(file) {
28333
28657
  try {
28334
- const raw = import_node_fs22.default.readFileSync(file, "utf8");
28658
+ const raw = import_node_fs24.default.readFileSync(file, "utf8");
28335
28659
  const parsed = JSON.parse(raw);
28336
28660
  if (typeof parsed?.token !== "string" || parsed.token.length === 0) {
28337
28661
  return null;
@@ -28349,25 +28673,25 @@ function readAuthFile(file) {
28349
28673
  }
28350
28674
  }
28351
28675
  function writeAuthFile(file, content) {
28352
- import_node_fs22.default.mkdirSync(import_node_path24.default.dirname(file), { recursive: true });
28353
- import_node_fs22.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
28676
+ import_node_fs24.default.mkdirSync(import_node_path26.default.dirname(file), { recursive: true });
28677
+ import_node_fs24.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
28354
28678
  try {
28355
- import_node_fs22.default.chmodSync(file, 384);
28679
+ import_node_fs24.default.chmodSync(file, 384);
28356
28680
  } catch {
28357
28681
  }
28358
28682
  }
28359
28683
 
28360
28684
  // src/owner-profile.ts
28361
- var import_node_fs23 = __toESM(require("fs"), 1);
28685
+ var import_node_fs25 = __toESM(require("fs"), 1);
28362
28686
  var import_node_os12 = __toESM(require("os"), 1);
28363
- var import_node_path25 = __toESM(require("path"), 1);
28687
+ var import_node_path27 = __toESM(require("path"), 1);
28364
28688
  var PROFILE_FILENAME = "profile.json";
28365
28689
  function loadOwnerDisplayName(dataDir) {
28366
28690
  const fallback = import_node_os12.default.userInfo().username;
28367
- const profilePath = import_node_path25.default.join(dataDir, PROFILE_FILENAME);
28691
+ const profilePath = import_node_path27.default.join(dataDir, PROFILE_FILENAME);
28368
28692
  let raw;
28369
28693
  try {
28370
- raw = import_node_fs23.default.readFileSync(profilePath, "utf8");
28694
+ raw = import_node_fs25.default.readFileSync(profilePath, "utf8");
28371
28695
  } catch {
28372
28696
  return fallback;
28373
28697
  }
@@ -28396,12 +28720,12 @@ init_protocol();
28396
28720
  init_protocol();
28397
28721
 
28398
28722
  // src/session/fork.ts
28399
- var import_node_fs24 = __toESM(require("fs"), 1);
28723
+ var import_node_fs26 = __toESM(require("fs"), 1);
28400
28724
  var import_node_os13 = __toESM(require("os"), 1);
28401
- var import_node_path26 = __toESM(require("path"), 1);
28725
+ var import_node_path28 = __toESM(require("path"), 1);
28402
28726
  init_claude_history();
28403
28727
  function readJsonlEntries(file) {
28404
- const raw = import_node_fs24.default.readFileSync(file, "utf8");
28728
+ const raw = import_node_fs26.default.readFileSync(file, "utf8");
28405
28729
  const out = [];
28406
28730
  for (const line of raw.split("\n")) {
28407
28731
  const t = line.trim();
@@ -28414,10 +28738,10 @@ function readJsonlEntries(file) {
28414
28738
  return out;
28415
28739
  }
28416
28740
  function forkSession(input) {
28417
- const baseDir = input.baseDir ?? import_node_path26.default.join(import_node_os13.default.homedir(), ".claude");
28418
- const projectDir = import_node_path26.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
28419
- const sourceFile = import_node_path26.default.join(projectDir, `${input.toolSessionId}.jsonl`);
28420
- if (!import_node_fs24.default.existsSync(sourceFile)) {
28741
+ const baseDir = input.baseDir ?? import_node_path28.default.join(import_node_os13.default.homedir(), ".claude");
28742
+ const projectDir = import_node_path28.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
28743
+ const sourceFile = import_node_path28.default.join(projectDir, `${input.toolSessionId}.jsonl`);
28744
+ if (!import_node_fs26.default.existsSync(sourceFile)) {
28421
28745
  throw new Error(`fork: source transcript not found: ${sourceFile}`);
28422
28746
  }
28423
28747
  const entries = readJsonlEntries(sourceFile);
@@ -28447,9 +28771,9 @@ function forkSession(input) {
28447
28771
  }
28448
28772
  forkedLines.push(JSON.stringify(forked));
28449
28773
  }
28450
- const forkedFilePath = import_node_path26.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
28451
- import_node_fs24.default.mkdirSync(projectDir, { recursive: true });
28452
- import_node_fs24.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
28774
+ const forkedFilePath = import_node_path28.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
28775
+ import_node_fs26.default.mkdirSync(projectDir, { recursive: true });
28776
+ import_node_fs26.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
28453
28777
  return { forkedToolSessionId, forkedFilePath };
28454
28778
  }
28455
28779
 
@@ -28805,7 +29129,7 @@ function buildPermissionHandlers(deps) {
28805
29129
  }
28806
29130
 
28807
29131
  // src/handlers/history.ts
28808
- var path36 = __toESM(require("path"), 1);
29132
+ var path38 = __toESM(require("path"), 1);
28809
29133
  init_protocol();
28810
29134
 
28811
29135
  // src/session/recent-dirs.ts
@@ -28823,7 +29147,7 @@ function listRecentDirs(store, limit = 50) {
28823
29147
  }
28824
29148
 
28825
29149
  // src/permission/persona-paths.ts
28826
- var path35 = __toESM(require("path"), 1);
29150
+ var path37 = __toESM(require("path"), 1);
28827
29151
  function getAllowedPersonaIds(grants, action) {
28828
29152
  const ids = /* @__PURE__ */ new Set();
28829
29153
  for (const g2 of grants) {
@@ -28836,26 +29160,26 @@ function getAllowedPersonaIds(grants, action) {
28836
29160
  return ids;
28837
29161
  }
28838
29162
  function isGuestPathAllowed(grants, absPath, personaRoot, action = "read") {
28839
- const root = path35.resolve(personaRoot);
28840
- const target = path35.resolve(absPath);
28841
- const sep3 = root.endsWith(path35.sep) ? "" : path35.sep;
29163
+ const root = path37.resolve(personaRoot);
29164
+ const target = path37.resolve(absPath);
29165
+ const sep3 = root.endsWith(path37.sep) ? "" : path37.sep;
28842
29166
  if (!target.startsWith(root + sep3)) return false;
28843
- const rel = path35.relative(root, target);
29167
+ const rel = path37.relative(root, target);
28844
29168
  if (!rel || rel.startsWith("..")) return false;
28845
- const personaId = rel.split(path35.sep)[0];
29169
+ const personaId = rel.split(path37.sep)[0];
28846
29170
  if (!personaId) return false;
28847
29171
  const allowed = getAllowedPersonaIds(grants, action);
28848
29172
  if (allowed === "*") return true;
28849
29173
  return allowed.has(personaId);
28850
29174
  }
28851
29175
  function personaIdFromPath(absPath, personaRoot) {
28852
- const root = path35.resolve(personaRoot);
28853
- const target = path35.resolve(absPath);
28854
- const sep3 = root.endsWith(path35.sep) ? "" : path35.sep;
29176
+ const root = path37.resolve(personaRoot);
29177
+ const target = path37.resolve(absPath);
29178
+ const sep3 = root.endsWith(path37.sep) ? "" : path37.sep;
28855
29179
  if (!target.startsWith(root + sep3)) return null;
28856
- const rel = path35.relative(root, target);
29180
+ const rel = path37.relative(root, target);
28857
29181
  if (!rel || rel.startsWith("..")) return null;
28858
- const id = rel.split(path35.sep)[0];
29182
+ const id = rel.split(path37.sep)[0];
28859
29183
  return id || null;
28860
29184
  }
28861
29185
 
@@ -28880,7 +29204,7 @@ function buildHistoryHandlers(deps) {
28880
29204
  if (!pid) return false;
28881
29205
  return isGuestPathAllowed(
28882
29206
  ctx.grants,
28883
- path36.join(personaRoot, pid),
29207
+ path38.join(personaRoot, pid),
28884
29208
  personaRoot,
28885
29209
  "read"
28886
29210
  );
@@ -28891,7 +29215,7 @@ function buildHistoryHandlers(deps) {
28891
29215
  };
28892
29216
  const list = async (frame, _client, ctx) => {
28893
29217
  const args = HistoryListArgs.parse(frame);
28894
- assertGuestPath(ctx, path36.resolve(args.projectPath), personaRoot, "history:list");
29218
+ assertGuestPath(ctx, path38.resolve(args.projectPath), personaRoot, "history:list");
28895
29219
  const sessions = await history.listSessions(args);
28896
29220
  return { response: { type: "history:list", sessions } };
28897
29221
  };
@@ -28923,13 +29247,13 @@ function buildHistoryHandlers(deps) {
28923
29247
  };
28924
29248
  const subagents = async (frame, _client, ctx) => {
28925
29249
  const args = HistorySubagentsArgs.parse(frame);
28926
- assertGuestPath(ctx, path36.resolve(args.cwd), personaRoot, "history:subagents");
29250
+ assertGuestPath(ctx, path38.resolve(args.cwd), personaRoot, "history:subagents");
28927
29251
  const subs = await history.listSubagents(args);
28928
29252
  return { response: { type: "history:subagents", subagents: subs } };
28929
29253
  };
28930
29254
  const subagentRead = async (frame, _client, ctx) => {
28931
29255
  const args = HistorySubagentReadArgs.parse(frame);
28932
- assertGuestPath(ctx, path36.resolve(args.cwd), personaRoot, "history:subagent-read");
29256
+ assertGuestPath(ctx, path38.resolve(args.cwd), personaRoot, "history:subagent-read");
28933
29257
  const res = await history.readSubagent(args);
28934
29258
  return { response: { type: "history:subagent-read", ...res } };
28935
29259
  };
@@ -28937,7 +29261,7 @@ function buildHistoryHandlers(deps) {
28937
29261
  const dirs = listRecentDirs(store);
28938
29262
  if (ctx?.principal.kind === "guest" && personaRoot) {
28939
29263
  const filtered = dirs.filter(
28940
- (d) => isGuestPathAllowed(ctx.grants, path36.resolve(d.cwd), personaRoot, "read")
29264
+ (d) => isGuestPathAllowed(ctx.grants, path38.resolve(d.cwd), personaRoot, "read")
28941
29265
  );
28942
29266
  return { response: { type: "history:recentDirs", dirs: filtered } };
28943
29267
  }
@@ -28954,7 +29278,7 @@ function buildHistoryHandlers(deps) {
28954
29278
  }
28955
29279
 
28956
29280
  // src/handlers/workspace.ts
28957
- var path37 = __toESM(require("path"), 1);
29281
+ var path39 = __toESM(require("path"), 1);
28958
29282
  var os14 = __toESM(require("os"), 1);
28959
29283
  init_protocol();
28960
29284
  init_protocol();
@@ -28995,22 +29319,22 @@ function buildWorkspaceHandlers(deps) {
28995
29319
  const args = WorkspaceListArgs.parse(frame);
28996
29320
  const isGuest = ctx?.principal.kind === "guest";
28997
29321
  const fallbackCwd = isGuest && personaRoot ? personaRoot : os14.homedir();
28998
- const resolvedCwd = path37.resolve(args.cwd ?? fallbackCwd);
28999
- const target = args.path ? path37.resolve(resolvedCwd, args.path) : resolvedCwd;
29322
+ const resolvedCwd = path39.resolve(args.cwd ?? fallbackCwd);
29323
+ const target = args.path ? path39.resolve(resolvedCwd, args.path) : resolvedCwd;
29000
29324
  assertGuestPath2(ctx, target, personaRoot, "workspace:list");
29001
29325
  const res = workspace.list({ ...args, cwd: resolvedCwd });
29002
29326
  return { response: { type: "workspace:list", ...res } };
29003
29327
  };
29004
29328
  const read = async (frame, _client, ctx) => {
29005
29329
  const args = WorkspaceReadArgs.parse(frame);
29006
- const target = path37.isAbsolute(args.path) ? path37.resolve(args.path) : path37.resolve(args.cwd, args.path);
29330
+ const target = path39.isAbsolute(args.path) ? path39.resolve(args.path) : path39.resolve(args.cwd, args.path);
29007
29331
  assertGuestPath2(ctx, target, personaRoot, "workspace:read");
29008
29332
  const res = workspace.read(args);
29009
29333
  return { response: { type: "workspace:read", ...res } };
29010
29334
  };
29011
29335
  const skillsList = async (frame, _client, ctx) => {
29012
29336
  const args = SkillsListArgs.parse(frame);
29013
- const cwdAbs = path37.resolve(args.cwd);
29337
+ const cwdAbs = path39.resolve(args.cwd);
29014
29338
  assertGuestPath2(ctx, cwdAbs, personaRoot, "skills:list");
29015
29339
  const list2 = skills.list(args);
29016
29340
  if (ctx?.principal.kind === "guest" && personaRoot) {
@@ -29022,7 +29346,7 @@ function buildWorkspaceHandlers(deps) {
29022
29346
  };
29023
29347
  const agentsList = async (frame, _client, ctx) => {
29024
29348
  const args = AgentsListArgs.parse(frame);
29025
- const cwdAbs = path37.resolve(args.cwd);
29349
+ const cwdAbs = path39.resolve(args.cwd);
29026
29350
  assertGuestPath2(ctx, cwdAbs, personaRoot, "agents:list");
29027
29351
  const list2 = agents.list(args);
29028
29352
  if (ctx?.principal.kind === "guest" && personaRoot) {
@@ -29041,20 +29365,20 @@ function buildWorkspaceHandlers(deps) {
29041
29365
  }
29042
29366
 
29043
29367
  // src/handlers/git.ts
29044
- var path39 = __toESM(require("path"), 1);
29368
+ var path41 = __toESM(require("path"), 1);
29045
29369
  init_protocol();
29046
29370
  init_protocol();
29047
29371
 
29048
29372
  // src/workspace/git.ts
29049
29373
  var import_node_child_process6 = require("child_process");
29050
- var import_node_fs25 = __toESM(require("fs"), 1);
29051
- var import_node_path27 = __toESM(require("path"), 1);
29374
+ var import_node_fs27 = __toESM(require("fs"), 1);
29375
+ var import_node_path29 = __toESM(require("path"), 1);
29052
29376
  var import_node_util = require("util");
29053
29377
  var pexec = (0, import_node_util.promisify)(import_node_child_process6.execFile);
29054
29378
  function normalizePath(p2) {
29055
- const resolved = import_node_path27.default.resolve(p2);
29379
+ const resolved = import_node_path29.default.resolve(p2);
29056
29380
  try {
29057
- return import_node_fs25.default.realpathSync(resolved);
29381
+ return import_node_fs27.default.realpathSync(resolved);
29058
29382
  } catch {
29059
29383
  return resolved;
29060
29384
  }
@@ -29127,7 +29451,7 @@ async function listGitBranches(cwd) {
29127
29451
  // src/handlers/git.ts
29128
29452
  function assertGuestCwd(ctx, cwd, personaRoot, method) {
29129
29453
  if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
29130
- if (!isGuestPathAllowed(ctx.grants, path39.resolve(cwd), personaRoot, "read")) {
29454
+ if (!isGuestPathAllowed(ctx.grants, path41.resolve(cwd), personaRoot, "read")) {
29131
29455
  throw new ClawdError(
29132
29456
  ERROR_CODES.UNAUTHORIZED,
29133
29457
  `guest ${ctx.principal.id} cannot ${method} cwd ${cwd}`
@@ -29614,7 +29938,7 @@ function buildPersonaHandlers(deps) {
29614
29938
  }
29615
29939
 
29616
29940
  // src/handlers/attachment.ts
29617
- var import_node_path28 = __toESM(require("path"), 1);
29941
+ var import_node_path30 = __toESM(require("path"), 1);
29618
29942
  init_protocol();
29619
29943
  init_protocol();
29620
29944
  var DEFAULT_TTL_SECONDS = 24 * 3600;
@@ -29668,12 +29992,12 @@ function buildAttachmentHandlers(deps) {
29668
29992
  `session ${args.sessionId} scope unresolved`
29669
29993
  );
29670
29994
  }
29671
- const cwdAbs = import_node_path28.default.resolve(sessionFile.cwd);
29672
- const candidateAbs = import_node_path28.default.isAbsolute(args.relPath) ? import_node_path28.default.resolve(args.relPath) : import_node_path28.default.resolve(cwdAbs, args.relPath);
29995
+ const cwdAbs = import_node_path30.default.resolve(sessionFile.cwd);
29996
+ const candidateAbs = import_node_path30.default.isAbsolute(args.relPath) ? import_node_path30.default.resolve(args.relPath) : import_node_path30.default.resolve(cwdAbs, args.relPath);
29673
29997
  assertGuestAttachmentPath(ctx, candidateAbs, deps.personaRoot, "attachment.signUrl");
29674
29998
  const entries = deps.groupFileStore.list(scope, args.sessionId);
29675
29999
  const entry = entries.find((e) => {
29676
- const storedAbs = import_node_path28.default.isAbsolute(e.relPath) ? import_node_path28.default.resolve(e.relPath) : import_node_path28.default.resolve(cwdAbs, e.relPath);
30000
+ const storedAbs = import_node_path30.default.isAbsolute(e.relPath) ? import_node_path30.default.resolve(e.relPath) : import_node_path30.default.resolve(cwdAbs, e.relPath);
29677
30001
  return storedAbs === candidateAbs && !e.stale;
29678
30002
  });
29679
30003
  if (!entry) {
@@ -29697,7 +30021,7 @@ function buildAttachmentHandlers(deps) {
29697
30021
  if (!ctx || ctx.principal.kind !== "guest" || !deps.personaRoot || !deps.sessionStore) return;
29698
30022
  const f = deps.sessionStore.read(sessionId);
29699
30023
  if (!f) return;
29700
- assertGuestAttachmentPath(ctx, import_node_path28.default.resolve(f.cwd), deps.personaRoot, method);
30024
+ assertGuestAttachmentPath(ctx, import_node_path30.default.resolve(f.cwd), deps.personaRoot, method);
29701
30025
  }
29702
30026
  const groupAdd = async (frame, _client, ctx) => {
29703
30027
  if (!deps.groupFileStore || !deps.getSessionScope) {
@@ -29738,7 +30062,7 @@ function buildAttachmentHandlers(deps) {
29738
30062
  }
29739
30063
  const entries = deps.groupFileStore.list(scope, parsed.data.sessionId);
29740
30064
  const target = entries.find((e) => e.relPath === parsed.data.relPath);
29741
- if (target?.from === "owner") {
30065
+ if (target?.from === "owner" || target?.from === "user-upload") {
29742
30066
  deps.groupFileStore.remove(scope, parsed.data.sessionId, parsed.data.relPath);
29743
30067
  } else {
29744
30068
  deps.groupFileStore.markStale(scope, parsed.data.sessionId, parsed.data.relPath);
@@ -29939,7 +30263,7 @@ function computeGrantForFrame(method, frame) {
29939
30263
  async function startDaemon(config) {
29940
30264
  const logger = createLogger({
29941
30265
  level: config.logLevel,
29942
- file: import_node_path29.default.join(config.dataDir, "clawd.log")
30266
+ file: import_node_path31.default.join(config.dataDir, "clawd.log")
29943
30267
  });
29944
30268
  logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
29945
30269
  const stateMgr = new StateFileManager({ dataDir: config.dataDir });
@@ -30026,7 +30350,7 @@ async function startDaemon(config) {
30026
30350
  const agents = new AgentsScanner();
30027
30351
  const history = new ClaudeHistoryReader();
30028
30352
  let transport = null;
30029
- const personaStore = new PersonaStore(import_node_path29.default.join(config.dataDir, "personas"));
30353
+ const personaStore = new PersonaStore(import_node_path31.default.join(config.dataDir, "personas"));
30030
30354
  const defaultsRoot = findDefaultsRoot();
30031
30355
  if (defaultsRoot) {
30032
30356
  seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
@@ -30043,7 +30367,7 @@ async function startDaemon(config) {
30043
30367
  getAdapter,
30044
30368
  historyReader: history,
30045
30369
  dataDir: config.dataDir,
30046
- personaRoot: import_node_path29.default.join(config.dataDir, "personas"),
30370
+ personaRoot: import_node_path31.default.join(config.dataDir, "personas"),
30047
30371
  personaStore,
30048
30372
  ownerDisplayName,
30049
30373
  ownerPrincipalId,
@@ -30067,10 +30391,10 @@ async function startDaemon(config) {
30067
30391
  // 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
30068
30392
  attachmentGroup: {
30069
30393
  onFileEdit: (input) => {
30070
- const absPath = import_node_path29.default.isAbsolute(input.relPath) ? input.relPath : import_node_path29.default.join(input.cwd, input.relPath);
30394
+ const absPath = import_node_path31.default.isAbsolute(input.relPath) ? input.relPath : import_node_path31.default.join(input.cwd, input.relPath);
30071
30395
  let size = 0;
30072
30396
  try {
30073
- size = import_node_fs26.default.statSync(absPath).size;
30397
+ size = import_node_fs28.default.statSync(absPath).size;
30074
30398
  } catch (err) {
30075
30399
  logger.warn("attachment.onFileEdit stat failed", {
30076
30400
  sessionId: input.sessionId,
@@ -30188,10 +30512,10 @@ async function startDaemon(config) {
30188
30512
  // 'persona/<pid>/owner',default 走 'default'。
30189
30513
  getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
30190
30514
  // guest path guard:candidate 必须在 personaRoot 子树下
30191
- personaRoot: import_node_path29.default.join(config.dataDir, "personas")
30515
+ personaRoot: import_node_path31.default.join(config.dataDir, "personas")
30192
30516
  },
30193
30517
  // workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
30194
- personaRoot: import_node_path29.default.join(config.dataDir, "personas"),
30518
+ personaRoot: import_node_path31.default.join(config.dataDir, "personas"),
30195
30519
  // capability:list / delete handler 依赖
30196
30520
  capabilityManager,
30197
30521
  // personal-cap:get 返回的 shareBaseUrl 用此 base URL:
@@ -30227,7 +30551,19 @@ async function startDaemon(config) {
30227
30551
  sessionStore: store,
30228
30552
  // /files HMAC verify 用 auth.json 的 signSecret 字段(与 attachment.signUrl 同源)。
30229
30553
  // --auth-token CLI 模式没 signSecret → 路由返 501,sign URL 功能整体禁用。
30230
- getSignSecret: () => authFile?.signSecret ?? null
30554
+ getSignSecret: () => authFile?.signSecret ?? null,
30555
+ // chatinput 附件上传(POST /attachments/upload,spec §6.1)。upload pipeline
30556
+ // 在 session.cwd 下的 .attachments/ 落盘;signSecret 与 /files HMAC 同源;
30557
+ // tunnel 未启时返 503 TUNNEL_NOT_READY 让前端显式提示。
30558
+ attachmentUpload: {
30559
+ groupFileStore,
30560
+ getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
30561
+ getSessionDir: (sid) => {
30562
+ const f = manager.findOwnedSession(sid);
30563
+ return f ? f.cwd : null;
30564
+ },
30565
+ getHttpBaseUrl: () => currentTunnelUrl ? getHttpBaseUrl() : null
30566
+ }
30231
30567
  });
30232
30568
  wsServer = new LocalWsServer({
30233
30569
  host: config.host,
@@ -30392,8 +30728,8 @@ async function startDaemon(config) {
30392
30728
  const lines = [
30393
30729
  `Tunnel: ${r.url}`,
30394
30730
  ...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
30395
- `Frpc config: ${import_node_path29.default.join(config.dataDir, "frpc.toml")}`,
30396
- `Frpc log: ${import_node_path29.default.join(config.dataDir, "frpc.log")}`
30731
+ `Frpc config: ${import_node_path31.default.join(config.dataDir, "frpc.toml")}`,
30732
+ `Frpc log: ${import_node_path31.default.join(config.dataDir, "frpc.log")}`
30397
30733
  ];
30398
30734
  const width = Math.max(...lines.map((l) => l.length));
30399
30735
  const bar = "\u2550".repeat(width + 4);
@@ -30406,8 +30742,8 @@ ${bar}
30406
30742
 
30407
30743
  `);
30408
30744
  try {
30409
- const connectPath = import_node_path29.default.join(config.dataDir, "connect.txt");
30410
- import_node_fs26.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
30745
+ const connectPath = import_node_path31.default.join(config.dataDir, "connect.txt");
30746
+ import_node_fs28.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
30411
30747
  } catch {
30412
30748
  }
30413
30749
  } catch (err) {
@@ -30432,8 +30768,29 @@ ${bar}
30432
30768
  logger.warn("tunnel unavailable, degraded to local mode", { reason: tunnelError });
30433
30769
  }
30434
30770
  }
30771
+ const tickAttachmentGc = () => {
30772
+ try {
30773
+ runAttachmentGc({
30774
+ dataDir: config.dataDir,
30775
+ groupFileStore,
30776
+ sessionScopes: manager.listOwnedSessionScopes().map(({ scope, sessionId }) => ({
30777
+ scope,
30778
+ sessionId
30779
+ })),
30780
+ // 注入 cwd 让 GC 直接定位 .attachments 目录(与 upload 落盘点一致)
30781
+ getSessionCwd: (sid) => manager.findOwnedSession(sid)?.cwd ?? null,
30782
+ logger
30783
+ });
30784
+ } catch (err) {
30785
+ logger.warn("attachment gc tick failed", { err: err.message });
30786
+ }
30787
+ };
30788
+ setImmediate(tickAttachmentGc);
30789
+ const attachmentGcInterval = setInterval(tickAttachmentGc, 24 * 3600 * 1e3);
30790
+ attachmentGcInterval.unref();
30435
30791
  const shutdown = async () => {
30436
30792
  logger.info("stopping clawd");
30793
+ clearInterval(attachmentGcInterval);
30437
30794
  observer.stopAll();
30438
30795
  manager.stopAll();
30439
30796
  if (tunnelMgr) {
@@ -30451,9 +30808,9 @@ ${bar}
30451
30808
  };
30452
30809
  }
30453
30810
  function migrateDropPersonsDir(dataDir) {
30454
- const dir = import_node_path29.default.join(dataDir, "persons");
30811
+ const dir = import_node_path31.default.join(dataDir, "persons");
30455
30812
  try {
30456
- import_node_fs26.default.rmSync(dir, { recursive: true, force: true });
30813
+ import_node_fs28.default.rmSync(dir, { recursive: true, force: true });
30457
30814
  } catch {
30458
30815
  }
30459
30816
  }