@clawos-dev/clawd 0.2.98 → 0.2.99-beta.190.29dcdb5

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 +934 -144
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -168,7 +168,19 @@ var init_methods = __esm({
168
168
  "extension.list",
169
169
  "extension.open",
170
170
  "extension.close",
171
- "extension.uninstall"
171
+ "extension.uninstall",
172
+ // ---- extension sharing v1 (spec 2026-05-28-clawd-extension-sharing-design) ----
173
+ // owner-side: 公开 / 撤回 / 查 publish 状态(owner-only ADMIN_ANY)
174
+ "extension.publish",
175
+ "extension.unpublish",
176
+ "extension.publish:status",
177
+ // cross-tunnel: guest 从 owner 拉 published list + bundle(CAPABILITY_SCOPED)
178
+ "extension.list-published",
179
+ "extension.fetchBundle",
180
+ // guest-self: guest 在自己 daemon 上做安装与更新(ADMIN_ANY,guest 是 owner of self daemon).
181
+ // 无 subscription 持久化:guest 端身份完全来自文件系统 + reconciled publishedExtensions.
182
+ "extension.install-from-channel",
183
+ "extension.update-from-channel"
172
184
  ];
173
185
  }
174
186
  });
@@ -660,8 +672,8 @@ var init_parseUtil = __esm({
660
672
  init_errors2();
661
673
  init_en();
662
674
  makeIssue = (params) => {
663
- const { data, path: path53, errorMaps, issueData } = params;
664
- const fullPath = [...path53, ...issueData.path || []];
675
+ const { data, path: path58, errorMaps, issueData } = params;
676
+ const fullPath = [...path58, ...issueData.path || []];
665
677
  const fullIssue = {
666
678
  ...issueData,
667
679
  path: fullPath
@@ -972,11 +984,11 @@ var init_types = __esm({
972
984
  init_parseUtil();
973
985
  init_util();
974
986
  ParseInputLazyPath = class {
975
- constructor(parent, value, path53, key) {
987
+ constructor(parent, value, path58, key) {
976
988
  this._cachedPath = [];
977
989
  this.parent = parent;
978
990
  this.data = value;
979
- this._path = path53;
991
+ this._path = path58;
980
992
  this._key = key;
981
993
  }
982
994
  get path() {
@@ -5355,7 +5367,18 @@ var init_received_capability = __esm({
5355
5367
  });
5356
5368
 
5357
5369
  // ../protocol/src/extension.ts
5358
- var EXT_ID_REGEX, ExtensionStateSchema, ExtensionManifestSchema, ExtensionRecordSchema;
5370
+ function ownerNamespace(ownerPrincipalId) {
5371
+ let h = 2166136261;
5372
+ for (let i = 0; i < ownerPrincipalId.length; i++) {
5373
+ h ^= ownerPrincipalId.charCodeAt(i);
5374
+ h = Math.imul(h, 16777619) >>> 0;
5375
+ }
5376
+ return (h >>> 0).toString(16).padStart(8, "0");
5377
+ }
5378
+ function namespacedExtId(slug, ownerPrincipalId) {
5379
+ return `${slug}-${ownerNamespace(ownerPrincipalId)}`;
5380
+ }
5381
+ var EXT_ID_REGEX, ExtensionStateSchema, ExtensionManifestSchema, ExtensionRecordSchema, PublishedSnapshotSchema, ChannelRefSchema, SourceRefSchema, PublishedExtensionRecordSchema, PublishCheckSchema, PublishStatusResultSchema;
5359
5382
  var init_extension = __esm({
5360
5383
  "../protocol/src/extension.ts"() {
5361
5384
  "use strict";
@@ -5409,6 +5432,72 @@ var init_extension = __esm({
5409
5432
  invalidReason: external_exports.string().min(1)
5410
5433
  })
5411
5434
  ]);
5435
+ PublishedSnapshotSchema = external_exports.object({
5436
+ snapshotHash: external_exports.string().min(1),
5437
+ version: external_exports.string().min(1),
5438
+ publishedAt: external_exports.number()
5439
+ });
5440
+ ChannelRefSchema = external_exports.discriminatedUnion("kind", [
5441
+ external_exports.object({
5442
+ kind: external_exports.literal("p2p-tunnel"),
5443
+ ownerPrincipalId: external_exports.string().min(1),
5444
+ extId: external_exports.string().min(1)
5445
+ })
5446
+ ]);
5447
+ SourceRefSchema = external_exports.discriminatedUnion("kind", [
5448
+ external_exports.object({
5449
+ kind: external_exports.literal("p2p-tunnel"),
5450
+ ownerPrincipalId: external_exports.string().min(1),
5451
+ extId: external_exports.string().min(1),
5452
+ snapshotHash: external_exports.string().min(1)
5453
+ })
5454
+ ]);
5455
+ PublishedExtensionRecordSchema = external_exports.object({
5456
+ extId: external_exports.string().min(1),
5457
+ name: external_exports.string().min(1),
5458
+ version: external_exports.string().min(1),
5459
+ contentHash: external_exports.string().min(1),
5460
+ publishedAt: external_exports.number()
5461
+ });
5462
+ PublishCheckSchema = external_exports.discriminatedUnion("kind", [
5463
+ external_exports.object({ kind: external_exports.literal("clean") }),
5464
+ external_exports.object({
5465
+ kind: external_exports.literal("ready-new-version"),
5466
+ fromVersion: external_exports.string().min(1),
5467
+ toVersion: external_exports.string().min(1)
5468
+ }),
5469
+ external_exports.object({
5470
+ kind: external_exports.literal("error-same-hash"),
5471
+ version: external_exports.string().min(1)
5472
+ }),
5473
+ external_exports.object({
5474
+ kind: external_exports.literal("error-version-not-bumped"),
5475
+ localVersion: external_exports.string().min(1),
5476
+ publishedVersion: external_exports.string().min(1)
5477
+ }),
5478
+ external_exports.object({
5479
+ kind: external_exports.literal("error-version-regression"),
5480
+ localVersion: external_exports.string().min(1),
5481
+ publishedVersion: external_exports.string().min(1)
5482
+ })
5483
+ ]);
5484
+ PublishStatusResultSchema = external_exports.object({
5485
+ localManifest: external_exports.object({
5486
+ name: external_exports.string().min(1),
5487
+ version: external_exports.string().min(1),
5488
+ contentHash: external_exports.string().min(1)
5489
+ }),
5490
+ publishState: external_exports.discriminatedUnion("kind", [
5491
+ external_exports.object({ kind: external_exports.literal("unpublished") }),
5492
+ external_exports.object({
5493
+ kind: external_exports.literal("published"),
5494
+ version: external_exports.string().min(1),
5495
+ contentHash: external_exports.string().min(1),
5496
+ publishedAt: external_exports.number()
5497
+ })
5498
+ ]),
5499
+ check: PublishCheckSchema
5500
+ });
5412
5501
  }
5413
5502
  });
5414
5503
 
@@ -5703,8 +5792,8 @@ var require_req = __commonJS({
5703
5792
  if (req.originalUrl) {
5704
5793
  _req.url = req.originalUrl;
5705
5794
  } else {
5706
- const path53 = req.path;
5707
- _req.url = typeof path53 === "string" ? path53 : req.url ? req.url.path || req.url : void 0;
5795
+ const path58 = req.path;
5796
+ _req.url = typeof path58 === "string" ? path58 : req.url ? req.url.path || req.url : void 0;
5708
5797
  }
5709
5798
  if (req.query) {
5710
5799
  _req.query = req.query;
@@ -5869,14 +5958,14 @@ var require_redact = __commonJS({
5869
5958
  }
5870
5959
  return obj;
5871
5960
  }
5872
- function parsePath(path53) {
5961
+ function parsePath(path58) {
5873
5962
  const parts = [];
5874
5963
  let current = "";
5875
5964
  let inBrackets = false;
5876
5965
  let inQuotes = false;
5877
5966
  let quoteChar = "";
5878
- for (let i = 0; i < path53.length; i++) {
5879
- const char = path53[i];
5967
+ for (let i = 0; i < path58.length; i++) {
5968
+ const char = path58[i];
5880
5969
  if (!inBrackets && char === ".") {
5881
5970
  if (current) {
5882
5971
  parts.push(current);
@@ -6007,10 +6096,10 @@ var require_redact = __commonJS({
6007
6096
  return current;
6008
6097
  }
6009
6098
  function redactPaths(obj, paths, censor, remove = false) {
6010
- for (const path53 of paths) {
6011
- const parts = parsePath(path53);
6099
+ for (const path58 of paths) {
6100
+ const parts = parsePath(path58);
6012
6101
  if (parts.includes("*")) {
6013
- redactWildcardPath(obj, parts, censor, path53, remove);
6102
+ redactWildcardPath(obj, parts, censor, path58, remove);
6014
6103
  } else {
6015
6104
  if (remove) {
6016
6105
  removeKey(obj, parts);
@@ -6095,8 +6184,8 @@ var require_redact = __commonJS({
6095
6184
  }
6096
6185
  } else {
6097
6186
  if (afterWildcard.includes("*")) {
6098
- const wrappedCensor = typeof censor === "function" ? (value, path53) => {
6099
- const fullPath = [...pathArray.slice(0, pathLength), ...path53];
6187
+ const wrappedCensor = typeof censor === "function" ? (value, path58) => {
6188
+ const fullPath = [...pathArray.slice(0, pathLength), ...path58];
6100
6189
  return censor(value, fullPath);
6101
6190
  } : censor;
6102
6191
  redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
@@ -6131,8 +6220,8 @@ var require_redact = __commonJS({
6131
6220
  return null;
6132
6221
  }
6133
6222
  const pathStructure = /* @__PURE__ */ new Map();
6134
- for (const path53 of pathsToClone) {
6135
- const parts = parsePath(path53);
6223
+ for (const path58 of pathsToClone) {
6224
+ const parts = parsePath(path58);
6136
6225
  let current = pathStructure;
6137
6226
  for (let i = 0; i < parts.length; i++) {
6138
6227
  const part = parts[i];
@@ -6184,24 +6273,24 @@ var require_redact = __commonJS({
6184
6273
  }
6185
6274
  return cloneSelectively(obj, pathStructure);
6186
6275
  }
6187
- function validatePath(path53) {
6188
- if (typeof path53 !== "string") {
6276
+ function validatePath(path58) {
6277
+ if (typeof path58 !== "string") {
6189
6278
  throw new Error("Paths must be (non-empty) strings");
6190
6279
  }
6191
- if (path53 === "") {
6280
+ if (path58 === "") {
6192
6281
  throw new Error("Invalid redaction path ()");
6193
6282
  }
6194
- if (path53.includes("..")) {
6195
- throw new Error(`Invalid redaction path (${path53})`);
6283
+ if (path58.includes("..")) {
6284
+ throw new Error(`Invalid redaction path (${path58})`);
6196
6285
  }
6197
- if (path53.includes(",")) {
6198
- throw new Error(`Invalid redaction path (${path53})`);
6286
+ if (path58.includes(",")) {
6287
+ throw new Error(`Invalid redaction path (${path58})`);
6199
6288
  }
6200
6289
  let bracketCount = 0;
6201
6290
  let inQuotes = false;
6202
6291
  let quoteChar = "";
6203
- for (let i = 0; i < path53.length; i++) {
6204
- const char = path53[i];
6292
+ for (let i = 0; i < path58.length; i++) {
6293
+ const char = path58[i];
6205
6294
  if ((char === '"' || char === "'") && bracketCount > 0) {
6206
6295
  if (!inQuotes) {
6207
6296
  inQuotes = true;
@@ -6215,20 +6304,20 @@ var require_redact = __commonJS({
6215
6304
  } else if (char === "]" && !inQuotes) {
6216
6305
  bracketCount--;
6217
6306
  if (bracketCount < 0) {
6218
- throw new Error(`Invalid redaction path (${path53})`);
6307
+ throw new Error(`Invalid redaction path (${path58})`);
6219
6308
  }
6220
6309
  }
6221
6310
  }
6222
6311
  if (bracketCount !== 0) {
6223
- throw new Error(`Invalid redaction path (${path53})`);
6312
+ throw new Error(`Invalid redaction path (${path58})`);
6224
6313
  }
6225
6314
  }
6226
6315
  function validatePaths(paths) {
6227
6316
  if (!Array.isArray(paths)) {
6228
6317
  throw new TypeError("paths must be an array");
6229
6318
  }
6230
- for (const path53 of paths) {
6231
- validatePath(path53);
6319
+ for (const path58 of paths) {
6320
+ validatePath(path58);
6232
6321
  }
6233
6322
  }
6234
6323
  function slowRedact(options = {}) {
@@ -6396,8 +6485,8 @@ var require_redaction = __commonJS({
6396
6485
  if (shape[k2] === null) {
6397
6486
  o[k2] = (value) => topCensor(value, [k2]);
6398
6487
  } else {
6399
- const wrappedCensor = typeof censor === "function" ? (value, path53) => {
6400
- return censor(value, [k2, ...path53]);
6488
+ const wrappedCensor = typeof censor === "function" ? (value, path58) => {
6489
+ return censor(value, [k2, ...path58]);
6401
6490
  } : censor;
6402
6491
  o[k2] = Redact({
6403
6492
  paths: shape[k2],
@@ -6615,10 +6704,10 @@ var require_atomic_sleep = __commonJS({
6615
6704
  var require_sonic_boom = __commonJS({
6616
6705
  "../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
6617
6706
  "use strict";
6618
- var fs41 = require("fs");
6707
+ var fs46 = require("fs");
6619
6708
  var EventEmitter2 = require("events");
6620
6709
  var inherits = require("util").inherits;
6621
- var path53 = require("path");
6710
+ var path58 = require("path");
6622
6711
  var sleep = require_atomic_sleep();
6623
6712
  var assert = require("assert");
6624
6713
  var BUSY_WRITE_TIMEOUT = 100;
@@ -6672,20 +6761,20 @@ var require_sonic_boom = __commonJS({
6672
6761
  const mode = sonic.mode;
6673
6762
  if (sonic.sync) {
6674
6763
  try {
6675
- if (sonic.mkdir) fs41.mkdirSync(path53.dirname(file), { recursive: true });
6676
- const fd = fs41.openSync(file, flags, mode);
6764
+ if (sonic.mkdir) fs46.mkdirSync(path58.dirname(file), { recursive: true });
6765
+ const fd = fs46.openSync(file, flags, mode);
6677
6766
  fileOpened(null, fd);
6678
6767
  } catch (err) {
6679
6768
  fileOpened(err);
6680
6769
  throw err;
6681
6770
  }
6682
6771
  } else if (sonic.mkdir) {
6683
- fs41.mkdir(path53.dirname(file), { recursive: true }, (err) => {
6772
+ fs46.mkdir(path58.dirname(file), { recursive: true }, (err) => {
6684
6773
  if (err) return fileOpened(err);
6685
- fs41.open(file, flags, mode, fileOpened);
6774
+ fs46.open(file, flags, mode, fileOpened);
6686
6775
  });
6687
6776
  } else {
6688
- fs41.open(file, flags, mode, fileOpened);
6777
+ fs46.open(file, flags, mode, fileOpened);
6689
6778
  }
6690
6779
  }
6691
6780
  function SonicBoom(opts) {
@@ -6726,8 +6815,8 @@ var require_sonic_boom = __commonJS({
6726
6815
  this.flush = flushBuffer;
6727
6816
  this.flushSync = flushBufferSync;
6728
6817
  this._actualWrite = actualWriteBuffer;
6729
- fsWriteSync = () => fs41.writeSync(this.fd, this._writingBuf);
6730
- fsWrite = () => fs41.write(this.fd, this._writingBuf, this.release);
6818
+ fsWriteSync = () => fs46.writeSync(this.fd, this._writingBuf);
6819
+ fsWrite = () => fs46.write(this.fd, this._writingBuf, this.release);
6731
6820
  } else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
6732
6821
  this._writingBuf = "";
6733
6822
  this.write = write;
@@ -6736,15 +6825,15 @@ var require_sonic_boom = __commonJS({
6736
6825
  this._actualWrite = actualWrite;
6737
6826
  fsWriteSync = () => {
6738
6827
  if (Buffer.isBuffer(this._writingBuf)) {
6739
- return fs41.writeSync(this.fd, this._writingBuf);
6828
+ return fs46.writeSync(this.fd, this._writingBuf);
6740
6829
  }
6741
- return fs41.writeSync(this.fd, this._writingBuf, "utf8");
6830
+ return fs46.writeSync(this.fd, this._writingBuf, "utf8");
6742
6831
  };
6743
6832
  fsWrite = () => {
6744
6833
  if (Buffer.isBuffer(this._writingBuf)) {
6745
- return fs41.write(this.fd, this._writingBuf, this.release);
6834
+ return fs46.write(this.fd, this._writingBuf, this.release);
6746
6835
  }
6747
- return fs41.write(this.fd, this._writingBuf, "utf8", this.release);
6836
+ return fs46.write(this.fd, this._writingBuf, "utf8", this.release);
6748
6837
  };
6749
6838
  } else {
6750
6839
  throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
@@ -6801,7 +6890,7 @@ var require_sonic_boom = __commonJS({
6801
6890
  }
6802
6891
  }
6803
6892
  if (this._fsync) {
6804
- fs41.fsyncSync(this.fd);
6893
+ fs46.fsyncSync(this.fd);
6805
6894
  }
6806
6895
  const len = this._len;
6807
6896
  if (this._reopening) {
@@ -6915,7 +7004,7 @@ var require_sonic_boom = __commonJS({
6915
7004
  const onDrain = () => {
6916
7005
  if (!this._fsync) {
6917
7006
  try {
6918
- fs41.fsync(this.fd, (err) => {
7007
+ fs46.fsync(this.fd, (err) => {
6919
7008
  this._flushPending = false;
6920
7009
  cb(err);
6921
7010
  });
@@ -7017,7 +7106,7 @@ var require_sonic_boom = __commonJS({
7017
7106
  const fd = this.fd;
7018
7107
  this.once("ready", () => {
7019
7108
  if (fd !== this.fd) {
7020
- fs41.close(fd, (err) => {
7109
+ fs46.close(fd, (err) => {
7021
7110
  if (err) {
7022
7111
  return this.emit("error", err);
7023
7112
  }
@@ -7066,7 +7155,7 @@ var require_sonic_boom = __commonJS({
7066
7155
  buf = this._bufs[0];
7067
7156
  }
7068
7157
  try {
7069
- const n = Buffer.isBuffer(buf) ? fs41.writeSync(this.fd, buf) : fs41.writeSync(this.fd, buf, "utf8");
7158
+ const n = Buffer.isBuffer(buf) ? fs46.writeSync(this.fd, buf) : fs46.writeSync(this.fd, buf, "utf8");
7070
7159
  const releasedBufObj = releaseWritingBuf(buf, this._len, n);
7071
7160
  buf = releasedBufObj.writingBuf;
7072
7161
  this._len = releasedBufObj.len;
@@ -7082,7 +7171,7 @@ var require_sonic_boom = __commonJS({
7082
7171
  }
7083
7172
  }
7084
7173
  try {
7085
- fs41.fsyncSync(this.fd);
7174
+ fs46.fsyncSync(this.fd);
7086
7175
  } catch {
7087
7176
  }
7088
7177
  }
@@ -7103,7 +7192,7 @@ var require_sonic_boom = __commonJS({
7103
7192
  buf = mergeBuf(this._bufs[0], this._lens[0]);
7104
7193
  }
7105
7194
  try {
7106
- const n = fs41.writeSync(this.fd, buf);
7195
+ const n = fs46.writeSync(this.fd, buf);
7107
7196
  buf = buf.subarray(n);
7108
7197
  this._len = Math.max(this._len - n, 0);
7109
7198
  if (buf.length <= 0) {
@@ -7131,13 +7220,13 @@ var require_sonic_boom = __commonJS({
7131
7220
  this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
7132
7221
  if (this.sync) {
7133
7222
  try {
7134
- const written = Buffer.isBuffer(this._writingBuf) ? fs41.writeSync(this.fd, this._writingBuf) : fs41.writeSync(this.fd, this._writingBuf, "utf8");
7223
+ const written = Buffer.isBuffer(this._writingBuf) ? fs46.writeSync(this.fd, this._writingBuf) : fs46.writeSync(this.fd, this._writingBuf, "utf8");
7135
7224
  release(null, written);
7136
7225
  } catch (err) {
7137
7226
  release(err);
7138
7227
  }
7139
7228
  } else {
7140
- fs41.write(this.fd, this._writingBuf, release);
7229
+ fs46.write(this.fd, this._writingBuf, release);
7141
7230
  }
7142
7231
  }
7143
7232
  function actualWriteBuffer() {
@@ -7146,7 +7235,7 @@ var require_sonic_boom = __commonJS({
7146
7235
  this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
7147
7236
  if (this.sync) {
7148
7237
  try {
7149
- const written = fs41.writeSync(this.fd, this._writingBuf);
7238
+ const written = fs46.writeSync(this.fd, this._writingBuf);
7150
7239
  release(null, written);
7151
7240
  } catch (err) {
7152
7241
  release(err);
@@ -7155,7 +7244,7 @@ var require_sonic_boom = __commonJS({
7155
7244
  if (kCopyBuffer) {
7156
7245
  this._writingBuf = Buffer.from(this._writingBuf);
7157
7246
  }
7158
- fs41.write(this.fd, this._writingBuf, release);
7247
+ fs46.write(this.fd, this._writingBuf, release);
7159
7248
  }
7160
7249
  }
7161
7250
  function actualClose(sonic) {
@@ -7171,12 +7260,12 @@ var require_sonic_boom = __commonJS({
7171
7260
  sonic._lens = [];
7172
7261
  assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
7173
7262
  try {
7174
- fs41.fsync(sonic.fd, closeWrapped);
7263
+ fs46.fsync(sonic.fd, closeWrapped);
7175
7264
  } catch {
7176
7265
  }
7177
7266
  function closeWrapped() {
7178
7267
  if (sonic.fd !== 1 && sonic.fd !== 2) {
7179
- fs41.close(sonic.fd, done);
7268
+ fs46.close(sonic.fd, done);
7180
7269
  } else {
7181
7270
  done();
7182
7271
  }
@@ -9540,7 +9629,7 @@ var require_multistream = __commonJS({
9540
9629
  var require_pino = __commonJS({
9541
9630
  "../node_modules/.pnpm/pino@9.14.0/node_modules/pino/pino.js"(exports2, module2) {
9542
9631
  "use strict";
9543
- var os19 = require("os");
9632
+ var os21 = require("os");
9544
9633
  var stdSerializers = require_pino_std_serializers();
9545
9634
  var caller = require_caller();
9546
9635
  var redaction = require_redaction();
@@ -9587,7 +9676,7 @@ var require_pino = __commonJS({
9587
9676
  } = symbols;
9588
9677
  var { epochTime, nullTime } = time;
9589
9678
  var { pid } = process;
9590
- var hostname = os19.hostname();
9679
+ var hostname = os21.hostname();
9591
9680
  var defaultErrorSerializer = stdSerializers.err;
9592
9681
  var defaultOptions = {
9593
9682
  level: "info",
@@ -10311,11 +10400,11 @@ var init_lib = __esm({
10311
10400
  }
10312
10401
  }
10313
10402
  },
10314
- addToPath: function addToPath(path53, added, removed, oldPosInc, options) {
10315
- var last = path53.lastComponent;
10403
+ addToPath: function addToPath(path58, added, removed, oldPosInc, options) {
10404
+ var last = path58.lastComponent;
10316
10405
  if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
10317
10406
  return {
10318
- oldPos: path53.oldPos + oldPosInc,
10407
+ oldPos: path58.oldPos + oldPosInc,
10319
10408
  lastComponent: {
10320
10409
  count: last.count + 1,
10321
10410
  added,
@@ -10325,7 +10414,7 @@ var init_lib = __esm({
10325
10414
  };
10326
10415
  } else {
10327
10416
  return {
10328
- oldPos: path53.oldPos + oldPosInc,
10417
+ oldPos: path58.oldPos + oldPosInc,
10329
10418
  lastComponent: {
10330
10419
  count: 1,
10331
10420
  added,
@@ -10771,10 +10860,10 @@ function attachmentToHistoryMessage(o, ts) {
10771
10860
  const memories = raw.map((m2) => {
10772
10861
  if (!m2 || typeof m2 !== "object") return null;
10773
10862
  const rec = m2;
10774
- const path53 = typeof rec.path === "string" ? rec.path : null;
10863
+ const path58 = typeof rec.path === "string" ? rec.path : null;
10775
10864
  const content = typeof rec.content === "string" ? rec.content : null;
10776
- if (!path53 || content == null) return null;
10777
- const entry = { path: path53, content };
10865
+ if (!path58 || content == null) return null;
10866
+ const entry = { path: path58, content };
10778
10867
  if (typeof rec.mtimeMs === "number") entry.mtimeMs = rec.mtimeMs;
10779
10868
  return entry;
10780
10869
  }).filter((m2) => m2 !== null);
@@ -11605,10 +11694,10 @@ function parseAttachment(obj) {
11605
11694
  const memories = raw.map((m2) => {
11606
11695
  if (!m2 || typeof m2 !== "object") return null;
11607
11696
  const rec = m2;
11608
- const path53 = typeof rec.path === "string" ? rec.path : null;
11697
+ const path58 = typeof rec.path === "string" ? rec.path : null;
11609
11698
  const content = typeof rec.content === "string" ? rec.content : null;
11610
- if (!path53 || content == null) return null;
11611
- const out = { path: path53, content };
11699
+ if (!path58 || content == null) return null;
11700
+ const out = { path: path58, content };
11612
11701
  if (typeof rec.mtimeMs === "number") out.mtimeMs = rec.mtimeMs;
11613
11702
  return out;
11614
11703
  }).filter((m2) => m2 !== null);
@@ -23397,8 +23486,8 @@ var require_utils = __commonJS({
23397
23486
  var result = transform[inputType][outputType](input);
23398
23487
  return result;
23399
23488
  };
23400
- exports2.resolve = function(path53) {
23401
- var parts = path53.split("/");
23489
+ exports2.resolve = function(path58) {
23490
+ var parts = path58.split("/");
23402
23491
  var result = [];
23403
23492
  for (var index = 0; index < parts.length; index++) {
23404
23493
  var part = parts[index];
@@ -29251,18 +29340,18 @@ var require_object = __commonJS({
29251
29340
  var object = new ZipObject(name, zipObjectContent, o);
29252
29341
  this.files[name] = object;
29253
29342
  };
29254
- var parentFolder = function(path53) {
29255
- if (path53.slice(-1) === "/") {
29256
- path53 = path53.substring(0, path53.length - 1);
29343
+ var parentFolder = function(path58) {
29344
+ if (path58.slice(-1) === "/") {
29345
+ path58 = path58.substring(0, path58.length - 1);
29257
29346
  }
29258
- var lastSlash = path53.lastIndexOf("/");
29259
- return lastSlash > 0 ? path53.substring(0, lastSlash) : "";
29347
+ var lastSlash = path58.lastIndexOf("/");
29348
+ return lastSlash > 0 ? path58.substring(0, lastSlash) : "";
29260
29349
  };
29261
- var forceTrailingSlash = function(path53) {
29262
- if (path53.slice(-1) !== "/") {
29263
- path53 += "/";
29350
+ var forceTrailingSlash = function(path58) {
29351
+ if (path58.slice(-1) !== "/") {
29352
+ path58 += "/";
29264
29353
  }
29265
- return path53;
29354
+ return path58;
29266
29355
  };
29267
29356
  var folderAdd = function(name, createFolders) {
29268
29357
  createFolders = typeof createFolders !== "undefined" ? createFolders : defaults.createFolders;
@@ -30228,9 +30317,9 @@ var require_load = __commonJS({
30228
30317
  var require_lib3 = __commonJS({
30229
30318
  "../node_modules/.pnpm/jszip@3.10.1/node_modules/jszip/lib/index.js"(exports2, module2) {
30230
30319
  "use strict";
30231
- function JSZip2() {
30232
- if (!(this instanceof JSZip2)) {
30233
- return new JSZip2();
30320
+ function JSZip5() {
30321
+ if (!(this instanceof JSZip5)) {
30322
+ return new JSZip5();
30234
30323
  }
30235
30324
  if (arguments.length) {
30236
30325
  throw new Error("The constructor with parameters has been removed in JSZip 3.0, please check the upgrade guide.");
@@ -30239,7 +30328,7 @@ var require_lib3 = __commonJS({
30239
30328
  this.comment = null;
30240
30329
  this.root = "";
30241
30330
  this.clone = function() {
30242
- var newObj = new JSZip2();
30331
+ var newObj = new JSZip5();
30243
30332
  for (var i in this) {
30244
30333
  if (typeof this[i] !== "function") {
30245
30334
  newObj[i] = this[i];
@@ -30248,23 +30337,23 @@ var require_lib3 = __commonJS({
30248
30337
  return newObj;
30249
30338
  };
30250
30339
  }
30251
- JSZip2.prototype = require_object();
30252
- JSZip2.prototype.loadAsync = require_load();
30253
- JSZip2.support = require_support();
30254
- JSZip2.defaults = require_defaults();
30255
- JSZip2.version = "3.10.1";
30256
- JSZip2.loadAsync = function(content, options) {
30257
- return new JSZip2().loadAsync(content, options);
30340
+ JSZip5.prototype = require_object();
30341
+ JSZip5.prototype.loadAsync = require_load();
30342
+ JSZip5.support = require_support();
30343
+ JSZip5.defaults = require_defaults();
30344
+ JSZip5.version = "3.10.1";
30345
+ JSZip5.loadAsync = function(content, options) {
30346
+ return new JSZip5().loadAsync(content, options);
30258
30347
  };
30259
- JSZip2.external = require_external();
30260
- module2.exports = JSZip2;
30348
+ JSZip5.external = require_external();
30349
+ module2.exports = JSZip5;
30261
30350
  }
30262
30351
  });
30263
30352
 
30264
30353
  // src/run-case/recorder.ts
30265
30354
  function startRunCaseRecorder(opts) {
30266
30355
  const now = opts.now ?? Date.now;
30267
- const dir = import_node_path39.default.dirname(opts.recordPath);
30356
+ const dir = import_node_path44.default.dirname(opts.recordPath);
30268
30357
  let stream = null;
30269
30358
  let closing = false;
30270
30359
  let closedSettled = false;
@@ -30304,12 +30393,12 @@ function startRunCaseRecorder(opts) {
30304
30393
  };
30305
30394
  return { tap, close, closed };
30306
30395
  }
30307
- var import_node_fs30, import_node_path39;
30396
+ var import_node_fs30, import_node_path44;
30308
30397
  var init_recorder = __esm({
30309
30398
  "src/run-case/recorder.ts"() {
30310
30399
  "use strict";
30311
30400
  import_node_fs30 = __toESM(require("fs"), 1);
30312
- import_node_path39 = __toESM(require("path"), 1);
30401
+ import_node_path44 = __toESM(require("path"), 1);
30313
30402
  }
30314
30403
  });
30315
30404
 
@@ -30352,7 +30441,7 @@ var init_wire = __esm({
30352
30441
  // src/run-case/controller.ts
30353
30442
  async function runController(opts) {
30354
30443
  const now = opts.now ?? Date.now;
30355
- const cwd = opts.cwd ?? (0, import_node_fs31.mkdtempSync)(import_node_path40.default.join(import_node_os17.default.tmpdir(), "clawd-runcase-"));
30444
+ const cwd = opts.cwd ?? (0, import_node_fs31.mkdtempSync)(import_node_path45.default.join(import_node_os19.default.tmpdir(), "clawd-runcase-"));
30356
30445
  const ownsCwd = opts.cwd === void 0;
30357
30446
  const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
30358
30447
  const spawnCtx = { cwd };
@@ -30519,13 +30608,13 @@ async function runController(opts) {
30519
30608
  }
30520
30609
  return exitCode ?? 0;
30521
30610
  }
30522
- var import_node_fs31, import_node_os17, import_node_path40;
30611
+ var import_node_fs31, import_node_os19, import_node_path45;
30523
30612
  var init_controller = __esm({
30524
30613
  "src/run-case/controller.ts"() {
30525
30614
  "use strict";
30526
30615
  import_node_fs31 = require("fs");
30527
- import_node_os17 = __toESM(require("os"), 1);
30528
- import_node_path40 = __toESM(require("path"), 1);
30616
+ import_node_os19 = __toESM(require("os"), 1);
30617
+ import_node_path45 = __toESM(require("path"), 1);
30529
30618
  init_claude();
30530
30619
  init_stdout_splitter();
30531
30620
  init_permission_stdio();
@@ -30757,7 +30846,7 @@ Env (advanced):
30757
30846
  `;
30758
30847
 
30759
30848
  // src/index.ts
30760
- var import_node_path38 = __toESM(require("path"), 1);
30849
+ var import_node_path43 = __toESM(require("path"), 1);
30761
30850
  var import_node_fs29 = __toESM(require("fs"), 1);
30762
30851
 
30763
30852
  // src/logger.ts
@@ -35581,7 +35670,7 @@ function scanAgentsDir(dir, source, seen, out) {
35581
35670
  }
35582
35671
  }
35583
35672
  function scanPluginAgentsTree(root, pluginName, seen, out) {
35584
- function walk(dir, namespaces) {
35673
+ function walk2(dir, namespaces) {
35585
35674
  let entries;
35586
35675
  try {
35587
35676
  entries = import_node_fs12.default.readdirSync(dir, { withFileTypes: true });
@@ -35591,7 +35680,7 @@ function scanPluginAgentsTree(root, pluginName, seen, out) {
35591
35680
  for (const ent of entries) {
35592
35681
  const childPath = import_node_path13.default.join(dir, ent.name);
35593
35682
  if (ent.isDirectory() || ent.isSymbolicLink() && isDirLikeSync2(childPath)) {
35594
- walk(childPath, [...namespaces, ent.name]);
35683
+ walk2(childPath, [...namespaces, ent.name]);
35595
35684
  continue;
35596
35685
  }
35597
35686
  if (!ent.name.endsWith(".md")) continue;
@@ -35611,7 +35700,7 @@ function scanPluginAgentsTree(root, pluginName, seen, out) {
35611
35700
  });
35612
35701
  }
35613
35702
  }
35614
- walk(root, []);
35703
+ walk2(root, []);
35615
35704
  }
35616
35705
  function readInstalledPlugins2(home) {
35617
35706
  const pluginsDir = import_node_path13.default.join(home, ".claude", "plugins");
@@ -36350,7 +36439,8 @@ var LocalWsServer = class {
36350
36439
  if (verdict !== "pass") {
36351
36440
  if (!wasAuthed && authGate.isAuthed(client.id)) {
36352
36441
  try {
36353
- const full = this.opts.readyFrameBuilder({ remoteAddress });
36442
+ const principalKind = this.clients.get(client.id)?.ctx?.principal.kind;
36443
+ const full = this.opts.readyFrameBuilder({ remoteAddress, principalKind });
36354
36444
  this.safeSend(this.clients.get(client.id).ws, { type: "ready", ...full });
36355
36445
  } catch (err) {
36356
36446
  this.logger?.warn("post-auth ready frame build failed", { err: err.message });
@@ -40357,7 +40447,430 @@ function buildAttachmentHandlers(deps) {
40357
40447
  }
40358
40448
 
40359
40449
  // src/handlers/extension.ts
40450
+ var import_promises7 = __toESM(require("fs/promises"), 1);
40451
+ var import_node_path38 = __toESM(require("path"), 1);
40360
40452
  init_protocol();
40453
+
40454
+ // src/extension/bundle-zip.ts
40455
+ var import_promises4 = __toESM(require("fs/promises"), 1);
40456
+ var import_node_path34 = __toESM(require("path"), 1);
40457
+ var import_node_crypto11 = __toESM(require("crypto"), 1);
40458
+ var import_jszip2 = __toESM(require_lib3(), 1);
40459
+ async function bundleExtensionDir(dir) {
40460
+ const entries = await listFilesSorted(dir);
40461
+ const zip = new import_jszip2.default();
40462
+ for (const rel of entries) {
40463
+ const abs = import_node_path34.default.join(dir, rel);
40464
+ const content = await import_promises4.default.readFile(abs);
40465
+ zip.file(rel, content, { date: FIXED_DATE });
40466
+ }
40467
+ const buffer = await zip.generateAsync({
40468
+ type: "nodebuffer",
40469
+ compression: "DEFLATE",
40470
+ compressionOptions: { level: 6 }
40471
+ });
40472
+ const sha256 = import_node_crypto11.default.createHash("sha256").update(buffer).digest("hex");
40473
+ return { buffer, sha256 };
40474
+ }
40475
+ var FIXED_DATE = /* @__PURE__ */ new Date("2020-01-01T00:00:00.000Z");
40476
+ var IGNORE_BASENAMES = /* @__PURE__ */ new Set([".DS_Store"]);
40477
+ async function listFilesSorted(rootDir) {
40478
+ const out = [];
40479
+ await walk(rootDir, "", out);
40480
+ out.sort();
40481
+ return out;
40482
+ }
40483
+ async function walk(absRoot, relPrefix, out) {
40484
+ const dirAbs = import_node_path34.default.join(absRoot, relPrefix);
40485
+ const entries = await import_promises4.default.readdir(dirAbs, { withFileTypes: true });
40486
+ for (const e of entries) {
40487
+ if (IGNORE_BASENAMES.has(e.name)) continue;
40488
+ const rel = relPrefix ? `${relPrefix}/${e.name}` : e.name;
40489
+ if (e.isDirectory()) {
40490
+ await walk(absRoot, rel, out);
40491
+ } else if (e.isFile()) {
40492
+ out.push(rel);
40493
+ }
40494
+ }
40495
+ }
40496
+
40497
+ // src/extension/publish-check.ts
40498
+ function computePublishCheck(args) {
40499
+ const { localHash, localVersion, head } = args;
40500
+ if (head === null) {
40501
+ return {
40502
+ kind: "ready-new-version",
40503
+ fromVersion: "0.0.0",
40504
+ toVersion: localVersion
40505
+ };
40506
+ }
40507
+ const hashMatch = localHash === head.snapshotHash;
40508
+ if (hashMatch) {
40509
+ if (localVersion === head.version) {
40510
+ return { kind: "clean" };
40511
+ }
40512
+ return { kind: "error-same-hash", version: head.version };
40513
+ }
40514
+ const cmp = compareSemver(localVersion, head.version);
40515
+ if (cmp > 0) {
40516
+ return {
40517
+ kind: "ready-new-version",
40518
+ fromVersion: head.version,
40519
+ toVersion: localVersion
40520
+ };
40521
+ }
40522
+ if (cmp === 0) {
40523
+ return {
40524
+ kind: "error-version-not-bumped",
40525
+ localVersion,
40526
+ publishedVersion: head.version
40527
+ };
40528
+ }
40529
+ return {
40530
+ kind: "error-version-regression",
40531
+ localVersion,
40532
+ publishedVersion: head.version
40533
+ };
40534
+ }
40535
+ function compareSemver(a, b2) {
40536
+ const pa = parseSimpleSemver(a);
40537
+ const pb = parseSimpleSemver(b2);
40538
+ for (let i = 0; i < 3; i++) {
40539
+ if (pa[i] < pb[i]) return -1;
40540
+ if (pa[i] > pb[i]) return 1;
40541
+ }
40542
+ return 0;
40543
+ }
40544
+ function parseSimpleSemver(v2) {
40545
+ const parts = v2.split(".");
40546
+ if (parts.length !== 3) {
40547
+ throw new Error(`unsupported version "${v2}" \u2014 only MAJOR.MINOR.PATCH allowed`);
40548
+ }
40549
+ const nums = parts.map((p2) => {
40550
+ if (!/^\d+$/.test(p2)) {
40551
+ throw new Error(`unsupported version "${v2}" \u2014 only numeric segments allowed`);
40552
+ }
40553
+ return parseInt(p2, 10);
40554
+ });
40555
+ return nums;
40556
+ }
40557
+
40558
+ // src/extension/install-flow.ts
40559
+ var import_promises5 = __toESM(require("fs/promises"), 1);
40560
+ var import_node_path36 = __toESM(require("path"), 1);
40561
+ var import_node_os17 = __toESM(require("os"), 1);
40562
+ var import_node_crypto12 = __toESM(require("crypto"), 1);
40563
+ var import_jszip3 = __toESM(require_lib3(), 1);
40564
+
40565
+ // src/extension/paths.ts
40566
+ var import_node_os16 = __toESM(require("os"), 1);
40567
+ var import_node_path35 = __toESM(require("path"), 1);
40568
+ function clawdHomeRoot(override) {
40569
+ return override ?? process.env.CLAWD_HOME ?? import_node_path35.default.join(import_node_os16.default.homedir(), ".clawd");
40570
+ }
40571
+ function extensionsRoot(override) {
40572
+ return import_node_path35.default.join(clawdHomeRoot(override), "extensions");
40573
+ }
40574
+ function publishedChannelsFile(override) {
40575
+ return import_node_path35.default.join(clawdHomeRoot(override), "extensions-published.json");
40576
+ }
40577
+
40578
+ // src/extension/install-flow.ts
40579
+ var InstallError = class extends Error {
40580
+ constructor(code, message) {
40581
+ super(message);
40582
+ this.code = code;
40583
+ }
40584
+ code;
40585
+ };
40586
+ async function installFromChannel(args, deps) {
40587
+ const { channelRef, snapshotHash, bundleZip } = args;
40588
+ const computed = import_node_crypto12.default.createHash("sha256").update(bundleZip).digest("hex");
40589
+ if (computed !== snapshotHash) {
40590
+ throw new InstallError(
40591
+ "HASH_MISMATCH",
40592
+ `expected ${snapshotHash.slice(0, 8)}\u2026 but got ${computed.slice(0, 8)}\u2026`
40593
+ );
40594
+ }
40595
+ let zip;
40596
+ try {
40597
+ zip = await import_jszip3.default.loadAsync(bundleZip);
40598
+ } catch (e) {
40599
+ throw new InstallError("ZIP_INVALID", `failed to load zip: ${e.message}`);
40600
+ }
40601
+ for (const name of Object.keys(zip.files)) {
40602
+ if (name.includes("..") || name.startsWith("/") || import_node_path36.default.isAbsolute(name)) {
40603
+ throw new InstallError("ZIP_INVALID", `unsafe zip entry: ${name}`);
40604
+ }
40605
+ }
40606
+ const manifestEntry = zip.file("manifest.json");
40607
+ if (!manifestEntry) {
40608
+ throw new InstallError("MANIFEST_NOT_FOUND", "manifest.json missing from bundle");
40609
+ }
40610
+ let manifestJson;
40611
+ try {
40612
+ manifestJson = JSON.parse(await manifestEntry.async("string"));
40613
+ } catch (e) {
40614
+ throw new InstallError(
40615
+ "INVALID_MANIFEST",
40616
+ `manifest.json parse: ${e.message}`
40617
+ );
40618
+ }
40619
+ const parsed = ExtensionManifestSchema.safeParse(manifestJson);
40620
+ if (!parsed.success) {
40621
+ throw new InstallError(
40622
+ "INVALID_MANIFEST",
40623
+ parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")
40624
+ );
40625
+ }
40626
+ const ownerSlug = parsed.data.id;
40627
+ if (ownerSlug !== channelRef.extId) {
40628
+ throw new InstallError(
40629
+ "SLUG_MISMATCH",
40630
+ `bundle manifest.id "${ownerSlug}" does not match channelRef.extId "${channelRef.extId}"`
40631
+ );
40632
+ }
40633
+ const localExtId = namespacedExtId(ownerSlug, channelRef.ownerPrincipalId);
40634
+ const destDir = import_node_path36.default.join(deps.extensionsRoot, localExtId);
40635
+ let destExists = false;
40636
+ try {
40637
+ await import_promises5.default.access(destDir);
40638
+ destExists = true;
40639
+ } catch {
40640
+ }
40641
+ if (destExists) {
40642
+ throw new InstallError(
40643
+ "ALREADY_EXISTS",
40644
+ `extension ${localExtId} already installed locally (from owner ${channelRef.ownerPrincipalId})`
40645
+ );
40646
+ }
40647
+ const stage = await import_promises5.default.mkdtemp(
40648
+ import_node_path36.default.join(import_node_os17.default.tmpdir(), `clawd-ext-install-${localExtId}-`)
40649
+ );
40650
+ try {
40651
+ for (const [name, entry] of Object.entries(zip.files)) {
40652
+ const dest = import_node_path36.default.join(stage, name);
40653
+ if (entry.dir) {
40654
+ await import_promises5.default.mkdir(dest, { recursive: true });
40655
+ continue;
40656
+ }
40657
+ await import_promises5.default.mkdir(import_node_path36.default.dirname(dest), { recursive: true });
40658
+ if (name === "manifest.json") {
40659
+ const rewritten = { ...parsed.data, id: localExtId };
40660
+ await import_promises5.default.writeFile(dest, JSON.stringify(rewritten, null, 2));
40661
+ } else {
40662
+ await import_promises5.default.writeFile(dest, await entry.async("nodebuffer"));
40663
+ }
40664
+ }
40665
+ await import_promises5.default.mkdir(deps.extensionsRoot, { recursive: true });
40666
+ await import_promises5.default.rename(stage, destDir);
40667
+ } catch (e) {
40668
+ await import_promises5.default.rm(stage, { recursive: true, force: true }).catch(() => {
40669
+ });
40670
+ throw e;
40671
+ }
40672
+ await deps.runtime.open(localExtId);
40673
+ return { extId: localExtId, installedVersion: parsed.data.version };
40674
+ }
40675
+
40676
+ // src/extension/update-flow.ts
40677
+ var import_promises6 = __toESM(require("fs/promises"), 1);
40678
+ var import_node_path37 = __toESM(require("path"), 1);
40679
+ var import_node_os18 = __toESM(require("os"), 1);
40680
+ var import_node_crypto13 = __toESM(require("crypto"), 1);
40681
+ var import_jszip4 = __toESM(require_lib3(), 1);
40682
+ var UpdateError = class extends Error {
40683
+ constructor(code, message) {
40684
+ super(message);
40685
+ this.code = code;
40686
+ }
40687
+ code;
40688
+ };
40689
+ async function updateFromChannel(args, deps) {
40690
+ const { channelRef, snapshotHash, bundleZip } = args;
40691
+ const localExtId = namespacedExtId(
40692
+ channelRef.extId,
40693
+ channelRef.ownerPrincipalId
40694
+ );
40695
+ const liveDir = import_node_path37.default.join(deps.extensionsRoot, localExtId);
40696
+ const prevDir = `${liveDir}.prev`;
40697
+ let existingVersion;
40698
+ try {
40699
+ const raw = await import_promises6.default.readFile(import_node_path37.default.join(liveDir, "manifest.json"), "utf8");
40700
+ const parsed2 = ExtensionManifestSchema.safeParse(JSON.parse(raw));
40701
+ if (!parsed2.success) {
40702
+ throw new UpdateError(
40703
+ "INVALID_MANIFEST",
40704
+ `existing manifest invalid: ${parsed2.error.issues.map((i) => i.message).join("; ")}`
40705
+ );
40706
+ }
40707
+ existingVersion = parsed2.data.version;
40708
+ } catch (e) {
40709
+ if (e.code === "ENOENT") {
40710
+ throw new UpdateError(
40711
+ "NOT_INSTALLED",
40712
+ `${localExtId} is not installed locally`
40713
+ );
40714
+ }
40715
+ if (e instanceof UpdateError) throw e;
40716
+ throw e;
40717
+ }
40718
+ const computed = import_node_crypto13.default.createHash("sha256").update(bundleZip).digest("hex");
40719
+ if (computed !== snapshotHash) {
40720
+ throw new UpdateError(
40721
+ "HASH_MISMATCH",
40722
+ `expected ${snapshotHash.slice(0, 8)}\u2026 but got ${computed.slice(0, 8)}\u2026`
40723
+ );
40724
+ }
40725
+ let zip;
40726
+ try {
40727
+ zip = await import_jszip4.default.loadAsync(bundleZip);
40728
+ } catch (e) {
40729
+ throw new UpdateError("ZIP_INVALID", `failed to load zip: ${e.message}`);
40730
+ }
40731
+ for (const name of Object.keys(zip.files)) {
40732
+ if (name.includes("..") || name.startsWith("/") || import_node_path37.default.isAbsolute(name)) {
40733
+ throw new UpdateError("ZIP_INVALID", `unsafe zip entry: ${name}`);
40734
+ }
40735
+ }
40736
+ const manifestEntry = zip.file("manifest.json");
40737
+ if (!manifestEntry) {
40738
+ throw new UpdateError("MANIFEST_NOT_FOUND", "manifest.json missing from bundle");
40739
+ }
40740
+ let manifestJson;
40741
+ try {
40742
+ manifestJson = JSON.parse(await manifestEntry.async("string"));
40743
+ } catch (e) {
40744
+ throw new UpdateError(
40745
+ "INVALID_MANIFEST",
40746
+ `manifest.json parse: ${e.message}`
40747
+ );
40748
+ }
40749
+ const parsed = ExtensionManifestSchema.safeParse(manifestJson);
40750
+ if (!parsed.success) {
40751
+ throw new UpdateError(
40752
+ "INVALID_MANIFEST",
40753
+ parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")
40754
+ );
40755
+ }
40756
+ const ownerSlug = parsed.data.id;
40757
+ if (ownerSlug !== channelRef.extId) {
40758
+ throw new UpdateError(
40759
+ "SLUG_MISMATCH",
40760
+ `bundle manifest.id "${ownerSlug}" does not match channelRef.extId "${channelRef.extId}"`
40761
+ );
40762
+ }
40763
+ await deps.runtime.close(localExtId);
40764
+ await import_promises6.default.rm(prevDir, { recursive: true, force: true });
40765
+ await import_promises6.default.rename(liveDir, prevDir);
40766
+ const stage = await import_promises6.default.mkdtemp(
40767
+ import_node_path37.default.join(import_node_os18.default.tmpdir(), `clawd-ext-update-${localExtId}-`)
40768
+ );
40769
+ try {
40770
+ for (const [name, entry] of Object.entries(zip.files)) {
40771
+ const dest = import_node_path37.default.join(stage, name);
40772
+ if (entry.dir) {
40773
+ await import_promises6.default.mkdir(dest, { recursive: true });
40774
+ continue;
40775
+ }
40776
+ await import_promises6.default.mkdir(import_node_path37.default.dirname(dest), { recursive: true });
40777
+ if (name === "manifest.json") {
40778
+ const rewritten = { ...parsed.data, id: localExtId };
40779
+ await import_promises6.default.writeFile(dest, JSON.stringify(rewritten, null, 2));
40780
+ } else {
40781
+ await import_promises6.default.writeFile(dest, await entry.async("nodebuffer"));
40782
+ }
40783
+ }
40784
+ await import_promises6.default.rename(stage, liveDir);
40785
+ } catch (e) {
40786
+ await import_promises6.default.rm(stage, { recursive: true, force: true }).catch(() => {
40787
+ });
40788
+ await import_promises6.default.rename(prevDir, liveDir).catch(() => {
40789
+ });
40790
+ await deps.runtime.open(localExtId).catch(() => {
40791
+ });
40792
+ return {
40793
+ kind: "rolled-back",
40794
+ extId: localExtId,
40795
+ rolledBackTo: existingVersion,
40796
+ reason: `extract failed: ${e.message}`
40797
+ };
40798
+ }
40799
+ try {
40800
+ await deps.runtime.open(localExtId);
40801
+ } catch (e) {
40802
+ await rollback(deps, localExtId, liveDir, prevDir);
40803
+ return {
40804
+ kind: "rolled-back",
40805
+ extId: localExtId,
40806
+ rolledBackTo: existingVersion,
40807
+ reason: e.message
40808
+ };
40809
+ }
40810
+ await import_promises6.default.rm(prevDir, { recursive: true, force: true });
40811
+ return {
40812
+ kind: "updated",
40813
+ extId: localExtId,
40814
+ installedVersion: parsed.data.version
40815
+ };
40816
+ }
40817
+ async function rollback(deps, localExtId, liveDir, prevDir) {
40818
+ await import_promises6.default.rm(liveDir, { recursive: true, force: true }).catch(() => {
40819
+ });
40820
+ await import_promises6.default.rename(prevDir, liveDir).catch(() => {
40821
+ });
40822
+ await deps.runtime.open(localExtId).catch(() => {
40823
+ });
40824
+ }
40825
+
40826
+ // src/handlers/extension.ts
40827
+ function pickChannelRef(frame) {
40828
+ const ref = frame.channelRef;
40829
+ const parsed = ChannelRefSchema.safeParse(ref);
40830
+ if (!parsed.success) {
40831
+ throw new ClawdError(
40832
+ ERROR_CODES.INVALID_PARAM,
40833
+ `channelRef invalid: ${parsed.error.issues.map((i) => i.message).join("; ")}`
40834
+ );
40835
+ }
40836
+ return parsed.data;
40837
+ }
40838
+ function pickStringField(frame, name) {
40839
+ const v2 = frame[name];
40840
+ if (typeof v2 !== "string" || v2.length === 0) {
40841
+ throw new ClawdError(ERROR_CODES.INVALID_PARAM, `${name} required`);
40842
+ }
40843
+ return v2;
40844
+ }
40845
+ async function readManifest(root, extId) {
40846
+ const file = import_node_path38.default.join(root, extId, "manifest.json");
40847
+ let raw;
40848
+ try {
40849
+ raw = await import_promises7.default.readFile(file, "utf8");
40850
+ } catch (e) {
40851
+ if (e.code === "ENOENT") {
40852
+ throw new ClawdError(ERROR_CODES.INVALID_PARAM, `extension ${extId} not installed`);
40853
+ }
40854
+ throw e;
40855
+ }
40856
+ let json;
40857
+ try {
40858
+ json = JSON.parse(raw);
40859
+ } catch (e) {
40860
+ throw new ClawdError(
40861
+ ERROR_CODES.INVALID_PARAM,
40862
+ `manifest.json invalid: ${e.message}`
40863
+ );
40864
+ }
40865
+ const parsed = ExtensionManifestSchema.safeParse(json);
40866
+ if (!parsed.success) {
40867
+ throw new ClawdError(
40868
+ ERROR_CODES.INVALID_PARAM,
40869
+ `manifest schema mismatch: ${parsed.error.issues.map((i) => i.message).join("; ")}`
40870
+ );
40871
+ }
40872
+ return parsed.data;
40873
+ }
40361
40874
  function assertOwner(ctx) {
40362
40875
  if (!ctx || ctx.principal.kind !== "owner") {
40363
40876
  throw new ClawdError(ERROR_CODES.FORBIDDEN, "owner only");
@@ -40412,21 +40925,183 @@ function buildExtensionHandlers(deps) {
40412
40925
  await deps.uninstall({ root: deps.root, extId, runtime: deps.runtime });
40413
40926
  return { response: { ok: true } };
40414
40927
  };
40928
+ async function buildSnapshotMeta(extId) {
40929
+ const manifest = await readManifest(deps.root, extId);
40930
+ const { sha256 } = await bundleExtensionDir(import_node_path38.default.join(deps.root, extId));
40931
+ return { manifest, contentHash: sha256 };
40932
+ }
40933
+ const publish = async (frame, _client, ctx) => {
40934
+ assertOwner(ctx);
40935
+ const extId = pickExtId(frame);
40936
+ const { manifest, contentHash } = await buildSnapshotMeta(extId);
40937
+ const head = deps.publishedChannelStore.get(extId);
40938
+ const check = computePublishCheck({
40939
+ localHash: contentHash,
40940
+ localVersion: manifest.version,
40941
+ head
40942
+ });
40943
+ if (check.kind === "clean" && head) {
40944
+ return { response: { snapshotHash: head.snapshotHash, version: head.version } };
40945
+ }
40946
+ if (check.kind === "error-same-hash" && head) {
40947
+ return { response: { snapshotHash: head.snapshotHash, version: head.version } };
40948
+ }
40949
+ if (check.kind === "error-version-not-bumped") {
40950
+ throw new ClawdError(
40951
+ ERROR_CODES.VALIDATION_ERROR,
40952
+ `VERSION_NOT_BUMPED: manifest.version (${check.localVersion}) needs to be > ${check.publishedVersion}`
40953
+ );
40954
+ }
40955
+ if (check.kind === "error-version-regression") {
40956
+ throw new ClawdError(
40957
+ ERROR_CODES.VALIDATION_ERROR,
40958
+ `VERSION_REGRESSION: manifest.version (${check.localVersion}) is lower than published version (${check.publishedVersion})`
40959
+ );
40960
+ }
40961
+ const snapshot = {
40962
+ snapshotHash: contentHash,
40963
+ version: manifest.version,
40964
+ publishedAt: Date.now()
40965
+ };
40966
+ await deps.publishedChannelStore.set(extId, snapshot);
40967
+ return {
40968
+ response: { snapshotHash: snapshot.snapshotHash, version: snapshot.version }
40969
+ };
40970
+ };
40971
+ const unpublish = async (frame, _client, ctx) => {
40972
+ assertOwner(ctx);
40973
+ const extId = pickExtId(frame);
40974
+ await deps.publishedChannelStore.delete(extId);
40975
+ return { response: {} };
40976
+ };
40977
+ const publishStatus = async (frame, _client, ctx) => {
40978
+ assertOwner(ctx);
40979
+ const extId = pickExtId(frame);
40980
+ const { manifest, contentHash } = await buildSnapshotMeta(extId);
40981
+ const head = deps.publishedChannelStore.get(extId);
40982
+ const check = computePublishCheck({
40983
+ localHash: contentHash,
40984
+ localVersion: manifest.version,
40985
+ head
40986
+ });
40987
+ const result = {
40988
+ localManifest: {
40989
+ name: manifest.name,
40990
+ version: manifest.version,
40991
+ contentHash
40992
+ },
40993
+ publishState: head ? {
40994
+ kind: "published",
40995
+ version: head.version,
40996
+ contentHash: head.snapshotHash,
40997
+ publishedAt: head.publishedAt
40998
+ } : { kind: "unpublished" },
40999
+ check
41000
+ };
41001
+ return { response: result };
41002
+ };
41003
+ const listPublished = async (_frame, _client, ctx) => {
41004
+ if (!ctx || ctx.principal.kind !== "guest") {
41005
+ throw new ClawdError(ERROR_CODES.FORBIDDEN, "guest only");
41006
+ }
41007
+ const heads = deps.publishedChannelStore.list();
41008
+ const out = [];
41009
+ for (const { extId, head } of heads) {
41010
+ try {
41011
+ const manifest = await readManifest(deps.root, extId);
41012
+ out.push({
41013
+ extId,
41014
+ name: manifest.name,
41015
+ version: head.version,
41016
+ contentHash: head.snapshotHash,
41017
+ publishedAt: head.publishedAt
41018
+ });
41019
+ } catch {
41020
+ }
41021
+ }
41022
+ return { response: { extensions: out } };
41023
+ };
41024
+ const fetchBundle = async (frame, _client, ctx) => {
41025
+ if (!ctx || ctx.principal.kind !== "guest") {
41026
+ throw new ClawdError(ERROR_CODES.FORBIDDEN, "guest only");
41027
+ }
41028
+ const extId = pickExtId(frame);
41029
+ const expectedHash = pickStringField(frame, "snapshotHash");
41030
+ const { buffer, sha256 } = await bundleExtensionDir(import_node_path38.default.join(deps.root, extId));
41031
+ if (sha256 !== expectedHash) {
41032
+ throw new ClawdError(
41033
+ ERROR_CODES.VALIDATION_ERROR,
41034
+ `HASH_MISMATCH: bundle hash ${sha256.slice(0, 8)}\u2026 \u2260 requested ${expectedHash.slice(0, 8)}\u2026`
41035
+ );
41036
+ }
41037
+ return { response: { zipBase64: buffer.toString("base64") } };
41038
+ };
41039
+ const installFromChannelHandler = async (frame, _client, ctx) => {
41040
+ assertOwner(ctx);
41041
+ const channelRef = pickChannelRef(frame);
41042
+ const snapshotHash = pickStringField(frame, "snapshotHash");
41043
+ const zipBase64 = pickStringField(frame, "zipBase64");
41044
+ const bundleZip = Buffer.from(zipBase64, "base64");
41045
+ try {
41046
+ const { extId, installedVersion } = await installFromChannel(
41047
+ { channelRef, snapshotHash, bundleZip },
41048
+ {
41049
+ extensionsRoot: deps.root,
41050
+ runtime: deps.runtime
41051
+ }
41052
+ );
41053
+ return { response: { extId, installedVersion } };
41054
+ } catch (e) {
41055
+ if (e instanceof InstallError) {
41056
+ throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, `${e.code}: ${e.message}`);
41057
+ }
41058
+ throw e;
41059
+ }
41060
+ };
41061
+ const updateFromChannelHandler = async (frame, _client, ctx) => {
41062
+ assertOwner(ctx);
41063
+ const channelRef = pickChannelRef(frame);
41064
+ const snapshotHash = pickStringField(frame, "snapshotHash");
41065
+ const zipBase64 = pickStringField(frame, "zipBase64");
41066
+ const bundleZip = Buffer.from(zipBase64, "base64");
41067
+ try {
41068
+ const result = await updateFromChannel(
41069
+ { channelRef, snapshotHash, bundleZip },
41070
+ {
41071
+ extensionsRoot: deps.root,
41072
+ runtime: deps.runtime
41073
+ }
41074
+ );
41075
+ return { response: result };
41076
+ } catch (e) {
41077
+ if (e instanceof UpdateError) {
41078
+ throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, `${e.code}: ${e.message}`);
41079
+ }
41080
+ throw e;
41081
+ }
41082
+ };
40415
41083
  return {
40416
41084
  "extension.list": list,
40417
41085
  "extension.open": open,
40418
41086
  "extension.close": close,
40419
- "extension.uninstall": uninstall2
41087
+ "extension.uninstall": uninstall2,
41088
+ "extension.publish": publish,
41089
+ "extension.unpublish": unpublish,
41090
+ "extension.publish:status": publishStatus,
41091
+ "extension.list-published": listPublished,
41092
+ "extension.fetchBundle": fetchBundle,
41093
+ "extension.install-from-channel": installFromChannelHandler,
41094
+ "extension.update-from-channel": updateFromChannelHandler
40420
41095
  };
40421
41096
  }
40422
41097
 
40423
41098
  // src/extension/registry.ts
40424
- var import_promises4 = __toESM(require("fs/promises"), 1);
40425
- var import_node_path34 = __toESM(require("path"), 1);
41099
+ var import_promises8 = __toESM(require("fs/promises"), 1);
41100
+ var import_node_path39 = __toESM(require("path"), 1);
40426
41101
  async function loadAll(root) {
40427
41102
  let entries;
40428
41103
  try {
40429
- entries = await import_promises4.default.readdir(root, { withFileTypes: true });
41104
+ entries = await import_promises8.default.readdir(root, { withFileTypes: true });
40430
41105
  } catch (e) {
40431
41106
  if (e.code === "ENOENT") return [];
40432
41107
  throw e;
@@ -40435,16 +41110,16 @@ async function loadAll(root) {
40435
41110
  for (const ent of entries) {
40436
41111
  if (!ent.isDirectory()) continue;
40437
41112
  if (ent.name.startsWith(".")) continue;
40438
- records.push(await loadOne(import_node_path34.default.join(root, ent.name), ent.name));
41113
+ records.push(await loadOne(import_node_path39.default.join(root, ent.name), ent.name));
40439
41114
  }
40440
41115
  records.sort((a, b2) => a.extId < b2.extId ? -1 : a.extId > b2.extId ? 1 : 0);
40441
41116
  return records;
40442
41117
  }
40443
41118
  async function loadOne(dir, dirName) {
40444
- const manifestPath = import_node_path34.default.join(dir, "manifest.json");
41119
+ const manifestPath = import_node_path39.default.join(dir, "manifest.json");
40445
41120
  let raw;
40446
41121
  try {
40447
- raw = await import_promises4.default.readFile(manifestPath, "utf8");
41122
+ raw = await import_promises8.default.readFile(manifestPath, "utf8");
40448
41123
  } catch {
40449
41124
  return {
40450
41125
  extId: dirName,
@@ -40485,8 +41160,8 @@ async function loadOne(dir, dirName) {
40485
41160
  }
40486
41161
 
40487
41162
  // src/extension/uninstall.ts
40488
- var import_promises5 = __toESM(require("fs/promises"), 1);
40489
- var import_node_path35 = __toESM(require("path"), 1);
41163
+ var import_promises9 = __toESM(require("fs/promises"), 1);
41164
+ var import_node_path40 = __toESM(require("path"), 1);
40490
41165
  var UninstallError = class extends Error {
40491
41166
  constructor(code, message) {
40492
41167
  super(message);
@@ -40495,22 +41170,14 @@ var UninstallError = class extends Error {
40495
41170
  code;
40496
41171
  };
40497
41172
  async function uninstall(deps) {
40498
- const dir = import_node_path35.default.join(deps.root, deps.extId);
41173
+ const dir = import_node_path40.default.join(deps.root, deps.extId);
40499
41174
  try {
40500
- await import_promises5.default.access(dir);
41175
+ await import_promises9.default.access(dir);
40501
41176
  } catch {
40502
41177
  throw new UninstallError("NOT_FOUND", `extension ${deps.extId} not installed`);
40503
41178
  }
40504
41179
  await deps.runtime.close(deps.extId);
40505
- await import_promises5.default.rm(dir, { recursive: true, force: true });
40506
- }
40507
-
40508
- // src/extension/paths.ts
40509
- var import_node_os16 = __toESM(require("os"), 1);
40510
- var import_node_path36 = __toESM(require("path"), 1);
40511
- function extensionsRoot(override) {
40512
- const home = override ?? process.env.CLAWD_HOME ?? import_node_path36.default.join(import_node_os16.default.homedir(), ".clawd");
40513
- return import_node_path36.default.join(home, "extensions");
41180
+ await import_promises9.default.rm(dir, { recursive: true, force: true });
40514
41181
  }
40515
41182
 
40516
41183
  // src/handlers/index.ts
@@ -40554,7 +41221,8 @@ function buildMethodHandlers(deps) {
40554
41221
  loadAll,
40555
41222
  runtime: deps.extensionRuntime,
40556
41223
  uninstall,
40557
- root: extensionsRoot()
41224
+ root: extensionsRoot(),
41225
+ publishedChannelStore: deps.publishedChannelStore
40558
41226
  })
40559
41227
  };
40560
41228
  }
@@ -40677,7 +41345,18 @@ var METHOD_GRANT_MAP = {
40677
41345
  "extension.list": ADMIN_ANY,
40678
41346
  "extension.open": ADMIN_ANY,
40679
41347
  "extension.close": ADMIN_ANY,
40680
- "extension.uninstall": ADMIN_ANY
41348
+ "extension.uninstall": ADMIN_ANY,
41349
+ // ---- extension sharing v1(spec 2026-05-28-clawd-extension-sharing-design) ----
41350
+ // owner-side:owner 自己管理 publish 状态
41351
+ "extension.publish": ADMIN_ANY,
41352
+ "extension.unpublish": ADMIN_ANY,
41353
+ "extension.publish:status": ADMIN_ANY,
41354
+ // cross-tunnel:guest 从 owner 拉 published list + bundle
41355
+ "extension.list-published": CAPABILITY_SCOPED,
41356
+ "extension.fetchBundle": CAPABILITY_SCOPED,
41357
+ // guest-self:guest 在自己 daemon 上触发安装与更新(无持久化 subscription 概念)
41358
+ "extension.install-from-channel": ADMIN_ANY,
41359
+ "extension.update-from-channel": ADMIN_ANY
40681
41360
  };
40682
41361
  function computeGrantForFrame(method, frame) {
40683
41362
  const rule = METHOD_GRANT_MAP[method];
@@ -40694,8 +41373,8 @@ function computeGrantForFrame(method, frame) {
40694
41373
 
40695
41374
  // src/extension/runtime.ts
40696
41375
  var import_node_child_process7 = require("child_process");
40697
- var import_node_path37 = __toESM(require("path"), 1);
40698
- var import_promises6 = require("timers/promises");
41376
+ var import_node_path41 = __toESM(require("path"), 1);
41377
+ var import_promises10 = require("timers/promises");
40699
41378
 
40700
41379
  // src/extension/port-allocator.ts
40701
41380
  var import_node_net = __toESM(require("net"), 1);
@@ -40791,7 +41470,7 @@ var Runtime = class {
40791
41470
  /\$CLAWOS_EXT_PORT/g,
40792
41471
  String(port)
40793
41472
  );
40794
- const dir = import_node_path37.default.join(this.root, extId);
41473
+ const dir = import_node_path41.default.join(this.root, extId);
40795
41474
  const env = {
40796
41475
  ...process.env,
40797
41476
  CLAWOS_EXT_PORT: String(port),
@@ -40867,7 +41546,7 @@ ${handle.stderrTail}`
40867
41546
  if (res.ok) return;
40868
41547
  } catch {
40869
41548
  }
40870
- await (0, import_promises6.setTimeout)(this.healthzIntervalMs);
41549
+ await (0, import_promises10.setTimeout)(this.healthzIntervalMs);
40871
41550
  }
40872
41551
  throw new RuntimeError(
40873
41552
  "HEALTHZ_FAILED",
@@ -40892,7 +41571,7 @@ ${handle.stderrTail}`
40892
41571
  };
40893
41572
  killGroup("SIGTERM");
40894
41573
  const exited = new Promise((resolve6) => child.once("exit", () => resolve6()));
40895
- const timed = (0, import_promises6.setTimeout)(5e3).then(() => "timeout");
41574
+ const timed = (0, import_promises10.setTimeout)(5e3).then(() => "timeout");
40896
41575
  const winner = await Promise.race([exited.then(() => "exited"), timed]);
40897
41576
  if (winner === "timeout" && child.exitCode === null) {
40898
41577
  killGroup("SIGKILL");
@@ -40901,11 +41580,118 @@ ${handle.stderrTail}`
40901
41580
  }
40902
41581
  };
40903
41582
 
41583
+ // src/extension/published-channels.ts
41584
+ var import_promises11 = __toESM(require("fs/promises"), 1);
41585
+ var import_node_path42 = __toESM(require("path"), 1);
41586
+ init_zod();
41587
+ var PublishedChannelsError = class extends Error {
41588
+ constructor(code, message) {
41589
+ super(message);
41590
+ this.code = code;
41591
+ }
41592
+ code;
41593
+ };
41594
+ var FileSchema = external_exports.object({
41595
+ version: external_exports.literal(1),
41596
+ channels: external_exports.record(
41597
+ external_exports.string(),
41598
+ external_exports.object({
41599
+ extId: external_exports.string().min(1),
41600
+ head: PublishedSnapshotSchema.nullable()
41601
+ })
41602
+ )
41603
+ });
41604
+ var PublishedChannelStore = class {
41605
+ constructor(filePath) {
41606
+ this.filePath = filePath;
41607
+ }
41608
+ filePath;
41609
+ channels = /* @__PURE__ */ new Map();
41610
+ loaded = false;
41611
+ async load() {
41612
+ this.channels.clear();
41613
+ let raw;
41614
+ try {
41615
+ raw = await import_promises11.default.readFile(this.filePath, "utf8");
41616
+ } catch (e) {
41617
+ if (e.code === "ENOENT") {
41618
+ this.loaded = true;
41619
+ return;
41620
+ }
41621
+ throw e;
41622
+ }
41623
+ let json;
41624
+ try {
41625
+ json = JSON.parse(raw);
41626
+ } catch (e) {
41627
+ throw new PublishedChannelsError(
41628
+ "CORRUPTED_PUBLISHED_CHANNELS",
41629
+ `failed to parse ${this.filePath}: ${e.message}`
41630
+ );
41631
+ }
41632
+ const parsed = FileSchema.safeParse(json);
41633
+ if (!parsed.success) {
41634
+ throw new PublishedChannelsError(
41635
+ "CORRUPTED_PUBLISHED_CHANNELS",
41636
+ `schema mismatch in ${this.filePath}: ${parsed.error.issues.map((i) => i.message).join("; ")}`
41637
+ );
41638
+ }
41639
+ for (const [extId, entry] of Object.entries(parsed.data.channels)) {
41640
+ this.channels.set(extId, entry.head);
41641
+ }
41642
+ this.loaded = true;
41643
+ }
41644
+ /** Returns the head snapshot, or null when unpublished / unknown extId. */
41645
+ get(extId) {
41646
+ this.assertLoaded();
41647
+ return this.channels.get(extId) ?? null;
41648
+ }
41649
+ /** Returns all extIds with a head (i.e. currently published). */
41650
+ list() {
41651
+ this.assertLoaded();
41652
+ const out = [];
41653
+ for (const [extId, head] of this.channels.entries()) {
41654
+ if (head) out.push({ extId, head });
41655
+ }
41656
+ return out;
41657
+ }
41658
+ async set(extId, head) {
41659
+ this.assertLoaded();
41660
+ this.channels.set(extId, head);
41661
+ await this.save();
41662
+ }
41663
+ async delete(extId) {
41664
+ this.assertLoaded();
41665
+ this.channels.delete(extId);
41666
+ await this.save();
41667
+ }
41668
+ assertLoaded() {
41669
+ if (!this.loaded) {
41670
+ throw new Error("PublishedChannelStore not loaded \u2014 call load() first");
41671
+ }
41672
+ }
41673
+ async save() {
41674
+ const data = {
41675
+ version: 1,
41676
+ channels: Object.fromEntries(
41677
+ Array.from(this.channels.entries()).map(([extId, head]) => [
41678
+ extId,
41679
+ { extId, head }
41680
+ ])
41681
+ )
41682
+ };
41683
+ const tmp = `${this.filePath}.tmp`;
41684
+ await import_promises11.default.mkdir(import_node_path42.default.dirname(this.filePath), { recursive: true });
41685
+ await import_promises11.default.writeFile(tmp, JSON.stringify(data, null, 2), { mode: 384 });
41686
+ await import_promises11.default.rename(tmp, this.filePath);
41687
+ }
41688
+ };
41689
+
40904
41690
  // src/index.ts
40905
41691
  async function startDaemon(config) {
40906
41692
  const logger = createLogger({
40907
41693
  level: config.logLevel,
40908
- file: import_node_path38.default.join(config.dataDir, "clawd.log")
41694
+ file: import_node_path43.default.join(config.dataDir, "clawd.log")
40909
41695
  });
40910
41696
  logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
40911
41697
  const stateMgr = new StateFileManager({ dataDir: config.dataDir });
@@ -40992,7 +41778,7 @@ async function startDaemon(config) {
40992
41778
  const agents = new AgentsScanner();
40993
41779
  const history = new ClaudeHistoryReader();
40994
41780
  let transport = null;
40995
- const personaStore = new PersonaStore(import_node_path38.default.join(config.dataDir, "personas"));
41781
+ const personaStore = new PersonaStore(import_node_path43.default.join(config.dataDir, "personas"));
40996
41782
  const defaultsRoot = findDefaultsRoot();
40997
41783
  if (defaultsRoot) {
40998
41784
  seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
@@ -41009,7 +41795,7 @@ async function startDaemon(config) {
41009
41795
  getAdapter,
41010
41796
  historyReader: history,
41011
41797
  dataDir: config.dataDir,
41012
- personaRoot: import_node_path38.default.join(config.dataDir, "personas"),
41798
+ personaRoot: import_node_path43.default.join(config.dataDir, "personas"),
41013
41799
  personaStore,
41014
41800
  ownerDisplayName,
41015
41801
  ownerPrincipalId,
@@ -41033,7 +41819,7 @@ async function startDaemon(config) {
41033
41819
  // 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
41034
41820
  attachmentGroup: {
41035
41821
  onFileEdit: (input) => {
41036
- const absPath = import_node_path38.default.isAbsolute(input.relPath) ? input.relPath : import_node_path38.default.join(input.cwd, input.relPath);
41822
+ const absPath = import_node_path43.default.isAbsolute(input.relPath) ? input.relPath : import_node_path43.default.join(input.cwd, input.relPath);
41037
41823
  let size = 0;
41038
41824
  try {
41039
41825
  size = import_node_fs29.default.statSync(absPath).size;
@@ -41120,6 +41906,8 @@ async function startDaemon(config) {
41120
41906
  return `http://${config.host}:${config.port}`;
41121
41907
  };
41122
41908
  const extensionRuntime = new Runtime({ root: extensionsRoot() });
41909
+ const publishedChannelStore = new PublishedChannelStore(publishedChannelsFile());
41910
+ await publishedChannelStore.load();
41123
41911
  const handlers = buildMethodHandlers({
41124
41912
  manager,
41125
41913
  workspace,
@@ -41159,10 +41947,10 @@ async function startDaemon(config) {
41159
41947
  // 'persona/<pid>/owner',default 走 'default'。
41160
41948
  getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
41161
41949
  // guest path guard:candidate 必须在 personaRoot 子树下
41162
- personaRoot: import_node_path38.default.join(config.dataDir, "personas")
41950
+ personaRoot: import_node_path43.default.join(config.dataDir, "personas")
41163
41951
  },
41164
41952
  // workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
41165
- personaRoot: import_node_path38.default.join(config.dataDir, "personas"),
41953
+ personaRoot: import_node_path43.default.join(config.dataDir, "personas"),
41166
41954
  // capability:list / delete handler 依赖
41167
41955
  capabilityManager,
41168
41956
  // personal-cap:get 返回的 shareBaseUrl 用此 base URL:
@@ -41189,7 +41977,9 @@ async function startDaemon(config) {
41189
41977
  connectRemote,
41190
41978
  // extension runtime (v1: local-mode only). Instance owned by daemon top
41191
41979
  // level so stopAll() runs in the shutdown hook below.
41192
- extensionRuntime
41980
+ extensionRuntime,
41981
+ // extension sharing v1 — owner-side published-channels store.
41982
+ publishedChannelStore
41193
41983
  });
41194
41984
  const authResolver = new AuthContextResolver({
41195
41985
  ownerToken: resolvedAuthToken
@@ -41374,8 +42164,8 @@ async function startDaemon(config) {
41374
42164
  const lines = [
41375
42165
  `Tunnel: ${r.url}`,
41376
42166
  ...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
41377
- `Frpc config: ${import_node_path38.default.join(config.dataDir, "frpc.toml")}`,
41378
- `Frpc log: ${import_node_path38.default.join(config.dataDir, "frpc.log")}`
42167
+ `Frpc config: ${import_node_path43.default.join(config.dataDir, "frpc.toml")}`,
42168
+ `Frpc log: ${import_node_path43.default.join(config.dataDir, "frpc.log")}`
41379
42169
  ];
41380
42170
  const width = Math.max(...lines.map((l) => l.length));
41381
42171
  const bar = "\u2550".repeat(width + 4);
@@ -41388,7 +42178,7 @@ ${bar}
41388
42178
 
41389
42179
  `);
41390
42180
  try {
41391
- const connectPath = import_node_path38.default.join(config.dataDir, "connect.txt");
42181
+ const connectPath = import_node_path43.default.join(config.dataDir, "connect.txt");
41392
42182
  import_node_fs29.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
41393
42183
  } catch {
41394
42184
  }
@@ -41455,7 +42245,7 @@ ${bar}
41455
42245
  };
41456
42246
  }
41457
42247
  function migrateDropPersonsDir(dataDir) {
41458
- const dir = import_node_path38.default.join(dataDir, "persons");
42248
+ const dir = import_node_path43.default.join(dataDir, "persons");
41459
42249
  try {
41460
42250
  import_node_fs29.default.rmSync(dir, { recursive: true, force: true });
41461
42251
  } catch {