@clawos-dev/clawd 0.2.85-beta.160.958069f → 0.2.86-beta.161.3250922

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 +663 -451
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -643,8 +643,8 @@ var init_parseUtil = __esm({
643
643
  init_errors2();
644
644
  init_en();
645
645
  makeIssue = (params) => {
646
- const { data, path: path44, errorMaps, issueData } = params;
647
- const fullPath = [...path44, ...issueData.path || []];
646
+ const { data, path: path45, errorMaps, issueData } = params;
647
+ const fullPath = [...path45, ...issueData.path || []];
648
648
  const fullIssue = {
649
649
  ...issueData,
650
650
  path: fullPath
@@ -955,11 +955,11 @@ var init_types = __esm({
955
955
  init_parseUtil();
956
956
  init_util();
957
957
  ParseInputLazyPath = class {
958
- constructor(parent, value, path44, key) {
958
+ constructor(parent, value, path45, key) {
959
959
  this._cachedPath = [];
960
960
  this.parent = parent;
961
961
  this.data = value;
962
- this._path = path44;
962
+ this._path = path45;
963
963
  this._key = key;
964
964
  }
965
965
  get path() {
@@ -4343,13 +4343,13 @@ var init_zod = __esm({
4343
4343
  });
4344
4344
 
4345
4345
  // ../protocol/src/attachment-schemas.ts
4346
- var TOKEN_ROLES, GROUP_FILE_SOURCES, GroupFileEntrySchema, AttachmentSignUrlArgs, AttachmentSignUrlResponseSchema, AttachmentGroupAddArgs, AttachmentGroupAddResponseSchema, AttachmentGroupRemoveArgs, AttachmentGroupRemoveResponseSchema, AttachmentGroupListArgs, AttachmentGroupListResponseSchema;
4346
+ var TOKEN_ROLES, GROUP_FILE_SOURCES, GroupFileEntrySchema, AttachmentSignUrlArgs, AttachmentSignUrlResponseSchema, AttachmentGroupAddArgs, AttachmentGroupAddResponseSchema, AttachmentGroupRemoveArgs, AttachmentGroupRemoveResponseSchema, AttachmentGroupListArgs, AttachmentGroupListResponseSchema, AttachmentUploadResponseSchema;
4347
4347
  var init_attachment_schemas = __esm({
4348
4348
  "../protocol/src/attachment-schemas.ts"() {
4349
4349
  "use strict";
4350
4350
  init_zod();
4351
4351
  TOKEN_ROLES = ["owner"];
4352
- GROUP_FILE_SOURCES = ["agent", "owner"];
4352
+ GROUP_FILE_SOURCES = ["agent", "owner", "user-upload"];
4353
4353
  GroupFileEntrySchema = external_exports.object({
4354
4354
  /** daemon 派发的稳定 id(用于 RPC remove / UI key) */
4355
4355
  id: external_exports.string().min(1),
@@ -4404,6 +4404,16 @@ var init_attachment_schemas = __esm({
4404
4404
  AttachmentGroupListResponseSchema = external_exports.object({
4405
4405
  entries: external_exports.array(GroupFileEntrySchema)
4406
4406
  });
4407
+ AttachmentUploadResponseSchema = external_exports.object({
4408
+ /** sha256(bytes) 前 16 位,前端持有的稳定 ref */
4409
+ attachmentId: external_exports.string().min(1),
4410
+ /** 完整 tunnel signed URL,前端拼 marker / 直接送 Anthropic 都用它 */
4411
+ url: external_exports.string().min(1),
4412
+ /** 落在 session 目录下的相对路径(后续 group RPC / drawer 展示用) */
4413
+ relPath: external_exports.string().min(1),
4414
+ sizeBytes: external_exports.number().int().nonnegative(),
4415
+ mimeType: external_exports.string().min(1)
4416
+ });
4407
4417
  }
4408
4418
  });
4409
4419
 
@@ -5580,8 +5590,8 @@ var require_req = __commonJS({
5580
5590
  if (req.originalUrl) {
5581
5591
  _req.url = req.originalUrl;
5582
5592
  } else {
5583
- const path44 = req.path;
5584
- _req.url = typeof path44 === "string" ? path44 : req.url ? req.url.path || req.url : void 0;
5593
+ const path45 = req.path;
5594
+ _req.url = typeof path45 === "string" ? path45 : req.url ? req.url.path || req.url : void 0;
5585
5595
  }
5586
5596
  if (req.query) {
5587
5597
  _req.query = req.query;
@@ -5746,14 +5756,14 @@ var require_redact = __commonJS({
5746
5756
  }
5747
5757
  return obj;
5748
5758
  }
5749
- function parsePath(path44) {
5759
+ function parsePath(path45) {
5750
5760
  const parts = [];
5751
5761
  let current = "";
5752
5762
  let inBrackets = false;
5753
5763
  let inQuotes = false;
5754
5764
  let quoteChar = "";
5755
- for (let i = 0; i < path44.length; i++) {
5756
- const char = path44[i];
5765
+ for (let i = 0; i < path45.length; i++) {
5766
+ const char = path45[i];
5757
5767
  if (!inBrackets && char === ".") {
5758
5768
  if (current) {
5759
5769
  parts.push(current);
@@ -5884,10 +5894,10 @@ var require_redact = __commonJS({
5884
5894
  return current;
5885
5895
  }
5886
5896
  function redactPaths(obj, paths, censor, remove = false) {
5887
- for (const path44 of paths) {
5888
- const parts = parsePath(path44);
5897
+ for (const path45 of paths) {
5898
+ const parts = parsePath(path45);
5889
5899
  if (parts.includes("*")) {
5890
- redactWildcardPath(obj, parts, censor, path44, remove);
5900
+ redactWildcardPath(obj, parts, censor, path45, remove);
5891
5901
  } else {
5892
5902
  if (remove) {
5893
5903
  removeKey(obj, parts);
@@ -5972,8 +5982,8 @@ var require_redact = __commonJS({
5972
5982
  }
5973
5983
  } else {
5974
5984
  if (afterWildcard.includes("*")) {
5975
- const wrappedCensor = typeof censor === "function" ? (value, path44) => {
5976
- const fullPath = [...pathArray.slice(0, pathLength), ...path44];
5985
+ const wrappedCensor = typeof censor === "function" ? (value, path45) => {
5986
+ const fullPath = [...pathArray.slice(0, pathLength), ...path45];
5977
5987
  return censor(value, fullPath);
5978
5988
  } : censor;
5979
5989
  redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
@@ -6008,8 +6018,8 @@ var require_redact = __commonJS({
6008
6018
  return null;
6009
6019
  }
6010
6020
  const pathStructure = /* @__PURE__ */ new Map();
6011
- for (const path44 of pathsToClone) {
6012
- const parts = parsePath(path44);
6021
+ for (const path45 of pathsToClone) {
6022
+ const parts = parsePath(path45);
6013
6023
  let current = pathStructure;
6014
6024
  for (let i = 0; i < parts.length; i++) {
6015
6025
  const part = parts[i];
@@ -6061,24 +6071,24 @@ var require_redact = __commonJS({
6061
6071
  }
6062
6072
  return cloneSelectively(obj, pathStructure);
6063
6073
  }
6064
- function validatePath(path44) {
6065
- if (typeof path44 !== "string") {
6074
+ function validatePath(path45) {
6075
+ if (typeof path45 !== "string") {
6066
6076
  throw new Error("Paths must be (non-empty) strings");
6067
6077
  }
6068
- if (path44 === "") {
6078
+ if (path45 === "") {
6069
6079
  throw new Error("Invalid redaction path ()");
6070
6080
  }
6071
- if (path44.includes("..")) {
6072
- throw new Error(`Invalid redaction path (${path44})`);
6081
+ if (path45.includes("..")) {
6082
+ throw new Error(`Invalid redaction path (${path45})`);
6073
6083
  }
6074
- if (path44.includes(",")) {
6075
- throw new Error(`Invalid redaction path (${path44})`);
6084
+ if (path45.includes(",")) {
6085
+ throw new Error(`Invalid redaction path (${path45})`);
6076
6086
  }
6077
6087
  let bracketCount = 0;
6078
6088
  let inQuotes = false;
6079
6089
  let quoteChar = "";
6080
- for (let i = 0; i < path44.length; i++) {
6081
- const char = path44[i];
6090
+ for (let i = 0; i < path45.length; i++) {
6091
+ const char = path45[i];
6082
6092
  if ((char === '"' || char === "'") && bracketCount > 0) {
6083
6093
  if (!inQuotes) {
6084
6094
  inQuotes = true;
@@ -6092,20 +6102,20 @@ var require_redact = __commonJS({
6092
6102
  } else if (char === "]" && !inQuotes) {
6093
6103
  bracketCount--;
6094
6104
  if (bracketCount < 0) {
6095
- throw new Error(`Invalid redaction path (${path44})`);
6105
+ throw new Error(`Invalid redaction path (${path45})`);
6096
6106
  }
6097
6107
  }
6098
6108
  }
6099
6109
  if (bracketCount !== 0) {
6100
- throw new Error(`Invalid redaction path (${path44})`);
6110
+ throw new Error(`Invalid redaction path (${path45})`);
6101
6111
  }
6102
6112
  }
6103
6113
  function validatePaths(paths) {
6104
6114
  if (!Array.isArray(paths)) {
6105
6115
  throw new TypeError("paths must be an array");
6106
6116
  }
6107
- for (const path44 of paths) {
6108
- validatePath(path44);
6117
+ for (const path45 of paths) {
6118
+ validatePath(path45);
6109
6119
  }
6110
6120
  }
6111
6121
  function slowRedact(options = {}) {
@@ -6273,8 +6283,8 @@ var require_redaction = __commonJS({
6273
6283
  if (shape[k2] === null) {
6274
6284
  o[k2] = (value) => topCensor(value, [k2]);
6275
6285
  } else {
6276
- const wrappedCensor = typeof censor === "function" ? (value, path44) => {
6277
- return censor(value, [k2, ...path44]);
6286
+ const wrappedCensor = typeof censor === "function" ? (value, path45) => {
6287
+ return censor(value, [k2, ...path45]);
6278
6288
  } : censor;
6279
6289
  o[k2] = Redact({
6280
6290
  paths: shape[k2],
@@ -6492,10 +6502,10 @@ var require_atomic_sleep = __commonJS({
6492
6502
  var require_sonic_boom = __commonJS({
6493
6503
  "../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
6494
6504
  "use strict";
6495
- var fs35 = require("fs");
6505
+ var fs36 = require("fs");
6496
6506
  var EventEmitter2 = require("events");
6497
6507
  var inherits = require("util").inherits;
6498
- var path44 = require("path");
6508
+ var path45 = require("path");
6499
6509
  var sleep = require_atomic_sleep();
6500
6510
  var assert = require("assert");
6501
6511
  var BUSY_WRITE_TIMEOUT = 100;
@@ -6549,20 +6559,20 @@ var require_sonic_boom = __commonJS({
6549
6559
  const mode = sonic.mode;
6550
6560
  if (sonic.sync) {
6551
6561
  try {
6552
- if (sonic.mkdir) fs35.mkdirSync(path44.dirname(file), { recursive: true });
6553
- const fd = fs35.openSync(file, flags, mode);
6562
+ if (sonic.mkdir) fs36.mkdirSync(path45.dirname(file), { recursive: true });
6563
+ const fd = fs36.openSync(file, flags, mode);
6554
6564
  fileOpened(null, fd);
6555
6565
  } catch (err) {
6556
6566
  fileOpened(err);
6557
6567
  throw err;
6558
6568
  }
6559
6569
  } else if (sonic.mkdir) {
6560
- fs35.mkdir(path44.dirname(file), { recursive: true }, (err) => {
6570
+ fs36.mkdir(path45.dirname(file), { recursive: true }, (err) => {
6561
6571
  if (err) return fileOpened(err);
6562
- fs35.open(file, flags, mode, fileOpened);
6572
+ fs36.open(file, flags, mode, fileOpened);
6563
6573
  });
6564
6574
  } else {
6565
- fs35.open(file, flags, mode, fileOpened);
6575
+ fs36.open(file, flags, mode, fileOpened);
6566
6576
  }
6567
6577
  }
6568
6578
  function SonicBoom(opts) {
@@ -6603,8 +6613,8 @@ var require_sonic_boom = __commonJS({
6603
6613
  this.flush = flushBuffer;
6604
6614
  this.flushSync = flushBufferSync;
6605
6615
  this._actualWrite = actualWriteBuffer;
6606
- fsWriteSync = () => fs35.writeSync(this.fd, this._writingBuf);
6607
- fsWrite = () => fs35.write(this.fd, this._writingBuf, this.release);
6616
+ fsWriteSync = () => fs36.writeSync(this.fd, this._writingBuf);
6617
+ fsWrite = () => fs36.write(this.fd, this._writingBuf, this.release);
6608
6618
  } else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
6609
6619
  this._writingBuf = "";
6610
6620
  this.write = write;
@@ -6613,15 +6623,15 @@ var require_sonic_boom = __commonJS({
6613
6623
  this._actualWrite = actualWrite;
6614
6624
  fsWriteSync = () => {
6615
6625
  if (Buffer.isBuffer(this._writingBuf)) {
6616
- return fs35.writeSync(this.fd, this._writingBuf);
6626
+ return fs36.writeSync(this.fd, this._writingBuf);
6617
6627
  }
6618
- return fs35.writeSync(this.fd, this._writingBuf, "utf8");
6628
+ return fs36.writeSync(this.fd, this._writingBuf, "utf8");
6619
6629
  };
6620
6630
  fsWrite = () => {
6621
6631
  if (Buffer.isBuffer(this._writingBuf)) {
6622
- return fs35.write(this.fd, this._writingBuf, this.release);
6632
+ return fs36.write(this.fd, this._writingBuf, this.release);
6623
6633
  }
6624
- return fs35.write(this.fd, this._writingBuf, "utf8", this.release);
6634
+ return fs36.write(this.fd, this._writingBuf, "utf8", this.release);
6625
6635
  };
6626
6636
  } else {
6627
6637
  throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
@@ -6678,7 +6688,7 @@ var require_sonic_boom = __commonJS({
6678
6688
  }
6679
6689
  }
6680
6690
  if (this._fsync) {
6681
- fs35.fsyncSync(this.fd);
6691
+ fs36.fsyncSync(this.fd);
6682
6692
  }
6683
6693
  const len = this._len;
6684
6694
  if (this._reopening) {
@@ -6792,7 +6802,7 @@ var require_sonic_boom = __commonJS({
6792
6802
  const onDrain = () => {
6793
6803
  if (!this._fsync) {
6794
6804
  try {
6795
- fs35.fsync(this.fd, (err) => {
6805
+ fs36.fsync(this.fd, (err) => {
6796
6806
  this._flushPending = false;
6797
6807
  cb(err);
6798
6808
  });
@@ -6894,7 +6904,7 @@ var require_sonic_boom = __commonJS({
6894
6904
  const fd = this.fd;
6895
6905
  this.once("ready", () => {
6896
6906
  if (fd !== this.fd) {
6897
- fs35.close(fd, (err) => {
6907
+ fs36.close(fd, (err) => {
6898
6908
  if (err) {
6899
6909
  return this.emit("error", err);
6900
6910
  }
@@ -6943,7 +6953,7 @@ var require_sonic_boom = __commonJS({
6943
6953
  buf = this._bufs[0];
6944
6954
  }
6945
6955
  try {
6946
- const n = Buffer.isBuffer(buf) ? fs35.writeSync(this.fd, buf) : fs35.writeSync(this.fd, buf, "utf8");
6956
+ const n = Buffer.isBuffer(buf) ? fs36.writeSync(this.fd, buf) : fs36.writeSync(this.fd, buf, "utf8");
6947
6957
  const releasedBufObj = releaseWritingBuf(buf, this._len, n);
6948
6958
  buf = releasedBufObj.writingBuf;
6949
6959
  this._len = releasedBufObj.len;
@@ -6959,7 +6969,7 @@ var require_sonic_boom = __commonJS({
6959
6969
  }
6960
6970
  }
6961
6971
  try {
6962
- fs35.fsyncSync(this.fd);
6972
+ fs36.fsyncSync(this.fd);
6963
6973
  } catch {
6964
6974
  }
6965
6975
  }
@@ -6980,7 +6990,7 @@ var require_sonic_boom = __commonJS({
6980
6990
  buf = mergeBuf(this._bufs[0], this._lens[0]);
6981
6991
  }
6982
6992
  try {
6983
- const n = fs35.writeSync(this.fd, buf);
6993
+ const n = fs36.writeSync(this.fd, buf);
6984
6994
  buf = buf.subarray(n);
6985
6995
  this._len = Math.max(this._len - n, 0);
6986
6996
  if (buf.length <= 0) {
@@ -7008,13 +7018,13 @@ var require_sonic_boom = __commonJS({
7008
7018
  this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
7009
7019
  if (this.sync) {
7010
7020
  try {
7011
- const written = Buffer.isBuffer(this._writingBuf) ? fs35.writeSync(this.fd, this._writingBuf) : fs35.writeSync(this.fd, this._writingBuf, "utf8");
7021
+ const written = Buffer.isBuffer(this._writingBuf) ? fs36.writeSync(this.fd, this._writingBuf) : fs36.writeSync(this.fd, this._writingBuf, "utf8");
7012
7022
  release(null, written);
7013
7023
  } catch (err) {
7014
7024
  release(err);
7015
7025
  }
7016
7026
  } else {
7017
- fs35.write(this.fd, this._writingBuf, release);
7027
+ fs36.write(this.fd, this._writingBuf, release);
7018
7028
  }
7019
7029
  }
7020
7030
  function actualWriteBuffer() {
@@ -7023,7 +7033,7 @@ var require_sonic_boom = __commonJS({
7023
7033
  this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
7024
7034
  if (this.sync) {
7025
7035
  try {
7026
- const written = fs35.writeSync(this.fd, this._writingBuf);
7036
+ const written = fs36.writeSync(this.fd, this._writingBuf);
7027
7037
  release(null, written);
7028
7038
  } catch (err) {
7029
7039
  release(err);
@@ -7032,7 +7042,7 @@ var require_sonic_boom = __commonJS({
7032
7042
  if (kCopyBuffer) {
7033
7043
  this._writingBuf = Buffer.from(this._writingBuf);
7034
7044
  }
7035
- fs35.write(this.fd, this._writingBuf, release);
7045
+ fs36.write(this.fd, this._writingBuf, release);
7036
7046
  }
7037
7047
  }
7038
7048
  function actualClose(sonic) {
@@ -7048,12 +7058,12 @@ var require_sonic_boom = __commonJS({
7048
7058
  sonic._lens = [];
7049
7059
  assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
7050
7060
  try {
7051
- fs35.fsync(sonic.fd, closeWrapped);
7061
+ fs36.fsync(sonic.fd, closeWrapped);
7052
7062
  } catch {
7053
7063
  }
7054
7064
  function closeWrapped() {
7055
7065
  if (sonic.fd !== 1 && sonic.fd !== 2) {
7056
- fs35.close(sonic.fd, done);
7066
+ fs36.close(sonic.fd, done);
7057
7067
  } else {
7058
7068
  done();
7059
7069
  }
@@ -7784,7 +7794,7 @@ var require_transport = __commonJS({
7784
7794
  stream.flushSync();
7785
7795
  }
7786
7796
  function transport(fullOptions) {
7787
- const { pipeline: pipeline2, targets, levels, dedupe, worker = {}, caller = getCallers(), sync = false } = fullOptions;
7797
+ const { pipeline: pipeline3, targets, levels, dedupe, worker = {}, caller = getCallers(), sync = false } = fullOptions;
7788
7798
  const options = {
7789
7799
  ...fullOptions.options
7790
7800
  };
@@ -7812,9 +7822,9 @@ var require_transport = __commonJS({
7812
7822
  };
7813
7823
  });
7814
7824
  });
7815
- } else if (pipeline2) {
7825
+ } else if (pipeline3) {
7816
7826
  target = bundlerOverrides["pino-worker"] || join11(__dirname, "worker.js");
7817
- options.pipelines = [pipeline2.map((dest) => {
7827
+ options.pipelines = [pipeline3.map((dest) => {
7818
7828
  return {
7819
7829
  ...dest,
7820
7830
  target: fixTarget(dest.target)
@@ -9417,7 +9427,7 @@ var require_multistream = __commonJS({
9417
9427
  var require_pino = __commonJS({
9418
9428
  "../node_modules/.pnpm/pino@9.14.0/node_modules/pino/pino.js"(exports2, module2) {
9419
9429
  "use strict";
9420
- var os17 = require("os");
9430
+ var os16 = require("os");
9421
9431
  var stdSerializers = require_pino_std_serializers();
9422
9432
  var caller = require_caller();
9423
9433
  var redaction = require_redaction();
@@ -9464,7 +9474,7 @@ var require_pino = __commonJS({
9464
9474
  } = symbols;
9465
9475
  var { epochTime, nullTime } = time;
9466
9476
  var { pid } = process;
9467
- var hostname = os17.hostname();
9477
+ var hostname = os16.hostname();
9468
9478
  var defaultErrorSerializer = stdSerializers.err;
9469
9479
  var defaultOptions = {
9470
9480
  level: "info",
@@ -10188,11 +10198,11 @@ var init_lib = __esm({
10188
10198
  }
10189
10199
  }
10190
10200
  },
10191
- addToPath: function addToPath(path44, added, removed, oldPosInc, options) {
10192
- var last = path44.lastComponent;
10201
+ addToPath: function addToPath(path45, added, removed, oldPosInc, options) {
10202
+ var last = path45.lastComponent;
10193
10203
  if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
10194
10204
  return {
10195
- oldPos: path44.oldPos + oldPosInc,
10205
+ oldPos: path45.oldPos + oldPosInc,
10196
10206
  lastComponent: {
10197
10207
  count: last.count + 1,
10198
10208
  added,
@@ -10202,7 +10212,7 @@ var init_lib = __esm({
10202
10212
  };
10203
10213
  } else {
10204
10214
  return {
10205
- oldPos: path44.oldPos + oldPosInc,
10215
+ oldPos: path45.oldPos + oldPosInc,
10206
10216
  lastComponent: {
10207
10217
  count: 1,
10208
10218
  added,
@@ -10633,10 +10643,10 @@ function attachmentToHistoryMessage(o, ts) {
10633
10643
  const memories = raw.map((m2) => {
10634
10644
  if (!m2 || typeof m2 !== "object") return null;
10635
10645
  const rec = m2;
10636
- const path44 = typeof rec.path === "string" ? rec.path : null;
10646
+ const path45 = typeof rec.path === "string" ? rec.path : null;
10637
10647
  const content = typeof rec.content === "string" ? rec.content : null;
10638
- if (!path44 || content == null) return null;
10639
- const entry = { path: path44, content };
10648
+ if (!path45 || content == null) return null;
10649
+ const entry = { path: path45, content };
10640
10650
  if (typeof rec.mtimeMs === "number") entry.mtimeMs = rec.mtimeMs;
10641
10651
  return entry;
10642
10652
  }).filter((m2) => m2 !== null);
@@ -11462,10 +11472,10 @@ function parseAttachment(obj) {
11462
11472
  const memories = raw.map((m2) => {
11463
11473
  if (!m2 || typeof m2 !== "object") return null;
11464
11474
  const rec = m2;
11465
- const path44 = typeof rec.path === "string" ? rec.path : null;
11475
+ const path45 = typeof rec.path === "string" ? rec.path : null;
11466
11476
  const content = typeof rec.content === "string" ? rec.content : null;
11467
- if (!path44 || content == null) return null;
11468
- const out = { path: path44, content };
11477
+ if (!path45 || content == null) return null;
11478
+ const out = { path: path45, content };
11469
11479
  if (typeof rec.mtimeMs === "number") out.mtimeMs = rec.mtimeMs;
11470
11480
  return out;
11471
11481
  }).filter((m2) => m2 !== null);
@@ -20380,7 +20390,7 @@ var require_websocket_server = __commonJS({
20380
20390
  // src/run-case/recorder.ts
20381
20391
  function startRunCaseRecorder(opts) {
20382
20392
  const now = opts.now ?? Date.now;
20383
- const dir = import_node_path30.default.dirname(opts.recordPath);
20393
+ const dir = import_node_path31.default.dirname(opts.recordPath);
20384
20394
  let stream = null;
20385
20395
  let closing = false;
20386
20396
  let closedSettled = false;
@@ -20394,8 +20404,8 @@ function startRunCaseRecorder(opts) {
20394
20404
  });
20395
20405
  const ensureStream = () => {
20396
20406
  if (stream) return stream;
20397
- import_node_fs27.default.mkdirSync(dir, { recursive: true });
20398
- stream = import_node_fs27.default.createWriteStream(opts.recordPath, { flags: "a" });
20407
+ import_node_fs28.default.mkdirSync(dir, { recursive: true });
20408
+ stream = import_node_fs28.default.createWriteStream(opts.recordPath, { flags: "a" });
20399
20409
  stream.on("close", () => closedResolve());
20400
20410
  return stream;
20401
20411
  };
@@ -20420,12 +20430,12 @@ function startRunCaseRecorder(opts) {
20420
20430
  };
20421
20431
  return { tap, close, closed };
20422
20432
  }
20423
- var import_node_fs27, import_node_path30;
20433
+ var import_node_fs28, import_node_path31;
20424
20434
  var init_recorder = __esm({
20425
20435
  "src/run-case/recorder.ts"() {
20426
20436
  "use strict";
20427
- import_node_fs27 = __toESM(require("fs"), 1);
20428
- import_node_path30 = __toESM(require("path"), 1);
20437
+ import_node_fs28 = __toESM(require("fs"), 1);
20438
+ import_node_path31 = __toESM(require("path"), 1);
20429
20439
  }
20430
20440
  });
20431
20441
 
@@ -20468,7 +20478,7 @@ var init_wire = __esm({
20468
20478
  // src/run-case/controller.ts
20469
20479
  async function runController(opts) {
20470
20480
  const now = opts.now ?? Date.now;
20471
- const cwd = opts.cwd ?? (0, import_node_fs28.mkdtempSync)(import_node_path31.default.join(import_node_os15.default.tmpdir(), "clawd-runcase-"));
20481
+ const cwd = opts.cwd ?? (0, import_node_fs29.mkdtempSync)(import_node_path32.default.join(import_node_os14.default.tmpdir(), "clawd-runcase-"));
20472
20482
  const ownsCwd = opts.cwd === void 0;
20473
20483
  const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
20474
20484
  const spawnCtx = { cwd };
@@ -20629,19 +20639,19 @@ async function runController(opts) {
20629
20639
  if (sigintHandler) process.off("SIGINT", sigintHandler);
20630
20640
  if (ownsCwd) {
20631
20641
  try {
20632
- (0, import_node_fs28.rmSync)(cwd, { recursive: true, force: true });
20642
+ (0, import_node_fs29.rmSync)(cwd, { recursive: true, force: true });
20633
20643
  } catch {
20634
20644
  }
20635
20645
  }
20636
20646
  return exitCode ?? 0;
20637
20647
  }
20638
- var import_node_fs28, import_node_os15, import_node_path31;
20648
+ var import_node_fs29, import_node_os14, import_node_path32;
20639
20649
  var init_controller = __esm({
20640
20650
  "src/run-case/controller.ts"() {
20641
20651
  "use strict";
20642
- import_node_fs28 = require("fs");
20643
- import_node_os15 = __toESM(require("os"), 1);
20644
- import_node_path31 = __toESM(require("path"), 1);
20652
+ import_node_fs29 = require("fs");
20653
+ import_node_os14 = __toESM(require("os"), 1);
20654
+ import_node_path32 = __toESM(require("path"), 1);
20645
20655
  init_claude();
20646
20656
  init_stdout_splitter();
20647
20657
  init_permission_stdio();
@@ -20873,8 +20883,8 @@ Env (advanced):
20873
20883
  `;
20874
20884
 
20875
20885
  // src/index.ts
20876
- var import_node_path29 = __toESM(require("path"), 1);
20877
- var import_node_fs26 = __toESM(require("fs"), 1);
20886
+ var import_node_path30 = __toESM(require("path"), 1);
20887
+ var import_node_fs27 = __toESM(require("fs"), 1);
20878
20888
 
20879
20889
  // src/logger.ts
20880
20890
  var import_node_fs2 = __toESM(require("fs"), 1);
@@ -22609,6 +22619,18 @@ var SessionManager = class {
22609
22619
  (a, b2) => a.updatedAt > b2.updatedAt ? -1 : a.updatedAt < b2.updatedAt ? 1 : 0
22610
22620
  );
22611
22621
  }
22622
+ /**
22623
+ * 跨 scope 列出所有 owner 视角的 session(default + persona owner + persona guest),
22624
+ * 附带派生的 SessionScope。attachment GC tick 用 —— 需要遍历所有活跃 session 的
22625
+ * .attachments 目录 + 群文件清单求 liveSet。
22626
+ */
22627
+ listOwnedSessionScopes() {
22628
+ return this.listAllOwned().map((file) => ({
22629
+ scope: this.scopeForFile(file),
22630
+ sessionId: file.sessionId,
22631
+ cwd: file.cwd
22632
+ }));
22633
+ }
22612
22634
  // 写回 SessionFile,自动根据 ownerPersonaId / creatorPrincipalId 派生写入 scope.
22613
22635
  // 调用方原先都是 `deps.store.write(updated)`,全部走这里收口路由.
22614
22636
  writeOwned(file) {
@@ -25509,151 +25531,28 @@ var AgentsScanner = class {
25509
25531
  };
25510
25532
 
25511
25533
  // src/observer/session-observer.ts
25512
- var import_node_fs13 = __toESM(require("fs"), 1);
25513
- var import_node_os9 = __toESM(require("os"), 1);
25514
- var import_node_path14 = __toESM(require("path"), 1);
25515
- init_claude_history();
25516
-
25517
- // src/observer/subagent-meta-observer.ts
25518
25534
  var import_node_fs12 = __toESM(require("fs"), 1);
25519
25535
  var import_node_os8 = __toESM(require("os"), 1);
25520
25536
  var import_node_path13 = __toESM(require("path"), 1);
25521
25537
  init_claude_history();
25522
- var META_RE = /^agent-([A-Za-z0-9_-]+)\.meta\.json$/;
25523
- var SubagentMetaObserver = class {
25524
- constructor(opts) {
25525
- this.opts = opts;
25526
- this.home = opts.home ?? import_node_os8.default.homedir();
25527
- }
25528
- opts;
25529
- home;
25530
- watches = /* @__PURE__ */ new Map();
25531
- // public for spec only:测试直接拼路径写假 meta.json;生产 start() 内部自己解析
25532
- resolveSubagentDir(cwd, toolSessionId) {
25533
- return import_node_path13.default.join(
25534
- this.home,
25535
- ".claude",
25536
- "projects",
25537
- cwdToHashDir(cwd),
25538
- toolSessionId,
25539
- "subagents"
25540
- );
25541
- }
25542
- start(args) {
25543
- this.stop(args.sessionId);
25544
- const dirPath = this.resolveSubagentDir(args.cwd, args.toolSessionId);
25545
- const w2 = {
25546
- sessionId: args.sessionId,
25547
- dirPath,
25548
- watcher: null,
25549
- pollTimer: null,
25550
- emitted: /* @__PURE__ */ new Set()
25551
- };
25552
- this.watches.set(args.sessionId, w2);
25553
- this.scan(w2);
25554
- this.attachWatcher(w2);
25555
- w2.pollTimer = setInterval(() => this.scan(w2), 200);
25556
- return { dirPath };
25557
- }
25558
- // fs.watch 必须在目录存在后调用;首次启动时 subagents/ 通常不存在 —— attachWatcher
25559
- // 失败就交给 pollTimer 200ms 兜底,目录一出现就被下次 poll 捡起来,那时也尝试 attach
25560
- attachWatcher(w2) {
25561
- if (w2.watcher) return;
25562
- try {
25563
- w2.watcher = import_node_fs12.default.watch(w2.dirPath, { persistent: false }, (_evt, name) => {
25564
- if (!name) return;
25565
- const m2 = META_RE.exec(String(name));
25566
- if (!m2) return;
25567
- this.scanOne(w2, String(name));
25568
- });
25569
- } catch {
25570
- }
25571
- }
25572
- scan(w2) {
25573
- let entries;
25574
- try {
25575
- entries = import_node_fs12.default.readdirSync(w2.dirPath);
25576
- } catch {
25577
- return;
25578
- }
25579
- if (!w2.watcher) this.attachWatcher(w2);
25580
- for (const name of entries) {
25581
- this.scanOne(w2, name);
25582
- }
25583
- }
25584
- scanOne(w2, name) {
25585
- const m2 = META_RE.exec(name);
25586
- if (!m2) return;
25587
- const agentId = m2[1];
25588
- if (w2.emitted.has(agentId)) return;
25589
- const file = import_node_path13.default.join(w2.dirPath, name);
25590
- let raw;
25591
- try {
25592
- raw = import_node_fs12.default.readFileSync(file, "utf8");
25593
- } catch {
25594
- return;
25595
- }
25596
- let obj;
25597
- try {
25598
- obj = JSON.parse(raw);
25599
- } catch {
25600
- return;
25601
- }
25602
- const toolUseId = typeof obj.toolUseId === "string" ? obj.toolUseId : "";
25603
- if (!toolUseId) return;
25604
- w2.emitted.add(agentId);
25605
- const ev = {
25606
- kind: "subagent_progress",
25607
- toolUseId,
25608
- agentId,
25609
- status: "started"
25610
- };
25611
- if (typeof obj.description === "string" && obj.description) ev.description = obj.description;
25612
- this.opts.onEvent({ sessionId: w2.sessionId, events: [ev] });
25613
- }
25614
- isObserving(sessionId) {
25615
- return this.watches.has(sessionId);
25616
- }
25617
- stop(sessionId) {
25618
- const w2 = this.watches.get(sessionId);
25619
- if (!w2) return false;
25620
- try {
25621
- w2.watcher?.close();
25622
- } catch {
25623
- }
25624
- if (w2.pollTimer) clearInterval(w2.pollTimer);
25625
- this.watches.delete(sessionId);
25626
- return true;
25627
- }
25628
- stopAll() {
25629
- for (const id of [...this.watches.keys()]) this.stop(id);
25630
- }
25631
- };
25632
-
25633
- // src/observer/session-observer.ts
25634
25538
  var SessionObserver = class {
25635
25539
  constructor(opts) {
25636
25540
  this.opts = opts;
25637
- this.home = opts.home ?? import_node_os9.default.homedir();
25638
- this.metaObserver = opts.enableSubagentMetaObserver ? new SubagentMetaObserver({ home: this.home, onEvent: opts.onEvent }) : null;
25541
+ this.home = opts.home ?? import_node_os8.default.homedir();
25639
25542
  }
25640
25543
  opts;
25641
25544
  home;
25642
25545
  watches = /* @__PURE__ */ new Map();
25643
- // 子 observer:监听 <projectsRoot>/<hashDir>/<toolSessionId>/subagents/ 目录的 meta.json,
25644
- // 仅 TUI 模式启用——SDK 模式靠 `system.task_started` 帧(claude.ts:466)emit subagent_progress
25645
- // 已经能解锁 AgentCallLine 抽屉入口,再跑 meta watcher 会让 ring buffer 留双帧
25646
- metaObserver;
25647
25546
  resolveJsonlPath(cwd, toolSessionId, override) {
25648
25547
  if (override) return override;
25649
- return import_node_path14.default.join(this.home, ".claude", "projects", cwdToHashDir(cwd), `${toolSessionId}.jsonl`);
25548
+ return import_node_path13.default.join(this.home, ".claude", "projects", cwdToHashDir(cwd), `${toolSessionId}.jsonl`);
25650
25549
  }
25651
25550
  start(args) {
25652
25551
  this.stop(args.sessionId);
25653
25552
  const filePath = this.resolveJsonlPath(args.cwd, args.toolSessionId, args.jsonlPath);
25654
25553
  let size = 0;
25655
25554
  try {
25656
- size = import_node_fs13.default.statSync(filePath).size;
25555
+ size = import_node_fs12.default.statSync(filePath).size;
25657
25556
  } catch {
25658
25557
  }
25659
25558
  const w2 = {
@@ -25666,10 +25565,10 @@ var SessionObserver = class {
25666
25565
  adapter: args.adapter
25667
25566
  };
25668
25567
  try {
25669
- import_node_fs13.default.mkdirSync(import_node_path14.default.dirname(filePath), { recursive: true });
25568
+ import_node_fs12.default.mkdirSync(import_node_path13.default.dirname(filePath), { recursive: true });
25670
25569
  } catch {
25671
25570
  }
25672
- w2.watcher = import_node_fs13.default.watch(import_node_path14.default.dirname(filePath), { persistent: false }, (_event, changedName) => {
25571
+ w2.watcher = import_node_fs12.default.watch(import_node_path13.default.dirname(filePath), { persistent: false }, (_event, changedName) => {
25673
25572
  if (!changedName || !filePath.endsWith(changedName)) return;
25674
25573
  this.poll(w2);
25675
25574
  });
@@ -25678,11 +25577,6 @@ var SessionObserver = class {
25678
25577
  this.opts.onStatus?.(args.sessionId, "observing");
25679
25578
  this.hydrateMetaTail(w2);
25680
25579
  this.poll(w2);
25681
- this.metaObserver?.start({
25682
- sessionId: args.sessionId,
25683
- cwd: args.cwd,
25684
- toolSessionId: args.toolSessionId
25685
- });
25686
25580
  return { filePath };
25687
25581
  }
25688
25582
  // 读 JSONL 文件尾部 N 行,把每行的 meta_update 字段**合并成一个 patch** 喂给 reducer。
@@ -25692,7 +25586,7 @@ var SessionObserver = class {
25692
25586
  // 异常静默吞,不阻塞 watcher 启动
25693
25587
  hydrateMetaTail(w2, maxLines = 200) {
25694
25588
  try {
25695
- const raw = import_node_fs13.default.readFileSync(w2.filePath, "utf8");
25589
+ const raw = import_node_fs12.default.readFileSync(w2.filePath, "utf8");
25696
25590
  if (!raw) return;
25697
25591
  const allLines = raw.split("\n").filter((l) => l.trim().length > 0);
25698
25592
  if (allLines.length === 0) return;
@@ -25716,7 +25610,7 @@ var SessionObserver = class {
25716
25610
  poll(w2) {
25717
25611
  let size = 0;
25718
25612
  try {
25719
- size = import_node_fs13.default.statSync(w2.filePath).size;
25613
+ size = import_node_fs12.default.statSync(w2.filePath).size;
25720
25614
  } catch {
25721
25615
  return;
25722
25616
  }
@@ -25725,11 +25619,11 @@ var SessionObserver = class {
25725
25619
  w2.buf = "";
25726
25620
  }
25727
25621
  if (size === w2.lastSize) return;
25728
- const fd = import_node_fs13.default.openSync(w2.filePath, "r");
25622
+ const fd = import_node_fs12.default.openSync(w2.filePath, "r");
25729
25623
  try {
25730
25624
  const len = size - w2.lastSize;
25731
25625
  const buf = Buffer.alloc(len);
25732
- import_node_fs13.default.readSync(fd, buf, 0, len, w2.lastSize);
25626
+ import_node_fs12.default.readSync(fd, buf, 0, len, w2.lastSize);
25733
25627
  w2.lastSize = size;
25734
25628
  w2.buf += buf.toString("utf8");
25735
25629
  let newlineIndex;
@@ -25743,7 +25637,7 @@ var SessionObserver = class {
25743
25637
  }
25744
25638
  }
25745
25639
  } finally {
25746
- import_node_fs13.default.closeSync(fd);
25640
+ import_node_fs12.default.closeSync(fd);
25747
25641
  }
25748
25642
  }
25749
25643
  // 解析 JSONL 单行:仅当是主链 user 文本行(非 sidechain / 非 sub-agent / message.role='user'
@@ -25796,13 +25690,11 @@ var SessionObserver = class {
25796
25690
  }
25797
25691
  if (w2.pollTimer) clearInterval(w2.pollTimer);
25798
25692
  this.watches.delete(sessionId);
25799
- this.metaObserver?.stop(sessionId);
25800
25693
  this.opts.onStatus?.(sessionId, "stopped");
25801
25694
  return true;
25802
25695
  }
25803
25696
  stopAll() {
25804
25697
  for (const id of [...this.watches.keys()]) this.stop(id);
25805
- this.metaObserver?.stopAll();
25806
25698
  }
25807
25699
  };
25808
25700
 
@@ -26403,14 +26295,14 @@ function authenticate(token, deps) {
26403
26295
  }
26404
26296
 
26405
26297
  // src/permission/capability-store.ts
26406
- var fs16 = __toESM(require("fs"), 1);
26407
- var path19 = __toESM(require("path"), 1);
26298
+ var fs15 = __toESM(require("fs"), 1);
26299
+ var path18 = __toESM(require("path"), 1);
26408
26300
  var CAPABILITIES_FILE_NAME = "capabilities.json";
26409
26301
  var FILE_VERSION = 1;
26410
26302
  var CapabilityStore = class {
26411
26303
  constructor(dataDir) {
26412
26304
  this.dataDir = dataDir;
26413
- fs16.mkdirSync(dataDir, { recursive: true });
26305
+ fs15.mkdirSync(dataDir, { recursive: true });
26414
26306
  this.cache = this.readFromDisk();
26415
26307
  }
26416
26308
  dataDir;
@@ -26434,13 +26326,13 @@ var CapabilityStore = class {
26434
26326
  this.flush();
26435
26327
  }
26436
26328
  filePath() {
26437
- return path19.join(this.dataDir, CAPABILITIES_FILE_NAME);
26329
+ return path18.join(this.dataDir, CAPABILITIES_FILE_NAME);
26438
26330
  }
26439
26331
  readFromDisk() {
26440
26332
  const file = this.filePath();
26441
26333
  let raw;
26442
26334
  try {
26443
- raw = fs16.readFileSync(file, "utf8");
26335
+ raw = fs15.readFileSync(file, "utf8");
26444
26336
  } catch (err) {
26445
26337
  if (err?.code === "ENOENT") return [];
26446
26338
  return [];
@@ -26468,10 +26360,10 @@ var CapabilityStore = class {
26468
26360
  }
26469
26361
  atomicWrite(file, content) {
26470
26362
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
26471
- fs16.writeFileSync(tmp, content, { mode: 384 });
26472
- fs16.renameSync(tmp, file);
26363
+ fs15.writeFileSync(tmp, content, { mode: 384 });
26364
+ fs15.renameSync(tmp, file);
26473
26365
  try {
26474
- fs16.chmodSync(file, 384);
26366
+ fs15.chmodSync(file, 384);
26475
26367
  } catch {
26476
26368
  }
26477
26369
  }
@@ -26564,14 +26456,14 @@ var CapabilityManager = class {
26564
26456
  };
26565
26457
 
26566
26458
  // src/permission/cleanup.ts
26567
- var fs17 = __toESM(require("fs"), 1);
26459
+ var fs16 = __toESM(require("fs"), 1);
26568
26460
  function cleanupGuestSessionsForCapability(cap, factory) {
26569
26461
  const removed = [];
26570
26462
  for (const g2 of cap.grants) {
26571
26463
  if (g2.resource.type !== "persona") continue;
26572
26464
  const dir = factory.vmGuestRoot(g2.resource.id, cap.id);
26573
26465
  try {
26574
- fs17.rmSync(dir, { recursive: true, force: true });
26466
+ fs16.rmSync(dir, { recursive: true, force: true });
26575
26467
  removed.push(dir);
26576
26468
  } catch {
26577
26469
  }
@@ -26580,13 +26472,13 @@ function cleanupGuestSessionsForCapability(cap, factory) {
26580
26472
  }
26581
26473
 
26582
26474
  // src/permission/personal-capability.ts
26583
- var import_node_fs14 = __toESM(require("fs"), 1);
26584
- var import_node_path15 = __toESM(require("path"), 1);
26475
+ var import_node_fs13 = __toESM(require("fs"), 1);
26476
+ var import_node_path14 = __toESM(require("path"), 1);
26585
26477
  var import_node_crypto4 = __toESM(require("crypto"), 1);
26586
26478
  var PERSONAL_CAP_FILE_NAME = "personal-capability.json";
26587
26479
  var PERSONAL_CAP_DISPLAY_NAME = "personal";
26588
26480
  function personalCapFilePath(dataDir) {
26589
- return import_node_path15.default.join(dataDir, PERSONAL_CAP_FILE_NAME);
26481
+ return import_node_path14.default.join(dataDir, PERSONAL_CAP_FILE_NAME);
26590
26482
  }
26591
26483
  function loadOrCreatePersonalCapability(opts) {
26592
26484
  const file = personalCapFilePath(opts.dataDir);
@@ -26616,7 +26508,7 @@ function loadOrCreatePersonalCapability(opts) {
26616
26508
  function readFile(file) {
26617
26509
  let raw;
26618
26510
  try {
26619
- raw = import_node_fs14.default.readFileSync(file, "utf8");
26511
+ raw = import_node_fs13.default.readFileSync(file, "utf8");
26620
26512
  } catch (err) {
26621
26513
  const code = err?.code;
26622
26514
  if (code === "ENOENT") return null;
@@ -26637,10 +26529,10 @@ function readFile(file) {
26637
26529
  return { token: obj.token, capability: capParsed.data };
26638
26530
  }
26639
26531
  function writeFile(file, content) {
26640
- import_node_fs14.default.mkdirSync(import_node_path15.default.dirname(file), { recursive: true });
26641
- import_node_fs14.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
26532
+ import_node_fs13.default.mkdirSync(import_node_path14.default.dirname(file), { recursive: true });
26533
+ import_node_fs13.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
26642
26534
  try {
26643
- import_node_fs14.default.chmodSync(file, 384);
26535
+ import_node_fs13.default.chmodSync(file, 384);
26644
26536
  } catch {
26645
26537
  }
26646
26538
  }
@@ -26655,13 +26547,13 @@ function sha256Hex2(s) {
26655
26547
  }
26656
26548
 
26657
26549
  // src/inbox/inbox-store.ts
26658
- var fs19 = __toESM(require("fs"), 1);
26659
- var path21 = __toESM(require("path"), 1);
26550
+ var fs18 = __toESM(require("fs"), 1);
26551
+ var path20 = __toESM(require("path"), 1);
26660
26552
  var INBOX_SUBDIR = "inbox";
26661
26553
  var InboxStore = class {
26662
26554
  constructor(dataDir) {
26663
26555
  this.dataDir = dataDir;
26664
- fs19.mkdirSync(this.dirPath(), { recursive: true });
26556
+ fs18.mkdirSync(this.dirPath(), { recursive: true });
26665
26557
  }
26666
26558
  dataDir;
26667
26559
  /**
@@ -26673,7 +26565,7 @@ var InboxStore = class {
26673
26565
  const file = this.filePath(peerOwnerId);
26674
26566
  let raw;
26675
26567
  try {
26676
- raw = fs19.readFileSync(file, "utf8");
26568
+ raw = fs18.readFileSync(file, "utf8");
26677
26569
  } catch (err) {
26678
26570
  if (err?.code === "ENOENT") return [];
26679
26571
  return [];
@@ -26689,7 +26581,7 @@ var InboxStore = class {
26689
26581
  const dir = this.dirPath();
26690
26582
  let entries;
26691
26583
  try {
26692
- entries = fs19.readdirSync(dir);
26584
+ entries = fs18.readdirSync(dir);
26693
26585
  } catch (err) {
26694
26586
  if (err?.code === "ENOENT") return [];
26695
26587
  return [];
@@ -26705,9 +26597,9 @@ var InboxStore = class {
26705
26597
  if (existing.some((m2) => m2.id === message.id)) return;
26706
26598
  const file = this.filePath(message.peerOwnerId);
26707
26599
  const line = JSON.stringify(message) + "\n";
26708
- fs19.appendFileSync(file, line, { mode: 384 });
26600
+ fs18.appendFileSync(file, line, { mode: 384 });
26709
26601
  try {
26710
- fs19.chmodSync(file, 384);
26602
+ fs18.chmodSync(file, 384);
26711
26603
  } catch {
26712
26604
  }
26713
26605
  }
@@ -26737,7 +26629,7 @@ var InboxStore = class {
26737
26629
  removeByPeerOwnerId(peerOwnerId) {
26738
26630
  const file = this.filePath(peerOwnerId);
26739
26631
  try {
26740
- fs19.unlinkSync(file);
26632
+ fs18.unlinkSync(file);
26741
26633
  } catch (err) {
26742
26634
  if (err?.code === "ENOENT") return;
26743
26635
  }
@@ -26746,18 +26638,18 @@ var InboxStore = class {
26746
26638
  const file = this.filePath(peerOwnerId);
26747
26639
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
26748
26640
  const content = messages.map((m2) => JSON.stringify(m2)).join("\n") + (messages.length > 0 ? "\n" : "");
26749
- fs19.writeFileSync(tmp, content, { mode: 384 });
26750
- fs19.renameSync(tmp, file);
26641
+ fs18.writeFileSync(tmp, content, { mode: 384 });
26642
+ fs18.renameSync(tmp, file);
26751
26643
  try {
26752
- fs19.chmodSync(file, 384);
26644
+ fs18.chmodSync(file, 384);
26753
26645
  } catch {
26754
26646
  }
26755
26647
  }
26756
26648
  dirPath() {
26757
- return path21.join(this.dataDir, INBOX_SUBDIR);
26649
+ return path20.join(this.dataDir, INBOX_SUBDIR);
26758
26650
  }
26759
26651
  filePath(peerOwnerId) {
26760
- return path21.join(this.dirPath(), `${peerOwnerId}.jsonl`);
26652
+ return path20.join(this.dirPath(), `${peerOwnerId}.jsonl`);
26761
26653
  }
26762
26654
  };
26763
26655
  function parseAllLines(raw) {
@@ -26844,8 +26736,8 @@ var InboxManager = class {
26844
26736
  };
26845
26737
 
26846
26738
  // src/state/received-capability-store.ts
26847
- var fs20 = __toESM(require("fs"), 1);
26848
- var path22 = __toESM(require("path"), 1);
26739
+ var fs19 = __toESM(require("fs"), 1);
26740
+ var path21 = __toESM(require("path"), 1);
26849
26741
  var FILE_NAME = "received-capabilities.json";
26850
26742
  var ReceivedCapabilityStore = class {
26851
26743
  constructor(dataDir) {
@@ -26855,10 +26747,10 @@ var ReceivedCapabilityStore = class {
26855
26747
  caps = /* @__PURE__ */ new Map();
26856
26748
  load() {
26857
26749
  this.caps.clear();
26858
- const file = path22.join(this.dataDir, FILE_NAME);
26750
+ const file = path21.join(this.dataDir, FILE_NAME);
26859
26751
  let raw;
26860
26752
  try {
26861
- raw = fs20.readFileSync(file, "utf8");
26753
+ raw = fs19.readFileSync(file, "utf8");
26862
26754
  } catch (err) {
26863
26755
  if (err?.code !== "ENOENT") this.renameBak(file);
26864
26756
  return;
@@ -26895,20 +26787,20 @@ var ReceivedCapabilityStore = class {
26895
26787
  this.flush();
26896
26788
  }
26897
26789
  flush() {
26898
- const file = path22.join(this.dataDir, FILE_NAME);
26790
+ const file = path21.join(this.dataDir, FILE_NAME);
26899
26791
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
26900
26792
  const content = JSON.stringify(
26901
26793
  { receivedCaps: Array.from(this.caps.values()) },
26902
26794
  null,
26903
26795
  2
26904
26796
  );
26905
- fs20.mkdirSync(this.dataDir, { recursive: true });
26906
- fs20.writeFileSync(tmp, content, { mode: 384 });
26907
- fs20.renameSync(tmp, file);
26797
+ fs19.mkdirSync(this.dataDir, { recursive: true });
26798
+ fs19.writeFileSync(tmp, content, { mode: 384 });
26799
+ fs19.renameSync(tmp, file);
26908
26800
  }
26909
26801
  renameBak(file) {
26910
26802
  try {
26911
- fs20.renameSync(file, `${file}.bak`);
26803
+ fs19.renameSync(file, `${file}.bak`);
26912
26804
  } catch {
26913
26805
  }
26914
26806
  }
@@ -27006,61 +26898,61 @@ async function connectRemote(args) {
27006
26898
  }
27007
26899
 
27008
26900
  // src/migrations/2026-05-20-flatten-sessions.ts
27009
- var fs21 = __toESM(require("fs"), 1);
27010
- var path23 = __toESM(require("path"), 1);
26901
+ var fs20 = __toESM(require("fs"), 1);
26902
+ var path22 = __toESM(require("path"), 1);
27011
26903
  var MIGRATION_FLAG_NAME = ".migration.v1.done";
27012
26904
  function migrateFlattenSessions(opts) {
27013
26905
  const dataDir = opts.dataDir;
27014
26906
  const now = opts.now ?? Date.now;
27015
- const sessionsDir = path23.join(dataDir, "sessions");
27016
- const flagPath = path23.join(sessionsDir, MIGRATION_FLAG_NAME);
26907
+ const sessionsDir = path22.join(dataDir, "sessions");
26908
+ const flagPath = path22.join(sessionsDir, MIGRATION_FLAG_NAME);
27017
26909
  if (existsSync3(flagPath)) {
27018
26910
  return { skipped: true, flagWritten: false, movedBare: 0, movedVmOwner: 0, archivedListener: 0 };
27019
26911
  }
27020
26912
  let movedBare = 0;
27021
26913
  let movedVmOwner = 0;
27022
26914
  let archivedListener = 0;
27023
- const defaultDir = path23.join(sessionsDir, "default");
26915
+ const defaultDir = path22.join(sessionsDir, "default");
27024
26916
  if (existsSync3(defaultDir)) {
27025
26917
  for (const entry of readdirSafe(defaultDir)) {
27026
26918
  if (!entry.endsWith(".json")) continue;
27027
- const src = path23.join(defaultDir, entry);
27028
- const dst = path23.join(sessionsDir, entry);
27029
- fs21.renameSync(src, dst);
26919
+ const src = path22.join(defaultDir, entry);
26920
+ const dst = path22.join(sessionsDir, entry);
26921
+ fs20.renameSync(src, dst);
27030
26922
  movedBare += 1;
27031
26923
  }
27032
26924
  rmdirIfEmpty(defaultDir);
27033
26925
  }
27034
26926
  for (const pid of readdirSafe(sessionsDir)) {
27035
- const personaDir = path23.join(sessionsDir, pid);
26927
+ const personaDir = path22.join(sessionsDir, pid);
27036
26928
  if (!isDir(personaDir)) continue;
27037
26929
  if (pid === "default") continue;
27038
- const ownerSrc = path23.join(personaDir, "owner");
26930
+ const ownerSrc = path22.join(personaDir, "owner");
27039
26931
  if (existsSync3(ownerSrc) && isDir(ownerSrc)) {
27040
- const ownerDst = path23.join(dataDir, "personas", pid, ".clawd", "sessions", "owner");
27041
- fs21.mkdirSync(ownerDst, { recursive: true });
26932
+ const ownerDst = path22.join(dataDir, "personas", pid, ".clawd", "sessions", "owner");
26933
+ fs20.mkdirSync(ownerDst, { recursive: true });
27042
26934
  for (const file of readdirSafe(ownerSrc)) {
27043
26935
  if (!file.endsWith(".json")) continue;
27044
- fs21.renameSync(path23.join(ownerSrc, file), path23.join(ownerDst, file));
26936
+ fs20.renameSync(path22.join(ownerSrc, file), path22.join(ownerDst, file));
27045
26937
  movedVmOwner += 1;
27046
26938
  }
27047
26939
  rmdirIfEmpty(ownerSrc);
27048
26940
  }
27049
- const listenerSrc = path23.join(personaDir, "listener");
26941
+ const listenerSrc = path22.join(personaDir, "listener");
27050
26942
  if (existsSync3(listenerSrc) && isDir(listenerSrc)) {
27051
- const archiveDst = path23.join(dataDir, ".legacy", `listener-${pid}`);
27052
- fs21.mkdirSync(archiveDst, { recursive: true });
26943
+ const archiveDst = path22.join(dataDir, ".legacy", `listener-${pid}`);
26944
+ fs20.mkdirSync(archiveDst, { recursive: true });
27053
26945
  for (const file of readdirSafe(listenerSrc)) {
27054
26946
  if (!file.endsWith(".json")) continue;
27055
- fs21.renameSync(path23.join(listenerSrc, file), path23.join(archiveDst, file));
26947
+ fs20.renameSync(path22.join(listenerSrc, file), path22.join(archiveDst, file));
27056
26948
  archivedListener += 1;
27057
26949
  }
27058
26950
  rmdirIfEmpty(listenerSrc);
27059
26951
  }
27060
26952
  rmdirIfEmpty(personaDir);
27061
26953
  }
27062
- fs21.mkdirSync(sessionsDir, { recursive: true });
27063
- fs21.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
26954
+ fs20.mkdirSync(sessionsDir, { recursive: true });
26955
+ fs20.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
27064
26956
  return {
27065
26957
  skipped: false,
27066
26958
  flagWritten: true,
@@ -27071,7 +26963,7 @@ function migrateFlattenSessions(opts) {
27071
26963
  }
27072
26964
  function existsSync3(p2) {
27073
26965
  try {
27074
- fs21.statSync(p2);
26966
+ fs20.statSync(p2);
27075
26967
  return true;
27076
26968
  } catch {
27077
26969
  return false;
@@ -27079,21 +26971,21 @@ function existsSync3(p2) {
27079
26971
  }
27080
26972
  function isDir(p2) {
27081
26973
  try {
27082
- return fs21.statSync(p2).isDirectory();
26974
+ return fs20.statSync(p2).isDirectory();
27083
26975
  } catch {
27084
26976
  return false;
27085
26977
  }
27086
26978
  }
27087
26979
  function readdirSafe(p2) {
27088
26980
  try {
27089
- return fs21.readdirSync(p2);
26981
+ return fs20.readdirSync(p2);
27090
26982
  } catch {
27091
26983
  return [];
27092
26984
  }
27093
26985
  }
27094
26986
  function rmdirIfEmpty(p2) {
27095
26987
  try {
27096
- fs21.rmdirSync(p2);
26988
+ fs20.rmdirSync(p2);
27097
26989
  } catch {
27098
26990
  }
27099
26991
  }
@@ -27103,7 +26995,7 @@ var import_node_fs15 = __toESM(require("fs"), 1);
27103
26995
  var import_node_path17 = __toESM(require("path"), 1);
27104
26996
 
27105
26997
  // src/attachment/mime.ts
27106
- var import_node_path16 = __toESM(require("path"), 1);
26998
+ var import_node_path15 = __toESM(require("path"), 1);
27107
26999
  var TEXT_PLAIN = "text/plain; charset=utf-8";
27108
27000
  var EXT_TO_NATIVE_MIME = {
27109
27001
  // 图片
@@ -27210,7 +27102,7 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
27210
27102
  ".mk"
27211
27103
  ]);
27212
27104
  function lookupMime(filePathOrName) {
27213
- const ext = import_node_path16.default.extname(filePathOrName).toLowerCase();
27105
+ const ext = import_node_path15.default.extname(filePathOrName).toLowerCase();
27214
27106
  if (EXT_TO_NATIVE_MIME[ext]) return EXT_TO_NATIVE_MIME[ext];
27215
27107
  if (TEXT_EXTENSIONS.has(ext)) return TEXT_PLAIN;
27216
27108
  return "application/octet-stream";
@@ -27278,7 +27170,131 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
27278
27170
  return { ok: true, absPath };
27279
27171
  }
27280
27172
 
27173
+ // src/attachment/upload.ts
27174
+ var import_node_fs14 = __toESM(require("fs"), 1);
27175
+ var import_node_path16 = __toESM(require("path"), 1);
27176
+ var import_node_crypto6 = __toESM(require("crypto"), 1);
27177
+ var import_promises = require("stream/promises");
27178
+ var UploadError = class extends Error {
27179
+ constructor(code, message) {
27180
+ super(message);
27181
+ this.code = code;
27182
+ this.name = "UploadError";
27183
+ }
27184
+ code;
27185
+ };
27186
+ function assertValidFileName(fileName) {
27187
+ if (fileName.length === 0 || fileName === "." || fileName === ".." || fileName.startsWith(".") || fileName.includes("/") || fileName.includes("\\") || fileName !== import_node_path16.default.basename(fileName)) {
27188
+ throw new UploadError("INVALID_FILENAME", `fileName must be a plain basename, got: ${fileName}`);
27189
+ }
27190
+ }
27191
+ var HASH_PREFIX_LEN = 16;
27192
+ async function writeUploadedAttachment(args) {
27193
+ assertValidFileName(args.fileName);
27194
+ const attachmentsRoot = import_node_path16.default.join(args.sessionDir, ".attachments");
27195
+ try {
27196
+ import_node_fs14.default.mkdirSync(attachmentsRoot, { recursive: true });
27197
+ } catch (err) {
27198
+ throw new UploadError("STORAGE_ERROR", `mkdir failed: ${err.message}`);
27199
+ }
27200
+ const hasher = import_node_crypto6.default.createHash("sha256");
27201
+ let actualSize = 0;
27202
+ const tmpPath = import_node_path16.default.join(
27203
+ attachmentsRoot,
27204
+ `.upload-${process.pid}-${Date.now()}-${import_node_crypto6.default.randomBytes(4).toString("hex")}`
27205
+ );
27206
+ try {
27207
+ await (0, import_promises.pipeline)(
27208
+ args.body,
27209
+ async function* (source) {
27210
+ for await (const chunk of source) {
27211
+ const buf = chunk;
27212
+ actualSize += buf.length;
27213
+ hasher.update(buf);
27214
+ yield buf;
27215
+ }
27216
+ },
27217
+ import_node_fs14.default.createWriteStream(tmpPath, { mode: 384 })
27218
+ );
27219
+ } catch (err) {
27220
+ try {
27221
+ import_node_fs14.default.unlinkSync(tmpPath);
27222
+ } catch {
27223
+ }
27224
+ throw new UploadError("STORAGE_ERROR", `write failed: ${err.message}`);
27225
+ }
27226
+ if (actualSize !== args.contentLength) {
27227
+ try {
27228
+ import_node_fs14.default.unlinkSync(tmpPath);
27229
+ } catch {
27230
+ }
27231
+ throw new UploadError(
27232
+ "SIZE_MISMATCH",
27233
+ `expected ${args.contentLength} bytes, got ${actualSize}`
27234
+ );
27235
+ }
27236
+ const attachmentId = hasher.digest("hex").slice(0, HASH_PREFIX_LEN);
27237
+ const hashDir = import_node_path16.default.join(attachmentsRoot, attachmentId);
27238
+ let finalFileName;
27239
+ let hashDirExists = false;
27240
+ try {
27241
+ hashDirExists = import_node_fs14.default.statSync(hashDir).isDirectory();
27242
+ } catch {
27243
+ }
27244
+ if (hashDirExists) {
27245
+ const existing = import_node_fs14.default.readdirSync(hashDir).filter((n) => !n.startsWith("."));
27246
+ finalFileName = existing[0] ?? args.fileName;
27247
+ try {
27248
+ import_node_fs14.default.unlinkSync(tmpPath);
27249
+ } catch {
27250
+ }
27251
+ } else {
27252
+ try {
27253
+ import_node_fs14.default.mkdirSync(hashDir, { recursive: true });
27254
+ finalFileName = args.fileName;
27255
+ import_node_fs14.default.renameSync(tmpPath, import_node_path16.default.join(hashDir, finalFileName));
27256
+ } catch (err) {
27257
+ try {
27258
+ import_node_fs14.default.unlinkSync(tmpPath);
27259
+ } catch {
27260
+ }
27261
+ throw new UploadError("STORAGE_ERROR", `rename failed: ${err.message}`);
27262
+ }
27263
+ }
27264
+ const absPath = import_node_path16.default.join(hashDir, finalFileName);
27265
+ const relPath = import_node_path16.default.relative(args.sessionDir, absPath);
27266
+ args.groupFileStore.upsert(args.scope, args.sessionId, {
27267
+ relPath: absPath,
27268
+ // 存绝对路径,与现有 agent 入清单的形态一致(attachment.ts:144 双形态兼容)
27269
+ from: "user-upload",
27270
+ label: args.fileName,
27271
+ size: actualSize,
27272
+ mime: args.mimeType
27273
+ });
27274
+ return { attachmentId, absPath, relPath, sizeBytes: actualSize, mimeType: args.mimeType };
27275
+ }
27276
+
27281
27277
  // src/transport/http-router.ts
27278
+ var UPLOAD_MIME_ALLOW = /* @__PURE__ */ new Set([
27279
+ "image/png",
27280
+ "image/jpeg",
27281
+ "image/gif",
27282
+ "image/webp",
27283
+ "application/pdf"
27284
+ ]);
27285
+ var UPLOAD_MIME_ALLOW_PREFIX = ["text/"];
27286
+ var UPLOAD_MAX_BYTES = 25 * 1024 * 1024;
27287
+ function isUploadMimeAllowed(mime) {
27288
+ if (UPLOAD_MIME_ALLOW.has(mime)) return true;
27289
+ return UPLOAD_MIME_ALLOW_PREFIX.some((p2) => mime.startsWith(p2));
27290
+ }
27291
+ function isValidUploadFileName(fileName) {
27292
+ if (fileName.length === 0) return false;
27293
+ if (fileName === "." || fileName === "..") return false;
27294
+ if (fileName.startsWith(".")) return false;
27295
+ if (fileName.includes("/") || fileName.includes("\\")) return false;
27296
+ return fileName === import_node_path17.default.basename(fileName);
27297
+ }
27282
27298
  function createHttpRouter(deps) {
27283
27299
  return async (req, res) => {
27284
27300
  const url = parseUrl(req.url);
@@ -27292,7 +27308,7 @@ function createHttpRouter(deps) {
27292
27308
  });
27293
27309
  return true;
27294
27310
  }
27295
- if (!url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/")) {
27311
+ if (!url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/") && url.pathname !== "/attachments/upload") {
27296
27312
  return false;
27297
27313
  }
27298
27314
  if (url.pathname.startsWith("/files/") && req.method === "GET") {
@@ -27332,6 +27348,106 @@ function createHttpRouter(deps) {
27332
27348
  sendJson(res, 401, { code: "UNAUTHORIZED", message: "missing or invalid bearer token" });
27333
27349
  return true;
27334
27350
  }
27351
+ if (url.pathname === "/attachments/upload" && req.method === "POST") {
27352
+ if (ctx.role !== "owner") {
27353
+ sendJson(res, 403, { code: "FORBIDDEN", message: "owner only" });
27354
+ return true;
27355
+ }
27356
+ const up = deps.attachmentUpload;
27357
+ if (!up) {
27358
+ sendJson(res, 501, { code: "NOT_IMPLEMENTED", message: "attachment upload not wired" });
27359
+ return true;
27360
+ }
27361
+ const sessionId = url.searchParams.get("sessionId");
27362
+ const fileName = url.searchParams.get("fileName");
27363
+ const mimeType = url.searchParams.get("mimeType");
27364
+ const contentLengthRaw = req.headers["content-length"];
27365
+ if (!sessionId || !fileName || !mimeType || !contentLengthRaw) {
27366
+ sendJson(res, 400, {
27367
+ code: "INVALID_PARAM",
27368
+ message: "missing sessionId/fileName/mimeType/Content-Length"
27369
+ });
27370
+ return true;
27371
+ }
27372
+ if (!isValidUploadFileName(fileName)) {
27373
+ sendJson(res, 400, {
27374
+ code: "INVALID_FILENAME",
27375
+ message: `fileName must be a plain basename (no path separators, no dotfiles): ${fileName}`
27376
+ });
27377
+ return true;
27378
+ }
27379
+ const contentLength = Number.parseInt(contentLengthRaw, 10);
27380
+ if (!Number.isFinite(contentLength) || contentLength < 0) {
27381
+ sendJson(res, 400, { code: "INVALID_PARAM", message: "malformed Content-Length" });
27382
+ return true;
27383
+ }
27384
+ if (contentLength > UPLOAD_MAX_BYTES) {
27385
+ sendJson(res, 413, {
27386
+ code: "FILE_TOO_LARGE",
27387
+ message: `max ${UPLOAD_MAX_BYTES} bytes`
27388
+ });
27389
+ return true;
27390
+ }
27391
+ if (!isUploadMimeAllowed(mimeType)) {
27392
+ sendJson(res, 400, {
27393
+ code: "UNSUPPORTED_MIME",
27394
+ message: `mime ${mimeType} not allowed`
27395
+ });
27396
+ return true;
27397
+ }
27398
+ const sessionDir = up.getSessionDir(sessionId);
27399
+ const scope = up.getSessionScope(sessionId);
27400
+ if (!sessionDir || !scope) {
27401
+ sendJson(res, 404, { code: "NOT_FOUND", message: `session ${sessionId} not found` });
27402
+ return true;
27403
+ }
27404
+ const httpBaseUrl = up.getHttpBaseUrl();
27405
+ if (!httpBaseUrl) {
27406
+ sendJson(res, 503, {
27407
+ code: "TUNNEL_NOT_READY",
27408
+ message: "httpBaseUrl unavailable"
27409
+ });
27410
+ return true;
27411
+ }
27412
+ const secret = deps.getSignSecret?.();
27413
+ if (!secret) {
27414
+ sendJson(res, 501, {
27415
+ code: "NOT_IMPLEMENTED",
27416
+ message: "sign secret unavailable (noAuth?)"
27417
+ });
27418
+ return true;
27419
+ }
27420
+ try {
27421
+ const result = await writeUploadedAttachment({
27422
+ sessionDir,
27423
+ sessionId,
27424
+ scope,
27425
+ fileName,
27426
+ mimeType,
27427
+ contentLength,
27428
+ body: req,
27429
+ groupFileStore: up.groupFileStore
27430
+ });
27431
+ const parts = signUrlParts(secret, result.absPath, null);
27432
+ const fullUrl = buildSignedFileUrl(httpBaseUrl, parts);
27433
+ sendJson(res, 200, {
27434
+ attachmentId: result.attachmentId,
27435
+ url: fullUrl,
27436
+ relPath: result.relPath,
27437
+ sizeBytes: result.sizeBytes,
27438
+ mimeType: result.mimeType
27439
+ });
27440
+ } catch (err) {
27441
+ if (err instanceof UploadError) {
27442
+ const status = err.code === "SIZE_MISMATCH" ? 400 : 500;
27443
+ sendJson(res, status, { code: err.code, message: err.message });
27444
+ } else {
27445
+ deps.logger?.warn("attachment upload failed", { err: err.message });
27446
+ sendJson(res, 500, { code: "STORAGE_ERROR", message: err.message });
27447
+ }
27448
+ }
27449
+ return true;
27450
+ }
27335
27451
  const sessionFilesMatch = url.pathname.match(/^\/session\/([^/]+)\/files$/);
27336
27452
  if (sessionFilesMatch && req.method === "GET") {
27337
27453
  const sid = sessionFilesMatch[1];
@@ -27413,10 +27529,78 @@ function streamFile(res, absPath, logger) {
27413
27529
  stream.pipe(res);
27414
27530
  }
27415
27531
 
27416
- // src/attachment/group.ts
27532
+ // src/attachment/gc.ts
27417
27533
  var import_node_fs16 = __toESM(require("fs"), 1);
27418
27534
  var import_node_path18 = __toESM(require("path"), 1);
27419
- var import_node_crypto6 = __toESM(require("crypto"), 1);
27535
+ var DEFAULT_TTL_MS = 30 * 24 * 3600 * 1e3;
27536
+ function runAttachmentGc(args) {
27537
+ const now = (args.now ?? Date.now)();
27538
+ const ttlMs = args.ttlMs ?? DEFAULT_TTL_MS;
27539
+ const liveAbs = /* @__PURE__ */ new Set();
27540
+ for (const { scope, sessionId } of args.sessionScopes) {
27541
+ for (const entry of args.groupFileStore.list(scope, sessionId)) {
27542
+ if (entry.stale) continue;
27543
+ if (import_node_path18.default.isAbsolute(entry.relPath)) liveAbs.add(entry.relPath);
27544
+ }
27545
+ }
27546
+ for (const { scope, sessionId } of args.sessionScopes) {
27547
+ const sessionDir = args.getSessionCwd?.(sessionId) ?? import_node_path18.default.join(
27548
+ args.dataDir,
27549
+ "sessions",
27550
+ ...scopeSubPath(scope).map(safeFileName),
27551
+ safeFileName(sessionId)
27552
+ );
27553
+ const attRoot = import_node_path18.default.join(sessionDir, ".attachments");
27554
+ let hashDirs;
27555
+ try {
27556
+ hashDirs = import_node_fs16.default.readdirSync(attRoot);
27557
+ } catch (err) {
27558
+ if (err.code === "ENOENT") continue;
27559
+ args.logger?.warn("attachment gc: readdir failed", { attRoot, err: err.message });
27560
+ continue;
27561
+ }
27562
+ for (const hashDir of hashDirs) {
27563
+ const hashDirAbs = import_node_path18.default.join(attRoot, hashDir);
27564
+ let files;
27565
+ try {
27566
+ files = import_node_fs16.default.readdirSync(hashDirAbs);
27567
+ } catch {
27568
+ continue;
27569
+ }
27570
+ for (const name of files) {
27571
+ const file = import_node_path18.default.join(hashDirAbs, name);
27572
+ let stat;
27573
+ try {
27574
+ stat = import_node_fs16.default.statSync(file);
27575
+ } catch {
27576
+ continue;
27577
+ }
27578
+ if (!stat.isFile()) continue;
27579
+ const age = now - stat.mtimeMs;
27580
+ if (age < ttlMs) continue;
27581
+ if (liveAbs.has(file)) continue;
27582
+ try {
27583
+ import_node_fs16.default.unlinkSync(file);
27584
+ } catch (err) {
27585
+ args.logger?.warn("attachment gc: unlink failed", { file, err: err.message });
27586
+ }
27587
+ }
27588
+ try {
27589
+ if (import_node_fs16.default.readdirSync(hashDirAbs).length === 0) import_node_fs16.default.rmdirSync(hashDirAbs);
27590
+ } catch {
27591
+ }
27592
+ }
27593
+ try {
27594
+ if (import_node_fs16.default.readdirSync(attRoot).length === 0) import_node_fs16.default.rmdirSync(attRoot);
27595
+ } catch {
27596
+ }
27597
+ }
27598
+ }
27599
+
27600
+ // src/attachment/group.ts
27601
+ var import_node_fs17 = __toESM(require("fs"), 1);
27602
+ var import_node_path19 = __toESM(require("path"), 1);
27603
+ var import_node_crypto7 = __toESM(require("crypto"), 1);
27420
27604
  init_protocol();
27421
27605
  var GroupFileStore = class {
27422
27606
  dataDir;
@@ -27427,11 +27611,11 @@ var GroupFileStore = class {
27427
27611
  this.logger = opts.logger;
27428
27612
  }
27429
27613
  rootForScope(scope) {
27430
- return import_node_path18.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
27614
+ return import_node_path19.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
27431
27615
  }
27432
27616
  /** 与 SessionStore.filePath 平级,扩展名 .group-files.json */
27433
27617
  filePath(scope, sessionId) {
27434
- return import_node_path18.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
27618
+ return import_node_path19.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
27435
27619
  }
27436
27620
  cacheKey(scope, sessionId) {
27437
27621
  return scope.kind === "default" ? `default::${sessionId}` : `persona:${scope.personaId}:${scope.mode}::${sessionId}`;
@@ -27440,7 +27624,7 @@ var GroupFileStore = class {
27440
27624
  readFile(scope, sessionId) {
27441
27625
  const file = this.filePath(scope, sessionId);
27442
27626
  try {
27443
- const raw = import_node_fs16.default.readFileSync(file, "utf8");
27627
+ const raw = import_node_fs17.default.readFileSync(file, "utf8");
27444
27628
  const parsed = JSON.parse(raw);
27445
27629
  if (!Array.isArray(parsed)) {
27446
27630
  this.logger?.warn("GroupFileStore.readFile: not an array; resetting session entries", {
@@ -27466,10 +27650,10 @@ var GroupFileStore = class {
27466
27650
  }
27467
27651
  writeFile(scope, sessionId, entries) {
27468
27652
  const file = this.filePath(scope, sessionId);
27469
- import_node_fs16.default.mkdirSync(import_node_path18.default.dirname(file), { recursive: true });
27653
+ import_node_fs17.default.mkdirSync(import_node_path19.default.dirname(file), { recursive: true });
27470
27654
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
27471
- import_node_fs16.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
27472
- import_node_fs16.default.renameSync(tmp, file);
27655
+ import_node_fs17.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
27656
+ import_node_fs17.default.renameSync(tmp, file);
27473
27657
  }
27474
27658
  /** 拉一份当前 session 的清单。读盘 → cache;之后调用复用 cache */
27475
27659
  list(scope, sessionId) {
@@ -27505,7 +27689,7 @@ var GroupFileStore = class {
27505
27689
  entries[idx] = next;
27506
27690
  } else {
27507
27691
  next = {
27508
- id: `gf-${import_node_crypto6.default.randomBytes(6).toString("base64url")}`,
27692
+ id: `gf-${import_node_crypto7.default.randomBytes(6).toString("base64url")}`,
27509
27693
  relPath: input.relPath,
27510
27694
  from: input.from,
27511
27695
  label: input.label,
@@ -27555,10 +27739,10 @@ var GroupFileStore = class {
27555
27739
  };
27556
27740
 
27557
27741
  // src/discovery/state-file.ts
27558
- var import_node_fs17 = __toESM(require("fs"), 1);
27559
- var import_node_path19 = __toESM(require("path"), 1);
27742
+ var import_node_fs18 = __toESM(require("fs"), 1);
27743
+ var import_node_path20 = __toESM(require("path"), 1);
27560
27744
  function defaultStateFilePath(dataDir) {
27561
- return import_node_path19.default.join(dataDir, "state.json");
27745
+ return import_node_path20.default.join(dataDir, "state.json");
27562
27746
  }
27563
27747
  function isPidAlive(pid) {
27564
27748
  if (!Number.isFinite(pid) || pid <= 0) return false;
@@ -27580,7 +27764,7 @@ var StateFileManager = class {
27580
27764
  }
27581
27765
  read() {
27582
27766
  try {
27583
- const raw = import_node_fs17.default.readFileSync(this.file, "utf8");
27767
+ const raw = import_node_fs18.default.readFileSync(this.file, "utf8");
27584
27768
  const parsed = JSON.parse(raw);
27585
27769
  return parsed;
27586
27770
  } catch {
@@ -27594,34 +27778,34 @@ var StateFileManager = class {
27594
27778
  return { status: "stale", existing };
27595
27779
  }
27596
27780
  write(state) {
27597
- import_node_fs17.default.mkdirSync(import_node_path19.default.dirname(this.file), { recursive: true });
27781
+ import_node_fs18.default.mkdirSync(import_node_path20.default.dirname(this.file), { recursive: true });
27598
27782
  const tmp = `${this.file}.tmp.${process.pid}.${Date.now()}`;
27599
- import_node_fs17.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
27600
- import_node_fs17.default.renameSync(tmp, this.file);
27783
+ import_node_fs18.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
27784
+ import_node_fs18.default.renameSync(tmp, this.file);
27601
27785
  if (process.platform !== "win32") {
27602
27786
  try {
27603
- import_node_fs17.default.chmodSync(this.file, 384);
27787
+ import_node_fs18.default.chmodSync(this.file, 384);
27604
27788
  } catch {
27605
27789
  }
27606
27790
  }
27607
27791
  }
27608
27792
  delete() {
27609
27793
  try {
27610
- import_node_fs17.default.unlinkSync(this.file);
27794
+ import_node_fs18.default.unlinkSync(this.file);
27611
27795
  } catch {
27612
27796
  }
27613
27797
  }
27614
27798
  };
27615
27799
 
27616
27800
  // src/tunnel/tunnel-manager.ts
27617
- var import_node_fs21 = __toESM(require("fs"), 1);
27618
- var import_node_path23 = __toESM(require("path"), 1);
27619
- var import_node_crypto7 = __toESM(require("crypto"), 1);
27801
+ var import_node_fs22 = __toESM(require("fs"), 1);
27802
+ var import_node_path24 = __toESM(require("path"), 1);
27803
+ var import_node_crypto8 = __toESM(require("crypto"), 1);
27620
27804
  var import_node_child_process5 = require("child_process");
27621
27805
 
27622
27806
  // src/tunnel/tunnel-store.ts
27623
- var import_node_fs18 = __toESM(require("fs"), 1);
27624
- var import_node_path20 = __toESM(require("path"), 1);
27807
+ var import_node_fs19 = __toESM(require("fs"), 1);
27808
+ var import_node_path21 = __toESM(require("path"), 1);
27625
27809
  var TunnelStore = class {
27626
27810
  constructor(filePath) {
27627
27811
  this.filePath = filePath;
@@ -27629,7 +27813,7 @@ var TunnelStore = class {
27629
27813
  filePath;
27630
27814
  async get() {
27631
27815
  try {
27632
- const raw = await import_node_fs18.default.promises.readFile(this.filePath, "utf8");
27816
+ const raw = await import_node_fs19.default.promises.readFile(this.filePath, "utf8");
27633
27817
  const obj = JSON.parse(raw);
27634
27818
  if (!isPersistedTunnel(obj)) return null;
27635
27819
  return obj;
@@ -27640,22 +27824,22 @@ var TunnelStore = class {
27640
27824
  }
27641
27825
  }
27642
27826
  async set(v2) {
27643
- const dir = import_node_path20.default.dirname(this.filePath);
27644
- await import_node_fs18.default.promises.mkdir(dir, { recursive: true });
27827
+ const dir = import_node_path21.default.dirname(this.filePath);
27828
+ await import_node_fs19.default.promises.mkdir(dir, { recursive: true });
27645
27829
  const data = JSON.stringify(v2, null, 2);
27646
27830
  const tmp = `${this.filePath}.tmp.${process.pid}.${Date.now()}`;
27647
- await import_node_fs18.default.promises.writeFile(tmp, data, { mode: 384 });
27831
+ await import_node_fs19.default.promises.writeFile(tmp, data, { mode: 384 });
27648
27832
  if (process.platform !== "win32") {
27649
27833
  try {
27650
- await import_node_fs18.default.promises.chmod(tmp, 384);
27834
+ await import_node_fs19.default.promises.chmod(tmp, 384);
27651
27835
  } catch {
27652
27836
  }
27653
27837
  }
27654
- await import_node_fs18.default.promises.rename(tmp, this.filePath);
27838
+ await import_node_fs19.default.promises.rename(tmp, this.filePath);
27655
27839
  }
27656
27840
  async clear() {
27657
27841
  try {
27658
- await import_node_fs18.default.promises.unlink(this.filePath);
27842
+ await import_node_fs19.default.promises.unlink(this.filePath);
27659
27843
  } catch (err) {
27660
27844
  const code = err?.code;
27661
27845
  if (code !== "ENOENT") throw err;
@@ -27750,12 +27934,12 @@ function escape(v2) {
27750
27934
  }
27751
27935
 
27752
27936
  // src/tunnel/frpc-binary.ts
27753
- var import_node_fs19 = __toESM(require("fs"), 1);
27754
- var import_node_os10 = __toESM(require("os"), 1);
27755
- var import_node_path21 = __toESM(require("path"), 1);
27937
+ var import_node_fs20 = __toESM(require("fs"), 1);
27938
+ var import_node_os9 = __toESM(require("os"), 1);
27939
+ var import_node_path22 = __toESM(require("path"), 1);
27756
27940
  var import_node_child_process3 = require("child_process");
27757
27941
  var import_node_stream2 = require("stream");
27758
- var import_promises = require("stream/promises");
27942
+ var import_promises2 = require("stream/promises");
27759
27943
  var FRPC_VERSION = "0.68.0";
27760
27944
  var UnsupportedPlatformError = class extends Error {
27761
27945
  constructor(platform, arch) {
@@ -27784,20 +27968,20 @@ function frpcDownloadUrl(version2, p2) {
27784
27968
  }
27785
27969
  async function ensureFrpcBinary(opts) {
27786
27970
  if (opts.override) {
27787
- if (!import_node_fs19.default.existsSync(opts.override)) {
27971
+ if (!import_node_fs20.default.existsSync(opts.override)) {
27788
27972
  throw new Error(`frpc binary not found at override path: ${opts.override}`);
27789
27973
  }
27790
27974
  return opts.override;
27791
27975
  }
27792
27976
  const version2 = opts.version ?? FRPC_VERSION;
27793
27977
  const platform = opts.platform ?? detectPlatform();
27794
- const binDir = import_node_path21.default.join(opts.dataDir, "bin");
27795
- import_node_fs19.default.mkdirSync(binDir, { recursive: true });
27978
+ const binDir = import_node_path22.default.join(opts.dataDir, "bin");
27979
+ import_node_fs20.default.mkdirSync(binDir, { recursive: true });
27796
27980
  cleanupStaleArtifacts(binDir);
27797
- const stableBin = import_node_path21.default.join(binDir, "frpc");
27798
- if (import_node_fs19.default.existsSync(stableBin)) return stableBin;
27981
+ const stableBin = import_node_path22.default.join(binDir, "frpc");
27982
+ if (import_node_fs20.default.existsSync(stableBin)) return stableBin;
27799
27983
  const partialBin = `${stableBin}.partial`;
27800
- const tarballPath = import_node_path21.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
27984
+ const tarballPath = import_node_path22.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
27801
27985
  try {
27802
27986
  const url = frpcDownloadUrl(version2, platform);
27803
27987
  await downloadToFile(url, tarballPath, opts.fetchImpl);
@@ -27806,8 +27990,8 @@ async function ensureFrpcBinary(opts) {
27806
27990
  } else {
27807
27991
  await extractFrpcFromTarball(tarballPath, binDir, version2, platform, partialBin);
27808
27992
  }
27809
- import_node_fs19.default.chmodSync(partialBin, 493);
27810
- import_node_fs19.default.renameSync(partialBin, stableBin);
27993
+ import_node_fs20.default.chmodSync(partialBin, 493);
27994
+ import_node_fs20.default.renameSync(partialBin, stableBin);
27811
27995
  } finally {
27812
27996
  safeUnlink(tarballPath);
27813
27997
  safeUnlink(partialBin);
@@ -27817,15 +28001,15 @@ async function ensureFrpcBinary(opts) {
27817
28001
  function cleanupStaleArtifacts(binDir) {
27818
28002
  let entries;
27819
28003
  try {
27820
- entries = import_node_fs19.default.readdirSync(binDir);
28004
+ entries = import_node_fs20.default.readdirSync(binDir);
27821
28005
  } catch {
27822
28006
  return;
27823
28007
  }
27824
28008
  for (const name of entries) {
27825
28009
  if (name.endsWith(".partial") || name.startsWith("extract-")) {
27826
- const full = import_node_path21.default.join(binDir, name);
28010
+ const full = import_node_path22.default.join(binDir, name);
27827
28011
  try {
27828
- import_node_fs19.default.rmSync(full, { recursive: true, force: true });
28012
+ import_node_fs20.default.rmSync(full, { recursive: true, force: true });
27829
28013
  } catch {
27830
28014
  }
27831
28015
  }
@@ -27833,7 +28017,7 @@ function cleanupStaleArtifacts(binDir) {
27833
28017
  }
27834
28018
  function safeUnlink(p2) {
27835
28019
  try {
27836
- import_node_fs19.default.unlinkSync(p2);
28020
+ import_node_fs20.default.unlinkSync(p2);
27837
28021
  } catch {
27838
28022
  }
27839
28023
  }
@@ -27844,13 +28028,13 @@ async function downloadToFile(url, dest, fetchImpl) {
27844
28028
  if (!res.ok || !res.body) {
27845
28029
  throw new Error(`download failed: ${res.status} ${res.statusText}`);
27846
28030
  }
27847
- const out = import_node_fs19.default.createWriteStream(dest);
28031
+ const out = import_node_fs20.default.createWriteStream(dest);
27848
28032
  const nodeStream = import_node_stream2.Readable.fromWeb(res.body);
27849
- await (0, import_promises.pipeline)(nodeStream, out);
28033
+ await (0, import_promises2.pipeline)(nodeStream, out);
27850
28034
  }
27851
28035
  async function extractFrpcFromTarball(tarball, binDir, version2, platform, destBin) {
27852
- const work = import_node_path21.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
27853
- import_node_fs19.default.mkdirSync(work, { recursive: true });
28036
+ const work = import_node_path22.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
28037
+ import_node_fs20.default.mkdirSync(work, { recursive: true });
27854
28038
  try {
27855
28039
  await new Promise((resolve6, reject) => {
27856
28040
  const proc = (0, import_node_child_process3.spawn)("tar", ["xzf", tarball, "-C", work], { stdio: "pipe" });
@@ -27858,32 +28042,32 @@ async function extractFrpcFromTarball(tarball, binDir, version2, platform, destB
27858
28042
  proc.on("exit", (code) => code === 0 ? resolve6() : reject(new Error(`tar exited ${code}`)));
27859
28043
  });
27860
28044
  const dirName = `frp_${version2}_${platform.os}_${platform.arch}`;
27861
- const src = import_node_path21.default.join(work, dirName, "frpc");
27862
- if (!import_node_fs19.default.existsSync(src)) {
28045
+ const src = import_node_path22.default.join(work, dirName, "frpc");
28046
+ if (!import_node_fs20.default.existsSync(src)) {
27863
28047
  throw new Error(`frpc not found inside tarball at ${src}`);
27864
28048
  }
27865
- import_node_fs19.default.copyFileSync(src, destBin);
28049
+ import_node_fs20.default.copyFileSync(src, destBin);
27866
28050
  } finally {
27867
- import_node_fs19.default.rmSync(work, { recursive: true, force: true });
28051
+ import_node_fs20.default.rmSync(work, { recursive: true, force: true });
27868
28052
  }
27869
28053
  }
27870
28054
 
27871
28055
  // src/tunnel/frpc-process.ts
27872
- var import_node_fs20 = __toESM(require("fs"), 1);
27873
- var import_node_path22 = __toESM(require("path"), 1);
28056
+ var import_node_fs21 = __toESM(require("fs"), 1);
28057
+ var import_node_path23 = __toESM(require("path"), 1);
27874
28058
  var import_node_child_process4 = require("child_process");
27875
28059
  function frpcPidFilePath(dataDir) {
27876
- return import_node_path22.default.join(dataDir, "frpc.pid");
28060
+ return import_node_path23.default.join(dataDir, "frpc.pid");
27877
28061
  }
27878
28062
  function writeFrpcPid(dataDir, pid) {
27879
28063
  try {
27880
- import_node_fs20.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
28064
+ import_node_fs21.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
27881
28065
  } catch {
27882
28066
  }
27883
28067
  }
27884
28068
  function clearFrpcPid(dataDir) {
27885
28069
  try {
27886
- import_node_fs20.default.unlinkSync(frpcPidFilePath(dataDir));
28070
+ import_node_fs21.default.unlinkSync(frpcPidFilePath(dataDir));
27887
28071
  } catch {
27888
28072
  }
27889
28073
  }
@@ -27899,7 +28083,7 @@ function defaultIsPidAlive(pid) {
27899
28083
  }
27900
28084
  function defaultReadPidFile(file) {
27901
28085
  try {
27902
- return import_node_fs20.default.readFileSync(file, "utf8");
28086
+ return import_node_fs21.default.readFileSync(file, "utf8");
27903
28087
  } catch {
27904
28088
  return null;
27905
28089
  }
@@ -27915,7 +28099,7 @@ function defaultSleep(ms) {
27915
28099
  }
27916
28100
  async function killStaleFrpc(deps) {
27917
28101
  const pidFile = frpcPidFilePath(deps.dataDir);
27918
- const tomlPath = import_node_path22.default.join(deps.dataDir, "frpc.toml");
28102
+ const tomlPath = import_node_path23.default.join(deps.dataDir, "frpc.toml");
27919
28103
  const readPidFile = deps.readPidFileImpl ?? defaultReadPidFile;
27920
28104
  const isAlive = deps.isPidAliveImpl ?? defaultIsPidAlive;
27921
28105
  const killPid = deps.killPidImpl ?? defaultKillPid;
@@ -27939,7 +28123,7 @@ async function killStaleFrpc(deps) {
27939
28123
  }
27940
28124
  if (victims.size === 0) {
27941
28125
  try {
27942
- import_node_fs20.default.unlinkSync(pidFile);
28126
+ import_node_fs21.default.unlinkSync(pidFile);
27943
28127
  } catch {
27944
28128
  }
27945
28129
  return;
@@ -27950,7 +28134,7 @@ async function killStaleFrpc(deps) {
27950
28134
  }
27951
28135
  await sleep(deps.reapWaitMs ?? 300);
27952
28136
  try {
27953
- import_node_fs20.default.unlinkSync(pidFile);
28137
+ import_node_fs21.default.unlinkSync(pidFile);
27954
28138
  } catch {
27955
28139
  }
27956
28140
  }
@@ -27987,7 +28171,7 @@ var DEFAULT_TUNNEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
27987
28171
  var TunnelManager = class {
27988
28172
  constructor(deps) {
27989
28173
  this.deps = deps;
27990
- this.store = deps.store ?? new TunnelStore(import_node_path23.default.join(deps.dataDir, "tunnel.json"));
28174
+ this.store = deps.store ?? new TunnelStore(import_node_path24.default.join(deps.dataDir, "tunnel.json"));
27991
28175
  this.ttlMs = deps.ttlMs ?? DEFAULT_TUNNEL_TTL_MS;
27992
28176
  this.startupTimeoutMs = deps.startupTimeoutMs ?? 15e3;
27993
28177
  }
@@ -28114,8 +28298,8 @@ var TunnelManager = class {
28114
28298
  dataDir: this.deps.dataDir,
28115
28299
  override: this.deps.frpcBinaryOverride ?? void 0
28116
28300
  });
28117
- const tomlPath = import_node_path23.default.join(this.deps.dataDir, "frpc.toml");
28118
- const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto7.default.randomBytes(3).toString("hex")}`;
28301
+ const tomlPath = import_node_path24.default.join(this.deps.dataDir, "frpc.toml");
28302
+ const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto8.default.randomBytes(3).toString("hex")}`;
28119
28303
  const toml = buildFrpcToml({
28120
28304
  serverAddr: t.frpsHost,
28121
28305
  serverPort: t.frpsPort,
@@ -28125,12 +28309,12 @@ var TunnelManager = class {
28125
28309
  localPort,
28126
28310
  logLevel: "info"
28127
28311
  });
28128
- await import_node_fs21.default.promises.writeFile(tomlPath, toml, { mode: 384 });
28312
+ await import_node_fs22.default.promises.writeFile(tomlPath, toml, { mode: 384 });
28129
28313
  const proc = (this.deps.spawnImpl ?? import_node_child_process5.spawn)(frpcBin, ["-c", tomlPath], {
28130
28314
  stdio: ["ignore", "pipe", "pipe"]
28131
28315
  });
28132
- const logFilePath = import_node_path23.default.join(this.deps.dataDir, "frpc.log");
28133
- const logStream = import_node_fs21.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
28316
+ const logFilePath = import_node_path24.default.join(this.deps.dataDir, "frpc.log");
28317
+ const logStream = import_node_fs22.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
28134
28318
  logStream.on("error", () => {
28135
28319
  });
28136
28320
  const tee = (chunk) => {
@@ -28212,23 +28396,23 @@ async function waitForFrpcReady(proc, timeoutMs) {
28212
28396
  }
28213
28397
 
28214
28398
  // src/tunnel/device-key.ts
28215
- var import_node_os11 = __toESM(require("os"), 1);
28216
- var import_node_crypto8 = __toESM(require("crypto"), 1);
28399
+ var import_node_os10 = __toESM(require("os"), 1);
28400
+ var import_node_crypto9 = __toESM(require("crypto"), 1);
28217
28401
  var DERIVE_SALT = "clawd-tunnel-device-v1";
28218
28402
  function deriveStableDeviceKey(opts = {}) {
28219
- const hostname = opts.hostname ?? import_node_os11.default.hostname();
28220
- const uid = opts.uid ?? (typeof import_node_os11.default.userInfo === "function" ? import_node_os11.default.userInfo().uid : 0);
28403
+ const hostname = opts.hostname ?? import_node_os10.default.hostname();
28404
+ const uid = opts.uid ?? (typeof import_node_os10.default.userInfo === "function" ? import_node_os10.default.userInfo().uid : 0);
28221
28405
  const input = `${hostname}::${uid}`;
28222
- return import_node_crypto8.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
28406
+ return import_node_crypto9.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
28223
28407
  }
28224
28408
 
28225
28409
  // src/auth-store.ts
28226
- var import_node_fs22 = __toESM(require("fs"), 1);
28227
- var import_node_path24 = __toESM(require("path"), 1);
28228
- var import_node_crypto9 = __toESM(require("crypto"), 1);
28410
+ var import_node_fs23 = __toESM(require("fs"), 1);
28411
+ var import_node_path25 = __toESM(require("path"), 1);
28412
+ var import_node_crypto10 = __toESM(require("crypto"), 1);
28229
28413
  var AUTH_FILE_NAME = "auth.json";
28230
28414
  function authFilePath(dataDir) {
28231
- return import_node_path24.default.join(dataDir, AUTH_FILE_NAME);
28415
+ return import_node_path25.default.join(dataDir, AUTH_FILE_NAME);
28232
28416
  }
28233
28417
  function loadOrCreateAuthFile(opts) {
28234
28418
  const file = authFilePath(opts.dataDir);
@@ -28253,14 +28437,14 @@ function loadOrCreateAuthFile(opts) {
28253
28437
  return next;
28254
28438
  }
28255
28439
  function defaultGenerateToken2() {
28256
- return import_node_crypto9.default.randomBytes(32).toString("base64url");
28440
+ return import_node_crypto10.default.randomBytes(32).toString("base64url");
28257
28441
  }
28258
28442
  function defaultGenerateOwnerPrincipalId() {
28259
- return `owner-${import_node_crypto9.default.randomUUID()}`;
28443
+ return `owner-${import_node_crypto10.default.randomUUID()}`;
28260
28444
  }
28261
28445
  function readAuthFile(file) {
28262
28446
  try {
28263
- const raw = import_node_fs22.default.readFileSync(file, "utf8");
28447
+ const raw = import_node_fs23.default.readFileSync(file, "utf8");
28264
28448
  const parsed = JSON.parse(raw);
28265
28449
  if (typeof parsed?.token !== "string" || parsed.token.length === 0) {
28266
28450
  return null;
@@ -28278,25 +28462,25 @@ function readAuthFile(file) {
28278
28462
  }
28279
28463
  }
28280
28464
  function writeAuthFile(file, content) {
28281
- import_node_fs22.default.mkdirSync(import_node_path24.default.dirname(file), { recursive: true });
28282
- import_node_fs22.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
28465
+ import_node_fs23.default.mkdirSync(import_node_path25.default.dirname(file), { recursive: true });
28466
+ import_node_fs23.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
28283
28467
  try {
28284
- import_node_fs22.default.chmodSync(file, 384);
28468
+ import_node_fs23.default.chmodSync(file, 384);
28285
28469
  } catch {
28286
28470
  }
28287
28471
  }
28288
28472
 
28289
28473
  // src/owner-profile.ts
28290
- var import_node_fs23 = __toESM(require("fs"), 1);
28291
- var import_node_os12 = __toESM(require("os"), 1);
28292
- var import_node_path25 = __toESM(require("path"), 1);
28474
+ var import_node_fs24 = __toESM(require("fs"), 1);
28475
+ var import_node_os11 = __toESM(require("os"), 1);
28476
+ var import_node_path26 = __toESM(require("path"), 1);
28293
28477
  var PROFILE_FILENAME = "profile.json";
28294
28478
  function loadOwnerDisplayName(dataDir) {
28295
- const fallback = import_node_os12.default.userInfo().username;
28296
- const profilePath = import_node_path25.default.join(dataDir, PROFILE_FILENAME);
28479
+ const fallback = import_node_os11.default.userInfo().username;
28480
+ const profilePath = import_node_path26.default.join(dataDir, PROFILE_FILENAME);
28297
28481
  let raw;
28298
28482
  try {
28299
- raw = import_node_fs23.default.readFileSync(profilePath, "utf8");
28483
+ raw = import_node_fs24.default.readFileSync(profilePath, "utf8");
28300
28484
  } catch {
28301
28485
  return fallback;
28302
28486
  }
@@ -28325,12 +28509,12 @@ init_protocol();
28325
28509
  init_protocol();
28326
28510
 
28327
28511
  // src/session/fork.ts
28328
- var import_node_fs24 = __toESM(require("fs"), 1);
28329
- var import_node_os13 = __toESM(require("os"), 1);
28330
- var import_node_path26 = __toESM(require("path"), 1);
28512
+ var import_node_fs25 = __toESM(require("fs"), 1);
28513
+ var import_node_os12 = __toESM(require("os"), 1);
28514
+ var import_node_path27 = __toESM(require("path"), 1);
28331
28515
  init_claude_history();
28332
28516
  function readJsonlEntries(file) {
28333
- const raw = import_node_fs24.default.readFileSync(file, "utf8");
28517
+ const raw = import_node_fs25.default.readFileSync(file, "utf8");
28334
28518
  const out = [];
28335
28519
  for (const line of raw.split("\n")) {
28336
28520
  const t = line.trim();
@@ -28343,10 +28527,10 @@ function readJsonlEntries(file) {
28343
28527
  return out;
28344
28528
  }
28345
28529
  function forkSession(input) {
28346
- const baseDir = input.baseDir ?? import_node_path26.default.join(import_node_os13.default.homedir(), ".claude");
28347
- const projectDir = import_node_path26.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
28348
- const sourceFile = import_node_path26.default.join(projectDir, `${input.toolSessionId}.jsonl`);
28349
- if (!import_node_fs24.default.existsSync(sourceFile)) {
28530
+ const baseDir = input.baseDir ?? import_node_path27.default.join(import_node_os12.default.homedir(), ".claude");
28531
+ const projectDir = import_node_path27.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
28532
+ const sourceFile = import_node_path27.default.join(projectDir, `${input.toolSessionId}.jsonl`);
28533
+ if (!import_node_fs25.default.existsSync(sourceFile)) {
28350
28534
  throw new Error(`fork: source transcript not found: ${sourceFile}`);
28351
28535
  }
28352
28536
  const entries = readJsonlEntries(sourceFile);
@@ -28376,9 +28560,9 @@ function forkSession(input) {
28376
28560
  }
28377
28561
  forkedLines.push(JSON.stringify(forked));
28378
28562
  }
28379
- const forkedFilePath = import_node_path26.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
28380
- import_node_fs24.default.mkdirSync(projectDir, { recursive: true });
28381
- import_node_fs24.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
28563
+ const forkedFilePath = import_node_path27.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
28564
+ import_node_fs25.default.mkdirSync(projectDir, { recursive: true });
28565
+ import_node_fs25.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
28382
28566
  return { forkedToolSessionId, forkedFilePath };
28383
28567
  }
28384
28568
 
@@ -28710,7 +28894,7 @@ function buildPermissionHandlers(deps) {
28710
28894
  }
28711
28895
 
28712
28896
  // src/handlers/history.ts
28713
- var path36 = __toESM(require("path"), 1);
28897
+ var path37 = __toESM(require("path"), 1);
28714
28898
  init_protocol();
28715
28899
 
28716
28900
  // src/session/recent-dirs.ts
@@ -28728,7 +28912,7 @@ function listRecentDirs(store, limit = 50) {
28728
28912
  }
28729
28913
 
28730
28914
  // src/permission/persona-paths.ts
28731
- var path35 = __toESM(require("path"), 1);
28915
+ var path36 = __toESM(require("path"), 1);
28732
28916
  function getAllowedPersonaIds(grants, action) {
28733
28917
  const ids = /* @__PURE__ */ new Set();
28734
28918
  for (const g2 of grants) {
@@ -28741,26 +28925,26 @@ function getAllowedPersonaIds(grants, action) {
28741
28925
  return ids;
28742
28926
  }
28743
28927
  function isGuestPathAllowed(grants, absPath, personaRoot, action = "read") {
28744
- const root = path35.resolve(personaRoot);
28745
- const target = path35.resolve(absPath);
28746
- const sep3 = root.endsWith(path35.sep) ? "" : path35.sep;
28928
+ const root = path36.resolve(personaRoot);
28929
+ const target = path36.resolve(absPath);
28930
+ const sep3 = root.endsWith(path36.sep) ? "" : path36.sep;
28747
28931
  if (!target.startsWith(root + sep3)) return false;
28748
- const rel = path35.relative(root, target);
28932
+ const rel = path36.relative(root, target);
28749
28933
  if (!rel || rel.startsWith("..")) return false;
28750
- const personaId = rel.split(path35.sep)[0];
28934
+ const personaId = rel.split(path36.sep)[0];
28751
28935
  if (!personaId) return false;
28752
28936
  const allowed = getAllowedPersonaIds(grants, action);
28753
28937
  if (allowed === "*") return true;
28754
28938
  return allowed.has(personaId);
28755
28939
  }
28756
28940
  function personaIdFromPath(absPath, personaRoot) {
28757
- const root = path35.resolve(personaRoot);
28758
- const target = path35.resolve(absPath);
28759
- const sep3 = root.endsWith(path35.sep) ? "" : path35.sep;
28941
+ const root = path36.resolve(personaRoot);
28942
+ const target = path36.resolve(absPath);
28943
+ const sep3 = root.endsWith(path36.sep) ? "" : path36.sep;
28760
28944
  if (!target.startsWith(root + sep3)) return null;
28761
- const rel = path35.relative(root, target);
28945
+ const rel = path36.relative(root, target);
28762
28946
  if (!rel || rel.startsWith("..")) return null;
28763
- const id = rel.split(path35.sep)[0];
28947
+ const id = rel.split(path36.sep)[0];
28764
28948
  return id || null;
28765
28949
  }
28766
28950
 
@@ -28785,7 +28969,7 @@ function buildHistoryHandlers(deps) {
28785
28969
  if (!pid) return false;
28786
28970
  return isGuestPathAllowed(
28787
28971
  ctx.grants,
28788
- path36.join(personaRoot, pid),
28972
+ path37.join(personaRoot, pid),
28789
28973
  personaRoot,
28790
28974
  "read"
28791
28975
  );
@@ -28796,7 +28980,7 @@ function buildHistoryHandlers(deps) {
28796
28980
  };
28797
28981
  const list = async (frame, _client, ctx) => {
28798
28982
  const args = HistoryListArgs.parse(frame);
28799
- assertGuestPath(ctx, path36.resolve(args.projectPath), personaRoot, "history:list");
28983
+ assertGuestPath(ctx, path37.resolve(args.projectPath), personaRoot, "history:list");
28800
28984
  const sessions = await history.listSessions(args);
28801
28985
  return { response: { type: "history:list", sessions } };
28802
28986
  };
@@ -28828,13 +29012,13 @@ function buildHistoryHandlers(deps) {
28828
29012
  };
28829
29013
  const subagents = async (frame, _client, ctx) => {
28830
29014
  const args = HistorySubagentsArgs.parse(frame);
28831
- assertGuestPath(ctx, path36.resolve(args.cwd), personaRoot, "history:subagents");
29015
+ assertGuestPath(ctx, path37.resolve(args.cwd), personaRoot, "history:subagents");
28832
29016
  const subs = await history.listSubagents(args);
28833
29017
  return { response: { type: "history:subagents", subagents: subs } };
28834
29018
  };
28835
29019
  const subagentRead = async (frame, _client, ctx) => {
28836
29020
  const args = HistorySubagentReadArgs.parse(frame);
28837
- assertGuestPath(ctx, path36.resolve(args.cwd), personaRoot, "history:subagent-read");
29021
+ assertGuestPath(ctx, path37.resolve(args.cwd), personaRoot, "history:subagent-read");
28838
29022
  const res = await history.readSubagent(args);
28839
29023
  return { response: { type: "history:subagent-read", ...res } };
28840
29024
  };
@@ -28842,7 +29026,7 @@ function buildHistoryHandlers(deps) {
28842
29026
  const dirs = listRecentDirs(store);
28843
29027
  if (ctx?.principal.kind === "guest" && personaRoot) {
28844
29028
  const filtered = dirs.filter(
28845
- (d) => isGuestPathAllowed(ctx.grants, path36.resolve(d.cwd), personaRoot, "read")
29029
+ (d) => isGuestPathAllowed(ctx.grants, path37.resolve(d.cwd), personaRoot, "read")
28846
29030
  );
28847
29031
  return { response: { type: "history:recentDirs", dirs: filtered } };
28848
29032
  }
@@ -28859,8 +29043,8 @@ function buildHistoryHandlers(deps) {
28859
29043
  }
28860
29044
 
28861
29045
  // src/handlers/workspace.ts
28862
- var path37 = __toESM(require("path"), 1);
28863
- var os14 = __toESM(require("os"), 1);
29046
+ var path38 = __toESM(require("path"), 1);
29047
+ var os13 = __toESM(require("os"), 1);
28864
29048
  init_protocol();
28865
29049
  init_protocol();
28866
29050
  function buildEnabledPluginNames(personaManager, personaId) {
@@ -28899,23 +29083,23 @@ function buildWorkspaceHandlers(deps) {
28899
29083
  const list = async (frame, _client, ctx) => {
28900
29084
  const args = WorkspaceListArgs.parse(frame);
28901
29085
  const isGuest = ctx?.principal.kind === "guest";
28902
- const fallbackCwd = isGuest && personaRoot ? personaRoot : os14.homedir();
28903
- const resolvedCwd = path37.resolve(args.cwd ?? fallbackCwd);
28904
- const target = args.path ? path37.resolve(resolvedCwd, args.path) : resolvedCwd;
29086
+ const fallbackCwd = isGuest && personaRoot ? personaRoot : os13.homedir();
29087
+ const resolvedCwd = path38.resolve(args.cwd ?? fallbackCwd);
29088
+ const target = args.path ? path38.resolve(resolvedCwd, args.path) : resolvedCwd;
28905
29089
  assertGuestPath2(ctx, target, personaRoot, "workspace:list");
28906
29090
  const res = workspace.list({ ...args, cwd: resolvedCwd });
28907
29091
  return { response: { type: "workspace:list", ...res } };
28908
29092
  };
28909
29093
  const read = async (frame, _client, ctx) => {
28910
29094
  const args = WorkspaceReadArgs.parse(frame);
28911
- const target = path37.isAbsolute(args.path) ? path37.resolve(args.path) : path37.resolve(args.cwd, args.path);
29095
+ const target = path38.isAbsolute(args.path) ? path38.resolve(args.path) : path38.resolve(args.cwd, args.path);
28912
29096
  assertGuestPath2(ctx, target, personaRoot, "workspace:read");
28913
29097
  const res = workspace.read(args);
28914
29098
  return { response: { type: "workspace:read", ...res } };
28915
29099
  };
28916
29100
  const skillsList = async (frame, _client, ctx) => {
28917
29101
  const args = SkillsListArgs.parse(frame);
28918
- const cwdAbs = path37.resolve(args.cwd);
29102
+ const cwdAbs = path38.resolve(args.cwd);
28919
29103
  assertGuestPath2(ctx, cwdAbs, personaRoot, "skills:list");
28920
29104
  const list2 = skills.list(args);
28921
29105
  if (ctx?.principal.kind === "guest" && personaRoot) {
@@ -28927,7 +29111,7 @@ function buildWorkspaceHandlers(deps) {
28927
29111
  };
28928
29112
  const agentsList = async (frame, _client, ctx) => {
28929
29113
  const args = AgentsListArgs.parse(frame);
28930
- const cwdAbs = path37.resolve(args.cwd);
29114
+ const cwdAbs = path38.resolve(args.cwd);
28931
29115
  assertGuestPath2(ctx, cwdAbs, personaRoot, "agents:list");
28932
29116
  const list2 = agents.list(args);
28933
29117
  if (ctx?.principal.kind === "guest" && personaRoot) {
@@ -28946,20 +29130,20 @@ function buildWorkspaceHandlers(deps) {
28946
29130
  }
28947
29131
 
28948
29132
  // src/handlers/git.ts
28949
- var path39 = __toESM(require("path"), 1);
29133
+ var path40 = __toESM(require("path"), 1);
28950
29134
  init_protocol();
28951
29135
  init_protocol();
28952
29136
 
28953
29137
  // src/workspace/git.ts
28954
29138
  var import_node_child_process6 = require("child_process");
28955
- var import_node_fs25 = __toESM(require("fs"), 1);
28956
- var import_node_path27 = __toESM(require("path"), 1);
29139
+ var import_node_fs26 = __toESM(require("fs"), 1);
29140
+ var import_node_path28 = __toESM(require("path"), 1);
28957
29141
  var import_node_util = require("util");
28958
29142
  var pexec = (0, import_node_util.promisify)(import_node_child_process6.execFile);
28959
29143
  function normalizePath(p2) {
28960
- const resolved = import_node_path27.default.resolve(p2);
29144
+ const resolved = import_node_path28.default.resolve(p2);
28961
29145
  try {
28962
- return import_node_fs25.default.realpathSync(resolved);
29146
+ return import_node_fs26.default.realpathSync(resolved);
28963
29147
  } catch {
28964
29148
  return resolved;
28965
29149
  }
@@ -29032,7 +29216,7 @@ async function listGitBranches(cwd) {
29032
29216
  // src/handlers/git.ts
29033
29217
  function assertGuestCwd(ctx, cwd, personaRoot, method) {
29034
29218
  if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
29035
- if (!isGuestPathAllowed(ctx.grants, path39.resolve(cwd), personaRoot, "read")) {
29219
+ if (!isGuestPathAllowed(ctx.grants, path40.resolve(cwd), personaRoot, "read")) {
29036
29220
  throw new ClawdError(
29037
29221
  ERROR_CODES.UNAUTHORIZED,
29038
29222
  `guest ${ctx.principal.id} cannot ${method} cwd ${cwd}`
@@ -29395,7 +29579,7 @@ function buildWhoamiHandler(deps) {
29395
29579
  }
29396
29580
 
29397
29581
  // src/handlers/meta.ts
29398
- var import_node_os14 = __toESM(require("os"), 1);
29582
+ var import_node_os13 = __toESM(require("os"), 1);
29399
29583
  init_protocol();
29400
29584
 
29401
29585
  // src/version.ts
@@ -29425,7 +29609,7 @@ function buildReadyFrame(deps, client) {
29425
29609
  return {
29426
29610
  version,
29427
29611
  protocolVersion: PROTOCOL_VERSION,
29428
- hostname: import_node_os14.default.hostname(),
29612
+ hostname: import_node_os13.default.hostname(),
29429
29613
  os: process.platform,
29430
29614
  tools,
29431
29615
  runningSessions: info.runningSessions,
@@ -29519,7 +29703,7 @@ function buildPersonaHandlers(deps) {
29519
29703
  }
29520
29704
 
29521
29705
  // src/handlers/attachment.ts
29522
- var import_node_path28 = __toESM(require("path"), 1);
29706
+ var import_node_path29 = __toESM(require("path"), 1);
29523
29707
  init_protocol();
29524
29708
  init_protocol();
29525
29709
  var DEFAULT_TTL_SECONDS = 24 * 3600;
@@ -29573,12 +29757,12 @@ function buildAttachmentHandlers(deps) {
29573
29757
  `session ${args.sessionId} scope unresolved`
29574
29758
  );
29575
29759
  }
29576
- const cwdAbs = import_node_path28.default.resolve(sessionFile.cwd);
29577
- const candidateAbs = import_node_path28.default.isAbsolute(args.relPath) ? import_node_path28.default.resolve(args.relPath) : import_node_path28.default.resolve(cwdAbs, args.relPath);
29760
+ const cwdAbs = import_node_path29.default.resolve(sessionFile.cwd);
29761
+ const candidateAbs = import_node_path29.default.isAbsolute(args.relPath) ? import_node_path29.default.resolve(args.relPath) : import_node_path29.default.resolve(cwdAbs, args.relPath);
29578
29762
  assertGuestAttachmentPath(ctx, candidateAbs, deps.personaRoot, "attachment.signUrl");
29579
29763
  const entries = deps.groupFileStore.list(scope, args.sessionId);
29580
29764
  const entry = entries.find((e) => {
29581
- const storedAbs = import_node_path28.default.isAbsolute(e.relPath) ? import_node_path28.default.resolve(e.relPath) : import_node_path28.default.resolve(cwdAbs, e.relPath);
29765
+ const storedAbs = import_node_path29.default.isAbsolute(e.relPath) ? import_node_path29.default.resolve(e.relPath) : import_node_path29.default.resolve(cwdAbs, e.relPath);
29582
29766
  return storedAbs === candidateAbs && !e.stale;
29583
29767
  });
29584
29768
  if (!entry) {
@@ -29602,7 +29786,7 @@ function buildAttachmentHandlers(deps) {
29602
29786
  if (!ctx || ctx.principal.kind !== "guest" || !deps.personaRoot || !deps.sessionStore) return;
29603
29787
  const f = deps.sessionStore.read(sessionId);
29604
29788
  if (!f) return;
29605
- assertGuestAttachmentPath(ctx, import_node_path28.default.resolve(f.cwd), deps.personaRoot, method);
29789
+ assertGuestAttachmentPath(ctx, import_node_path29.default.resolve(f.cwd), deps.personaRoot, method);
29606
29790
  }
29607
29791
  const groupAdd = async (frame, _client, ctx) => {
29608
29792
  if (!deps.groupFileStore || !deps.getSessionScope) {
@@ -29643,7 +29827,7 @@ function buildAttachmentHandlers(deps) {
29643
29827
  }
29644
29828
  const entries = deps.groupFileStore.list(scope, parsed.data.sessionId);
29645
29829
  const target = entries.find((e) => e.relPath === parsed.data.relPath);
29646
- if (target?.from === "owner") {
29830
+ if (target?.from === "owner" || target?.from === "user-upload") {
29647
29831
  deps.groupFileStore.remove(scope, parsed.data.sessionId, parsed.data.relPath);
29648
29832
  } else {
29649
29833
  deps.groupFileStore.markStale(scope, parsed.data.sessionId, parsed.data.relPath);
@@ -29839,7 +30023,7 @@ function computeGrantForFrame(method, frame) {
29839
30023
  async function startDaemon(config) {
29840
30024
  const logger = createLogger({
29841
30025
  level: config.logLevel,
29842
- file: import_node_path29.default.join(config.dataDir, "clawd.log")
30026
+ file: import_node_path30.default.join(config.dataDir, "clawd.log")
29843
30027
  });
29844
30028
  logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
29845
30029
  const stateMgr = new StateFileManager({ dataDir: config.dataDir });
@@ -29926,7 +30110,7 @@ async function startDaemon(config) {
29926
30110
  const agents = new AgentsScanner();
29927
30111
  const history = new ClaudeHistoryReader();
29928
30112
  let transport = null;
29929
- const personaStore = new PersonaStore(import_node_path29.default.join(config.dataDir, "personas"));
30113
+ const personaStore = new PersonaStore(import_node_path30.default.join(config.dataDir, "personas"));
29930
30114
  const defaultsRoot = findDefaultsRoot();
29931
30115
  if (defaultsRoot) {
29932
30116
  seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
@@ -29943,7 +30127,7 @@ async function startDaemon(config) {
29943
30127
  getAdapter,
29944
30128
  historyReader: history,
29945
30129
  dataDir: config.dataDir,
29946
- personaRoot: import_node_path29.default.join(config.dataDir, "personas"),
30130
+ personaRoot: import_node_path30.default.join(config.dataDir, "personas"),
29947
30131
  personaStore,
29948
30132
  ownerDisplayName,
29949
30133
  ownerPrincipalId,
@@ -29967,10 +30151,10 @@ async function startDaemon(config) {
29967
30151
  // 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
29968
30152
  attachmentGroup: {
29969
30153
  onFileEdit: (input) => {
29970
- const absPath = import_node_path29.default.isAbsolute(input.relPath) ? input.relPath : import_node_path29.default.join(input.cwd, input.relPath);
30154
+ const absPath = import_node_path30.default.isAbsolute(input.relPath) ? input.relPath : import_node_path30.default.join(input.cwd, input.relPath);
29971
30155
  let size = 0;
29972
30156
  try {
29973
- size = import_node_fs26.default.statSync(absPath).size;
30157
+ size = import_node_fs27.default.statSync(absPath).size;
29974
30158
  } catch (err) {
29975
30159
  logger.warn("attachment.onFileEdit stat failed", {
29976
30160
  sessionId: input.sessionId,
@@ -29996,11 +30180,6 @@ async function startDaemon(config) {
29996
30180
  }
29997
30181
  });
29998
30182
  const observer = new SessionObserver({
29999
- // TUI 模式 pty 跑 CC 不发 SDK system.task_started 帧 → UI pending 期 progress.agentId
30000
- // 永远 undefined,子 agent 调用过程没有抽屉入口。开 meta observer 后从 subagents 目录
30001
- // 新出现的 agent-*.meta.json 派生 subagent_progress(started) 补这块缺口。SDK 模式仍走
30002
- // task_started 路径,避免双 emit 让 ring buffer 双帧。
30003
- enableSubagentMetaObserver: config.mode === "tui",
30004
30183
  onEvent: ({ sessionId, events }) => {
30005
30184
  manager.feedObserverEvents(sessionId, events);
30006
30185
  },
@@ -30088,10 +30267,10 @@ async function startDaemon(config) {
30088
30267
  // 'persona/<pid>/owner',default 走 'default'。
30089
30268
  getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
30090
30269
  // guest path guard:candidate 必须在 personaRoot 子树下
30091
- personaRoot: import_node_path29.default.join(config.dataDir, "personas")
30270
+ personaRoot: import_node_path30.default.join(config.dataDir, "personas")
30092
30271
  },
30093
30272
  // workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
30094
- personaRoot: import_node_path29.default.join(config.dataDir, "personas"),
30273
+ personaRoot: import_node_path30.default.join(config.dataDir, "personas"),
30095
30274
  // capability:list / delete handler 依赖
30096
30275
  capabilityManager,
30097
30276
  // personal-cap:get 返回的 shareBaseUrl 用此 base URL:
@@ -30127,7 +30306,19 @@ async function startDaemon(config) {
30127
30306
  sessionStore: store,
30128
30307
  // /files HMAC verify 用 auth.json 的 signSecret 字段(与 attachment.signUrl 同源)。
30129
30308
  // --auth-token CLI 模式没 signSecret → 路由返 501,sign URL 功能整体禁用。
30130
- getSignSecret: () => authFile?.signSecret ?? null
30309
+ getSignSecret: () => authFile?.signSecret ?? null,
30310
+ // chatinput 附件上传(POST /attachments/upload,spec §6.1)。upload pipeline
30311
+ // 在 session.cwd 下的 .attachments/ 落盘;signSecret 与 /files HMAC 同源;
30312
+ // tunnel 未启时返 503 TUNNEL_NOT_READY 让前端显式提示。
30313
+ attachmentUpload: {
30314
+ groupFileStore,
30315
+ getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
30316
+ getSessionDir: (sid) => {
30317
+ const f = manager.findOwnedSession(sid);
30318
+ return f ? f.cwd : null;
30319
+ },
30320
+ getHttpBaseUrl: () => currentTunnelUrl ? getHttpBaseUrl() : null
30321
+ }
30131
30322
  });
30132
30323
  wsServer = new LocalWsServer({
30133
30324
  host: config.host,
@@ -30292,8 +30483,8 @@ async function startDaemon(config) {
30292
30483
  const lines = [
30293
30484
  `Tunnel: ${r.url}`,
30294
30485
  ...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
30295
- `Frpc config: ${import_node_path29.default.join(config.dataDir, "frpc.toml")}`,
30296
- `Frpc log: ${import_node_path29.default.join(config.dataDir, "frpc.log")}`
30486
+ `Frpc config: ${import_node_path30.default.join(config.dataDir, "frpc.toml")}`,
30487
+ `Frpc log: ${import_node_path30.default.join(config.dataDir, "frpc.log")}`
30297
30488
  ];
30298
30489
  const width = Math.max(...lines.map((l) => l.length));
30299
30490
  const bar = "\u2550".repeat(width + 4);
@@ -30306,8 +30497,8 @@ ${bar}
30306
30497
 
30307
30498
  `);
30308
30499
  try {
30309
- const connectPath = import_node_path29.default.join(config.dataDir, "connect.txt");
30310
- import_node_fs26.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
30500
+ const connectPath = import_node_path30.default.join(config.dataDir, "connect.txt");
30501
+ import_node_fs27.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
30311
30502
  } catch {
30312
30503
  }
30313
30504
  } catch (err) {
@@ -30332,8 +30523,29 @@ ${bar}
30332
30523
  logger.warn("tunnel unavailable, degraded to local mode", { reason: tunnelError });
30333
30524
  }
30334
30525
  }
30526
+ const tickAttachmentGc = () => {
30527
+ try {
30528
+ runAttachmentGc({
30529
+ dataDir: config.dataDir,
30530
+ groupFileStore,
30531
+ sessionScopes: manager.listOwnedSessionScopes().map(({ scope, sessionId }) => ({
30532
+ scope,
30533
+ sessionId
30534
+ })),
30535
+ // 注入 cwd 让 GC 直接定位 .attachments 目录(与 upload 落盘点一致)
30536
+ getSessionCwd: (sid) => manager.findOwnedSession(sid)?.cwd ?? null,
30537
+ logger
30538
+ });
30539
+ } catch (err) {
30540
+ logger.warn("attachment gc tick failed", { err: err.message });
30541
+ }
30542
+ };
30543
+ setImmediate(tickAttachmentGc);
30544
+ const attachmentGcInterval = setInterval(tickAttachmentGc, 24 * 3600 * 1e3);
30545
+ attachmentGcInterval.unref();
30335
30546
  const shutdown = async () => {
30336
30547
  logger.info("stopping clawd");
30548
+ clearInterval(attachmentGcInterval);
30337
30549
  observer.stopAll();
30338
30550
  manager.stopAll();
30339
30551
  if (tunnelMgr) {
@@ -30351,9 +30563,9 @@ ${bar}
30351
30563
  };
30352
30564
  }
30353
30565
  function migrateDropPersonsDir(dataDir) {
30354
- const dir = import_node_path29.default.join(dataDir, "persons");
30566
+ const dir = import_node_path30.default.join(dataDir, "persons");
30355
30567
  try {
30356
- import_node_fs26.default.rmSync(dir, { recursive: true, force: true });
30568
+ import_node_fs27.default.rmSync(dir, { recursive: true, force: true });
30357
30569
  } catch {
30358
30570
  }
30359
30571
  }