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

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 +595 -236
  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) {
@@ -27170,8 +27192,8 @@ function rmdirIfEmpty(p2) {
27170
27192
  }
27171
27193
 
27172
27194
  // src/transport/http-router.ts
27173
- var import_node_fs15 = __toESM(require("fs"), 1);
27174
- var import_node_path17 = __toESM(require("path"), 1);
27195
+ var import_node_fs16 = __toESM(require("fs"), 1);
27196
+ var import_node_path18 = __toESM(require("path"), 1);
27175
27197
 
27176
27198
  // src/attachment/mime.ts
27177
27199
  var import_node_path16 = __toESM(require("path"), 1);
@@ -27349,7 +27371,137 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
27349
27371
  return { ok: true, absPath };
27350
27372
  }
27351
27373
 
27374
+ // src/attachment/upload.ts
27375
+ var import_node_fs15 = __toESM(require("fs"), 1);
27376
+ var import_node_path17 = __toESM(require("path"), 1);
27377
+ var import_node_crypto6 = __toESM(require("crypto"), 1);
27378
+ var import_promises = require("stream/promises");
27379
+ var UploadError = class extends Error {
27380
+ constructor(code, message) {
27381
+ super(message);
27382
+ this.code = code;
27383
+ this.name = "UploadError";
27384
+ }
27385
+ code;
27386
+ };
27387
+ function assertValidFileName(fileName) {
27388
+ if (fileName.length === 0 || fileName === "." || fileName === ".." || fileName.startsWith(".") || fileName.includes("/") || fileName.includes("\\") || fileName !== import_node_path17.default.basename(fileName)) {
27389
+ throw new UploadError("INVALID_FILENAME", `fileName must be a plain basename, got: ${fileName}`);
27390
+ }
27391
+ }
27392
+ var HASH_PREFIX_LEN = 16;
27393
+ async function writeUploadedAttachment(args) {
27394
+ assertValidFileName(args.fileName);
27395
+ const attachmentsRoot = import_node_path17.default.join(args.sessionDir, ".attachments");
27396
+ try {
27397
+ import_node_fs15.default.mkdirSync(attachmentsRoot, { recursive: true });
27398
+ } catch (err) {
27399
+ throw new UploadError("STORAGE_ERROR", `mkdir failed: ${err.message}`);
27400
+ }
27401
+ const hasher = import_node_crypto6.default.createHash("sha256");
27402
+ let actualSize = 0;
27403
+ const tmpPath = import_node_path17.default.join(
27404
+ attachmentsRoot,
27405
+ `.upload-${process.pid}-${Date.now()}-${import_node_crypto6.default.randomBytes(4).toString("hex")}`
27406
+ );
27407
+ try {
27408
+ await (0, import_promises.pipeline)(
27409
+ args.body,
27410
+ async function* (source) {
27411
+ for await (const chunk of source) {
27412
+ const buf = chunk;
27413
+ actualSize += buf.length;
27414
+ hasher.update(buf);
27415
+ yield buf;
27416
+ }
27417
+ },
27418
+ import_node_fs15.default.createWriteStream(tmpPath, { mode: 384 })
27419
+ );
27420
+ } catch (err) {
27421
+ try {
27422
+ import_node_fs15.default.unlinkSync(tmpPath);
27423
+ } catch {
27424
+ }
27425
+ throw new UploadError("STORAGE_ERROR", `write failed: ${err.message}`);
27426
+ }
27427
+ if (actualSize !== args.contentLength) {
27428
+ try {
27429
+ import_node_fs15.default.unlinkSync(tmpPath);
27430
+ } catch {
27431
+ }
27432
+ throw new UploadError(
27433
+ "SIZE_MISMATCH",
27434
+ `expected ${args.contentLength} bytes, got ${actualSize}`
27435
+ );
27436
+ }
27437
+ const attachmentId = hasher.digest("hex").slice(0, HASH_PREFIX_LEN);
27438
+ const hashDir = import_node_path17.default.join(attachmentsRoot, attachmentId);
27439
+ let finalFileName;
27440
+ let hashDirExists = false;
27441
+ try {
27442
+ hashDirExists = import_node_fs15.default.statSync(hashDir).isDirectory();
27443
+ } catch {
27444
+ }
27445
+ if (hashDirExists) {
27446
+ const existing = import_node_fs15.default.readdirSync(hashDir).filter((n) => !n.startsWith("."));
27447
+ finalFileName = existing[0] ?? args.fileName;
27448
+ try {
27449
+ import_node_fs15.default.unlinkSync(tmpPath);
27450
+ } catch {
27451
+ }
27452
+ } else {
27453
+ try {
27454
+ import_node_fs15.default.mkdirSync(hashDir, { recursive: true });
27455
+ finalFileName = args.fileName;
27456
+ import_node_fs15.default.renameSync(tmpPath, import_node_path17.default.join(hashDir, finalFileName));
27457
+ } catch (err) {
27458
+ try {
27459
+ import_node_fs15.default.unlinkSync(tmpPath);
27460
+ } catch {
27461
+ }
27462
+ throw new UploadError("STORAGE_ERROR", `rename failed: ${err.message}`);
27463
+ }
27464
+ }
27465
+ const absPath = import_node_path17.default.join(hashDir, finalFileName);
27466
+ const relPath = import_node_path17.default.relative(args.sessionDir, absPath);
27467
+ args.groupFileStore.upsert(args.scope, args.sessionId, {
27468
+ relPath: absPath,
27469
+ // 存绝对路径,与现有 agent 入清单的形态一致(attachment.ts:144 双形态兼容)
27470
+ from: "user-upload",
27471
+ label: args.fileName,
27472
+ size: actualSize,
27473
+ mime: args.mimeType
27474
+ });
27475
+ return { attachmentId, absPath, relPath, sizeBytes: actualSize, mimeType: args.mimeType };
27476
+ }
27477
+
27352
27478
  // src/transport/http-router.ts
27479
+ var UPLOAD_MIME_ALLOW = /* @__PURE__ */ new Set([
27480
+ "image/png",
27481
+ "image/jpeg",
27482
+ "image/gif",
27483
+ "image/webp",
27484
+ "application/pdf"
27485
+ ]);
27486
+ var UPLOAD_MIME_ALLOW_PREFIX = ["text/"];
27487
+ var UPLOAD_MAX_BYTES = 25 * 1024 * 1024;
27488
+ var UPLOAD_CORS_HEADERS = {
27489
+ "Access-Control-Allow-Origin": "*",
27490
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
27491
+ "Access-Control-Allow-Headers": "Authorization, Content-Type",
27492
+ "Access-Control-Max-Age": "86400"
27493
+ };
27494
+ function isUploadMimeAllowed(mime) {
27495
+ if (UPLOAD_MIME_ALLOW.has(mime)) return true;
27496
+ return UPLOAD_MIME_ALLOW_PREFIX.some((p2) => mime.startsWith(p2));
27497
+ }
27498
+ function isValidUploadFileName(fileName) {
27499
+ if (fileName.length === 0) return false;
27500
+ if (fileName === "." || fileName === "..") return false;
27501
+ if (fileName.startsWith(".")) return false;
27502
+ if (fileName.includes("/") || fileName.includes("\\")) return false;
27503
+ return fileName === import_node_path18.default.basename(fileName);
27504
+ }
27353
27505
  function createHttpRouter(deps) {
27354
27506
  return async (req, res) => {
27355
27507
  const url = parseUrl(req.url);
@@ -27363,9 +27515,14 @@ function createHttpRouter(deps) {
27363
27515
  });
27364
27516
  return true;
27365
27517
  }
27366
- if (!url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/")) {
27518
+ if (!url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/") && url.pathname !== "/attachments/upload") {
27367
27519
  return false;
27368
27520
  }
27521
+ if (url.pathname === "/attachments/upload" && req.method === "OPTIONS") {
27522
+ res.writeHead(204, UPLOAD_CORS_HEADERS);
27523
+ res.end();
27524
+ return true;
27525
+ }
27369
27526
  if (url.pathname.startsWith("/files/") && req.method === "GET") {
27370
27527
  const secret = deps.getSignSecret?.();
27371
27528
  if (!secret) {
@@ -27403,6 +27560,107 @@ function createHttpRouter(deps) {
27403
27560
  sendJson(res, 401, { code: "UNAUTHORIZED", message: "missing or invalid bearer token" });
27404
27561
  return true;
27405
27562
  }
27563
+ if (url.pathname === "/attachments/upload" && req.method === "POST") {
27564
+ const sendUploadJson = (status, body) => sendJson(res, status, body, UPLOAD_CORS_HEADERS);
27565
+ if (ctx.role !== "owner") {
27566
+ sendUploadJson(403, { code: "FORBIDDEN", message: "owner only" });
27567
+ return true;
27568
+ }
27569
+ const up = deps.attachmentUpload;
27570
+ if (!up) {
27571
+ sendUploadJson(501, { code: "NOT_IMPLEMENTED", message: "attachment upload not wired" });
27572
+ return true;
27573
+ }
27574
+ const sessionId = url.searchParams.get("sessionId");
27575
+ const fileName = url.searchParams.get("fileName");
27576
+ const mimeType = url.searchParams.get("mimeType");
27577
+ const contentLengthRaw = req.headers["content-length"];
27578
+ if (!sessionId || !fileName || !mimeType || !contentLengthRaw) {
27579
+ sendUploadJson(400, {
27580
+ code: "INVALID_PARAM",
27581
+ message: "missing sessionId/fileName/mimeType/Content-Length"
27582
+ });
27583
+ return true;
27584
+ }
27585
+ if (!isValidUploadFileName(fileName)) {
27586
+ sendUploadJson(400, {
27587
+ code: "INVALID_FILENAME",
27588
+ message: `fileName must be a plain basename (no path separators, no dotfiles): ${fileName}`
27589
+ });
27590
+ return true;
27591
+ }
27592
+ const contentLength = Number.parseInt(contentLengthRaw, 10);
27593
+ if (!Number.isFinite(contentLength) || contentLength < 0) {
27594
+ sendUploadJson(400, { code: "INVALID_PARAM", message: "malformed Content-Length" });
27595
+ return true;
27596
+ }
27597
+ if (contentLength > UPLOAD_MAX_BYTES) {
27598
+ sendUploadJson(413, {
27599
+ code: "FILE_TOO_LARGE",
27600
+ message: `max ${UPLOAD_MAX_BYTES} bytes`
27601
+ });
27602
+ return true;
27603
+ }
27604
+ if (!isUploadMimeAllowed(mimeType)) {
27605
+ sendUploadJson(400, {
27606
+ code: "UNSUPPORTED_MIME",
27607
+ message: `mime ${mimeType} not allowed`
27608
+ });
27609
+ return true;
27610
+ }
27611
+ const sessionDir = up.getSessionDir(sessionId);
27612
+ const scope = up.getSessionScope(sessionId);
27613
+ if (!sessionDir || !scope) {
27614
+ sendUploadJson(404, { code: "NOT_FOUND", message: `session ${sessionId} not found` });
27615
+ return true;
27616
+ }
27617
+ const httpBaseUrl = up.getHttpBaseUrl();
27618
+ if (!httpBaseUrl) {
27619
+ sendUploadJson(503, {
27620
+ code: "TUNNEL_NOT_READY",
27621
+ message: "httpBaseUrl unavailable"
27622
+ });
27623
+ return true;
27624
+ }
27625
+ const secret = deps.getSignSecret?.();
27626
+ if (!secret) {
27627
+ sendUploadJson(501, {
27628
+ code: "NOT_IMPLEMENTED",
27629
+ message: "sign secret unavailable (noAuth?)"
27630
+ });
27631
+ return true;
27632
+ }
27633
+ try {
27634
+ const result = await writeUploadedAttachment({
27635
+ sessionDir,
27636
+ sessionId,
27637
+ scope,
27638
+ fileName,
27639
+ mimeType,
27640
+ contentLength,
27641
+ body: req,
27642
+ groupFileStore: up.groupFileStore
27643
+ });
27644
+ const parts = signUrlParts(secret, result.absPath, null);
27645
+ const fullUrl = buildSignedFileUrl(httpBaseUrl, parts);
27646
+ sendUploadJson(200, {
27647
+ attachmentId: result.attachmentId,
27648
+ url: fullUrl,
27649
+ relPath: result.relPath,
27650
+ sizeBytes: result.sizeBytes,
27651
+ mimeType: result.mimeType
27652
+ });
27653
+ } catch (err) {
27654
+ if (err instanceof UploadError) {
27655
+ const status = err.code === "SIZE_MISMATCH" ? 400 : 500;
27656
+ sendUploadJson(status, { code: err.code, message: err.message });
27657
+ } else {
27658
+ deps.logger?.warn("attachment upload failed", { err: err.message });
27659
+ sendUploadJson(500, { code: "STORAGE_ERROR", message: err.message });
27660
+ }
27661
+ }
27662
+ return true;
27663
+ }
27406
27664
  const sessionFilesMatch = url.pathname.match(/^\/session\/([^/]+)\/files$/);
27407
27665
  if (sessionFilesMatch && req.method === "GET") {
27408
27666
  const sid = sessionFilesMatch[1];
@@ -27412,7 +27670,7 @@ function createHttpRouter(deps) {
27412
27670
  return true;
27413
27671
  }
27414
27672
  let absPath;
27415
- if (import_node_path17.default.isAbsolute(pathParam)) {
27673
+ if (import_node_path18.default.isAbsolute(pathParam)) {
27416
27674
  absPath = pathParam;
27417
27675
  } else if (deps.sessionStore) {
27418
27676
  const file = deps.sessionStore.read(sid);
@@ -27420,7 +27678,7 @@ function createHttpRouter(deps) {
27420
27678
  sendJson(res, 404, { code: "NOT_FOUND", message: `session ${sid} not found` });
27421
27679
  return true;
27422
27680
  }
27423
- absPath = import_node_path17.default.join(file.cwd, pathParam);
27681
+ absPath = import_node_path18.default.join(file.cwd, pathParam);
27424
27682
  } else {
27425
27683
  sendJson(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "sessionStore not wired" }));
27426
27684
  return true;
@@ -27453,7 +27711,7 @@ function withCtx(ctx, body) {
27453
27711
  function streamFile(res, absPath, logger) {
27454
27712
  let stat;
27455
27713
  try {
27456
- stat = import_node_fs15.default.statSync(absPath);
27714
+ stat = import_node_fs16.default.statSync(absPath);
27457
27715
  } catch (err) {
27458
27716
  const code = err?.code;
27459
27717
  if (code === "ENOENT") {
@@ -27468,7 +27726,7 @@ function streamFile(res, absPath, logger) {
27468
27726
  return;
27469
27727
  }
27470
27728
  const mime = lookupMime(absPath);
27471
- const basename = import_node_path17.default.basename(absPath);
27729
+ const basename = import_node_path18.default.basename(absPath);
27472
27730
  res.writeHead(200, {
27473
27731
  "Content-Type": mime,
27474
27732
  "Content-Length": String(stat.size),
@@ -27476,7 +27734,7 @@ function streamFile(res, absPath, logger) {
27476
27734
  // 防止浏览器把任意 mime 当 html 渲染
27477
27735
  "X-Content-Type-Options": "nosniff"
27478
27736
  });
27479
- const stream = import_node_fs15.default.createReadStream(absPath);
27737
+ const stream = import_node_fs16.default.createReadStream(absPath);
27480
27738
  stream.on("error", (err) => {
27481
27739
  logger?.warn("streamFile read error", { absPath, err: err.message });
27482
27740
  res.destroy();
@@ -27484,10 +27742,78 @@ function streamFile(res, absPath, logger) {
27484
27742
  stream.pipe(res);
27485
27743
  }
27486
27744
 
27745
+ // src/attachment/gc.ts
27746
+ var import_node_fs17 = __toESM(require("fs"), 1);
27747
+ var import_node_path19 = __toESM(require("path"), 1);
27748
+ var DEFAULT_TTL_MS = 30 * 24 * 3600 * 1e3;
27749
+ function runAttachmentGc(args) {
27750
+ const now = (args.now ?? Date.now)();
27751
+ const ttlMs = args.ttlMs ?? DEFAULT_TTL_MS;
27752
+ const liveAbs = /* @__PURE__ */ new Set();
27753
+ for (const { scope, sessionId } of args.sessionScopes) {
27754
+ for (const entry of args.groupFileStore.list(scope, sessionId)) {
27755
+ if (entry.stale) continue;
27756
+ if (import_node_path19.default.isAbsolute(entry.relPath)) liveAbs.add(entry.relPath);
27757
+ }
27758
+ }
27759
+ for (const { scope, sessionId } of args.sessionScopes) {
27760
+ const sessionDir = args.getSessionCwd?.(sessionId) ?? import_node_path19.default.join(
27761
+ args.dataDir,
27762
+ "sessions",
27763
+ ...scopeSubPath(scope).map(safeFileName),
27764
+ safeFileName(sessionId)
27765
+ );
27766
+ const attRoot = import_node_path19.default.join(sessionDir, ".attachments");
27767
+ let hashDirs;
27768
+ try {
27769
+ hashDirs = import_node_fs17.default.readdirSync(attRoot);
27770
+ } catch (err) {
27771
+ if (err.code === "ENOENT") continue;
27772
+ args.logger?.warn("attachment gc: readdir failed", { attRoot, err: err.message });
27773
+ continue;
27774
+ }
27775
+ for (const hashDir of hashDirs) {
27776
+ const hashDirAbs = import_node_path19.default.join(attRoot, hashDir);
27777
+ let files;
27778
+ try {
27779
+ files = import_node_fs17.default.readdirSync(hashDirAbs);
27780
+ } catch {
27781
+ continue;
27782
+ }
27783
+ for (const name of files) {
27784
+ const file = import_node_path19.default.join(hashDirAbs, name);
27785
+ let stat;
27786
+ try {
27787
+ stat = import_node_fs17.default.statSync(file);
27788
+ } catch {
27789
+ continue;
27790
+ }
27791
+ if (!stat.isFile()) continue;
27792
+ const age = now - stat.mtimeMs;
27793
+ if (age < ttlMs) continue;
27794
+ if (liveAbs.has(file)) continue;
27795
+ try {
27796
+ import_node_fs17.default.unlinkSync(file);
27797
+ } catch (err) {
27798
+ args.logger?.warn("attachment gc: unlink failed", { file, err: err.message });
27799
+ }
27800
+ }
27801
+ try {
27802
+ if (import_node_fs17.default.readdirSync(hashDirAbs).length === 0) import_node_fs17.default.rmdirSync(hashDirAbs);
27803
+ } catch {
27804
+ }
27805
+ }
27806
+ try {
27807
+ if (import_node_fs17.default.readdirSync(attRoot).length === 0) import_node_fs17.default.rmdirSync(attRoot);
27808
+ } catch {
27809
+ }
27810
+ }
27811
+ }
27812
+
27487
27813
  // 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);
27814
+ var import_node_fs18 = __toESM(require("fs"), 1);
27815
+ var import_node_path20 = __toESM(require("path"), 1);
27816
+ var import_node_crypto7 = __toESM(require("crypto"), 1);
27491
27817
  init_protocol();
27492
27818
  var GroupFileStore = class {
27493
27819
  dataDir;
@@ -27498,11 +27824,11 @@ var GroupFileStore = class {
27498
27824
  this.logger = opts.logger;
27499
27825
  }
27500
27826
  rootForScope(scope) {
27501
- return import_node_path18.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
27827
+ return import_node_path20.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
27502
27828
  }
27503
27829
  /** 与 SessionStore.filePath 平级,扩展名 .group-files.json */
27504
27830
  filePath(scope, sessionId) {
27505
- return import_node_path18.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
27831
+ return import_node_path20.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
27506
27832
  }
27507
27833
  cacheKey(scope, sessionId) {
27508
27834
  return scope.kind === "default" ? `default::${sessionId}` : `persona:${scope.personaId}:${scope.mode}::${sessionId}`;
@@ -27511,7 +27837,7 @@ var GroupFileStore = class {
27511
27837
  readFile(scope, sessionId) {
27512
27838
  const file = this.filePath(scope, sessionId);
27513
27839
  try {
27514
- const raw = import_node_fs16.default.readFileSync(file, "utf8");
27840
+ const raw = import_node_fs18.default.readFileSync(file, "utf8");
27515
27841
  const parsed = JSON.parse(raw);
27516
27842
  if (!Array.isArray(parsed)) {
27517
27843
  this.logger?.warn("GroupFileStore.readFile: not an array; resetting session entries", {
@@ -27537,10 +27863,10 @@ var GroupFileStore = class {
27537
27863
  }
27538
27864
  writeFile(scope, sessionId, entries) {
27539
27865
  const file = this.filePath(scope, sessionId);
27540
- import_node_fs16.default.mkdirSync(import_node_path18.default.dirname(file), { recursive: true });
27866
+ import_node_fs18.default.mkdirSync(import_node_path20.default.dirname(file), { recursive: true });
27541
27867
  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);
27868
+ import_node_fs18.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
27869
+ import_node_fs18.default.renameSync(tmp, file);
27544
27870
  }
27545
27871
  /** 拉一份当前 session 的清单。读盘 → cache;之后调用复用 cache */
27546
27872
  list(scope, sessionId) {
@@ -27576,7 +27902,7 @@ var GroupFileStore = class {
27576
27902
  entries[idx] = next;
27577
27903
  } else {
27578
27904
  next = {
27579
- id: `gf-${import_node_crypto6.default.randomBytes(6).toString("base64url")}`,
27905
+ id: `gf-${import_node_crypto7.default.randomBytes(6).toString("base64url")}`,
27580
27906
  relPath: input.relPath,
27581
27907
  from: input.from,
27582
27908
  label: input.label,
@@ -27626,10 +27952,10 @@ var GroupFileStore = class {
27626
27952
  };
27627
27953
 
27628
27954
  // src/discovery/state-file.ts
27629
- var import_node_fs17 = __toESM(require("fs"), 1);
27630
- var import_node_path19 = __toESM(require("path"), 1);
27955
+ var import_node_fs19 = __toESM(require("fs"), 1);
27956
+ var import_node_path21 = __toESM(require("path"), 1);
27631
27957
  function defaultStateFilePath(dataDir) {
27632
- return import_node_path19.default.join(dataDir, "state.json");
27958
+ return import_node_path21.default.join(dataDir, "state.json");
27633
27959
  }
27634
27960
  function isPidAlive(pid) {
27635
27961
  if (!Number.isFinite(pid) || pid <= 0) return false;
@@ -27651,7 +27977,7 @@ var StateFileManager = class {
27651
27977
  }
27652
27978
  read() {
27653
27979
  try {
27654
- const raw = import_node_fs17.default.readFileSync(this.file, "utf8");
27980
+ const raw = import_node_fs19.default.readFileSync(this.file, "utf8");
27655
27981
  const parsed = JSON.parse(raw);
27656
27982
  return parsed;
27657
27983
  } catch {
@@ -27665,34 +27991,34 @@ var StateFileManager = class {
27665
27991
  return { status: "stale", existing };
27666
27992
  }
27667
27993
  write(state) {
27668
- import_node_fs17.default.mkdirSync(import_node_path19.default.dirname(this.file), { recursive: true });
27994
+ import_node_fs19.default.mkdirSync(import_node_path21.default.dirname(this.file), { recursive: true });
27669
27995
  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);
27996
+ import_node_fs19.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
27997
+ import_node_fs19.default.renameSync(tmp, this.file);
27672
27998
  if (process.platform !== "win32") {
27673
27999
  try {
27674
- import_node_fs17.default.chmodSync(this.file, 384);
28000
+ import_node_fs19.default.chmodSync(this.file, 384);
27675
28001
  } catch {
27676
28002
  }
27677
28003
  }
27678
28004
  }
27679
28005
  delete() {
27680
28006
  try {
27681
- import_node_fs17.default.unlinkSync(this.file);
28007
+ import_node_fs19.default.unlinkSync(this.file);
27682
28008
  } catch {
27683
28009
  }
27684
28010
  }
27685
28011
  };
27686
28012
 
27687
28013
  // 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);
28014
+ var import_node_fs23 = __toESM(require("fs"), 1);
28015
+ var import_node_path25 = __toESM(require("path"), 1);
28016
+ var import_node_crypto8 = __toESM(require("crypto"), 1);
27691
28017
  var import_node_child_process5 = require("child_process");
27692
28018
 
27693
28019
  // src/tunnel/tunnel-store.ts
27694
- var import_node_fs18 = __toESM(require("fs"), 1);
27695
- var import_node_path20 = __toESM(require("path"), 1);
28020
+ var import_node_fs20 = __toESM(require("fs"), 1);
28021
+ var import_node_path22 = __toESM(require("path"), 1);
27696
28022
  var TunnelStore = class {
27697
28023
  constructor(filePath) {
27698
28024
  this.filePath = filePath;
@@ -27700,7 +28026,7 @@ var TunnelStore = class {
27700
28026
  filePath;
27701
28027
  async get() {
27702
28028
  try {
27703
- const raw = await import_node_fs18.default.promises.readFile(this.filePath, "utf8");
28029
+ const raw = await import_node_fs20.default.promises.readFile(this.filePath, "utf8");
27704
28030
  const obj = JSON.parse(raw);
27705
28031
  if (!isPersistedTunnel(obj)) return null;
27706
28032
  return obj;
@@ -27711,22 +28037,22 @@ var TunnelStore = class {
27711
28037
  }
27712
28038
  }
27713
28039
  async set(v2) {
27714
- const dir = import_node_path20.default.dirname(this.filePath);
27715
- await import_node_fs18.default.promises.mkdir(dir, { recursive: true });
28040
+ const dir = import_node_path22.default.dirname(this.filePath);
28041
+ await import_node_fs20.default.promises.mkdir(dir, { recursive: true });
27716
28042
  const data = JSON.stringify(v2, null, 2);
27717
28043
  const tmp = `${this.filePath}.tmp.${process.pid}.${Date.now()}`;
27718
- await import_node_fs18.default.promises.writeFile(tmp, data, { mode: 384 });
28044
+ await import_node_fs20.default.promises.writeFile(tmp, data, { mode: 384 });
27719
28045
  if (process.platform !== "win32") {
27720
28046
  try {
27721
- await import_node_fs18.default.promises.chmod(tmp, 384);
28047
+ await import_node_fs20.default.promises.chmod(tmp, 384);
27722
28048
  } catch {
27723
28049
  }
27724
28050
  }
27725
- await import_node_fs18.default.promises.rename(tmp, this.filePath);
28051
+ await import_node_fs20.default.promises.rename(tmp, this.filePath);
27726
28052
  }
27727
28053
  async clear() {
27728
28054
  try {
27729
- await import_node_fs18.default.promises.unlink(this.filePath);
28055
+ await import_node_fs20.default.promises.unlink(this.filePath);
27730
28056
  } catch (err) {
27731
28057
  const code = err?.code;
27732
28058
  if (code !== "ENOENT") throw err;
@@ -27821,12 +28147,12 @@ function escape(v2) {
27821
28147
  }
27822
28148
 
27823
28149
  // src/tunnel/frpc-binary.ts
27824
- var import_node_fs19 = __toESM(require("fs"), 1);
28150
+ var import_node_fs21 = __toESM(require("fs"), 1);
27825
28151
  var import_node_os10 = __toESM(require("os"), 1);
27826
- var import_node_path21 = __toESM(require("path"), 1);
28152
+ var import_node_path23 = __toESM(require("path"), 1);
27827
28153
  var import_node_child_process3 = require("child_process");
27828
28154
  var import_node_stream2 = require("stream");
27829
- var import_promises = require("stream/promises");
28155
+ var import_promises2 = require("stream/promises");
27830
28156
  var FRPC_VERSION = "0.68.0";
27831
28157
  var UnsupportedPlatformError = class extends Error {
27832
28158
  constructor(platform, arch) {
@@ -27855,20 +28181,20 @@ function frpcDownloadUrl(version2, p2) {
27855
28181
  }
27856
28182
  async function ensureFrpcBinary(opts) {
27857
28183
  if (opts.override) {
27858
- if (!import_node_fs19.default.existsSync(opts.override)) {
28184
+ if (!import_node_fs21.default.existsSync(opts.override)) {
27859
28185
  throw new Error(`frpc binary not found at override path: ${opts.override}`);
27860
28186
  }
27861
28187
  return opts.override;
27862
28188
  }
27863
28189
  const version2 = opts.version ?? FRPC_VERSION;
27864
28190
  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 });
28191
+ const binDir = import_node_path23.default.join(opts.dataDir, "bin");
28192
+ import_node_fs21.default.mkdirSync(binDir, { recursive: true });
27867
28193
  cleanupStaleArtifacts(binDir);
27868
- const stableBin = import_node_path21.default.join(binDir, "frpc");
27869
- if (import_node_fs19.default.existsSync(stableBin)) return stableBin;
28194
+ const stableBin = import_node_path23.default.join(binDir, "frpc");
28195
+ if (import_node_fs21.default.existsSync(stableBin)) return stableBin;
27870
28196
  const partialBin = `${stableBin}.partial`;
27871
- const tarballPath = import_node_path21.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
28197
+ const tarballPath = import_node_path23.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
27872
28198
  try {
27873
28199
  const url = frpcDownloadUrl(version2, platform);
27874
28200
  await downloadToFile(url, tarballPath, opts.fetchImpl);
@@ -27877,8 +28203,8 @@ async function ensureFrpcBinary(opts) {
27877
28203
  } else {
27878
28204
  await extractFrpcFromTarball(tarballPath, binDir, version2, platform, partialBin);
27879
28205
  }
27880
- import_node_fs19.default.chmodSync(partialBin, 493);
27881
- import_node_fs19.default.renameSync(partialBin, stableBin);
28206
+ import_node_fs21.default.chmodSync(partialBin, 493);
28207
+ import_node_fs21.default.renameSync(partialBin, stableBin);
27882
28208
  } finally {
27883
28209
  safeUnlink(tarballPath);
27884
28210
  safeUnlink(partialBin);
@@ -27888,15 +28214,15 @@ async function ensureFrpcBinary(opts) {
27888
28214
  function cleanupStaleArtifacts(binDir) {
27889
28215
  let entries;
27890
28216
  try {
27891
- entries = import_node_fs19.default.readdirSync(binDir);
28217
+ entries = import_node_fs21.default.readdirSync(binDir);
27892
28218
  } catch {
27893
28219
  return;
27894
28220
  }
27895
28221
  for (const name of entries) {
27896
28222
  if (name.endsWith(".partial") || name.startsWith("extract-")) {
27897
- const full = import_node_path21.default.join(binDir, name);
28223
+ const full = import_node_path23.default.join(binDir, name);
27898
28224
  try {
27899
- import_node_fs19.default.rmSync(full, { recursive: true, force: true });
28225
+ import_node_fs21.default.rmSync(full, { recursive: true, force: true });
27900
28226
  } catch {
27901
28227
  }
27902
28228
  }
@@ -27904,7 +28230,7 @@ function cleanupStaleArtifacts(binDir) {
27904
28230
  }
27905
28231
  function safeUnlink(p2) {
27906
28232
  try {
27907
- import_node_fs19.default.unlinkSync(p2);
28233
+ import_node_fs21.default.unlinkSync(p2);
27908
28234
  } catch {
27909
28235
  }
27910
28236
  }
@@ -27915,13 +28241,13 @@ async function downloadToFile(url, dest, fetchImpl) {
27915
28241
  if (!res.ok || !res.body) {
27916
28242
  throw new Error(`download failed: ${res.status} ${res.statusText}`);
27917
28243
  }
27918
- const out = import_node_fs19.default.createWriteStream(dest);
28244
+ const out = import_node_fs21.default.createWriteStream(dest);
27919
28245
  const nodeStream = import_node_stream2.Readable.fromWeb(res.body);
27920
- await (0, import_promises.pipeline)(nodeStream, out);
28246
+ await (0, import_promises2.pipeline)(nodeStream, out);
27921
28247
  }
27922
28248
  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 });
28249
+ const work = import_node_path23.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
28250
+ import_node_fs21.default.mkdirSync(work, { recursive: true });
27925
28251
  try {
27926
28252
  await new Promise((resolve6, reject) => {
27927
28253
  const proc = (0, import_node_child_process3.spawn)("tar", ["xzf", tarball, "-C", work], { stdio: "pipe" });
@@ -27929,32 +28255,32 @@ async function extractFrpcFromTarball(tarball, binDir, version2, platform, destB
27929
28255
  proc.on("exit", (code) => code === 0 ? resolve6() : reject(new Error(`tar exited ${code}`)));
27930
28256
  });
27931
28257
  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)) {
28258
+ const src = import_node_path23.default.join(work, dirName, "frpc");
28259
+ if (!import_node_fs21.default.existsSync(src)) {
27934
28260
  throw new Error(`frpc not found inside tarball at ${src}`);
27935
28261
  }
27936
- import_node_fs19.default.copyFileSync(src, destBin);
28262
+ import_node_fs21.default.copyFileSync(src, destBin);
27937
28263
  } finally {
27938
- import_node_fs19.default.rmSync(work, { recursive: true, force: true });
28264
+ import_node_fs21.default.rmSync(work, { recursive: true, force: true });
27939
28265
  }
27940
28266
  }
27941
28267
 
27942
28268
  // src/tunnel/frpc-process.ts
27943
- var import_node_fs20 = __toESM(require("fs"), 1);
27944
- var import_node_path22 = __toESM(require("path"), 1);
28269
+ var import_node_fs22 = __toESM(require("fs"), 1);
28270
+ var import_node_path24 = __toESM(require("path"), 1);
27945
28271
  var import_node_child_process4 = require("child_process");
27946
28272
  function frpcPidFilePath(dataDir) {
27947
- return import_node_path22.default.join(dataDir, "frpc.pid");
28273
+ return import_node_path24.default.join(dataDir, "frpc.pid");
27948
28274
  }
27949
28275
  function writeFrpcPid(dataDir, pid) {
27950
28276
  try {
27951
- import_node_fs20.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
28277
+ import_node_fs22.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
27952
28278
  } catch {
27953
28279
  }
27954
28280
  }
27955
28281
  function clearFrpcPid(dataDir) {
27956
28282
  try {
27957
- import_node_fs20.default.unlinkSync(frpcPidFilePath(dataDir));
28283
+ import_node_fs22.default.unlinkSync(frpcPidFilePath(dataDir));
27958
28284
  } catch {
27959
28285
  }
27960
28286
  }
@@ -27970,7 +28296,7 @@ function defaultIsPidAlive(pid) {
27970
28296
  }
27971
28297
  function defaultReadPidFile(file) {
27972
28298
  try {
27973
- return import_node_fs20.default.readFileSync(file, "utf8");
28299
+ return import_node_fs22.default.readFileSync(file, "utf8");
27974
28300
  } catch {
27975
28301
  return null;
27976
28302
  }
@@ -27986,7 +28312,7 @@ function defaultSleep(ms) {
27986
28312
  }
27987
28313
  async function killStaleFrpc(deps) {
27988
28314
  const pidFile = frpcPidFilePath(deps.dataDir);
27989
- const tomlPath = import_node_path22.default.join(deps.dataDir, "frpc.toml");
28315
+ const tomlPath = import_node_path24.default.join(deps.dataDir, "frpc.toml");
27990
28316
  const readPidFile = deps.readPidFileImpl ?? defaultReadPidFile;
27991
28317
  const isAlive = deps.isPidAliveImpl ?? defaultIsPidAlive;
27992
28318
  const killPid = deps.killPidImpl ?? defaultKillPid;
@@ -28010,7 +28336,7 @@ async function killStaleFrpc(deps) {
28010
28336
  }
28011
28337
  if (victims.size === 0) {
28012
28338
  try {
28013
- import_node_fs20.default.unlinkSync(pidFile);
28339
+ import_node_fs22.default.unlinkSync(pidFile);
28014
28340
  } catch {
28015
28341
  }
28016
28342
  return;
@@ -28021,7 +28347,7 @@ async function killStaleFrpc(deps) {
28021
28347
  }
28022
28348
  await sleep(deps.reapWaitMs ?? 300);
28023
28349
  try {
28024
- import_node_fs20.default.unlinkSync(pidFile);
28350
+ import_node_fs22.default.unlinkSync(pidFile);
28025
28351
  } catch {
28026
28352
  }
28027
28353
  }
@@ -28058,7 +28384,7 @@ var DEFAULT_TUNNEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
28058
28384
  var TunnelManager = class {
28059
28385
  constructor(deps) {
28060
28386
  this.deps = deps;
28061
- this.store = deps.store ?? new TunnelStore(import_node_path23.default.join(deps.dataDir, "tunnel.json"));
28387
+ this.store = deps.store ?? new TunnelStore(import_node_path25.default.join(deps.dataDir, "tunnel.json"));
28062
28388
  this.ttlMs = deps.ttlMs ?? DEFAULT_TUNNEL_TTL_MS;
28063
28389
  this.startupTimeoutMs = deps.startupTimeoutMs ?? 15e3;
28064
28390
  }
@@ -28185,8 +28511,8 @@ var TunnelManager = class {
28185
28511
  dataDir: this.deps.dataDir,
28186
28512
  override: this.deps.frpcBinaryOverride ?? void 0
28187
28513
  });
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")}`;
28514
+ const tomlPath = import_node_path25.default.join(this.deps.dataDir, "frpc.toml");
28515
+ const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto8.default.randomBytes(3).toString("hex")}`;
28190
28516
  const toml = buildFrpcToml({
28191
28517
  serverAddr: t.frpsHost,
28192
28518
  serverPort: t.frpsPort,
@@ -28196,12 +28522,12 @@ var TunnelManager = class {
28196
28522
  localPort,
28197
28523
  logLevel: "info"
28198
28524
  });
28199
- await import_node_fs21.default.promises.writeFile(tomlPath, toml, { mode: 384 });
28525
+ await import_node_fs23.default.promises.writeFile(tomlPath, toml, { mode: 384 });
28200
28526
  const proc = (this.deps.spawnImpl ?? import_node_child_process5.spawn)(frpcBin, ["-c", tomlPath], {
28201
28527
  stdio: ["ignore", "pipe", "pipe"]
28202
28528
  });
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 });
28529
+ const logFilePath = import_node_path25.default.join(this.deps.dataDir, "frpc.log");
28530
+ const logStream = import_node_fs23.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
28205
28531
  logStream.on("error", () => {
28206
28532
  });
28207
28533
  const tee = (chunk) => {
@@ -28284,22 +28610,22 @@ async function waitForFrpcReady(proc, timeoutMs) {
28284
28610
 
28285
28611
  // src/tunnel/device-key.ts
28286
28612
  var import_node_os11 = __toESM(require("os"), 1);
28287
- var import_node_crypto8 = __toESM(require("crypto"), 1);
28613
+ var import_node_crypto9 = __toESM(require("crypto"), 1);
28288
28614
  var DERIVE_SALT = "clawd-tunnel-device-v1";
28289
28615
  function deriveStableDeviceKey(opts = {}) {
28290
28616
  const hostname = opts.hostname ?? import_node_os11.default.hostname();
28291
28617
  const uid = opts.uid ?? (typeof import_node_os11.default.userInfo === "function" ? import_node_os11.default.userInfo().uid : 0);
28292
28618
  const input = `${hostname}::${uid}`;
28293
- return import_node_crypto8.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
28619
+ return import_node_crypto9.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
28294
28620
  }
28295
28621
 
28296
28622
  // 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);
28623
+ var import_node_fs24 = __toESM(require("fs"), 1);
28624
+ var import_node_path26 = __toESM(require("path"), 1);
28625
+ var import_node_crypto10 = __toESM(require("crypto"), 1);
28300
28626
  var AUTH_FILE_NAME = "auth.json";
28301
28627
  function authFilePath(dataDir) {
28302
- return import_node_path24.default.join(dataDir, AUTH_FILE_NAME);
28628
+ return import_node_path26.default.join(dataDir, AUTH_FILE_NAME);
28303
28629
  }
28304
28630
  function loadOrCreateAuthFile(opts) {
28305
28631
  const file = authFilePath(opts.dataDir);
@@ -28324,14 +28650,14 @@ function loadOrCreateAuthFile(opts) {
28324
28650
  return next;
28325
28651
  }
28326
28652
  function defaultGenerateToken2() {
28327
- return import_node_crypto9.default.randomBytes(32).toString("base64url");
28653
+ return import_node_crypto10.default.randomBytes(32).toString("base64url");
28328
28654
  }
28329
28655
  function defaultGenerateOwnerPrincipalId() {
28330
- return `owner-${import_node_crypto9.default.randomUUID()}`;
28656
+ return `owner-${import_node_crypto10.default.randomUUID()}`;
28331
28657
  }
28332
28658
  function readAuthFile(file) {
28333
28659
  try {
28334
- const raw = import_node_fs22.default.readFileSync(file, "utf8");
28660
+ const raw = import_node_fs24.default.readFileSync(file, "utf8");
28335
28661
  const parsed = JSON.parse(raw);
28336
28662
  if (typeof parsed?.token !== "string" || parsed.token.length === 0) {
28337
28663
  return null;
@@ -28349,25 +28675,25 @@ function readAuthFile(file) {
28349
28675
  }
28350
28676
  }
28351
28677
  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 });
28678
+ import_node_fs24.default.mkdirSync(import_node_path26.default.dirname(file), { recursive: true });
28679
+ import_node_fs24.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
28354
28680
  try {
28355
- import_node_fs22.default.chmodSync(file, 384);
28681
+ import_node_fs24.default.chmodSync(file, 384);
28356
28682
  } catch {
28357
28683
  }
28358
28684
  }
28359
28685
 
28360
28686
  // src/owner-profile.ts
28361
- var import_node_fs23 = __toESM(require("fs"), 1);
28687
+ var import_node_fs25 = __toESM(require("fs"), 1);
28362
28688
  var import_node_os12 = __toESM(require("os"), 1);
28363
- var import_node_path25 = __toESM(require("path"), 1);
28689
+ var import_node_path27 = __toESM(require("path"), 1);
28364
28690
  var PROFILE_FILENAME = "profile.json";
28365
28691
  function loadOwnerDisplayName(dataDir) {
28366
28692
  const fallback = import_node_os12.default.userInfo().username;
28367
- const profilePath = import_node_path25.default.join(dataDir, PROFILE_FILENAME);
28693
+ const profilePath = import_node_path27.default.join(dataDir, PROFILE_FILENAME);
28368
28694
  let raw;
28369
28695
  try {
28370
- raw = import_node_fs23.default.readFileSync(profilePath, "utf8");
28696
+ raw = import_node_fs25.default.readFileSync(profilePath, "utf8");
28371
28697
  } catch {
28372
28698
  return fallback;
28373
28699
  }
@@ -28396,12 +28722,12 @@ init_protocol();
28396
28722
  init_protocol();
28397
28723
 
28398
28724
  // src/session/fork.ts
28399
- var import_node_fs24 = __toESM(require("fs"), 1);
28725
+ var import_node_fs26 = __toESM(require("fs"), 1);
28400
28726
  var import_node_os13 = __toESM(require("os"), 1);
28401
- var import_node_path26 = __toESM(require("path"), 1);
28727
+ var import_node_path28 = __toESM(require("path"), 1);
28402
28728
  init_claude_history();
28403
28729
  function readJsonlEntries(file) {
28404
- const raw = import_node_fs24.default.readFileSync(file, "utf8");
28730
+ const raw = import_node_fs26.default.readFileSync(file, "utf8");
28405
28731
  const out = [];
28406
28732
  for (const line of raw.split("\n")) {
28407
28733
  const t = line.trim();
@@ -28414,10 +28740,10 @@ function readJsonlEntries(file) {
28414
28740
  return out;
28415
28741
  }
28416
28742
  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)) {
28743
+ const baseDir = input.baseDir ?? import_node_path28.default.join(import_node_os13.default.homedir(), ".claude");
28744
+ const projectDir = import_node_path28.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
28745
+ const sourceFile = import_node_path28.default.join(projectDir, `${input.toolSessionId}.jsonl`);
28746
+ if (!import_node_fs26.default.existsSync(sourceFile)) {
28421
28747
  throw new Error(`fork: source transcript not found: ${sourceFile}`);
28422
28748
  }
28423
28749
  const entries = readJsonlEntries(sourceFile);
@@ -28447,9 +28773,9 @@ function forkSession(input) {
28447
28773
  }
28448
28774
  forkedLines.push(JSON.stringify(forked));
28449
28775
  }
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 });
28776
+ const forkedFilePath = import_node_path28.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
28777
+ import_node_fs26.default.mkdirSync(projectDir, { recursive: true });
28778
+ import_node_fs26.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
28453
28779
  return { forkedToolSessionId, forkedFilePath };
28454
28780
  }
28455
28781
 
@@ -28805,7 +29131,7 @@ function buildPermissionHandlers(deps) {
28805
29131
  }
28806
29132
 
28807
29133
  // src/handlers/history.ts
28808
- var path36 = __toESM(require("path"), 1);
29134
+ var path38 = __toESM(require("path"), 1);
28809
29135
  init_protocol();
28810
29136
 
28811
29137
  // src/session/recent-dirs.ts
@@ -28823,7 +29149,7 @@ function listRecentDirs(store, limit = 50) {
28823
29149
  }
28824
29150
 
28825
29151
  // src/permission/persona-paths.ts
28826
- var path35 = __toESM(require("path"), 1);
29152
+ var path37 = __toESM(require("path"), 1);
28827
29153
  function getAllowedPersonaIds(grants, action) {
28828
29154
  const ids = /* @__PURE__ */ new Set();
28829
29155
  for (const g2 of grants) {
@@ -28836,26 +29162,26 @@ function getAllowedPersonaIds(grants, action) {
28836
29162
  return ids;
28837
29163
  }
28838
29164
  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;
29165
+ const root = path37.resolve(personaRoot);
29166
+ const target = path37.resolve(absPath);
29167
+ const sep3 = root.endsWith(path37.sep) ? "" : path37.sep;
28842
29168
  if (!target.startsWith(root + sep3)) return false;
28843
- const rel = path35.relative(root, target);
29169
+ const rel = path37.relative(root, target);
28844
29170
  if (!rel || rel.startsWith("..")) return false;
28845
- const personaId = rel.split(path35.sep)[0];
29171
+ const personaId = rel.split(path37.sep)[0];
28846
29172
  if (!personaId) return false;
28847
29173
  const allowed = getAllowedPersonaIds(grants, action);
28848
29174
  if (allowed === "*") return true;
28849
29175
  return allowed.has(personaId);
28850
29176
  }
28851
29177
  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;
29178
+ const root = path37.resolve(personaRoot);
29179
+ const target = path37.resolve(absPath);
29180
+ const sep3 = root.endsWith(path37.sep) ? "" : path37.sep;
28855
29181
  if (!target.startsWith(root + sep3)) return null;
28856
- const rel = path35.relative(root, target);
29182
+ const rel = path37.relative(root, target);
28857
29183
  if (!rel || rel.startsWith("..")) return null;
28858
- const id = rel.split(path35.sep)[0];
29184
+ const id = rel.split(path37.sep)[0];
28859
29185
  return id || null;
28860
29186
  }
28861
29187
 
@@ -28880,7 +29206,7 @@ function buildHistoryHandlers(deps) {
28880
29206
  if (!pid) return false;
28881
29207
  return isGuestPathAllowed(
28882
29208
  ctx.grants,
28883
- path36.join(personaRoot, pid),
29209
+ path38.join(personaRoot, pid),
28884
29210
  personaRoot,
28885
29211
  "read"
28886
29212
  );
@@ -28891,7 +29217,7 @@ function buildHistoryHandlers(deps) {
28891
29217
  };
28892
29218
  const list = async (frame, _client, ctx) => {
28893
29219
  const args = HistoryListArgs.parse(frame);
28894
- assertGuestPath(ctx, path36.resolve(args.projectPath), personaRoot, "history:list");
29220
+ assertGuestPath(ctx, path38.resolve(args.projectPath), personaRoot, "history:list");
28895
29221
  const sessions = await history.listSessions(args);
28896
29222
  return { response: { type: "history:list", sessions } };
28897
29223
  };
@@ -28923,13 +29249,13 @@ function buildHistoryHandlers(deps) {
28923
29249
  };
28924
29250
  const subagents = async (frame, _client, ctx) => {
28925
29251
  const args = HistorySubagentsArgs.parse(frame);
28926
- assertGuestPath(ctx, path36.resolve(args.cwd), personaRoot, "history:subagents");
29252
+ assertGuestPath(ctx, path38.resolve(args.cwd), personaRoot, "history:subagents");
28927
29253
  const subs = await history.listSubagents(args);
28928
29254
  return { response: { type: "history:subagents", subagents: subs } };
28929
29255
  };
28930
29256
  const subagentRead = async (frame, _client, ctx) => {
28931
29257
  const args = HistorySubagentReadArgs.parse(frame);
28932
- assertGuestPath(ctx, path36.resolve(args.cwd), personaRoot, "history:subagent-read");
29258
+ assertGuestPath(ctx, path38.resolve(args.cwd), personaRoot, "history:subagent-read");
28933
29259
  const res = await history.readSubagent(args);
28934
29260
  return { response: { type: "history:subagent-read", ...res } };
28935
29261
  };
@@ -28937,7 +29263,7 @@ function buildHistoryHandlers(deps) {
28937
29263
  const dirs = listRecentDirs(store);
28938
29264
  if (ctx?.principal.kind === "guest" && personaRoot) {
28939
29265
  const filtered = dirs.filter(
28940
- (d) => isGuestPathAllowed(ctx.grants, path36.resolve(d.cwd), personaRoot, "read")
29266
+ (d) => isGuestPathAllowed(ctx.grants, path38.resolve(d.cwd), personaRoot, "read")
28941
29267
  );
28942
29268
  return { response: { type: "history:recentDirs", dirs: filtered } };
28943
29269
  }
@@ -28954,7 +29280,7 @@ function buildHistoryHandlers(deps) {
28954
29280
  }
28955
29281
 
28956
29282
  // src/handlers/workspace.ts
28957
- var path37 = __toESM(require("path"), 1);
29283
+ var path39 = __toESM(require("path"), 1);
28958
29284
  var os14 = __toESM(require("os"), 1);
28959
29285
  init_protocol();
28960
29286
  init_protocol();
@@ -28995,22 +29321,22 @@ function buildWorkspaceHandlers(deps) {
28995
29321
  const args = WorkspaceListArgs.parse(frame);
28996
29322
  const isGuest = ctx?.principal.kind === "guest";
28997
29323
  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;
29324
+ const resolvedCwd = path39.resolve(args.cwd ?? fallbackCwd);
29325
+ const target = args.path ? path39.resolve(resolvedCwd, args.path) : resolvedCwd;
29000
29326
  assertGuestPath2(ctx, target, personaRoot, "workspace:list");
29001
29327
  const res = workspace.list({ ...args, cwd: resolvedCwd });
29002
29328
  return { response: { type: "workspace:list", ...res } };
29003
29329
  };
29004
29330
  const read = async (frame, _client, ctx) => {
29005
29331
  const args = WorkspaceReadArgs.parse(frame);
29006
- const target = path37.isAbsolute(args.path) ? path37.resolve(args.path) : path37.resolve(args.cwd, args.path);
29332
+ const target = path39.isAbsolute(args.path) ? path39.resolve(args.path) : path39.resolve(args.cwd, args.path);
29007
29333
  assertGuestPath2(ctx, target, personaRoot, "workspace:read");
29008
29334
  const res = workspace.read(args);
29009
29335
  return { response: { type: "workspace:read", ...res } };
29010
29336
  };
29011
29337
  const skillsList = async (frame, _client, ctx) => {
29012
29338
  const args = SkillsListArgs.parse(frame);
29013
- const cwdAbs = path37.resolve(args.cwd);
29339
+ const cwdAbs = path39.resolve(args.cwd);
29014
29340
  assertGuestPath2(ctx, cwdAbs, personaRoot, "skills:list");
29015
29341
  const list2 = skills.list(args);
29016
29342
  if (ctx?.principal.kind === "guest" && personaRoot) {
@@ -29022,7 +29348,7 @@ function buildWorkspaceHandlers(deps) {
29022
29348
  };
29023
29349
  const agentsList = async (frame, _client, ctx) => {
29024
29350
  const args = AgentsListArgs.parse(frame);
29025
- const cwdAbs = path37.resolve(args.cwd);
29351
+ const cwdAbs = path39.resolve(args.cwd);
29026
29352
  assertGuestPath2(ctx, cwdAbs, personaRoot, "agents:list");
29027
29353
  const list2 = agents.list(args);
29028
29354
  if (ctx?.principal.kind === "guest" && personaRoot) {
@@ -29041,20 +29367,20 @@ function buildWorkspaceHandlers(deps) {
29041
29367
  }
29042
29368
 
29043
29369
  // src/handlers/git.ts
29044
- var path39 = __toESM(require("path"), 1);
29370
+ var path41 = __toESM(require("path"), 1);
29045
29371
  init_protocol();
29046
29372
  init_protocol();
29047
29373
 
29048
29374
  // src/workspace/git.ts
29049
29375
  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);
29376
+ var import_node_fs27 = __toESM(require("fs"), 1);
29377
+ var import_node_path29 = __toESM(require("path"), 1);
29052
29378
  var import_node_util = require("util");
29053
29379
  var pexec = (0, import_node_util.promisify)(import_node_child_process6.execFile);
29054
29380
  function normalizePath(p2) {
29055
- const resolved = import_node_path27.default.resolve(p2);
29381
+ const resolved = import_node_path29.default.resolve(p2);
29056
29382
  try {
29057
- return import_node_fs25.default.realpathSync(resolved);
29383
+ return import_node_fs27.default.realpathSync(resolved);
29058
29384
  } catch {
29059
29385
  return resolved;
29060
29386
  }
@@ -29127,7 +29453,7 @@ async function listGitBranches(cwd) {
29127
29453
  // src/handlers/git.ts
29128
29454
  function assertGuestCwd(ctx, cwd, personaRoot, method) {
29129
29455
  if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
29130
- if (!isGuestPathAllowed(ctx.grants, path39.resolve(cwd), personaRoot, "read")) {
29456
+ if (!isGuestPathAllowed(ctx.grants, path41.resolve(cwd), personaRoot, "read")) {
29131
29457
  throw new ClawdError(
29132
29458
  ERROR_CODES.UNAUTHORIZED,
29133
29459
  `guest ${ctx.principal.id} cannot ${method} cwd ${cwd}`
@@ -29614,7 +29940,7 @@ function buildPersonaHandlers(deps) {
29614
29940
  }
29615
29941
 
29616
29942
  // src/handlers/attachment.ts
29617
- var import_node_path28 = __toESM(require("path"), 1);
29943
+ var import_node_path30 = __toESM(require("path"), 1);
29618
29944
  init_protocol();
29619
29945
  init_protocol();
29620
29946
  var DEFAULT_TTL_SECONDS = 24 * 3600;
@@ -29668,12 +29994,12 @@ function buildAttachmentHandlers(deps) {
29668
29994
  `session ${args.sessionId} scope unresolved`
29669
29995
  );
29670
29996
  }
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);
29997
+ const cwdAbs = import_node_path30.default.resolve(sessionFile.cwd);
29998
+ 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
29999
  assertGuestAttachmentPath(ctx, candidateAbs, deps.personaRoot, "attachment.signUrl");
29674
30000
  const entries = deps.groupFileStore.list(scope, args.sessionId);
29675
30001
  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);
30002
+ 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
30003
  return storedAbs === candidateAbs && !e.stale;
29678
30004
  });
29679
30005
  if (!entry) {
@@ -29697,7 +30023,7 @@ function buildAttachmentHandlers(deps) {
29697
30023
  if (!ctx || ctx.principal.kind !== "guest" || !deps.personaRoot || !deps.sessionStore) return;
29698
30024
  const f = deps.sessionStore.read(sessionId);
29699
30025
  if (!f) return;
29700
- assertGuestAttachmentPath(ctx, import_node_path28.default.resolve(f.cwd), deps.personaRoot, method);
30026
+ assertGuestAttachmentPath(ctx, import_node_path30.default.resolve(f.cwd), deps.personaRoot, method);
29701
30027
  }
29702
30028
  const groupAdd = async (frame, _client, ctx) => {
29703
30029
  if (!deps.groupFileStore || !deps.getSessionScope) {
@@ -29738,7 +30064,7 @@ function buildAttachmentHandlers(deps) {
29738
30064
  }
29739
30065
  const entries = deps.groupFileStore.list(scope, parsed.data.sessionId);
29740
30066
  const target = entries.find((e) => e.relPath === parsed.data.relPath);
29741
- if (target?.from === "owner") {
30067
+ if (target?.from === "owner" || target?.from === "user-upload") {
29742
30068
  deps.groupFileStore.remove(scope, parsed.data.sessionId, parsed.data.relPath);
29743
30069
  } else {
29744
30070
  deps.groupFileStore.markStale(scope, parsed.data.sessionId, parsed.data.relPath);
@@ -29939,7 +30265,7 @@ function computeGrantForFrame(method, frame) {
29939
30265
  async function startDaemon(config) {
29940
30266
  const logger = createLogger({
29941
30267
  level: config.logLevel,
29942
- file: import_node_path29.default.join(config.dataDir, "clawd.log")
30268
+ file: import_node_path31.default.join(config.dataDir, "clawd.log")
29943
30269
  });
29944
30270
  logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
29945
30271
  const stateMgr = new StateFileManager({ dataDir: config.dataDir });
@@ -30026,7 +30352,7 @@ async function startDaemon(config) {
30026
30352
  const agents = new AgentsScanner();
30027
30353
  const history = new ClaudeHistoryReader();
30028
30354
  let transport = null;
30029
- const personaStore = new PersonaStore(import_node_path29.default.join(config.dataDir, "personas"));
30355
+ const personaStore = new PersonaStore(import_node_path31.default.join(config.dataDir, "personas"));
30030
30356
  const defaultsRoot = findDefaultsRoot();
30031
30357
  if (defaultsRoot) {
30032
30358
  seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
@@ -30043,7 +30369,7 @@ async function startDaemon(config) {
30043
30369
  getAdapter,
30044
30370
  historyReader: history,
30045
30371
  dataDir: config.dataDir,
30046
- personaRoot: import_node_path29.default.join(config.dataDir, "personas"),
30372
+ personaRoot: import_node_path31.default.join(config.dataDir, "personas"),
30047
30373
  personaStore,
30048
30374
  ownerDisplayName,
30049
30375
  ownerPrincipalId,
@@ -30067,10 +30393,10 @@ async function startDaemon(config) {
30067
30393
  // 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
30068
30394
  attachmentGroup: {
30069
30395
  onFileEdit: (input) => {
30070
- const absPath = import_node_path29.default.isAbsolute(input.relPath) ? input.relPath : import_node_path29.default.join(input.cwd, input.relPath);
30396
+ const absPath = import_node_path31.default.isAbsolute(input.relPath) ? input.relPath : import_node_path31.default.join(input.cwd, input.relPath);
30071
30397
  let size = 0;
30072
30398
  try {
30073
- size = import_node_fs26.default.statSync(absPath).size;
30399
+ size = import_node_fs28.default.statSync(absPath).size;
30074
30400
  } catch (err) {
30075
30401
  logger.warn("attachment.onFileEdit stat failed", {
30076
30402
  sessionId: input.sessionId,
@@ -30188,10 +30514,10 @@ async function startDaemon(config) {
30188
30514
  // 'persona/<pid>/owner',default 走 'default'。
30189
30515
  getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
30190
30516
  // guest path guard:candidate 必须在 personaRoot 子树下
30191
- personaRoot: import_node_path29.default.join(config.dataDir, "personas")
30517
+ personaRoot: import_node_path31.default.join(config.dataDir, "personas")
30192
30518
  },
30193
30519
  // workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
30194
- personaRoot: import_node_path29.default.join(config.dataDir, "personas"),
30520
+ personaRoot: import_node_path31.default.join(config.dataDir, "personas"),
30195
30521
  // capability:list / delete handler 依赖
30196
30522
  capabilityManager,
30197
30523
  // personal-cap:get 返回的 shareBaseUrl 用此 base URL:
@@ -30227,7 +30553,19 @@ async function startDaemon(config) {
30227
30553
  sessionStore: store,
30228
30554
  // /files HMAC verify 用 auth.json 的 signSecret 字段(与 attachment.signUrl 同源)。
30229
30555
  // --auth-token CLI 模式没 signSecret → 路由返 501,sign URL 功能整体禁用。
30230
- getSignSecret: () => authFile?.signSecret ?? null
30556
+ getSignSecret: () => authFile?.signSecret ?? null,
30557
+ // chatinput 附件上传(POST /attachments/upload,spec §6.1)。upload pipeline
30558
+ // 在 session.cwd 下的 .attachments/ 落盘;signSecret 与 /files HMAC 同源;
30559
+ // tunnel 未启时返 503 TUNNEL_NOT_READY 让前端显式提示。
30560
+ attachmentUpload: {
30561
+ groupFileStore,
30562
+ getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
30563
+ getSessionDir: (sid) => {
30564
+ const f = manager.findOwnedSession(sid);
30565
+ return f ? f.cwd : null;
30566
+ },
30567
+ getHttpBaseUrl: () => currentTunnelUrl ? getHttpBaseUrl() : null
30568
+ }
30231
30569
  });
30232
30570
  wsServer = new LocalWsServer({
30233
30571
  host: config.host,
@@ -30392,8 +30730,8 @@ async function startDaemon(config) {
30392
30730
  const lines = [
30393
30731
  `Tunnel: ${r.url}`,
30394
30732
  ...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")}`
30733
+ `Frpc config: ${import_node_path31.default.join(config.dataDir, "frpc.toml")}`,
30734
+ `Frpc log: ${import_node_path31.default.join(config.dataDir, "frpc.log")}`
30397
30735
  ];
30398
30736
  const width = Math.max(...lines.map((l) => l.length));
30399
30737
  const bar = "\u2550".repeat(width + 4);
@@ -30406,8 +30744,8 @@ ${bar}
30406
30744
 
30407
30745
  `);
30408
30746
  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 });
30747
+ const connectPath = import_node_path31.default.join(config.dataDir, "connect.txt");
30748
+ import_node_fs28.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
30411
30749
  } catch {
30412
30750
  }
30413
30751
  } catch (err) {
@@ -30432,8 +30770,29 @@ ${bar}
30432
30770
  logger.warn("tunnel unavailable, degraded to local mode", { reason: tunnelError });
30433
30771
  }
30434
30772
  }
30773
+ const tickAttachmentGc = () => {
30774
+ try {
30775
+ runAttachmentGc({
30776
+ dataDir: config.dataDir,
30777
+ groupFileStore,
30778
+ sessionScopes: manager.listOwnedSessionScopes().map(({ scope, sessionId }) => ({
30779
+ scope,
30780
+ sessionId
30781
+ })),
30782
+ // 注入 cwd 让 GC 直接定位 .attachments 目录(与 upload 落盘点一致)
30783
+ getSessionCwd: (sid) => manager.findOwnedSession(sid)?.cwd ?? null,
30784
+ logger
30785
+ });
30786
+ } catch (err) {
30787
+ logger.warn("attachment gc tick failed", { err: err.message });
30788
+ }
30789
+ };
30790
+ setImmediate(tickAttachmentGc);
30791
+ const attachmentGcInterval = setInterval(tickAttachmentGc, 24 * 3600 * 1e3);
30792
+ attachmentGcInterval.unref();
30435
30793
  const shutdown = async () => {
30436
30794
  logger.info("stopping clawd");
30795
+ clearInterval(attachmentGcInterval);
30437
30796
  observer.stopAll();
30438
30797
  manager.stopAll();
30439
30798
  if (tunnelMgr) {
@@ -30451,9 +30810,9 @@ ${bar}
30451
30810
  };
30452
30811
  }
30453
30812
  function migrateDropPersonsDir(dataDir) {
30454
- const dir = import_node_path29.default.join(dataDir, "persons");
30813
+ const dir = import_node_path31.default.join(dataDir, "persons");
30455
30814
  try {
30456
- import_node_fs26.default.rmSync(dir, { recursive: true, force: true });
30815
+ import_node_fs28.default.rmSync(dir, { recursive: true, force: true });
30457
30816
  } catch {
30458
30817
  }
30459
30818
  }