@clawos-dev/clawd 0.2.199 → 0.2.200

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.
package/dist/cli.cjs CHANGED
@@ -158,6 +158,11 @@ var init_methods = __esm({
158
158
  "contact:list",
159
159
  "contact:pin",
160
160
  "contact:remove",
161
+ // ---- contact:setSshAccess / contact:sshKey:issue (SSH 反向访问,PR: contact-ssh-sandbox) ----
162
+ // owner UI 授权 contact 通过 daemon 专用 sshd 反向 SSH 进本机;guest daemon 用 sshKey:issue 拉 privkey.
163
+ // 详见 protocol/src/contact-ssh.ts.
164
+ "contact:setSshAccess",
165
+ "contact:sshKey:issue",
161
166
  // ---- visitor:* (web 访客 · persona web 分享,spec 2026-06-24-persona-web-share-design) ----
162
167
  // owner-only:列出登录访问过本机 public persona 的 web 访客(零安装、飞书登录、daemon 自签
163
168
  // visitor token)。存储 ~/.clawd/visitors.json(VisitorStore),登录(exchange)即 upsert;
@@ -737,8 +742,8 @@ var init_parseUtil = __esm({
737
742
  init_errors2();
738
743
  init_en();
739
744
  makeIssue = (params) => {
740
- const { data, path: path68, errorMaps, issueData } = params;
741
- const fullPath = [...path68, ...issueData.path || []];
745
+ const { data, path: path73, errorMaps, issueData } = params;
746
+ const fullPath = [...path73, ...issueData.path || []];
742
747
  const fullIssue = {
743
748
  ...issueData,
744
749
  path: fullPath
@@ -1049,11 +1054,11 @@ var init_types = __esm({
1049
1054
  init_parseUtil();
1050
1055
  init_util();
1051
1056
  ParseInputLazyPath = class {
1052
- constructor(parent, value, path68, key) {
1057
+ constructor(parent, value, path73, key) {
1053
1058
  this._cachedPath = [];
1054
1059
  this.parent = parent;
1055
1060
  this.data = value;
1056
- this._path = path68;
1061
+ this._path = path73;
1057
1062
  this._key = key;
1058
1063
  }
1059
1064
  get path() {
@@ -5678,7 +5683,19 @@ var init_contact = __esm({
5678
5683
  * 老 contacts.json 缺此字段 → zod default 补 null(无破坏性升级;不 orphan)。
5679
5684
  * 对齐 SessionFile.pinnedAt 语义(daemon 侧持久化,contact:pin RPC 更新 → contact:pinned push)。
5680
5685
  */
5681
- pinnedAt: external_exports.number().int().nullable().default(null)
5686
+ pinnedAt: external_exports.number().int().nullable().default(null),
5687
+ /**
5688
+ * SSH 反向访问授权(PR: contact-ssh-sandbox)。owner 视角:允许该 contact 通过 daemon
5689
+ * 专用 sshd 反向 SSH 进本机。default=false 保证老 contacts.json 兼容(zod default 补齐)。
5690
+ * 授权粒度 per-people (per-contact):借用别人 persona 本来就带信任对方本人的前提;
5691
+ * 极端场景(区别对方多个 persona)留 per-persona override 演进空间。
5692
+ */
5693
+ sshAllowed: external_exports.boolean().default(false),
5694
+ /**
5695
+ * 授权访问的目录白名单(绝对路径)。空数组 + sshAllowed=true 意味 SSH 通道能拨到但无处可读,
5696
+ * jail 会拒绝 shell 启动。由 clawd-ssh-jail 运行时读,生成 sbpl / bwrap policy 白名单。
5697
+ */
5698
+ exposedDirs: external_exports.array(external_exports.string()).default([])
5682
5699
  }).strict();
5683
5700
  ContactWireSchema = ContactSchema;
5684
5701
  ContactRemoveArgsSchema = external_exports.object({
@@ -5717,6 +5734,40 @@ var init_contact = __esm({
5717
5734
  }
5718
5735
  });
5719
5736
 
5737
+ // ../protocol/src/contact-ssh.ts
5738
+ var ContactSetSshAccessArgsSchema, ContactSetSshAccessOkSchema, ContactSshKeyIssueArgsSchema, ContactSshKeyIssueOkSchema, ContactSshAccessUpdatedFrameSchema;
5739
+ var init_contact_ssh = __esm({
5740
+ "../protocol/src/contact-ssh.ts"() {
5741
+ "use strict";
5742
+ init_zod();
5743
+ ContactSetSshAccessArgsSchema = external_exports.object({
5744
+ deviceId: external_exports.string().min(1),
5745
+ sshAllowed: external_exports.boolean(),
5746
+ exposedDirs: external_exports.array(external_exports.string())
5747
+ }).strict();
5748
+ ContactSetSshAccessOkSchema = external_exports.object({
5749
+ type: external_exports.literal("contact:setSshAccess:ok"),
5750
+ deviceId: external_exports.string().min(1),
5751
+ sshAllowed: external_exports.boolean(),
5752
+ exposedDirs: external_exports.array(external_exports.string())
5753
+ }).strict();
5754
+ ContactSshKeyIssueArgsSchema = external_exports.object({
5755
+ deviceId: external_exports.string().min(1)
5756
+ }).strict();
5757
+ ContactSshKeyIssueOkSchema = external_exports.object({
5758
+ type: external_exports.literal("contact:sshKey:issue:ok"),
5759
+ privateKeyPem: external_exports.string().min(1),
5760
+ publicKeyLine: external_exports.string().min(1)
5761
+ }).strict();
5762
+ ContactSshAccessUpdatedFrameSchema = external_exports.object({
5763
+ type: external_exports.literal("contact:ssh-access-updated"),
5764
+ deviceId: external_exports.string().min(1),
5765
+ sshAllowed: external_exports.boolean(),
5766
+ exposedDirs: external_exports.array(external_exports.string())
5767
+ }).strict();
5768
+ }
5769
+ });
5770
+
5720
5771
  // ../protocol/src/extension.ts
5721
5772
  function isAllowedHostedUrl(raw) {
5722
5773
  let u;
@@ -6118,6 +6169,7 @@ var init_runtime = __esm({
6118
6169
  init_capability();
6119
6170
  init_inbox();
6120
6171
  init_contact();
6172
+ init_contact_ssh();
6121
6173
  init_extension();
6122
6174
  init_feishu_auth();
6123
6175
  init_dispatch();
@@ -6397,8 +6449,8 @@ var require_req = __commonJS({
6397
6449
  if (req.originalUrl) {
6398
6450
  _req.url = req.originalUrl;
6399
6451
  } else {
6400
- const path68 = req.path;
6401
- _req.url = typeof path68 === "string" ? path68 : req.url ? req.url.path || req.url : void 0;
6452
+ const path73 = req.path;
6453
+ _req.url = typeof path73 === "string" ? path73 : req.url ? req.url.path || req.url : void 0;
6402
6454
  }
6403
6455
  if (req.query) {
6404
6456
  _req.query = req.query;
@@ -6563,14 +6615,14 @@ var require_redact = __commonJS({
6563
6615
  }
6564
6616
  return obj;
6565
6617
  }
6566
- function parsePath(path68) {
6618
+ function parsePath(path73) {
6567
6619
  const parts = [];
6568
6620
  let current = "";
6569
6621
  let inBrackets = false;
6570
6622
  let inQuotes = false;
6571
6623
  let quoteChar = "";
6572
- for (let i = 0; i < path68.length; i++) {
6573
- const char = path68[i];
6624
+ for (let i = 0; i < path73.length; i++) {
6625
+ const char = path73[i];
6574
6626
  if (!inBrackets && char === ".") {
6575
6627
  if (current) {
6576
6628
  parts.push(current);
@@ -6701,10 +6753,10 @@ var require_redact = __commonJS({
6701
6753
  return current;
6702
6754
  }
6703
6755
  function redactPaths(obj, paths, censor, remove = false) {
6704
- for (const path68 of paths) {
6705
- const parts = parsePath(path68);
6756
+ for (const path73 of paths) {
6757
+ const parts = parsePath(path73);
6706
6758
  if (parts.includes("*")) {
6707
- redactWildcardPath(obj, parts, censor, path68, remove);
6759
+ redactWildcardPath(obj, parts, censor, path73, remove);
6708
6760
  } else {
6709
6761
  if (remove) {
6710
6762
  removeKey(obj, parts);
@@ -6789,8 +6841,8 @@ var require_redact = __commonJS({
6789
6841
  }
6790
6842
  } else {
6791
6843
  if (afterWildcard.includes("*")) {
6792
- const wrappedCensor = typeof censor === "function" ? (value, path68) => {
6793
- const fullPath = [...pathArray.slice(0, pathLength), ...path68];
6844
+ const wrappedCensor = typeof censor === "function" ? (value, path73) => {
6845
+ const fullPath = [...pathArray.slice(0, pathLength), ...path73];
6794
6846
  return censor(value, fullPath);
6795
6847
  } : censor;
6796
6848
  redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
@@ -6825,8 +6877,8 @@ var require_redact = __commonJS({
6825
6877
  return null;
6826
6878
  }
6827
6879
  const pathStructure = /* @__PURE__ */ new Map();
6828
- for (const path68 of pathsToClone) {
6829
- const parts = parsePath(path68);
6880
+ for (const path73 of pathsToClone) {
6881
+ const parts = parsePath(path73);
6830
6882
  let current = pathStructure;
6831
6883
  for (let i = 0; i < parts.length; i++) {
6832
6884
  const part = parts[i];
@@ -6878,24 +6930,24 @@ var require_redact = __commonJS({
6878
6930
  }
6879
6931
  return cloneSelectively(obj, pathStructure);
6880
6932
  }
6881
- function validatePath(path68) {
6882
- if (typeof path68 !== "string") {
6933
+ function validatePath(path73) {
6934
+ if (typeof path73 !== "string") {
6883
6935
  throw new Error("Paths must be (non-empty) strings");
6884
6936
  }
6885
- if (path68 === "") {
6937
+ if (path73 === "") {
6886
6938
  throw new Error("Invalid redaction path ()");
6887
6939
  }
6888
- if (path68.includes("..")) {
6889
- throw new Error(`Invalid redaction path (${path68})`);
6940
+ if (path73.includes("..")) {
6941
+ throw new Error(`Invalid redaction path (${path73})`);
6890
6942
  }
6891
- if (path68.includes(",")) {
6892
- throw new Error(`Invalid redaction path (${path68})`);
6943
+ if (path73.includes(",")) {
6944
+ throw new Error(`Invalid redaction path (${path73})`);
6893
6945
  }
6894
6946
  let bracketCount = 0;
6895
6947
  let inQuotes = false;
6896
6948
  let quoteChar = "";
6897
- for (let i = 0; i < path68.length; i++) {
6898
- const char = path68[i];
6949
+ for (let i = 0; i < path73.length; i++) {
6950
+ const char = path73[i];
6899
6951
  if ((char === '"' || char === "'") && bracketCount > 0) {
6900
6952
  if (!inQuotes) {
6901
6953
  inQuotes = true;
@@ -6909,20 +6961,20 @@ var require_redact = __commonJS({
6909
6961
  } else if (char === "]" && !inQuotes) {
6910
6962
  bracketCount--;
6911
6963
  if (bracketCount < 0) {
6912
- throw new Error(`Invalid redaction path (${path68})`);
6964
+ throw new Error(`Invalid redaction path (${path73})`);
6913
6965
  }
6914
6966
  }
6915
6967
  }
6916
6968
  if (bracketCount !== 0) {
6917
- throw new Error(`Invalid redaction path (${path68})`);
6969
+ throw new Error(`Invalid redaction path (${path73})`);
6918
6970
  }
6919
6971
  }
6920
6972
  function validatePaths(paths) {
6921
6973
  if (!Array.isArray(paths)) {
6922
6974
  throw new TypeError("paths must be an array");
6923
6975
  }
6924
- for (const path68 of paths) {
6925
- validatePath(path68);
6976
+ for (const path73 of paths) {
6977
+ validatePath(path73);
6926
6978
  }
6927
6979
  }
6928
6980
  function slowRedact(options = {}) {
@@ -7090,8 +7142,8 @@ var require_redaction = __commonJS({
7090
7142
  if (shape[k2] === null) {
7091
7143
  o[k2] = (value) => topCensor(value, [k2]);
7092
7144
  } else {
7093
- const wrappedCensor = typeof censor === "function" ? (value, path68) => {
7094
- return censor(value, [k2, ...path68]);
7145
+ const wrappedCensor = typeof censor === "function" ? (value, path73) => {
7146
+ return censor(value, [k2, ...path73]);
7095
7147
  } : censor;
7096
7148
  o[k2] = Redact({
7097
7149
  paths: shape[k2],
@@ -7309,10 +7361,10 @@ var require_atomic_sleep = __commonJS({
7309
7361
  var require_sonic_boom = __commonJS({
7310
7362
  "../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
7311
7363
  "use strict";
7312
- var fs61 = require("fs");
7364
+ var fs66 = require("fs");
7313
7365
  var EventEmitter3 = require("events");
7314
7366
  var inherits = require("util").inherits;
7315
- var path68 = require("path");
7367
+ var path73 = require("path");
7316
7368
  var sleep2 = require_atomic_sleep();
7317
7369
  var assert = require("assert");
7318
7370
  var BUSY_WRITE_TIMEOUT = 100;
@@ -7366,20 +7418,20 @@ var require_sonic_boom = __commonJS({
7366
7418
  const mode = sonic.mode;
7367
7419
  if (sonic.sync) {
7368
7420
  try {
7369
- if (sonic.mkdir) fs61.mkdirSync(path68.dirname(file), { recursive: true });
7370
- const fd = fs61.openSync(file, flags, mode);
7421
+ if (sonic.mkdir) fs66.mkdirSync(path73.dirname(file), { recursive: true });
7422
+ const fd = fs66.openSync(file, flags, mode);
7371
7423
  fileOpened(null, fd);
7372
7424
  } catch (err) {
7373
7425
  fileOpened(err);
7374
7426
  throw err;
7375
7427
  }
7376
7428
  } else if (sonic.mkdir) {
7377
- fs61.mkdir(path68.dirname(file), { recursive: true }, (err) => {
7429
+ fs66.mkdir(path73.dirname(file), { recursive: true }, (err) => {
7378
7430
  if (err) return fileOpened(err);
7379
- fs61.open(file, flags, mode, fileOpened);
7431
+ fs66.open(file, flags, mode, fileOpened);
7380
7432
  });
7381
7433
  } else {
7382
- fs61.open(file, flags, mode, fileOpened);
7434
+ fs66.open(file, flags, mode, fileOpened);
7383
7435
  }
7384
7436
  }
7385
7437
  function SonicBoom(opts) {
@@ -7420,8 +7472,8 @@ var require_sonic_boom = __commonJS({
7420
7472
  this.flush = flushBuffer;
7421
7473
  this.flushSync = flushBufferSync;
7422
7474
  this._actualWrite = actualWriteBuffer;
7423
- fsWriteSync = () => fs61.writeSync(this.fd, this._writingBuf);
7424
- fsWrite = () => fs61.write(this.fd, this._writingBuf, this.release);
7475
+ fsWriteSync = () => fs66.writeSync(this.fd, this._writingBuf);
7476
+ fsWrite = () => fs66.write(this.fd, this._writingBuf, this.release);
7425
7477
  } else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
7426
7478
  this._writingBuf = "";
7427
7479
  this.write = write;
@@ -7430,15 +7482,15 @@ var require_sonic_boom = __commonJS({
7430
7482
  this._actualWrite = actualWrite;
7431
7483
  fsWriteSync = () => {
7432
7484
  if (Buffer.isBuffer(this._writingBuf)) {
7433
- return fs61.writeSync(this.fd, this._writingBuf);
7485
+ return fs66.writeSync(this.fd, this._writingBuf);
7434
7486
  }
7435
- return fs61.writeSync(this.fd, this._writingBuf, "utf8");
7487
+ return fs66.writeSync(this.fd, this._writingBuf, "utf8");
7436
7488
  };
7437
7489
  fsWrite = () => {
7438
7490
  if (Buffer.isBuffer(this._writingBuf)) {
7439
- return fs61.write(this.fd, this._writingBuf, this.release);
7491
+ return fs66.write(this.fd, this._writingBuf, this.release);
7440
7492
  }
7441
- return fs61.write(this.fd, this._writingBuf, "utf8", this.release);
7493
+ return fs66.write(this.fd, this._writingBuf, "utf8", this.release);
7442
7494
  };
7443
7495
  } else {
7444
7496
  throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
@@ -7495,7 +7547,7 @@ var require_sonic_boom = __commonJS({
7495
7547
  }
7496
7548
  }
7497
7549
  if (this._fsync) {
7498
- fs61.fsyncSync(this.fd);
7550
+ fs66.fsyncSync(this.fd);
7499
7551
  }
7500
7552
  const len = this._len;
7501
7553
  if (this._reopening) {
@@ -7609,7 +7661,7 @@ var require_sonic_boom = __commonJS({
7609
7661
  const onDrain = () => {
7610
7662
  if (!this._fsync) {
7611
7663
  try {
7612
- fs61.fsync(this.fd, (err) => {
7664
+ fs66.fsync(this.fd, (err) => {
7613
7665
  this._flushPending = false;
7614
7666
  cb(err);
7615
7667
  });
@@ -7711,7 +7763,7 @@ var require_sonic_boom = __commonJS({
7711
7763
  const fd = this.fd;
7712
7764
  this.once("ready", () => {
7713
7765
  if (fd !== this.fd) {
7714
- fs61.close(fd, (err) => {
7766
+ fs66.close(fd, (err) => {
7715
7767
  if (err) {
7716
7768
  return this.emit("error", err);
7717
7769
  }
@@ -7760,7 +7812,7 @@ var require_sonic_boom = __commonJS({
7760
7812
  buf = this._bufs[0];
7761
7813
  }
7762
7814
  try {
7763
- const n = Buffer.isBuffer(buf) ? fs61.writeSync(this.fd, buf) : fs61.writeSync(this.fd, buf, "utf8");
7815
+ const n = Buffer.isBuffer(buf) ? fs66.writeSync(this.fd, buf) : fs66.writeSync(this.fd, buf, "utf8");
7764
7816
  const releasedBufObj = releaseWritingBuf(buf, this._len, n);
7765
7817
  buf = releasedBufObj.writingBuf;
7766
7818
  this._len = releasedBufObj.len;
@@ -7776,7 +7828,7 @@ var require_sonic_boom = __commonJS({
7776
7828
  }
7777
7829
  }
7778
7830
  try {
7779
- fs61.fsyncSync(this.fd);
7831
+ fs66.fsyncSync(this.fd);
7780
7832
  } catch {
7781
7833
  }
7782
7834
  }
@@ -7797,7 +7849,7 @@ var require_sonic_boom = __commonJS({
7797
7849
  buf = mergeBuf(this._bufs[0], this._lens[0]);
7798
7850
  }
7799
7851
  try {
7800
- const n = fs61.writeSync(this.fd, buf);
7852
+ const n = fs66.writeSync(this.fd, buf);
7801
7853
  buf = buf.subarray(n);
7802
7854
  this._len = Math.max(this._len - n, 0);
7803
7855
  if (buf.length <= 0) {
@@ -7825,13 +7877,13 @@ var require_sonic_boom = __commonJS({
7825
7877
  this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
7826
7878
  if (this.sync) {
7827
7879
  try {
7828
- const written = Buffer.isBuffer(this._writingBuf) ? fs61.writeSync(this.fd, this._writingBuf) : fs61.writeSync(this.fd, this._writingBuf, "utf8");
7880
+ const written = Buffer.isBuffer(this._writingBuf) ? fs66.writeSync(this.fd, this._writingBuf) : fs66.writeSync(this.fd, this._writingBuf, "utf8");
7829
7881
  release(null, written);
7830
7882
  } catch (err) {
7831
7883
  release(err);
7832
7884
  }
7833
7885
  } else {
7834
- fs61.write(this.fd, this._writingBuf, release);
7886
+ fs66.write(this.fd, this._writingBuf, release);
7835
7887
  }
7836
7888
  }
7837
7889
  function actualWriteBuffer() {
@@ -7840,7 +7892,7 @@ var require_sonic_boom = __commonJS({
7840
7892
  this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
7841
7893
  if (this.sync) {
7842
7894
  try {
7843
- const written = fs61.writeSync(this.fd, this._writingBuf);
7895
+ const written = fs66.writeSync(this.fd, this._writingBuf);
7844
7896
  release(null, written);
7845
7897
  } catch (err) {
7846
7898
  release(err);
@@ -7849,7 +7901,7 @@ var require_sonic_boom = __commonJS({
7849
7901
  if (kCopyBuffer) {
7850
7902
  this._writingBuf = Buffer.from(this._writingBuf);
7851
7903
  }
7852
- fs61.write(this.fd, this._writingBuf, release);
7904
+ fs66.write(this.fd, this._writingBuf, release);
7853
7905
  }
7854
7906
  }
7855
7907
  function actualClose(sonic) {
@@ -7865,12 +7917,12 @@ var require_sonic_boom = __commonJS({
7865
7917
  sonic._lens = [];
7866
7918
  assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
7867
7919
  try {
7868
- fs61.fsync(sonic.fd, closeWrapped);
7920
+ fs66.fsync(sonic.fd, closeWrapped);
7869
7921
  } catch {
7870
7922
  }
7871
7923
  function closeWrapped() {
7872
7924
  if (sonic.fd !== 1 && sonic.fd !== 2) {
7873
- fs61.close(sonic.fd, done);
7925
+ fs66.close(sonic.fd, done);
7874
7926
  } else {
7875
7927
  done();
7876
7928
  }
@@ -11005,11 +11057,11 @@ var init_lib = __esm({
11005
11057
  }
11006
11058
  }
11007
11059
  },
11008
- addToPath: function addToPath(path68, added, removed, oldPosInc, options) {
11009
- var last = path68.lastComponent;
11060
+ addToPath: function addToPath(path73, added, removed, oldPosInc, options) {
11061
+ var last = path73.lastComponent;
11010
11062
  if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
11011
11063
  return {
11012
- oldPos: path68.oldPos + oldPosInc,
11064
+ oldPos: path73.oldPos + oldPosInc,
11013
11065
  lastComponent: {
11014
11066
  count: last.count + 1,
11015
11067
  added,
@@ -11019,7 +11071,7 @@ var init_lib = __esm({
11019
11071
  };
11020
11072
  } else {
11021
11073
  return {
11022
- oldPos: path68.oldPos + oldPosInc,
11074
+ oldPos: path73.oldPos + oldPosInc,
11023
11075
  lastComponent: {
11024
11076
  count: 1,
11025
11077
  added,
@@ -11485,10 +11537,10 @@ function attachmentToHistoryMessage(o, ts) {
11485
11537
  const memories = raw.map((m2) => {
11486
11538
  if (!m2 || typeof m2 !== "object") return null;
11487
11539
  const rec3 = m2;
11488
- const path68 = typeof rec3.path === "string" ? rec3.path : null;
11540
+ const path73 = typeof rec3.path === "string" ? rec3.path : null;
11489
11541
  const content = typeof rec3.content === "string" ? rec3.content : null;
11490
- if (!path68 || content == null) return null;
11491
- const entry = { path: path68, content };
11542
+ if (!path73 || content == null) return null;
11543
+ const entry = { path: path73, content };
11492
11544
  if (typeof rec3.mtimeMs === "number") entry.mtimeMs = rec3.mtimeMs;
11493
11545
  return entry;
11494
11546
  }).filter((m2) => m2 !== null);
@@ -12300,10 +12352,10 @@ function parseAttachment(obj) {
12300
12352
  const memories = raw.map((m2) => {
12301
12353
  if (!m2 || typeof m2 !== "object") return null;
12302
12354
  const rec3 = m2;
12303
- const path68 = typeof rec3.path === "string" ? rec3.path : null;
12355
+ const path73 = typeof rec3.path === "string" ? rec3.path : null;
12304
12356
  const content = typeof rec3.content === "string" ? rec3.content : null;
12305
- if (!path68 || content == null) return null;
12306
- const out = { path: path68, content };
12357
+ if (!path73 || content == null) return null;
12358
+ const out = { path: path73, content };
12307
12359
  if (typeof rec3.mtimeMs === "number") out.mtimeMs = rec3.mtimeMs;
12308
12360
  return out;
12309
12361
  }).filter((m2) => m2 !== null);
@@ -33268,8 +33320,8 @@ var require_utils = __commonJS({
33268
33320
  var result = transform[inputType][outputType](input);
33269
33321
  return result;
33270
33322
  };
33271
- exports2.resolve = function(path68) {
33272
- var parts = path68.split("/");
33323
+ exports2.resolve = function(path73) {
33324
+ var parts = path73.split("/");
33273
33325
  var result = [];
33274
33326
  for (var index = 0; index < parts.length; index++) {
33275
33327
  var part = parts[index];
@@ -39122,18 +39174,18 @@ var require_object = __commonJS({
39122
39174
  var object = new ZipObject(name, zipObjectContent, o);
39123
39175
  this.files[name] = object;
39124
39176
  };
39125
- var parentFolder = function(path68) {
39126
- if (path68.slice(-1) === "/") {
39127
- path68 = path68.substring(0, path68.length - 1);
39177
+ var parentFolder = function(path73) {
39178
+ if (path73.slice(-1) === "/") {
39179
+ path73 = path73.substring(0, path73.length - 1);
39128
39180
  }
39129
- var lastSlash = path68.lastIndexOf("/");
39130
- return lastSlash > 0 ? path68.substring(0, lastSlash) : "";
39181
+ var lastSlash = path73.lastIndexOf("/");
39182
+ return lastSlash > 0 ? path73.substring(0, lastSlash) : "";
39131
39183
  };
39132
- var forceTrailingSlash = function(path68) {
39133
- if (path68.slice(-1) !== "/") {
39134
- path68 += "/";
39184
+ var forceTrailingSlash = function(path73) {
39185
+ if (path73.slice(-1) !== "/") {
39186
+ path73 += "/";
39135
39187
  }
39136
- return path68;
39188
+ return path73;
39137
39189
  };
39138
39190
  var folderAdd = function(name, createFolders) {
39139
39191
  createFolders = typeof createFolders !== "undefined" ? createFolders : defaults.createFolders;
@@ -40135,7 +40187,7 @@ var require_lib3 = __commonJS({
40135
40187
  // src/run-case/recorder.ts
40136
40188
  function startRunCaseRecorder(opts) {
40137
40189
  const now = opts.now ?? Date.now;
40138
- const dir = import_node_path56.default.dirname(opts.recordPath);
40190
+ const dir = import_node_path61.default.dirname(opts.recordPath);
40139
40191
  let stream = null;
40140
40192
  let closing = false;
40141
40193
  let closedSettled = false;
@@ -40149,8 +40201,8 @@ function startRunCaseRecorder(opts) {
40149
40201
  });
40150
40202
  const ensureStream = () => {
40151
40203
  if (stream) return stream;
40152
- import_node_fs43.default.mkdirSync(dir, { recursive: true });
40153
- stream = import_node_fs43.default.createWriteStream(opts.recordPath, { flags: "a" });
40204
+ import_node_fs48.default.mkdirSync(dir, { recursive: true });
40205
+ stream = import_node_fs48.default.createWriteStream(opts.recordPath, { flags: "a" });
40154
40206
  stream.on("close", () => closedResolve());
40155
40207
  return stream;
40156
40208
  };
@@ -40175,12 +40227,12 @@ function startRunCaseRecorder(opts) {
40175
40227
  };
40176
40228
  return { tap, close, closed };
40177
40229
  }
40178
- var import_node_fs43, import_node_path56;
40230
+ var import_node_fs48, import_node_path61;
40179
40231
  var init_recorder = __esm({
40180
40232
  "src/run-case/recorder.ts"() {
40181
40233
  "use strict";
40182
- import_node_fs43 = __toESM(require("fs"), 1);
40183
- import_node_path56 = __toESM(require("path"), 1);
40234
+ import_node_fs48 = __toESM(require("fs"), 1);
40235
+ import_node_path61 = __toESM(require("path"), 1);
40184
40236
  }
40185
40237
  });
40186
40238
 
@@ -40223,7 +40275,7 @@ var init_wire = __esm({
40223
40275
  // src/run-case/controller.ts
40224
40276
  async function runController(opts) {
40225
40277
  const now = opts.now ?? Date.now;
40226
- const cwd = opts.cwd ?? (0, import_node_fs44.mkdtempSync)(import_node_path57.default.join(import_node_os22.default.tmpdir(), "clawd-runcase-"));
40278
+ const cwd = opts.cwd ?? (0, import_node_fs49.mkdtempSync)(import_node_path62.default.join(import_node_os22.default.tmpdir(), "clawd-runcase-"));
40227
40279
  const ownsCwd = opts.cwd === void 0;
40228
40280
  const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
40229
40281
  const spawnCtx = { cwd };
@@ -40384,19 +40436,19 @@ async function runController(opts) {
40384
40436
  if (sigintHandler) process.off("SIGINT", sigintHandler);
40385
40437
  if (ownsCwd) {
40386
40438
  try {
40387
- (0, import_node_fs44.rmSync)(cwd, { recursive: true, force: true });
40439
+ (0, import_node_fs49.rmSync)(cwd, { recursive: true, force: true });
40388
40440
  } catch {
40389
40441
  }
40390
40442
  }
40391
40443
  return exitCode ?? 0;
40392
40444
  }
40393
- var import_node_fs44, import_node_os22, import_node_path57;
40445
+ var import_node_fs49, import_node_os22, import_node_path62;
40394
40446
  var init_controller = __esm({
40395
40447
  "src/run-case/controller.ts"() {
40396
40448
  "use strict";
40397
- import_node_fs44 = require("fs");
40449
+ import_node_fs49 = require("fs");
40398
40450
  import_node_os22 = __toESM(require("os"), 1);
40399
- import_node_path57 = __toESM(require("path"), 1);
40451
+ import_node_path62 = __toESM(require("path"), 1);
40400
40452
  init_claude();
40401
40453
  init_stdout_splitter();
40402
40454
  init_permission_stdio();
@@ -40499,6 +40551,7 @@ var import_node_path = __toESM(require("path"), 1);
40499
40551
  init_protocol();
40500
40552
  var DEFAULT_PORT = 18790;
40501
40553
  var DEFAULT_HOST = "127.0.0.1";
40554
+ var DEFAULT_SSHD_PORT = 22422;
40502
40555
  var DEFAULT_CLAWOS_API = "https://api.clawos.chat";
40503
40556
  var DEFAULT_LOG_ENDPOINT = "https://clawd-prod.cn-hangzhou.log.aliyuncs.com/logstores/app-logs/track";
40504
40557
  function resolveLogShipping(raw, cliNoShipping) {
@@ -40566,6 +40619,14 @@ function parseArgs(argv) {
40566
40619
  case "--no-log-shipping":
40567
40620
  out.noLogShipping = true;
40568
40621
  break;
40622
+ case "--sshd-port": {
40623
+ const n = Number.parseInt(next() ?? "", 10);
40624
+ if (!Number.isFinite(n) || n <= 0 || n > 65535) {
40625
+ throw new Error(`invalid --sshd-port value: ${argv[i]}`);
40626
+ }
40627
+ out.sshdPort = n;
40628
+ break;
40629
+ }
40569
40630
  default:
40570
40631
  if (a.startsWith("--")) throw new Error(`unknown flag: ${a}`);
40571
40632
  break;
@@ -40615,6 +40676,7 @@ function resolveConfig(opts) {
40615
40676
  fileCfg.logShipping,
40616
40677
  args.noLogShipping ?? false
40617
40678
  );
40679
+ const sshdPort = args.sshdPort ?? (typeof fileCfg.sshdPort === "number" ? fileCfg.sshdPort : DEFAULT_SSHD_PORT);
40618
40680
  return {
40619
40681
  port,
40620
40682
  host,
@@ -40628,7 +40690,8 @@ function resolveConfig(opts) {
40628
40690
  frpcBinary,
40629
40691
  mode,
40630
40692
  previewPorts,
40631
- logShipping
40693
+ logShipping,
40694
+ sshdPort
40632
40695
  };
40633
40696
  }
40634
40697
  var HELP_TEXT = `clawd [options]
@@ -40640,6 +40703,7 @@ var HELP_TEXT = `clawd [options]
40640
40703
  --clawos-api <url> tunnel register \u63A5\u53E3\u7684 base url\uFF08\u9ED8\u8BA4 https://api.clawos.chat\uFF09
40641
40704
  --auth-token <s> \u6307\u5B9A daemon auth token\uFF1B\u7F3A\u7701\u65F6 tunnel \u6A21\u5F0F\u4ECE ~/.clawd/auth.json \u590D\u7528\uFF0C\u6CA1\u6709\u5C31\u751F\u6210
40642
40705
  --no-log-shipping \u7981\u7528 SLS \u65E5\u5FD7\u4E0A\u884C\uFF08\u8986\u76D6 config.json logShipping.mode=off\uFF09
40706
+ --sshd-port <n> Contact SSH \u53CD\u5411\u8BBF\u95EE loopback \u7AEF\u53E3\uFF0C\u9ED8\u8BA4 22422
40643
40707
  --help / -h \u663E\u793A\u5E2E\u52A9
40644
40708
  --version / -v \u663E\u793A\u7248\u672C
40645
40709
 
@@ -40658,8 +40722,8 @@ Env (advanced):
40658
40722
  `;
40659
40723
 
40660
40724
  // src/index.ts
40661
- var import_node_path55 = __toESM(require("path"), 1);
40662
- var import_node_fs42 = __toESM(require("fs"), 1);
40725
+ var import_node_path60 = __toESM(require("path"), 1);
40726
+ var import_node_fs47 = __toESM(require("fs"), 1);
40663
40727
  var import_node_os21 = __toESM(require("os"), 1);
40664
40728
 
40665
40729
  // ../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/stringify.js
@@ -46170,8 +46234,8 @@ function turnStartInput(text) {
46170
46234
  const items = [];
46171
46235
  let leftover = text;
46172
46236
  for (const m2 of text.matchAll(SKILL_RE)) {
46173
- const [marker, name, path68] = m2;
46174
- items.push({ type: "skill", name, path: path68 });
46237
+ const [marker, name, path73] = m2;
46238
+ items.push({ type: "skill", name, path: path73 });
46175
46239
  leftover = leftover.replace(marker, "");
46176
46240
  }
46177
46241
  for (const m2 of text.matchAll(ATTACHMENT_RE2)) {
@@ -48695,13 +48759,13 @@ function mapSkillsListResponse(res) {
48695
48759
  const r = s ?? {};
48696
48760
  const name = str3(r.name);
48697
48761
  if (!name) continue;
48698
- const path68 = str3(r.path);
48762
+ const path73 = str3(r.path);
48699
48763
  const description = str3(r.description);
48700
48764
  const isPlugin = name.includes(":");
48701
48765
  out.push({
48702
48766
  name,
48703
48767
  source: isPlugin ? "plugin" : "project",
48704
- ...path68 ? { path: path68 } : {},
48768
+ ...path73 ? { path: path73 } : {},
48705
48769
  ...description ? { description } : {},
48706
48770
  ...isPlugin ? { plugin: name.split(":")[0] } : {}
48707
48771
  });
@@ -50495,6 +50559,23 @@ var ContactStore = class {
50495
50559
  this.flush();
50496
50560
  return true;
50497
50561
  }
50562
+ /**
50563
+ * 更新单条 contact 的 SSH 授权(PR: contact-ssh-sandbox)。对齐 setPin pattern:
50564
+ * store 只做原始 mutation,不做业务校验(如"sshAllowed=false 时清空 exposedDirs")——
50565
+ * 那是 handler / UI 的责任。数组语义是完全替换(不 append)。
50566
+ * @returns 是否命中:deviceId 不存在返 false;命中即 flush.
50567
+ */
50568
+ setSshAccess(deviceId, opts) {
50569
+ const existing = this.contacts.get(deviceId);
50570
+ if (!existing) return false;
50571
+ this.contacts.set(deviceId, {
50572
+ ...existing,
50573
+ sshAllowed: opts.sshAllowed,
50574
+ exposedDirs: opts.exposedDirs
50575
+ });
50576
+ this.flush();
50577
+ return true;
50578
+ }
50498
50579
  flush() {
50499
50580
  const file = path30.join(this.dataDir, FILE_NAME);
50500
50581
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
@@ -50642,7 +50723,9 @@ async function autoReverseContact(args) {
50642
50723
  connectToken: "",
50643
50724
  grants,
50644
50725
  addedAt: now(),
50645
- pinnedAt: null
50726
+ pinnedAt: null,
50727
+ sshAllowed: false,
50728
+ exposedDirs: []
50646
50729
  };
50647
50730
  args.store.upsert(base);
50648
50731
  args.broadcast({ type: "contact:added", contact: base });
@@ -52642,29 +52725,510 @@ async function waitForFrpcReady(proc, timeoutMs) {
52642
52725
  });
52643
52726
  }
52644
52727
 
52728
+ // src/sshd/sshd-manager.ts
52729
+ var import_node_fs36 = __toESM(require("fs"), 1);
52730
+ var import_node_path37 = __toESM(require("path"), 1);
52731
+ var import_node_child_process11 = require("child_process");
52732
+
52733
+ // src/sshd/sshd-config.ts
52734
+ function buildSshdConfig(input) {
52735
+ const lines = [
52736
+ `ListenAddress ${input.listenAddress}`,
52737
+ `Port ${input.port}`,
52738
+ `HostKey ${input.hostKeyPath}`,
52739
+ `PidFile ${input.pidFilePath}`,
52740
+ `AuthorizedKeysFile ${input.authorizedKeysFile}`,
52741
+ `PubkeyAuthentication yes`,
52742
+ `PasswordAuthentication no`,
52743
+ `ChallengeResponseAuthentication no`,
52744
+ `KbdInteractiveAuthentication no`,
52745
+ `PermitRootLogin no`,
52746
+ `StrictModes no`,
52747
+ `UsePAM no`,
52748
+ `LogLevel INFO`,
52749
+ `Subsystem sftp internal-sftp`
52750
+ ];
52751
+ return lines.join("\n") + "\n";
52752
+ }
52753
+
52754
+ // src/sshd/sshd-process.ts
52755
+ var import_node_fs34 = __toESM(require("fs"), 1);
52756
+ var import_node_path35 = __toESM(require("path"), 1);
52757
+ var import_node_child_process10 = require("child_process");
52758
+ function sshdPidFilePath(dataDir) {
52759
+ return import_node_path35.default.join(dataDir, "sshd", "sshd.pid");
52760
+ }
52761
+ function writeSshdPid(dataDir, pid) {
52762
+ try {
52763
+ const p2 = sshdPidFilePath(dataDir);
52764
+ import_node_fs34.default.mkdirSync(import_node_path35.default.dirname(p2), { recursive: true, mode: 448 });
52765
+ import_node_fs34.default.writeFileSync(p2, String(pid), { mode: 384 });
52766
+ } catch {
52767
+ }
52768
+ }
52769
+ function clearSshdPid(dataDir) {
52770
+ try {
52771
+ import_node_fs34.default.unlinkSync(sshdPidFilePath(dataDir));
52772
+ } catch {
52773
+ }
52774
+ }
52775
+ function defaultIsPidAlive2(pid) {
52776
+ if (!Number.isFinite(pid) || pid <= 0) return false;
52777
+ try {
52778
+ process.kill(pid, 0);
52779
+ return true;
52780
+ } catch (err) {
52781
+ const code = err.code;
52782
+ return code === "EPERM";
52783
+ }
52784
+ }
52785
+ function defaultReadPidFile2(file) {
52786
+ try {
52787
+ return import_node_fs34.default.readFileSync(file, "utf8");
52788
+ } catch {
52789
+ return null;
52790
+ }
52791
+ }
52792
+ function defaultKillPid2(pid, signal) {
52793
+ try {
52794
+ process.kill(pid, signal);
52795
+ } catch {
52796
+ }
52797
+ }
52798
+ function defaultSleep2(ms) {
52799
+ return new Promise((r) => setTimeout(r, ms));
52800
+ }
52801
+ async function killStaleSshd(deps) {
52802
+ const pidFile = sshdPidFilePath(deps.dataDir);
52803
+ const configPath = import_node_path35.default.join(deps.dataDir, "sshd", "sshd_config");
52804
+ const readPidFile = deps.readPidFileImpl ?? defaultReadPidFile2;
52805
+ const isAlive = deps.isPidAliveImpl ?? defaultIsPidAlive2;
52806
+ const killPid = deps.killPidImpl ?? defaultKillPid2;
52807
+ const scanPids = deps.scanSshdPidsImpl ?? ((cp) => defaultScanSshdPidsByCmdline(cp, deps.logger));
52808
+ const sleep2 = deps.sleepImpl ?? defaultSleep2;
52809
+ const victims = /* @__PURE__ */ new Set();
52810
+ const raw = readPidFile(pidFile);
52811
+ if (raw) {
52812
+ const pid = parseInt(raw.trim(), 10);
52813
+ if (Number.isFinite(pid) && pid > 0 && pid !== deps.ownPid && isAlive(pid)) {
52814
+ victims.add(pid);
52815
+ }
52816
+ }
52817
+ try {
52818
+ const scanned = await scanPids(configPath);
52819
+ for (const pid of scanned) {
52820
+ if (pid > 0 && pid !== deps.ownPid && isAlive(pid)) victims.add(pid);
52821
+ }
52822
+ } catch (e) {
52823
+ deps.logger?.warn("sshd: stale-sshd cmdline scan failed", { err: e.message });
52824
+ }
52825
+ if (victims.size === 0) {
52826
+ try {
52827
+ import_node_fs34.default.unlinkSync(pidFile);
52828
+ } catch {
52829
+ }
52830
+ return;
52831
+ }
52832
+ for (const pid of victims) {
52833
+ deps.logger?.warn("sshd: killing stale sshd before respawn", { pid });
52834
+ killPid(pid, "SIGKILL");
52835
+ }
52836
+ await sleep2(deps.reapWaitMs ?? 300);
52837
+ try {
52838
+ import_node_fs34.default.unlinkSync(pidFile);
52839
+ } catch {
52840
+ }
52841
+ }
52842
+ async function defaultScanSshdPidsByCmdline(configPath, logger) {
52843
+ if (process.platform === "win32") return [];
52844
+ return new Promise((resolve6) => {
52845
+ const ps = (0, import_node_child_process10.spawn)("ps", ["-axo", "pid=,command="], { stdio: ["ignore", "pipe", "ignore"] });
52846
+ let buf = "";
52847
+ ps.stdout.on("data", (c) => {
52848
+ buf += c.toString();
52849
+ });
52850
+ ps.on("exit", () => {
52851
+ const pids = [];
52852
+ for (const line of buf.split("\n")) {
52853
+ const m2 = /^\s*(\d+)\s+(.*)$/.exec(line);
52854
+ if (!m2) continue;
52855
+ const cmd = m2[2];
52856
+ if (!/\bsshd\b/.test(cmd)) continue;
52857
+ if (!cmd.includes(configPath)) continue;
52858
+ const pid = parseInt(m2[1], 10);
52859
+ if (Number.isFinite(pid) && pid > 0) pids.push(pid);
52860
+ }
52861
+ resolve6(pids);
52862
+ });
52863
+ ps.on("error", (e) => {
52864
+ logger?.warn("sshd: ps scan failed", { err: e.message });
52865
+ resolve6([]);
52866
+ });
52867
+ });
52868
+ }
52869
+
52870
+ // src/sshd/jail-script.ts
52871
+ var import_node_fs35 = __toESM(require("fs"), 1);
52872
+ var import_node_path36 = __toESM(require("path"), 1);
52873
+ var CLAWD_SSH_JAIL_SCRIPT = String.raw`#!/usr/bin/env bash
52874
+ # clawd-ssh-jail — SSH reverse access sandbox wrapper (managed by clawd; do not edit)
52875
+ #
52876
+ # 由 sshd authorized_keys 的 command= 强制入口调用。
52877
+ # 用法: sshd 会以 \`clawd-ssh-jail <deviceId>\` 起本脚本;$SSH_ORIGINAL_COMMAND = client
52878
+ # 真实请求(interactive shell 时为空)。
52879
+ #
52880
+ # 职责:
52881
+ # 1. 读 ~/.clawd/contacts.json 找 contact.exposedDirs
52882
+ # 2. macOS 用 sandbox-exec + sbpl; Linux 用 bwrap
52883
+ # 3. exec 沙箱 shell
52884
+
52885
+ set -euo pipefail
52886
+
52887
+ DEVICE_ID="\${1:-}"
52888
+ if [ -z "$DEVICE_ID" ]; then
52889
+ echo "clawd-ssh-jail: missing deviceId" >&2
52890
+ exit 1
52891
+ fi
52892
+
52893
+ CONTACTS="\${HOME}/.clawd/contacts.json"
52894
+ if [ ! -f "$CONTACTS" ]; then
52895
+ echo "clawd-ssh-jail: contacts.json missing" >&2
52896
+ exit 1
52897
+ fi
52898
+
52899
+ # 读 contact 的 exposedDirs (mac/linux 都自带 python3)
52900
+ EXPOSED_JSON=$(python3 -c "
52901
+ import json, sys
52902
+ with open('$CONTACTS') as f:
52903
+ data = json.load(f)
52904
+ for c in data.get('contacts', []):
52905
+ if c.get('deviceId') == '$DEVICE_ID':
52906
+ if not c.get('sshAllowed'):
52907
+ print('DENIED', file=sys.stderr); sys.exit(2)
52908
+ for d in c.get('exposedDirs', []):
52909
+ print(d)
52910
+ sys.exit(0)
52911
+ sys.exit(3)
52912
+ ")
52913
+
52914
+ if [ -z "$EXPOSED_JSON" ]; then
52915
+ echo "clawd-ssh-jail: contact not found or no exposed dirs" >&2
52916
+ exit 1
52917
+ fi
52918
+
52919
+ # 校验路径安全(bash 侧二次防御)
52920
+ while IFS= read -r line; do
52921
+ case "$line" in
52922
+ /*) : ;;
52923
+ *) echo "clawd-ssh-jail: bad path: $line" >&2; exit 1 ;;
52924
+ esac
52925
+ case "$line" in
52926
+ *[\"\'\`\$\;\|\&\(\)\{\}\[\]\<\>\*\?]*)
52927
+ echo "clawd-ssh-jail: unsafe path: $line" >&2; exit 1 ;;
52928
+ esac
52929
+ done <<< "$EXPOSED_JSON"
52930
+
52931
+ CMD="\${SSH_ORIGINAL_COMMAND:-}"
52932
+ if [ -z "$CMD" ]; then
52933
+ SHELL_CMD=(bash --login)
52934
+ else
52935
+ SHELL_CMD=(bash -c "$CMD")
52936
+ fi
52937
+
52938
+ case "$(uname -s)" in
52939
+ Darwin)
52940
+ POLICY="(version 1)
52941
+ (deny default)
52942
+ (allow process*)
52943
+ (allow signal (target self))
52944
+ (allow sysctl-read)
52945
+ (allow mach-lookup)
52946
+ (allow file-read-metadata)
52947
+ (allow network*)
52948
+ (allow file-read* (subpath \"/usr\"))
52949
+ (allow file-read* (subpath \"/bin\"))
52950
+ (allow file-read* (subpath \"/sbin\"))
52951
+ (allow file-read* (subpath \"/System\"))
52952
+ (allow file-read* (subpath \"/Library\"))
52953
+ (allow file-read* (subpath \"/etc\"))
52954
+ (allow file-read* (subpath \"/private/etc\"))"
52955
+ while IFS= read -r d; do
52956
+ POLICY+="
52957
+ (allow file-read* file-write* (subpath \"$d\"))"
52958
+ done <<< "$EXPOSED_JSON"
52959
+ exec sandbox-exec -p "$POLICY" "\${SHELL_CMD[@]}"
52960
+ ;;
52961
+ Linux)
52962
+ BWRAP_ARGS=(
52963
+ --unshare-user --unshare-ipc --unshare-pid --unshare-uts
52964
+ --die-with-parent
52965
+ --proc /proc --dev /dev --tmpfs /tmp
52966
+ --ro-bind /usr /usr --ro-bind /bin /bin --ro-bind /sbin /sbin
52967
+ --ro-bind /lib /lib --ro-bind /etc /etc
52968
+ )
52969
+ if [ -d /lib64 ]; then BWRAP_ARGS+=(--ro-bind /lib64 /lib64); fi
52970
+ while IFS= read -r d; do
52971
+ BWRAP_ARGS+=(--bind "$d" "$d")
52972
+ done <<< "$EXPOSED_JSON"
52973
+ exec bwrap "\${BWRAP_ARGS[@]}" "\${SHELL_CMD[@]}"
52974
+ ;;
52975
+ *)
52976
+ echo "clawd-ssh-jail: unsupported OS $(uname -s)" >&2
52977
+ exit 1
52978
+ ;;
52979
+ esac
52980
+ `;
52981
+ function ensureJailScript(dataDir) {
52982
+ const binDir = import_node_path36.default.join(dataDir, "bin");
52983
+ import_node_fs35.default.mkdirSync(binDir, { recursive: true, mode: 493 });
52984
+ const target = import_node_path36.default.join(binDir, "clawd-ssh-jail");
52985
+ import_node_fs35.default.writeFileSync(target, CLAWD_SSH_JAIL_SCRIPT, { mode: 493 });
52986
+ return target;
52987
+ }
52988
+
52989
+ // src/sshd/sshd-manager.ts
52990
+ var SshdManager = class {
52991
+ constructor(deps) {
52992
+ this.deps = deps;
52993
+ this.sshdDir = import_node_path37.default.join(deps.dataDir, "sshd");
52994
+ this.startupTimeoutMs = deps.startupTimeoutMs ?? 15e3;
52995
+ }
52996
+ deps;
52997
+ proc = null;
52998
+ sshdDir;
52999
+ stopping = false;
53000
+ exitHookInstalled = false;
53001
+ startupTimeoutMs;
53002
+ get port() {
53003
+ return this.deps.port;
53004
+ }
53005
+ async start() {
53006
+ const { logger } = this.deps;
53007
+ await (this.deps.killStaleImpl ?? killStaleSshd)({
53008
+ dataDir: this.deps.dataDir,
53009
+ ownPid: process.pid,
53010
+ logger
53011
+ });
53012
+ import_node_fs36.default.mkdirSync(this.sshdDir, { recursive: true, mode: 448 });
53013
+ import_node_fs36.default.mkdirSync(import_node_path37.default.join(this.sshdDir, "authorized_keys.d"), { recursive: true, mode: 448 });
53014
+ ensureJailScript(this.deps.dataDir);
53015
+ const hostKeyPath = import_node_path37.default.join(this.sshdDir, "host_key");
53016
+ if (!import_node_fs36.default.existsSync(hostKeyPath)) {
53017
+ await this.generateHostKey(hostKeyPath);
53018
+ }
53019
+ const akFile = import_node_path37.default.join(this.sshdDir, "authorized_keys.d", "clawd-contacts");
53020
+ if (!import_node_fs36.default.existsSync(akFile)) {
53021
+ import_node_fs36.default.writeFileSync(akFile, "", { mode: 384 });
53022
+ }
53023
+ const configPath = import_node_path37.default.join(this.sshdDir, "sshd_config");
53024
+ const config = buildSshdConfig({
53025
+ listenAddress: "127.0.0.1",
53026
+ port: this.deps.port,
53027
+ hostKeyPath,
53028
+ authorizedKeysFile: akFile,
53029
+ pidFilePath: import_node_path37.default.join(this.sshdDir, "sshd.pid")
53030
+ });
53031
+ import_node_fs36.default.writeFileSync(configPath, config, { mode: 384 });
53032
+ const sshdBin = this.deps.sshdBin ?? "/usr/sbin/sshd";
53033
+ const proc = (this.deps.spawnImpl ?? import_node_child_process11.spawn)(sshdBin, ["-D", "-e", "-f", configPath], {
53034
+ stdio: ["ignore", "pipe", "pipe"]
53035
+ });
53036
+ const logStream = import_node_fs36.default.createWriteStream(import_node_path37.default.join(this.sshdDir, "sshd.log"), {
53037
+ flags: "a",
53038
+ mode: 384
53039
+ });
53040
+ logStream.on("error", () => {
53041
+ });
53042
+ const tee = (c) => {
53043
+ logStream.write(String(c));
53044
+ };
53045
+ proc.stdout?.on("data", tee);
53046
+ proc.stderr?.on("data", tee);
53047
+ proc.once("exit", () => logStream.end());
53048
+ const ready = await waitForSshdReady(proc, this.startupTimeoutMs);
53049
+ if (!ready.ok) {
53050
+ try {
53051
+ proc.kill("SIGTERM");
53052
+ } catch {
53053
+ }
53054
+ const tail = ready.output.slice(-500);
53055
+ const msg = tail ? `${ready.error}
53056
+ ${tail}` : ready.error;
53057
+ throw new Error(msg);
53058
+ }
53059
+ if (typeof proc.pid === "number") writeSshdPid(this.deps.dataDir, proc.pid);
53060
+ this.proc = proc;
53061
+ this.installProcessExitHandlersIfNeeded();
53062
+ this.attachExitListener(proc);
53063
+ logger?.info("sshd: up", { port: this.deps.port, pid: proc.pid ?? null });
53064
+ return { port: this.deps.port };
53065
+ }
53066
+ async stop() {
53067
+ this.stopping = true;
53068
+ const proc = this.proc;
53069
+ this.proc = null;
53070
+ if (!proc) {
53071
+ clearSshdPid(this.deps.dataDir);
53072
+ return;
53073
+ }
53074
+ proc.kill("SIGTERM");
53075
+ await new Promise((resolve6) => {
53076
+ const t = setTimeout(() => {
53077
+ try {
53078
+ proc.kill("SIGKILL");
53079
+ } catch {
53080
+ }
53081
+ resolve6();
53082
+ }, 5e3);
53083
+ proc.once("exit", () => {
53084
+ clearTimeout(t);
53085
+ resolve6();
53086
+ });
53087
+ });
53088
+ clearSshdPid(this.deps.dataDir);
53089
+ }
53090
+ killSync() {
53091
+ const proc = this.proc;
53092
+ this.proc = null;
53093
+ clearSshdPid(this.deps.dataDir);
53094
+ if (!proc) return;
53095
+ try {
53096
+ proc.kill("SIGTERM");
53097
+ } catch {
53098
+ }
53099
+ }
53100
+ attachExitListener(proc) {
53101
+ proc.on("exit", (code) => {
53102
+ this.deps.logger?.warn("sshd exited", { code });
53103
+ if (this.stopping) return;
53104
+ this.proc = null;
53105
+ this.deps.onSshdExit?.({ code });
53106
+ });
53107
+ }
53108
+ installProcessExitHandlersIfNeeded() {
53109
+ if (this.exitHookInstalled) return;
53110
+ if (this.deps.installProcessExitHandlers !== true) return;
53111
+ this.exitHookInstalled = true;
53112
+ const sync = () => this.killSync();
53113
+ process.once("exit", sync);
53114
+ process.once("SIGHUP", sync);
53115
+ process.once("uncaughtException", sync);
53116
+ }
53117
+ async generateHostKey(hostKeyPath) {
53118
+ const keygenBin = this.deps.keygenBin ?? "/usr/bin/ssh-keygen";
53119
+ await new Promise((resolve6, reject) => {
53120
+ const p2 = (this.deps.spawnImpl ?? import_node_child_process11.spawn)(
53121
+ keygenBin,
53122
+ ["-t", "ed25519", "-f", hostKeyPath, "-N", "", "-q"],
53123
+ { stdio: "ignore" }
53124
+ );
53125
+ p2.on("exit", (code) => code === 0 ? resolve6() : reject(new Error(`ssh-keygen exit ${code}`)));
53126
+ p2.on("error", reject);
53127
+ });
53128
+ try {
53129
+ import_node_fs36.default.chmodSync(hostKeyPath, 384);
53130
+ } catch {
53131
+ }
53132
+ }
53133
+ };
53134
+ async function waitForSshdReady(proc, timeoutMs) {
53135
+ return new Promise((resolve6) => {
53136
+ let settled = false;
53137
+ let buf = "";
53138
+ const finish = (r) => {
53139
+ if (settled) return;
53140
+ settled = true;
53141
+ cleanup();
53142
+ resolve6(r);
53143
+ };
53144
+ const onData = (chunk) => {
53145
+ buf += String(chunk);
53146
+ if (/Server listening on/i.test(buf)) finish({ ok: true });
53147
+ if (/fatal:/i.test(buf) || /error: Bind to port/i.test(buf)) {
53148
+ finish({ ok: false, error: "sshd startup failed", output: buf });
53149
+ }
53150
+ };
53151
+ const onExit = (code) => finish({ ok: false, error: `sshd exited before ready (code=${code})`, output: buf });
53152
+ const onErr = (err) => finish({ ok: false, error: `sshd spawn error: ${err.message}`, output: buf });
53153
+ const cleanup = () => {
53154
+ proc.stdout?.off("data", onData);
53155
+ proc.stderr?.off("data", onData);
53156
+ proc.off("exit", onExit);
53157
+ proc.off("error", onErr);
53158
+ clearTimeout(timer);
53159
+ };
53160
+ proc.stdout?.on("data", onData);
53161
+ proc.stderr?.on("data", onData);
53162
+ proc.on("exit", onExit);
53163
+ proc.on("error", onErr);
53164
+ const timer = setTimeout(
53165
+ () => finish({ ok: false, error: `sshd startup timeout after ${timeoutMs}ms`, output: buf }),
53166
+ timeoutMs
53167
+ );
53168
+ });
53169
+ }
53170
+
53171
+ // src/sshd/authorized-keys.ts
53172
+ var import_node_fs37 = __toESM(require("fs"), 1);
53173
+ var import_node_path38 = __toESM(require("path"), 1);
53174
+ var JAIL_BIN_PATH_ENV = "CLAWD_JAIL_BIN_PATH";
53175
+ var AUTHORIZED_KEYS_FILE = "clawd-contacts";
53176
+ function jailBinPath() {
53177
+ return process.env[JAIL_BIN_PATH_ENV] ?? import_node_path38.default.join(process.env.HOME ?? "", ".clawd", "bin", "clawd-ssh-jail");
53178
+ }
53179
+ function rebuildAuthorizedKeys(store, sshdDir) {
53180
+ const akDir = import_node_path38.default.join(sshdDir, "authorized_keys.d");
53181
+ const target = import_node_path38.default.join(akDir, AUTHORIZED_KEYS_FILE);
53182
+ import_node_fs37.default.mkdirSync(akDir, { recursive: true, mode: 448 });
53183
+ const lines = ["# managed by clawd; do not edit", ""];
53184
+ for (const c of store.list()) {
53185
+ if (!c.sshAllowed) continue;
53186
+ const safe = /^[A-Za-z0-9_.-]+$/.test(c.deviceId);
53187
+ if (!safe) continue;
53188
+ const pubkey = readIssuedPubkey(sshdDir, c.deviceId);
53189
+ if (!pubkey) continue;
53190
+ const bin = jailBinPath();
53191
+ lines.push(`command="${bin} ${c.deviceId}",restrict ${pubkey.trim()}`);
53192
+ lines.push(`# contact:${c.deviceId}`);
53193
+ }
53194
+ const body = lines.join("\n") + "\n";
53195
+ const tmp = `${target}.tmp-${process.pid}-${Date.now()}`;
53196
+ import_node_fs37.default.writeFileSync(tmp, body, { mode: 384 });
53197
+ import_node_fs37.default.renameSync(tmp, target);
53198
+ }
53199
+ function readIssuedPubkey(sshdDir, deviceId) {
53200
+ const safeId = deviceId.replace(/[\/\\]/g, "_");
53201
+ const p2 = import_node_path38.default.join(sshdDir, "keys", `${safeId}.ed25519.pub`);
53202
+ try {
53203
+ return import_node_fs37.default.readFileSync(p2, "utf8");
53204
+ } catch {
53205
+ return null;
53206
+ }
53207
+ }
53208
+
52645
53209
  // src/tunnel/device-key.ts
52646
53210
  var import_node_os14 = __toESM(require("os"), 1);
52647
- var import_node_path35 = __toESM(require("path"), 1);
53211
+ var import_node_path39 = __toESM(require("path"), 1);
52648
53212
  var import_node_crypto11 = __toESM(require("crypto"), 1);
52649
53213
  var DERIVE_SALT = "clawd-tunnel-device-v1";
52650
53214
  function deriveStableDeviceKey(opts = {}) {
52651
53215
  const hostname = opts.hostname ?? import_node_os14.default.hostname();
52652
53216
  const uid = opts.uid ?? (typeof import_node_os14.default.userInfo === "function" ? import_node_os14.default.userInfo().uid : 0);
52653
53217
  const home = opts.home ?? import_node_os14.default.homedir();
52654
- const defaultDataDir = import_node_path35.default.resolve(import_node_path35.default.join(home, ".clawd"));
52655
- const normalizedDataDir = opts.dataDir ? import_node_path35.default.resolve(opts.dataDir) : null;
53218
+ const defaultDataDir = import_node_path39.default.resolve(import_node_path39.default.join(home, ".clawd"));
53219
+ const normalizedDataDir = opts.dataDir ? import_node_path39.default.resolve(opts.dataDir) : null;
52656
53220
  const isDefaultDir = normalizedDataDir == null || normalizedDataDir === defaultDataDir;
52657
53221
  const input = isDefaultDir ? `${hostname}::${uid}` : `${hostname}::${uid}::${normalizedDataDir}`;
52658
53222
  return import_node_crypto11.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
52659
53223
  }
52660
53224
 
52661
53225
  // src/auth-store.ts
52662
- var import_node_fs34 = __toESM(require("fs"), 1);
52663
- var import_node_path36 = __toESM(require("path"), 1);
53226
+ var import_node_fs38 = __toESM(require("fs"), 1);
53227
+ var import_node_path40 = __toESM(require("path"), 1);
52664
53228
  var import_node_crypto12 = __toESM(require("crypto"), 1);
52665
53229
  var AUTH_FILE_NAME = "auth.json";
52666
53230
  function authFilePath(dataDir) {
52667
- return import_node_path36.default.join(dataDir, AUTH_FILE_NAME);
53231
+ return import_node_path40.default.join(dataDir, AUTH_FILE_NAME);
52668
53232
  }
52669
53233
  function loadOrCreateAuthFile(opts) {
52670
53234
  const file = authFilePath(opts.dataDir);
@@ -52700,7 +53264,7 @@ function defaultGenerateOwnerPrincipalId() {
52700
53264
  }
52701
53265
  function readAuthFile(file) {
52702
53266
  try {
52703
- const raw = import_node_fs34.default.readFileSync(file, "utf8");
53267
+ const raw = import_node_fs38.default.readFileSync(file, "utf8");
52704
53268
  const parsed = JSON.parse(raw);
52705
53269
  if (typeof parsed?.token !== "string" || parsed.token.length === 0) {
52706
53270
  return null;
@@ -52720,25 +53284,25 @@ function readAuthFile(file) {
52720
53284
  }
52721
53285
  }
52722
53286
  function writeAuthFile(file, content) {
52723
- import_node_fs34.default.mkdirSync(import_node_path36.default.dirname(file), { recursive: true });
52724
- import_node_fs34.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
53287
+ import_node_fs38.default.mkdirSync(import_node_path40.default.dirname(file), { recursive: true });
53288
+ import_node_fs38.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
52725
53289
  try {
52726
- import_node_fs34.default.chmodSync(file, 384);
53290
+ import_node_fs38.default.chmodSync(file, 384);
52727
53291
  } catch {
52728
53292
  }
52729
53293
  }
52730
53294
 
52731
53295
  // src/owner-profile.ts
52732
- var import_node_fs35 = __toESM(require("fs"), 1);
53296
+ var import_node_fs39 = __toESM(require("fs"), 1);
52733
53297
  var import_node_os15 = __toESM(require("os"), 1);
52734
- var import_node_path37 = __toESM(require("path"), 1);
53298
+ var import_node_path41 = __toESM(require("path"), 1);
52735
53299
  var PROFILE_FILENAME = "profile.json";
52736
53300
  function loadOwnerDisplayName(dataDir) {
52737
53301
  const fallback = import_node_os15.default.userInfo().username;
52738
- const profilePath = import_node_path37.default.join(dataDir, PROFILE_FILENAME);
53302
+ const profilePath = import_node_path41.default.join(dataDir, PROFILE_FILENAME);
52739
53303
  let raw;
52740
53304
  try {
52741
- raw = import_node_fs35.default.readFileSync(profilePath, "utf8");
53305
+ raw = import_node_fs39.default.readFileSync(profilePath, "utf8");
52742
53306
  } catch {
52743
53307
  return fallback;
52744
53308
  }
@@ -52761,18 +53325,18 @@ function loadOwnerDisplayName(dataDir) {
52761
53325
  }
52762
53326
 
52763
53327
  // src/feishu-auth/owner-identity-store.ts
52764
- var import_node_fs36 = __toESM(require("fs"), 1);
52765
- var import_node_path38 = __toESM(require("path"), 1);
53328
+ var import_node_fs40 = __toESM(require("fs"), 1);
53329
+ var import_node_path42 = __toESM(require("path"), 1);
52766
53330
  var OWNER_IDENTITY_FILE_NAME = "owner-identity.json";
52767
53331
  var OwnerIdentityStore = class {
52768
53332
  file;
52769
53333
  constructor(dataDir) {
52770
- this.file = import_node_path38.default.join(dataDir, OWNER_IDENTITY_FILE_NAME);
53334
+ this.file = import_node_path42.default.join(dataDir, OWNER_IDENTITY_FILE_NAME);
52771
53335
  }
52772
53336
  read() {
52773
53337
  let raw;
52774
53338
  try {
52775
- raw = import_node_fs36.default.readFileSync(this.file, "utf8");
53339
+ raw = import_node_fs40.default.readFileSync(this.file, "utf8");
52776
53340
  } catch {
52777
53341
  return null;
52778
53342
  }
@@ -52800,16 +53364,16 @@ var OwnerIdentityStore = class {
52800
53364
  };
52801
53365
  }
52802
53366
  write(record) {
52803
- import_node_fs36.default.mkdirSync(import_node_path38.default.dirname(this.file), { recursive: true });
52804
- import_node_fs36.default.writeFileSync(this.file, JSON.stringify(record, null, 2), { mode: 384 });
53367
+ import_node_fs40.default.mkdirSync(import_node_path42.default.dirname(this.file), { recursive: true });
53368
+ import_node_fs40.default.writeFileSync(this.file, JSON.stringify(record, null, 2), { mode: 384 });
52805
53369
  try {
52806
- import_node_fs36.default.chmodSync(this.file, 384);
53370
+ import_node_fs40.default.chmodSync(this.file, 384);
52807
53371
  } catch {
52808
53372
  }
52809
53373
  }
52810
53374
  clear() {
52811
53375
  try {
52812
- import_node_fs36.default.unlinkSync(this.file);
53376
+ import_node_fs40.default.unlinkSync(this.file);
52813
53377
  } catch (err) {
52814
53378
  const code = err?.code;
52815
53379
  if (code !== "ENOENT") throw err;
@@ -52930,9 +53494,9 @@ var CentralClientError = class extends Error {
52930
53494
  code;
52931
53495
  cause;
52932
53496
  };
52933
- async function centralRequest(opts, path68, init) {
53497
+ async function centralRequest(opts, path73, init) {
52934
53498
  const f = opts.fetchImpl ?? globalThis.fetch;
52935
- const url = `${opts.api.replace(/\/+$/, "")}${path68}`;
53499
+ const url = `${opts.api.replace(/\/+$/, "")}${path73}`;
52936
53500
  const ctrl = new AbortController();
52937
53501
  const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 15e3);
52938
53502
  let res;
@@ -53074,8 +53638,8 @@ function verifyConnectToken(args) {
53074
53638
  }
53075
53639
 
53076
53640
  // src/feishu-auth/server-key.ts
53077
- var fs46 = __toESM(require("fs"), 1);
53078
- var path47 = __toESM(require("path"), 1);
53641
+ var fs50 = __toESM(require("fs"), 1);
53642
+ var path51 = __toESM(require("path"), 1);
53079
53643
  var FILE_NAME2 = "server-signing-key.json";
53080
53644
  var ServerKeyStore = class {
53081
53645
  constructor(dataDir) {
@@ -53083,12 +53647,12 @@ var ServerKeyStore = class {
53083
53647
  }
53084
53648
  dataDir;
53085
53649
  filePath() {
53086
- return path47.join(this.dataDir, FILE_NAME2);
53650
+ return path51.join(this.dataDir, FILE_NAME2);
53087
53651
  }
53088
53652
  /** 读缓存的公钥;无缓存 / 损坏 → null(调用方决定是否触发拉取) */
53089
53653
  read() {
53090
53654
  try {
53091
- const raw = fs46.readFileSync(this.filePath(), "utf8");
53655
+ const raw = fs50.readFileSync(this.filePath(), "utf8");
53092
53656
  const parsed = JSON.parse(raw);
53093
53657
  if (typeof parsed.publicKeyPem === "string" && parsed.publicKeyPem.includes("PUBLIC KEY")) {
53094
53658
  return parsed.publicKeyPem;
@@ -53103,12 +53667,12 @@ var ServerKeyStore = class {
53103
53667
  publicKeyPem,
53104
53668
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
53105
53669
  };
53106
- fs46.mkdirSync(this.dataDir, { recursive: true });
53107
- fs46.writeFileSync(this.filePath(), JSON.stringify(content, null, 2), { mode: 384 });
53670
+ fs50.mkdirSync(this.dataDir, { recursive: true });
53671
+ fs50.writeFileSync(this.filePath(), JSON.stringify(content, null, 2), { mode: 384 });
53108
53672
  }
53109
53673
  clear() {
53110
53674
  try {
53111
- fs46.unlinkSync(this.filePath());
53675
+ fs50.unlinkSync(this.filePath());
53112
53676
  } catch {
53113
53677
  }
53114
53678
  }
@@ -53121,12 +53685,12 @@ init_protocol();
53121
53685
  init_protocol();
53122
53686
 
53123
53687
  // src/session/fork.ts
53124
- var import_node_fs37 = __toESM(require("fs"), 1);
53688
+ var import_node_fs41 = __toESM(require("fs"), 1);
53125
53689
  var import_node_os16 = __toESM(require("os"), 1);
53126
- var import_node_path39 = __toESM(require("path"), 1);
53690
+ var import_node_path43 = __toESM(require("path"), 1);
53127
53691
  init_claude_history();
53128
53692
  function readJsonlEntries(file) {
53129
- const raw = import_node_fs37.default.readFileSync(file, "utf8");
53693
+ const raw = import_node_fs41.default.readFileSync(file, "utf8");
53130
53694
  const out = [];
53131
53695
  for (const line of raw.split("\n")) {
53132
53696
  const t = line.trim();
@@ -53139,10 +53703,10 @@ function readJsonlEntries(file) {
53139
53703
  return out;
53140
53704
  }
53141
53705
  function forkSession(input) {
53142
- const baseDir = input.baseDir ?? import_node_path39.default.join(import_node_os16.default.homedir(), ".claude");
53143
- const projectDir = import_node_path39.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
53144
- const sourceFile = import_node_path39.default.join(projectDir, `${input.toolSessionId}.jsonl`);
53145
- if (!import_node_fs37.default.existsSync(sourceFile)) {
53706
+ const baseDir = input.baseDir ?? import_node_path43.default.join(import_node_os16.default.homedir(), ".claude");
53707
+ const projectDir = import_node_path43.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
53708
+ const sourceFile = import_node_path43.default.join(projectDir, `${input.toolSessionId}.jsonl`);
53709
+ if (!import_node_fs41.default.existsSync(sourceFile)) {
53146
53710
  throw new Error(`fork: source transcript not found: ${sourceFile}`);
53147
53711
  }
53148
53712
  const entries = readJsonlEntries(sourceFile);
@@ -53172,9 +53736,9 @@ function forkSession(input) {
53172
53736
  }
53173
53737
  forkedLines.push(JSON.stringify(forked));
53174
53738
  }
53175
- const forkedFilePath = import_node_path39.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
53176
- import_node_fs37.default.mkdirSync(projectDir, { recursive: true });
53177
- import_node_fs37.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
53739
+ const forkedFilePath = import_node_path43.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
53740
+ import_node_fs41.default.mkdirSync(projectDir, { recursive: true });
53741
+ import_node_fs41.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
53178
53742
  return { forkedToolSessionId, forkedFilePath };
53179
53743
  }
53180
53744
 
@@ -53526,7 +54090,7 @@ function buildPermissionHandlers(deps) {
53526
54090
  }
53527
54091
 
53528
54092
  // src/handlers/history.ts
53529
- var path50 = __toESM(require("path"), 1);
54093
+ var path54 = __toESM(require("path"), 1);
53530
54094
  init_protocol();
53531
54095
 
53532
54096
  // src/session/recent-dirs.ts
@@ -53544,7 +54108,7 @@ function listRecentDirs(store, limit = 50) {
53544
54108
  }
53545
54109
 
53546
54110
  // src/permission/persona-paths.ts
53547
- var path49 = __toESM(require("path"), 1);
54111
+ var path53 = __toESM(require("path"), 1);
53548
54112
  function getAllowedPersonaIds(grants, action) {
53549
54113
  const ids = /* @__PURE__ */ new Set();
53550
54114
  for (const g2 of grants) {
@@ -53557,42 +54121,42 @@ function getAllowedPersonaIds(grants, action) {
53557
54121
  return ids;
53558
54122
  }
53559
54123
  function isGuestPathAllowed(grants, absPath, personaRoot, action = "read", userWorkDir) {
53560
- const target = path49.resolve(absPath);
54124
+ const target = path53.resolve(absPath);
53561
54125
  if (userWorkDir) {
53562
- const u = path49.resolve(userWorkDir);
53563
- const usep = u.endsWith(path49.sep) ? "" : path49.sep;
54126
+ const u = path53.resolve(userWorkDir);
54127
+ const usep = u.endsWith(path53.sep) ? "" : path53.sep;
53564
54128
  if (target === u || target.startsWith(u + usep)) return true;
53565
54129
  }
53566
- const root = path49.resolve(personaRoot);
53567
- const sep3 = root.endsWith(path49.sep) ? "" : path49.sep;
54130
+ const root = path53.resolve(personaRoot);
54131
+ const sep3 = root.endsWith(path53.sep) ? "" : path53.sep;
53568
54132
  if (!target.startsWith(root + sep3)) return false;
53569
- const rel = path49.relative(root, target);
54133
+ const rel = path53.relative(root, target);
53570
54134
  if (!rel || rel.startsWith("..")) return false;
53571
- const personaId = rel.split(path49.sep)[0];
54135
+ const personaId = rel.split(path53.sep)[0];
53572
54136
  if (!personaId) return false;
53573
54137
  const allowed = getAllowedPersonaIds(grants, action);
53574
54138
  if (allowed === "*") return true;
53575
54139
  return allowed.has(personaId);
53576
54140
  }
53577
54141
  function personaIdFromPath(absPath, personaRoot) {
53578
- const root = path49.resolve(personaRoot);
53579
- const target = path49.resolve(absPath);
53580
- const sep3 = root.endsWith(path49.sep) ? "" : path49.sep;
54142
+ const root = path53.resolve(personaRoot);
54143
+ const target = path53.resolve(absPath);
54144
+ const sep3 = root.endsWith(path53.sep) ? "" : path53.sep;
53581
54145
  if (!target.startsWith(root + sep3)) return null;
53582
- const rel = path49.relative(root, target);
54146
+ const rel = path53.relative(root, target);
53583
54147
  if (!rel || rel.startsWith("..")) return null;
53584
- const id = rel.split(path49.sep)[0];
54148
+ const id = rel.split(path53.sep)[0];
53585
54149
  return id || null;
53586
54150
  }
53587
54151
  function isPathWithin(dir, absPath) {
53588
- const d = path49.resolve(dir);
53589
- const t = path49.resolve(absPath);
53590
- const sep3 = d.endsWith(path49.sep) ? "" : path49.sep;
54152
+ const d = path53.resolve(dir);
54153
+ const t = path53.resolve(absPath);
54154
+ const sep3 = d.endsWith(path53.sep) ? "" : path53.sep;
53591
54155
  return t === d || t.startsWith(d + sep3);
53592
54156
  }
53593
54157
  function isPathInGuestBoundary(personaRoot, personaId, userWorkDir, absPath) {
53594
54158
  if (userWorkDir && isPathWithin(userWorkDir, absPath)) return true;
53595
- return personaIdFromPath(path49.resolve(absPath), personaRoot) === personaId;
54159
+ return personaIdFromPath(path53.resolve(absPath), personaRoot) === personaId;
53596
54160
  }
53597
54161
 
53598
54162
  // src/handlers/history.ts
@@ -53618,7 +54182,7 @@ function buildHistoryHandlers(deps) {
53618
54182
  if (!pid) return false;
53619
54183
  return isGuestPathAllowed(
53620
54184
  ctx.grants,
53621
- path50.join(personaRoot, pid),
54185
+ path54.join(personaRoot, pid),
53622
54186
  personaRoot,
53623
54187
  "read",
53624
54188
  userWorkDir
@@ -53630,7 +54194,7 @@ function buildHistoryHandlers(deps) {
53630
54194
  };
53631
54195
  const list = async (frame, _client, ctx) => {
53632
54196
  const args = HistoryListArgs.parse(frame);
53633
- assertGuestPath(ctx, path50.resolve(args.projectPath), personaRoot, "history:list");
54197
+ assertGuestPath(ctx, path54.resolve(args.projectPath), personaRoot, "history:list");
53634
54198
  const sessions = await history.listSessions(args);
53635
54199
  return { response: { type: "history:list", sessions } };
53636
54200
  };
@@ -53662,13 +54226,13 @@ function buildHistoryHandlers(deps) {
53662
54226
  };
53663
54227
  const subagents = async (frame, _client, ctx) => {
53664
54228
  const args = HistorySubagentsArgs.parse(frame);
53665
- assertGuestPath(ctx, path50.resolve(args.cwd), personaRoot, "history:subagents", usersRoot);
54229
+ assertGuestPath(ctx, path54.resolve(args.cwd), personaRoot, "history:subagents", usersRoot);
53666
54230
  const subs = await history.listSubagents(args);
53667
54231
  return { response: { type: "history:subagents", subagents: subs } };
53668
54232
  };
53669
54233
  const subagentRead = async (frame, _client, ctx) => {
53670
54234
  const args = HistorySubagentReadArgs.parse(frame);
53671
- assertGuestPath(ctx, path50.resolve(args.cwd), personaRoot, "history:subagent-read", usersRoot);
54235
+ assertGuestPath(ctx, path54.resolve(args.cwd), personaRoot, "history:subagent-read", usersRoot);
53672
54236
  const res = await history.readSubagent(args);
53673
54237
  return { response: { type: "history:subagent-read", ...res } };
53674
54238
  };
@@ -53677,7 +54241,7 @@ function buildHistoryHandlers(deps) {
53677
54241
  if (ctx?.principal.kind === "guest" && personaRoot) {
53678
54242
  const userWorkDir = usersRoot ? deriveUserWorkDir(ctx.principal.id, usersRoot) : void 0;
53679
54243
  const filtered = dirs.filter(
53680
- (d) => isGuestPathAllowed(ctx.grants, path50.resolve(d.cwd), personaRoot, "read", userWorkDir)
54244
+ (d) => isGuestPathAllowed(ctx.grants, path54.resolve(d.cwd), personaRoot, "read", userWorkDir)
53681
54245
  );
53682
54246
  return { response: { type: "history:recentDirs", dirs: filtered } };
53683
54247
  }
@@ -53694,7 +54258,7 @@ function buildHistoryHandlers(deps) {
53694
54258
  }
53695
54259
 
53696
54260
  // src/handlers/workspace.ts
53697
- var path51 = __toESM(require("path"), 1);
54261
+ var path55 = __toESM(require("path"), 1);
53698
54262
  var os16 = __toESM(require("os"), 1);
53699
54263
  init_protocol();
53700
54264
  init_protocol();
@@ -53736,22 +54300,22 @@ function buildWorkspaceHandlers(deps) {
53736
54300
  const args = WorkspaceListArgs.parse(frame);
53737
54301
  const isGuest = ctx?.principal.kind === "guest";
53738
54302
  const fallbackCwd = isGuest && personaRoot ? personaRoot : os16.homedir();
53739
- const resolvedCwd = path51.resolve(args.cwd ?? fallbackCwd);
53740
- const target = args.path ? path51.resolve(resolvedCwd, args.path) : resolvedCwd;
54303
+ const resolvedCwd = path55.resolve(args.cwd ?? fallbackCwd);
54304
+ const target = args.path ? path55.resolve(resolvedCwd, args.path) : resolvedCwd;
53741
54305
  assertGuestPath2(ctx, target, personaRoot, "workspace:list", usersRoot);
53742
54306
  const res = workspace.list({ ...args, cwd: resolvedCwd });
53743
54307
  return { response: { type: "workspace:list", ...res } };
53744
54308
  };
53745
54309
  const read = async (frame, _client, ctx) => {
53746
54310
  const args = WorkspaceReadArgs.parse(frame);
53747
- const target = path51.isAbsolute(args.path) ? path51.resolve(args.path) : path51.resolve(args.cwd, args.path);
54311
+ const target = path55.isAbsolute(args.path) ? path55.resolve(args.path) : path55.resolve(args.cwd, args.path);
53748
54312
  assertGuestPath2(ctx, target, personaRoot, "workspace:read", usersRoot);
53749
54313
  const res = workspace.read(args);
53750
54314
  return { response: { type: "workspace:read", ...res } };
53751
54315
  };
53752
54316
  const skillsList = async (frame, _client, ctx) => {
53753
54317
  const args = SkillsListArgs.parse(frame);
53754
- const cwdAbs = path51.resolve(args.cwd);
54318
+ const cwdAbs = path55.resolve(args.cwd);
53755
54319
  assertGuestPath2(ctx, cwdAbs, personaRoot, "skills:list", usersRoot);
53756
54320
  const list2 = await getSkillsForTool(args.tool ?? "claude", cwdAbs);
53757
54321
  if (ctx?.principal.kind === "guest" && personaRoot) {
@@ -53763,7 +54327,7 @@ function buildWorkspaceHandlers(deps) {
53763
54327
  };
53764
54328
  const agentsList = async (frame, _client, ctx) => {
53765
54329
  const args = AgentsListArgs.parse(frame);
53766
- const cwdAbs = path51.resolve(args.cwd);
54330
+ const cwdAbs = path55.resolve(args.cwd);
53767
54331
  assertGuestPath2(ctx, cwdAbs, personaRoot, "agents:list", usersRoot);
53768
54332
  if (args.tool === "codex") {
53769
54333
  return { response: { type: "agents:list", agents: [] } };
@@ -53785,20 +54349,20 @@ function buildWorkspaceHandlers(deps) {
53785
54349
  }
53786
54350
 
53787
54351
  // src/handlers/git.ts
53788
- var path53 = __toESM(require("path"), 1);
54352
+ var path57 = __toESM(require("path"), 1);
53789
54353
  init_protocol();
53790
54354
  init_protocol();
53791
54355
 
53792
54356
  // src/workspace/git.ts
53793
- var import_node_child_process10 = require("child_process");
53794
- var import_node_fs38 = __toESM(require("fs"), 1);
53795
- var import_node_path40 = __toESM(require("path"), 1);
54357
+ var import_node_child_process12 = require("child_process");
54358
+ var import_node_fs42 = __toESM(require("fs"), 1);
54359
+ var import_node_path44 = __toESM(require("path"), 1);
53796
54360
  var import_node_util = require("util");
53797
- var pexec = (0, import_node_util.promisify)(import_node_child_process10.execFile);
54361
+ var pexec = (0, import_node_util.promisify)(import_node_child_process12.execFile);
53798
54362
  function normalizePath(p2) {
53799
- const resolved = import_node_path40.default.resolve(p2);
54363
+ const resolved = import_node_path44.default.resolve(p2);
53800
54364
  try {
53801
- return import_node_fs38.default.realpathSync(resolved);
54365
+ return import_node_fs42.default.realpathSync(resolved);
53802
54366
  } catch {
53803
54367
  return resolved;
53804
54368
  }
@@ -53872,7 +54436,7 @@ async function listGitBranches(cwd) {
53872
54436
  function assertGuestCwd(ctx, cwd, personaRoot, method, usersRoot) {
53873
54437
  if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
53874
54438
  const userWorkDir = usersRoot ? deriveUserWorkDir(ctx.principal.id, usersRoot) : void 0;
53875
- if (!isGuestPathAllowed(ctx.grants, path53.resolve(cwd), personaRoot, "read", userWorkDir)) {
54439
+ if (!isGuestPathAllowed(ctx.grants, path57.resolve(cwd), personaRoot, "read", userWorkDir)) {
53876
54440
  throw new ClawdError(
53877
54441
  ERROR_CODES.UNAUTHORIZED,
53878
54442
  `guest ${ctx.principal.id} cannot ${method} cwd ${cwd}`
@@ -54205,6 +54769,128 @@ function buildContactHandlers(deps) {
54205
54769
  };
54206
54770
  }
54207
54771
 
54772
+ // src/handlers/contact-ssh.ts
54773
+ init_protocol();
54774
+
54775
+ // src/sshd/key-issue.ts
54776
+ var import_node_fs43 = __toESM(require("fs"), 1);
54777
+ var import_node_path45 = __toESM(require("path"), 1);
54778
+ var import_node_child_process13 = require("child_process");
54779
+ function safeDeviceId(deviceId) {
54780
+ return deviceId.replace(/[\/\\]/g, "_");
54781
+ }
54782
+ async function issueContactSshKey(deviceId, sshdDir, opts = {}) {
54783
+ const safeId = safeDeviceId(deviceId);
54784
+ const keysDir = import_node_path45.default.join(sshdDir, "keys");
54785
+ import_node_fs43.default.mkdirSync(keysDir, { recursive: true, mode: 448 });
54786
+ const privPath = import_node_path45.default.join(keysDir, `${safeId}.ed25519`);
54787
+ const pubPath = `${privPath}.pub`;
54788
+ if (import_node_fs43.default.existsSync(privPath) && import_node_fs43.default.existsSync(pubPath)) {
54789
+ return {
54790
+ privateKeyPem: import_node_fs43.default.readFileSync(privPath, "utf8"),
54791
+ publicKeyLine: import_node_fs43.default.readFileSync(pubPath, "utf8").trim()
54792
+ };
54793
+ }
54794
+ const bin = opts.keygenBin ?? "/usr/bin/ssh-keygen";
54795
+ await new Promise((resolve6, reject) => {
54796
+ const p2 = (opts.spawnImpl ?? import_node_child_process13.spawn)(
54797
+ bin,
54798
+ ["-t", "ed25519", "-f", privPath, "-N", "", "-q", "-C", `clawd-contact-${safeId}`],
54799
+ { stdio: "ignore" }
54800
+ );
54801
+ p2.on("exit", (code) => code === 0 ? resolve6() : reject(new Error(`ssh-keygen exit ${code}`)));
54802
+ p2.on("error", reject);
54803
+ });
54804
+ try {
54805
+ import_node_fs43.default.chmodSync(privPath, 384);
54806
+ } catch {
54807
+ }
54808
+ try {
54809
+ import_node_fs43.default.chmodSync(pubPath, 420);
54810
+ } catch {
54811
+ }
54812
+ return {
54813
+ privateKeyPem: import_node_fs43.default.readFileSync(privPath, "utf8"),
54814
+ publicKeyLine: import_node_fs43.default.readFileSync(pubPath, "utf8").trim()
54815
+ };
54816
+ }
54817
+
54818
+ // src/handlers/contact-ssh.ts
54819
+ function ensureOwner2(ctx) {
54820
+ if (!ctx || ctx.principal.kind !== "owner") {
54821
+ throw new ClawdError(
54822
+ ERROR_CODES.UNAUTHORIZED,
54823
+ "UNAUTHORIZED: contact:setSshAccess requires owner ctx"
54824
+ );
54825
+ }
54826
+ }
54827
+ function ensureGuest(ctx) {
54828
+ if (!ctx || ctx.principal.kind !== "guest") {
54829
+ throw new ClawdError(
54830
+ ERROR_CODES.UNAUTHORIZED,
54831
+ "UNAUTHORIZED: contact:sshKey:issue requires guest ctx"
54832
+ );
54833
+ }
54834
+ return ctx.principal.id;
54835
+ }
54836
+ function buildContactSshHandlers(deps) {
54837
+ const setSshAccess = async (frame, _client, ctx) => {
54838
+ ensureOwner2(ctx);
54839
+ const { type: _t, requestId: _r, ...rest } = frame;
54840
+ const args = ContactSetSshAccessArgsSchema.parse(rest);
54841
+ const hit = deps.store.setSshAccess(args.deviceId, {
54842
+ sshAllowed: args.sshAllowed,
54843
+ exposedDirs: args.exposedDirs
54844
+ });
54845
+ if (!hit) {
54846
+ throw new ClawdError(
54847
+ ERROR_CODES.CONTACT_NOT_FOUND,
54848
+ `CONTACT_NOT_FOUND: contact ${args.deviceId} not in store`
54849
+ );
54850
+ }
54851
+ rebuildAuthorizedKeys(deps.store, deps.sshdDir);
54852
+ deps.broadcast({
54853
+ type: "contact:ssh-access-updated",
54854
+ deviceId: args.deviceId,
54855
+ sshAllowed: args.sshAllowed,
54856
+ exposedDirs: args.exposedDirs
54857
+ });
54858
+ return {
54859
+ response: {
54860
+ type: "contact:setSshAccess:ok",
54861
+ deviceId: args.deviceId,
54862
+ sshAllowed: args.sshAllowed,
54863
+ exposedDirs: args.exposedDirs
54864
+ }
54865
+ };
54866
+ };
54867
+ const sshKeyIssue = async (frame, _client, ctx) => {
54868
+ const callerDeviceId = ensureGuest(ctx);
54869
+ const { type: _t, requestId: _r, ...rest } = frame;
54870
+ ContactSshKeyIssueArgsSchema.parse(rest);
54871
+ const contact = deps.store.get(callerDeviceId);
54872
+ if (!contact || !contact.sshAllowed) {
54873
+ throw new ClawdError(
54874
+ ERROR_CODES.UNAUTHORIZED,
54875
+ `UNAUTHORIZED: contact ${callerDeviceId} not authorized for SSH`
54876
+ );
54877
+ }
54878
+ const { privateKeyPem, publicKeyLine } = await issueContactSshKey(callerDeviceId, deps.sshdDir);
54879
+ rebuildAuthorizedKeys(deps.store, deps.sshdDir);
54880
+ return {
54881
+ response: {
54882
+ type: "contact:sshKey:issue:ok",
54883
+ privateKeyPem,
54884
+ publicKeyLine
54885
+ }
54886
+ };
54887
+ };
54888
+ return {
54889
+ "contact:setSshAccess": setSshAccess,
54890
+ "contact:sshKey:issue": sshKeyIssue
54891
+ };
54892
+ }
54893
+
54208
54894
  // src/handlers/whoami.ts
54209
54895
  init_protocol();
54210
54896
  function buildWhoamiHandler(deps) {
@@ -54300,7 +54986,7 @@ function buildFeishuAuthHandlers(deps) {
54300
54986
 
54301
54987
  // src/handlers/device.ts
54302
54988
  init_protocol();
54303
- function ensureOwner2(ctx) {
54989
+ function ensureOwner3(ctx) {
54304
54990
  if (!ctx || ctx.principal.kind !== "owner") {
54305
54991
  throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "UNAUTHORIZED: device:* requires owner ctx");
54306
54992
  }
@@ -54308,14 +54994,14 @@ function ensureOwner2(ctx) {
54308
54994
  function buildDeviceHandlers(deps) {
54309
54995
  const now = deps.now ?? Date.now;
54310
54996
  const list = async (_frame, _client, ctx) => {
54311
- ensureOwner2(ctx);
54997
+ ensureOwner3(ctx);
54312
54998
  const devices = await deps.listDevices();
54313
54999
  return {
54314
55000
  response: { type: "device:list:ok", devices }
54315
55001
  };
54316
55002
  };
54317
55003
  const connect = async (frame, _client, ctx) => {
54318
- ensureOwner2(ctx);
55004
+ ensureOwner3(ctx);
54319
55005
  const { type: _t, requestId: _r, ...rest } = frame;
54320
55006
  const args = DeviceConnectArgsSchema.parse(rest);
54321
55007
  const exchanged = await deps.exchange(args.deviceId);
@@ -54358,7 +55044,9 @@ function buildDeviceHandlers(deps) {
54358
55044
  connectToken: exchanged.token,
54359
55045
  grants: wh.grants,
54360
55046
  addedAt: now(),
54361
- pinnedAt: null
55047
+ pinnedAt: null,
55048
+ sshAllowed: false,
55049
+ exposedDirs: []
54362
55050
  };
54363
55051
  deps.store.upsert(contact);
54364
55052
  deps.broadcast({ type: "contact:added", contact });
@@ -54514,7 +55202,7 @@ function buildPersonaHandlers(deps) {
54514
55202
  }
54515
55203
 
54516
55204
  // src/handlers/attachment.ts
54517
- var import_node_path41 = __toESM(require("path"), 1);
55205
+ var import_node_path46 = __toESM(require("path"), 1);
54518
55206
  init_protocol();
54519
55207
  init_protocol();
54520
55208
  var DEFAULT_TTL_SECONDS = 24 * 3600;
@@ -54594,12 +55282,12 @@ function buildAttachmentHandlers(deps) {
54594
55282
  `session ${args.sessionId} scope unresolved`
54595
55283
  );
54596
55284
  }
54597
- const cwdAbs = import_node_path41.default.resolve(sessionFile.cwd);
54598
- const candidateAbs = import_node_path41.default.isAbsolute(args.relPath) ? import_node_path41.default.resolve(args.relPath) : import_node_path41.default.resolve(cwdAbs, args.relPath);
55285
+ const cwdAbs = import_node_path46.default.resolve(sessionFile.cwd);
55286
+ const candidateAbs = import_node_path46.default.isAbsolute(args.relPath) ? import_node_path46.default.resolve(args.relPath) : import_node_path46.default.resolve(cwdAbs, args.relPath);
54599
55287
  guardAttachmentPath(ctx, args.sessionId, candidateAbs, "attachment.signUrl", "group-acl");
54600
55288
  const entries = deps.groupFileStore.list(scope, args.sessionId);
54601
55289
  const entry = entries.find((e) => {
54602
- const storedAbs = import_node_path41.default.isAbsolute(e.relPath) ? import_node_path41.default.resolve(e.relPath) : import_node_path41.default.resolve(cwdAbs, e.relPath);
55290
+ const storedAbs = import_node_path46.default.isAbsolute(e.relPath) ? import_node_path46.default.resolve(e.relPath) : import_node_path46.default.resolve(cwdAbs, e.relPath);
54603
55291
  return storedAbs === candidateAbs && !e.stale;
54604
55292
  });
54605
55293
  if (!entry) {
@@ -54624,7 +55312,7 @@ function buildAttachmentHandlers(deps) {
54624
55312
  if (!ctx || ctx.principal.kind !== "guest" || !deps.personaRoot || !deps.sessionStore) return;
54625
55313
  const f = deps.sessionStore.read(sessionId);
54626
55314
  if (!f) return;
54627
- assertGuestAttachmentPath(ctx, import_node_path41.default.resolve(f.cwd), deps.personaRoot, method, deps.usersRoot);
55315
+ assertGuestAttachmentPath(ctx, import_node_path46.default.resolve(f.cwd), deps.personaRoot, method, deps.usersRoot);
54628
55316
  }
54629
55317
  const groupAdd = async (frame, _client, ctx) => {
54630
55318
  if (!deps.groupFileStore || !deps.getSessionScope) {
@@ -54639,8 +55327,8 @@ function buildAttachmentHandlers(deps) {
54639
55327
  if (!scope) {
54640
55328
  throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, `session ${args.sessionId} not found`);
54641
55329
  }
54642
- const cwdAbs = import_node_path41.default.resolve(deps.sessionStore?.read(args.sessionId)?.cwd ?? ".");
54643
- const candidateAbs = import_node_path41.default.isAbsolute(args.relPath) ? import_node_path41.default.resolve(args.relPath) : import_node_path41.default.resolve(cwdAbs, args.relPath);
55330
+ const cwdAbs = import_node_path46.default.resolve(deps.sessionStore?.read(args.sessionId)?.cwd ?? ".");
55331
+ const candidateAbs = import_node_path46.default.isAbsolute(args.relPath) ? import_node_path46.default.resolve(args.relPath) : import_node_path46.default.resolve(cwdAbs, args.relPath);
54644
55332
  guardAttachmentPath(ctx, args.sessionId, candidateAbs, "attachment.groupAdd", "cwd-subtree");
54645
55333
  const from = ctx?.principal.kind === "owner" ? "owner" : "agent";
54646
55334
  const size = 0;
@@ -54699,19 +55387,19 @@ function buildAttachmentHandlers(deps) {
54699
55387
 
54700
55388
  // src/handlers/extension.ts
54701
55389
  var import_promises8 = __toESM(require("fs/promises"), 1);
54702
- var import_node_path46 = __toESM(require("path"), 1);
55390
+ var import_node_path51 = __toESM(require("path"), 1);
54703
55391
  init_protocol();
54704
55392
 
54705
55393
  // src/extension/bundle-zip.ts
54706
55394
  var import_promises5 = __toESM(require("fs/promises"), 1);
54707
- var import_node_path42 = __toESM(require("path"), 1);
55395
+ var import_node_path47 = __toESM(require("path"), 1);
54708
55396
  var import_node_crypto14 = __toESM(require("crypto"), 1);
54709
55397
  var import_jszip2 = __toESM(require_lib3(), 1);
54710
55398
  async function bundleExtensionDir(dir) {
54711
55399
  const entries = await listFilesSorted(dir);
54712
55400
  const zip = new import_jszip2.default();
54713
55401
  for (const rel of entries) {
54714
- const abs = import_node_path42.default.join(dir, rel);
55402
+ const abs = import_node_path47.default.join(dir, rel);
54715
55403
  const content = await import_promises5.default.readFile(abs);
54716
55404
  zip.file(rel, content, { date: FIXED_DATE });
54717
55405
  }
@@ -54732,7 +55420,7 @@ async function listFilesSorted(rootDir) {
54732
55420
  return out;
54733
55421
  }
54734
55422
  async function walk(absRoot, relPrefix, out) {
54735
- const dirAbs = import_node_path42.default.join(absRoot, relPrefix);
55423
+ const dirAbs = import_node_path47.default.join(absRoot, relPrefix);
54736
55424
  const entries = await import_promises5.default.readdir(dirAbs, { withFileTypes: true });
54737
55425
  for (const e of entries) {
54738
55426
  if (IGNORE_BASENAMES.has(e.name)) continue;
@@ -54786,25 +55474,25 @@ function computePublishCheck(args) {
54786
55474
 
54787
55475
  // src/extension/install-flow.ts
54788
55476
  var import_promises6 = __toESM(require("fs/promises"), 1);
54789
- var import_node_path44 = __toESM(require("path"), 1);
55477
+ var import_node_path49 = __toESM(require("path"), 1);
54790
55478
  var import_node_os19 = __toESM(require("os"), 1);
54791
55479
  var import_node_crypto15 = __toESM(require("crypto"), 1);
54792
55480
  var import_jszip3 = __toESM(require_lib3(), 1);
54793
55481
 
54794
55482
  // src/extension/paths.ts
54795
55483
  var import_node_os18 = __toESM(require("os"), 1);
54796
- var import_node_path43 = __toESM(require("path"), 1);
55484
+ var import_node_path48 = __toESM(require("path"), 1);
54797
55485
  function clawdHomeRoot(override) {
54798
- return override ?? process.env.CLAWD_HOME ?? import_node_path43.default.join(import_node_os18.default.homedir(), ".clawd");
55486
+ return override ?? process.env.CLAWD_HOME ?? import_node_path48.default.join(import_node_os18.default.homedir(), ".clawd");
54799
55487
  }
54800
55488
  function extensionsRoot(override) {
54801
- return import_node_path43.default.join(clawdHomeRoot(override), "extensions");
55489
+ return import_node_path48.default.join(clawdHomeRoot(override), "extensions");
54802
55490
  }
54803
55491
  function publishedChannelsFile(override) {
54804
- return import_node_path43.default.join(clawdHomeRoot(override), "extensions-published.json");
55492
+ return import_node_path48.default.join(clawdHomeRoot(override), "extensions-published.json");
54805
55493
  }
54806
55494
  function bundleCacheRoot(override) {
54807
- return import_node_path43.default.join(clawdHomeRoot(override), "extension-bundles");
55495
+ return import_node_path48.default.join(clawdHomeRoot(override), "extension-bundles");
54808
55496
  }
54809
55497
 
54810
55498
  // src/extension/install-flow.ts
@@ -54831,7 +55519,7 @@ async function installFromChannel(args, deps) {
54831
55519
  throw new InstallError("ZIP_INVALID", `failed to load zip: ${e.message}`);
54832
55520
  }
54833
55521
  for (const name of Object.keys(zip.files)) {
54834
- if (name.includes("..") || name.startsWith("/") || import_node_path44.default.isAbsolute(name)) {
55522
+ if (name.includes("..") || name.startsWith("/") || import_node_path49.default.isAbsolute(name)) {
54835
55523
  throw new InstallError("ZIP_INVALID", `unsafe zip entry: ${name}`);
54836
55524
  }
54837
55525
  }
@@ -54863,7 +55551,7 @@ async function installFromChannel(args, deps) {
54863
55551
  );
54864
55552
  }
54865
55553
  const localExtId = namespacedExtId(ownerSlug, channelRef.ownerPrincipalId);
54866
- const destDir = import_node_path44.default.join(deps.extensionsRoot, localExtId);
55554
+ const destDir = import_node_path49.default.join(deps.extensionsRoot, localExtId);
54867
55555
  let destExists = false;
54868
55556
  try {
54869
55557
  await import_promises6.default.access(destDir);
@@ -54877,16 +55565,16 @@ async function installFromChannel(args, deps) {
54877
55565
  );
54878
55566
  }
54879
55567
  const stage = await import_promises6.default.mkdtemp(
54880
- import_node_path44.default.join(import_node_os19.default.tmpdir(), `clawd-ext-install-${localExtId}-`)
55568
+ import_node_path49.default.join(import_node_os19.default.tmpdir(), `clawd-ext-install-${localExtId}-`)
54881
55569
  );
54882
55570
  try {
54883
55571
  for (const [name, entry] of Object.entries(zip.files)) {
54884
- const dest = import_node_path44.default.join(stage, name);
55572
+ const dest = import_node_path49.default.join(stage, name);
54885
55573
  if (entry.dir) {
54886
55574
  await import_promises6.default.mkdir(dest, { recursive: true });
54887
55575
  continue;
54888
55576
  }
54889
- await import_promises6.default.mkdir(import_node_path44.default.dirname(dest), { recursive: true });
55577
+ await import_promises6.default.mkdir(import_node_path49.default.dirname(dest), { recursive: true });
54890
55578
  if (name === "manifest.json") {
54891
55579
  const rewritten = { ...parsed.data, id: localExtId };
54892
55580
  await import_promises6.default.writeFile(dest, JSON.stringify(rewritten, null, 2));
@@ -54907,7 +55595,7 @@ async function installFromChannel(args, deps) {
54907
55595
 
54908
55596
  // src/extension/update-flow.ts
54909
55597
  var import_promises7 = __toESM(require("fs/promises"), 1);
54910
- var import_node_path45 = __toESM(require("path"), 1);
55598
+ var import_node_path50 = __toESM(require("path"), 1);
54911
55599
  var import_node_os20 = __toESM(require("os"), 1);
54912
55600
  var import_node_crypto16 = __toESM(require("crypto"), 1);
54913
55601
  var import_jszip4 = __toESM(require_lib3(), 1);
@@ -54924,11 +55612,11 @@ async function updateFromChannel(args, deps) {
54924
55612
  channelRef.extId,
54925
55613
  channelRef.ownerPrincipalId
54926
55614
  );
54927
- const liveDir = import_node_path45.default.join(deps.extensionsRoot, localExtId);
55615
+ const liveDir = import_node_path50.default.join(deps.extensionsRoot, localExtId);
54928
55616
  const prevDir = `${liveDir}.prev`;
54929
55617
  let existingVersion;
54930
55618
  try {
54931
- const raw = await import_promises7.default.readFile(import_node_path45.default.join(liveDir, "manifest.json"), "utf8");
55619
+ const raw = await import_promises7.default.readFile(import_node_path50.default.join(liveDir, "manifest.json"), "utf8");
54932
55620
  const parsed2 = ExtensionManifestSchema.safeParse(JSON.parse(raw));
54933
55621
  if (!parsed2.success) {
54934
55622
  throw new UpdateError(
@@ -54961,7 +55649,7 @@ async function updateFromChannel(args, deps) {
54961
55649
  throw new UpdateError("ZIP_INVALID", `failed to load zip: ${e.message}`);
54962
55650
  }
54963
55651
  for (const name of Object.keys(zip.files)) {
54964
- if (name.includes("..") || name.startsWith("/") || import_node_path45.default.isAbsolute(name)) {
55652
+ if (name.includes("..") || name.startsWith("/") || import_node_path50.default.isAbsolute(name)) {
54965
55653
  throw new UpdateError("ZIP_INVALID", `unsafe zip entry: ${name}`);
54966
55654
  }
54967
55655
  }
@@ -54996,16 +55684,16 @@ async function updateFromChannel(args, deps) {
54996
55684
  await import_promises7.default.rm(prevDir, { recursive: true, force: true });
54997
55685
  await import_promises7.default.rename(liveDir, prevDir);
54998
55686
  const stage = await import_promises7.default.mkdtemp(
54999
- import_node_path45.default.join(import_node_os20.default.tmpdir(), `clawd-ext-update-${localExtId}-`)
55687
+ import_node_path50.default.join(import_node_os20.default.tmpdir(), `clawd-ext-update-${localExtId}-`)
55000
55688
  );
55001
55689
  try {
55002
55690
  for (const [name, entry] of Object.entries(zip.files)) {
55003
- const dest = import_node_path45.default.join(stage, name);
55691
+ const dest = import_node_path50.default.join(stage, name);
55004
55692
  if (entry.dir) {
55005
55693
  await import_promises7.default.mkdir(dest, { recursive: true });
55006
55694
  continue;
55007
55695
  }
55008
- await import_promises7.default.mkdir(import_node_path45.default.dirname(dest), { recursive: true });
55696
+ await import_promises7.default.mkdir(import_node_path50.default.dirname(dest), { recursive: true });
55009
55697
  if (name === "manifest.json") {
55010
55698
  const rewritten = { ...parsed.data, id: localExtId };
55011
55699
  await import_promises7.default.writeFile(dest, JSON.stringify(rewritten, null, 2));
@@ -55098,7 +55786,7 @@ async function rewriteManifestVersion(root, extId, newVersion, previousPublished
55098
55786
  );
55099
55787
  }
55100
55788
  }
55101
- const manifestPath = import_node_path46.default.join(root, extId, "manifest.json");
55789
+ const manifestPath = import_node_path51.default.join(root, extId, "manifest.json");
55102
55790
  const manifest = await readManifest(root, extId);
55103
55791
  const next = { ...manifest, version: newVersion };
55104
55792
  const tmp = `${manifestPath}.tmp`;
@@ -55106,7 +55794,7 @@ async function rewriteManifestVersion(root, extId, newVersion, previousPublished
55106
55794
  await import_promises8.default.rename(tmp, manifestPath);
55107
55795
  }
55108
55796
  async function readManifest(root, extId) {
55109
- const file = import_node_path46.default.join(root, extId, "manifest.json");
55797
+ const file = import_node_path51.default.join(root, extId, "manifest.json");
55110
55798
  let raw;
55111
55799
  try {
55112
55800
  raw = await import_promises8.default.readFile(file, "utf8");
@@ -55197,7 +55885,7 @@ function buildExtensionHandlers(deps) {
55197
55885
  };
55198
55886
  async function buildSnapshotMeta(extId) {
55199
55887
  const manifest = await readManifest(deps.root, extId);
55200
- const { sha256, buffer } = await bundleExtensionDir(import_node_path46.default.join(deps.root, extId));
55888
+ const { sha256, buffer } = await bundleExtensionDir(import_node_path51.default.join(deps.root, extId));
55201
55889
  return { manifest, contentHash: sha256, buffer };
55202
55890
  }
55203
55891
  const publish = async (frame, _client, ctx) => {
@@ -55378,9 +56066,9 @@ function buildExtensionHandlers(deps) {
55378
56066
  }
55379
56067
 
55380
56068
  // src/app-builder/project-store.ts
55381
- var import_node_fs39 = require("fs");
55382
- var import_node_child_process11 = require("child_process");
55383
- var import_node_path47 = require("path");
56069
+ var import_node_fs44 = require("fs");
56070
+ var import_node_child_process14 = require("child_process");
56071
+ var import_node_path52 = require("path");
55384
56072
  init_protocol();
55385
56073
  var PROJECTS_DIR = "projects";
55386
56074
  var META_FILE = ".clawd-project.json";
@@ -55394,19 +56082,19 @@ var ProjectStore = class {
55394
56082
  root;
55395
56083
  /** projects/<name>/.clawd-project.json 路径 */
55396
56084
  metaPath(name) {
55397
- return (0, import_node_path47.join)(this.projectsRoot(), name, META_FILE);
56085
+ return (0, import_node_path52.join)(this.projectsRoot(), name, META_FILE);
55398
56086
  }
55399
56087
  /** projects/<name>/ 目录路径(cwd 用) */
55400
56088
  projectDir(name) {
55401
- return (0, import_node_path47.join)(this.projectsRoot(), name);
56089
+ return (0, import_node_path52.join)(this.projectsRoot(), name);
55402
56090
  }
55403
56091
  projectsRoot() {
55404
- return (0, import_node_path47.join)(this.root, PROJECTS_DIR);
56092
+ return (0, import_node_path52.join)(this.root, PROJECTS_DIR);
55405
56093
  }
55406
56094
  async list() {
55407
56095
  let entries;
55408
56096
  try {
55409
- entries = await import_node_fs39.promises.readdir(this.projectsRoot());
56097
+ entries = await import_node_fs44.promises.readdir(this.projectsRoot());
55410
56098
  } catch (err) {
55411
56099
  if (err.code === "ENOENT") return [];
55412
56100
  throw err;
@@ -55414,7 +56102,7 @@ var ProjectStore = class {
55414
56102
  const out = [];
55415
56103
  for (const name of entries) {
55416
56104
  try {
55417
- const raw = await import_node_fs39.promises.readFile(this.metaPath(name), "utf8");
56105
+ const raw = await import_node_fs44.promises.readFile(this.metaPath(name), "utf8");
55418
56106
  const json = JSON.parse(raw);
55419
56107
  let migrated = false;
55420
56108
  if (typeof json.devCommand !== "string" || json.devCommand.length === 0) {
@@ -55425,7 +56113,7 @@ var ProjectStore = class {
55425
56113
  if (parsed.success) {
55426
56114
  out.push(parsed.data);
55427
56115
  if (migrated) {
55428
- void import_node_fs39.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
56116
+ void import_node_fs44.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
55429
56117
  });
55430
56118
  }
55431
56119
  }
@@ -55469,8 +56157,8 @@ var ProjectStore = class {
55469
56157
  throw new Error(`invalid name "${name}": ${validated.error.message}`);
55470
56158
  }
55471
56159
  const dir = this.projectDir(name);
55472
- await import_node_fs39.promises.mkdir(dir, { recursive: true });
55473
- await import_node_fs39.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
56160
+ await import_node_fs44.promises.mkdir(dir, { recursive: true });
56161
+ await import_node_fs44.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
55474
56162
  return meta;
55475
56163
  }
55476
56164
  /**
@@ -55484,7 +56172,7 @@ var ProjectStore = class {
55484
56172
  async scaffold(name, templateSrcDir, scaffoldScriptPath) {
55485
56173
  const destDir = this.projectDir(name);
55486
56174
  return await new Promise((resolve6, reject) => {
55487
- const child = (0, import_node_child_process11.spawn)("bash", [scaffoldScriptPath, name, templateSrcDir, destDir], {
56175
+ const child = (0, import_node_child_process14.spawn)("bash", [scaffoldScriptPath, name, templateSrcDir, destDir], {
55488
56176
  env: { ...process.env, PATH: process.env.PATH ?? "" },
55489
56177
  stdio: ["ignore", "pipe", "pipe"]
55490
56178
  });
@@ -55513,7 +56201,7 @@ var ProjectStore = class {
55513
56201
  }
55514
56202
  async delete(name) {
55515
56203
  const dir = this.projectDir(name);
55516
- await import_node_fs39.promises.rm(dir, { recursive: true, force: true });
56204
+ await import_node_fs44.promises.rm(dir, { recursive: true, force: true });
55517
56205
  }
55518
56206
  async updatePort(name, newPort) {
55519
56207
  if (newPort < PROJECT_PORT_MIN || newPort > PROJECT_PORT_MAX) {
@@ -55529,7 +56217,7 @@ var ProjectStore = class {
55529
56217
  throw new Error(`port ${newPort} already used / \u5DF2\u88AB project "${conflict.name}" \u5360\u7528`);
55530
56218
  }
55531
56219
  const updated = { ...target, port: newPort };
55532
- await import_node_fs39.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
56220
+ await import_node_fs44.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
55533
56221
  return updated;
55534
56222
  }
55535
56223
  /**
@@ -55546,7 +56234,7 @@ var ProjectStore = class {
55546
56234
  if (!validated.success) {
55547
56235
  throw new Error(`invalid prodUrl "${url}": ${validated.error.message}`);
55548
56236
  }
55549
- await import_node_fs39.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
56237
+ await import_node_fs44.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
55550
56238
  return validated.data;
55551
56239
  }
55552
56240
  /**
@@ -55567,7 +56255,7 @@ var ProjectStore = class {
55567
56255
  if (!validated.success) {
55568
56256
  throw new Error(`invalid publishJob: ${validated.error.message}`);
55569
56257
  }
55570
- await import_node_fs39.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
56258
+ await import_node_fs44.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
55571
56259
  return validated.data;
55572
56260
  }
55573
56261
  /** 清掉 .clawd-project.json.publishJob 字段。其他字段保持原样。 */
@@ -55582,13 +56270,13 @@ var ProjectStore = class {
55582
56270
  if (!validated.success) {
55583
56271
  throw new Error(`failed to clear publishJob: ${validated.error.message}`);
55584
56272
  }
55585
- await import_node_fs39.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
56273
+ await import_node_fs44.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
55586
56274
  return validated.data;
55587
56275
  }
55588
56276
  };
55589
56277
 
55590
56278
  // src/app-builder/kill-port.ts
55591
- var import_node_child_process12 = require("child_process");
56279
+ var import_node_child_process15 = require("child_process");
55592
56280
  async function killPortOccupants(port, ownedPids, logger) {
55593
56281
  let pids;
55594
56282
  try {
@@ -55630,7 +56318,7 @@ async function killPortOccupants(port, ownedPids, logger) {
55630
56318
  }
55631
56319
  function listPidsOnPort(port) {
55632
56320
  return new Promise((resolve6, reject) => {
55633
- (0, import_node_child_process12.execFile)(
56321
+ (0, import_node_child_process15.execFile)(
55634
56322
  "lsof",
55635
56323
  ["-ti", `:${port}`],
55636
56324
  { timeout: 3e3 },
@@ -55702,9 +56390,9 @@ var PublishJobRegistry = class {
55702
56390
  };
55703
56391
 
55704
56392
  // src/app-builder/publish-job-runner.ts
55705
- var import_node_child_process13 = require("child_process");
55706
- var import_node_fs40 = require("fs");
55707
- var import_node_path48 = require("path");
56393
+ var import_node_child_process16 = require("child_process");
56394
+ var import_node_fs45 = require("fs");
56395
+ var import_node_path53 = require("path");
55708
56396
 
55709
56397
  // src/app-builder/publish-stage-parser.ts
55710
56398
  var STAGE_RE = /^\s*::stage::(build|deploy|verify)\s*$/;
@@ -55731,19 +56419,19 @@ function tailStderrLines(buf, n) {
55731
56419
  // src/app-builder/publish-job-runner.ts
55732
56420
  async function startPublishJob(deps, args) {
55733
56421
  const { registry: registry2, projectDir } = deps;
55734
- const spawn12 = deps.spawnImpl ?? import_node_child_process13.spawn;
56422
+ const spawn15 = deps.spawnImpl ?? import_node_child_process16.spawn;
55735
56423
  if (registry2.has(args.name)) {
55736
56424
  return { jobId: registry2.get(args.name).jobId, status: "already-publishing" };
55737
56425
  }
55738
56426
  const projDir = projectDir(args.name);
55739
- const logPath = (0, import_node_path48.join)(projDir, ".publish.log");
56427
+ const logPath = (0, import_node_path53.join)(projDir, ".publish.log");
55740
56428
  let logStream = null;
55741
56429
  try {
55742
- logStream = (0, import_node_fs40.createWriteStream)(logPath, { flags: "w" });
56430
+ logStream = (0, import_node_fs45.createWriteStream)(logPath, { flags: "w" });
55743
56431
  } catch {
55744
56432
  logStream = null;
55745
56433
  }
55746
- const child = spawn12("bash", [args.scriptPath, projDir, args.personaRoot ?? ""], {
56434
+ const child = spawn15("bash", [args.scriptPath, projDir, args.personaRoot ?? ""], {
55747
56435
  cwd: projDir,
55748
56436
  env: process.env,
55749
56437
  stdio: ["ignore", "pipe", "pipe"]
@@ -55996,8 +56684,8 @@ async function recoverInterruptedJobs(deps) {
55996
56684
 
55997
56685
  // src/handlers/app-builder.ts
55998
56686
  init_protocol();
55999
- var import_node_path49 = require("path");
56000
- var import_node_fs41 = require("fs");
56687
+ var import_node_path54 = require("path");
56688
+ var import_node_fs46 = require("fs");
56001
56689
  var APP_BUILDER_PERSONAS = ["persona-app-builder", "persona-dataclaw-builder"];
56002
56690
  var DEV_SERVER_READY_TIMEOUT_MS = 3e4;
56003
56691
  async function recoverInterruptedPublishJobs(store, logger) {
@@ -56078,7 +56766,7 @@ function buildAppBuilderHandlers(deps) {
56078
56766
  async function listAllUsersProjects() {
56079
56767
  if (!deps.usersRoot || !deps.getStore) return [];
56080
56768
  const getStore = deps.getStore;
56081
- const userIds = await import_node_fs41.promises.readdir(deps.usersRoot).catch(() => []);
56769
+ const userIds = await import_node_fs46.promises.readdir(deps.usersRoot).catch(() => []);
56082
56770
  const perUser = await Promise.all(
56083
56771
  userIds.map((uid) => getStore(uid).list().catch(() => []))
56084
56772
  );
@@ -56154,8 +56842,8 @@ function buildAppBuilderHandlers(deps) {
56154
56842
  const project = await userStore.create(f.name, reservedPorts);
56155
56843
  try {
56156
56844
  const personaRoot = deps.resolvePersonaRoot ? deps.resolvePersonaRoot(session.ownerPersonaId ?? "") : deps.personaRoot;
56157
- const templateSrcDir = (0, import_node_path49.join)(personaRoot, "extension-kit", "examples", DEFAULT_TEMPLATE);
56158
- const scaffoldScript = (0, import_node_path49.join)(deps.deployKitRoot, "scripts", "new-extension.sh");
56845
+ const templateSrcDir = (0, import_node_path54.join)(personaRoot, "extension-kit", "examples", DEFAULT_TEMPLATE);
56846
+ const scaffoldScript = (0, import_node_path54.join)(deps.deployKitRoot, "scripts", "new-extension.sh");
56159
56847
  const scaffoldResult = await userStore.scaffold(project.name, templateSrcDir, scaffoldScript);
56160
56848
  deps.logger?.info("app-builder.scaffold.done", {
56161
56849
  name: project.name,
@@ -56376,7 +57064,7 @@ function buildAppBuilderHandlers(deps) {
56376
57064
  await userStore.clearPublishJob(args.name);
56377
57065
  }
56378
57066
  const personaRoot = deps.resolvePersonaRoot ? deps.resolvePersonaRoot(boundSession.ownerPersonaId ?? "") : deps.personaRoot;
56379
- const scriptPath = (0, import_node_path49.join)(deps.deployKitRoot, "scripts", "publish.sh");
57067
+ const scriptPath = (0, import_node_path54.join)(deps.deployKitRoot, "scripts", "publish.sh");
56380
57068
  deps.logger?.info("app-builder.publish.start", {
56381
57069
  name: args.name,
56382
57070
  sessionId: boundSession.sessionId,
@@ -56520,7 +57208,7 @@ function buildShiftHandlers(deps) {
56520
57208
 
56521
57209
  // src/handlers/visitor.ts
56522
57210
  init_protocol();
56523
- function ensureOwner3(ctx) {
57211
+ function ensureOwner4(ctx) {
56524
57212
  if (!ctx || ctx.principal.kind !== "owner") {
56525
57213
  throw new ClawdError(
56526
57214
  ERROR_CODES.UNAUTHORIZED,
@@ -56530,7 +57218,7 @@ function ensureOwner3(ctx) {
56530
57218
  }
56531
57219
  function buildVisitorHandlers(deps) {
56532
57220
  const list = async (_frame, _client, ctx) => {
56533
- ensureOwner3(ctx);
57221
+ ensureOwner4(ctx);
56534
57222
  return {
56535
57223
  response: {
56536
57224
  type: "visitor:list",
@@ -56545,7 +57233,7 @@ function buildVisitorHandlers(deps) {
56545
57233
 
56546
57234
  // src/extension/registry.ts
56547
57235
  var import_promises9 = __toESM(require("fs/promises"), 1);
56548
- var import_node_path50 = __toESM(require("path"), 1);
57236
+ var import_node_path55 = __toESM(require("path"), 1);
56549
57237
  async function loadAll(root) {
56550
57238
  let entries;
56551
57239
  try {
@@ -56558,13 +57246,13 @@ async function loadAll(root) {
56558
57246
  for (const ent of entries) {
56559
57247
  if (!ent.isDirectory()) continue;
56560
57248
  if (ent.name.startsWith(".")) continue;
56561
- records.push(await loadOne(import_node_path50.default.join(root, ent.name), ent.name));
57249
+ records.push(await loadOne(import_node_path55.default.join(root, ent.name), ent.name));
56562
57250
  }
56563
57251
  records.sort((a, b2) => a.extId < b2.extId ? -1 : a.extId > b2.extId ? 1 : 0);
56564
57252
  return records;
56565
57253
  }
56566
57254
  async function loadOne(dir, dirName) {
56567
- const manifestPath = import_node_path50.default.join(dir, "manifest.json");
57255
+ const manifestPath = import_node_path55.default.join(dir, "manifest.json");
56568
57256
  let raw;
56569
57257
  try {
56570
57258
  raw = await import_promises9.default.readFile(manifestPath, "utf8");
@@ -56609,7 +57297,7 @@ async function loadOne(dir, dirName) {
56609
57297
 
56610
57298
  // src/extension/uninstall.ts
56611
57299
  var import_promises10 = __toESM(require("fs/promises"), 1);
56612
- var import_node_path51 = __toESM(require("path"), 1);
57300
+ var import_node_path56 = __toESM(require("path"), 1);
56613
57301
  var UninstallError = class extends Error {
56614
57302
  constructor(code, message) {
56615
57303
  super(message);
@@ -56618,7 +57306,7 @@ var UninstallError = class extends Error {
56618
57306
  code;
56619
57307
  };
56620
57308
  async function uninstall(deps) {
56621
- const dir = import_node_path51.default.join(deps.root, deps.extId);
57309
+ const dir = import_node_path56.default.join(deps.root, deps.extId);
56622
57310
  try {
56623
57311
  await import_promises10.default.access(dir);
56624
57312
  } catch {
@@ -56673,6 +57361,11 @@ function buildMethodHandlers(deps) {
56673
57361
  broadcast: deps.broadcastToOwners,
56674
57362
  now: () => Date.now()
56675
57363
  }),
57364
+ ...buildContactSshHandlers({
57365
+ store: deps.contactStore,
57366
+ broadcast: deps.broadcastToOwners,
57367
+ sshdDir: deps.sshdDir
57368
+ }),
56676
57369
  whoami: buildWhoamiHandler({
56677
57370
  ownerDisplayName: deps.ownerDisplayName,
56678
57371
  ownerPrincipalId: deps.ownerPrincipalId,
@@ -56719,7 +57412,7 @@ function buildMethodHandlers(deps) {
56719
57412
  }
56720
57413
 
56721
57414
  // src/app-builder/dev-server-supervisor.ts
56722
- var import_node_child_process14 = require("child_process");
57415
+ var import_node_child_process17 = require("child_process");
56723
57416
  var import_node_events2 = require("events");
56724
57417
  var DEFAULT_READY_PATTERN = /Local:\s+https?:\/\/|Nest application successfully started|server listening on/i;
56725
57418
  var DevServerSupervisor = class extends import_node_events2.EventEmitter {
@@ -56756,7 +57449,7 @@ var DevServerSupervisor = class extends import_node_events2.EventEmitter {
56756
57449
  tunnelHost: args.tunnelHost,
56757
57450
  devCommand: cmd
56758
57451
  });
56759
- const child = (0, import_node_child_process14.spawn)("sh", ["-c", cmd], {
57452
+ const child = (0, import_node_child_process17.spawn)("sh", ["-c", cmd], {
56760
57453
  cwd: args.cwd,
56761
57454
  env,
56762
57455
  stdio: "pipe",
@@ -57008,6 +57701,12 @@ var METHOD_GRANT_MAP = {
57008
57701
  "contact:list": ADMIN_ANY,
57009
57702
  "contact:pin": ADMIN_ANY,
57010
57703
  "contact:remove": ADMIN_ANY,
57704
+ // contact:setSshAccess (owner UI 配 SSH 授权):ADMIN_ANY
57705
+ // contact:sshKey:issue (guest daemon 拉自己的 privkey):public — handler 内校
57706
+ // ctx.principal.kind==='guest' + store.get(callerId).sshAllowed
57707
+ // (对齐 inbox:postMessage 的"能连上=有 auth,业务在 handler 校"模式)
57708
+ "contact:setSshAccess": ADMIN_ANY,
57709
+ "contact:sshKey:issue": { kind: "public" },
57011
57710
  // ---- visitor:* (访客名单,owner-only) ----
57012
57711
  // owner 看完整访客名单(含没开会话的);guest 不可调(handler 内再 assertOwner 兜底)。
57013
57712
  "visitor:list": ADMIN_ANY,
@@ -57188,8 +57887,8 @@ async function dispatchRpc(method, frame, client, ctx, deps) {
57188
57887
  }
57189
57888
 
57190
57889
  // src/extension/runtime.ts
57191
- var import_node_child_process15 = require("child_process");
57192
- var import_node_path52 = __toESM(require("path"), 1);
57890
+ var import_node_child_process18 = require("child_process");
57891
+ var import_node_path57 = __toESM(require("path"), 1);
57193
57892
  var import_promises11 = require("timers/promises");
57194
57893
 
57195
57894
  // src/extension/port-allocator.ts
@@ -57290,13 +57989,13 @@ var Runtime = class {
57290
57989
  /\$CLAWOS_EXT_PORT/g,
57291
57990
  String(port)
57292
57991
  );
57293
- const dir = import_node_path52.default.join(this.root, extId);
57992
+ const dir = import_node_path57.default.join(this.root, extId);
57294
57993
  const env = {
57295
57994
  ...process.env,
57296
57995
  CLAWOS_EXT_PORT: String(port),
57297
57996
  CLAWOS_EXT_ID: extId
57298
57997
  };
57299
- const child = (0, import_node_child_process15.spawn)("sh", ["-c", cmd], {
57998
+ const child = (0, import_node_child_process18.spawn)("sh", ["-c", cmd], {
57300
57999
  cwd: dir,
57301
58000
  env,
57302
58001
  stdio: ["ignore", "pipe", "pipe"],
@@ -57402,7 +58101,7 @@ ${handle.stderrTail}`
57402
58101
 
57403
58102
  // src/extension/published-channels.ts
57404
58103
  var import_promises12 = __toESM(require("fs/promises"), 1);
57405
- var import_node_path53 = __toESM(require("path"), 1);
58104
+ var import_node_path58 = __toESM(require("path"), 1);
57406
58105
  init_zod();
57407
58106
  var PublishedChannelsError = class extends Error {
57408
58107
  constructor(code, message) {
@@ -57501,7 +58200,7 @@ var PublishedChannelStore = class {
57501
58200
  )
57502
58201
  };
57503
58202
  const tmp = `${this.filePath}.tmp`;
57504
- await import_promises12.default.mkdir(import_node_path53.default.dirname(this.filePath), { recursive: true });
58203
+ await import_promises12.default.mkdir(import_node_path58.default.dirname(this.filePath), { recursive: true });
57505
58204
  await import_promises12.default.writeFile(tmp, JSON.stringify(data, null, 2), { mode: 384 });
57506
58205
  await import_promises12.default.rename(tmp, this.filePath);
57507
58206
  }
@@ -57509,7 +58208,7 @@ var PublishedChannelStore = class {
57509
58208
 
57510
58209
  // src/extension/bundle-cache.ts
57511
58210
  var import_promises13 = __toESM(require("fs/promises"), 1);
57512
- var import_node_path54 = __toESM(require("path"), 1);
58211
+ var import_node_path59 = __toESM(require("path"), 1);
57513
58212
  var BundleCache = class {
57514
58213
  constructor(rootDir) {
57515
58214
  this.rootDir = rootDir;
@@ -57518,14 +58217,14 @@ var BundleCache = class {
57518
58217
  /** Atomic write: stage tmp → rename. Caller passes the hex sha256. */
57519
58218
  async write(snapshotHash, buffer) {
57520
58219
  await import_promises13.default.mkdir(this.rootDir, { recursive: true });
57521
- const file = import_node_path54.default.join(this.rootDir, `${snapshotHash}.zip`);
58220
+ const file = import_node_path59.default.join(this.rootDir, `${snapshotHash}.zip`);
57522
58221
  const tmp = `${file}.tmp`;
57523
58222
  await import_promises13.default.writeFile(tmp, buffer, { mode: 384 });
57524
58223
  await import_promises13.default.rename(tmp, file);
57525
58224
  }
57526
58225
  /** Returns the bundle bytes, or null when the file doesn't exist. */
57527
58226
  async read(snapshotHash) {
57528
- const file = import_node_path54.default.join(this.rootDir, `${snapshotHash}.zip`);
58227
+ const file = import_node_path59.default.join(this.rootDir, `${snapshotHash}.zip`);
57529
58228
  try {
57530
58229
  return await import_promises13.default.readFile(file);
57531
58230
  } catch (e) {
@@ -57535,7 +58234,7 @@ var BundleCache = class {
57535
58234
  }
57536
58235
  /** Idempotent — missing file is not an error. */
57537
58236
  async delete(snapshotHash) {
57538
- const file = import_node_path54.default.join(this.rootDir, `${snapshotHash}.zip`);
58237
+ const file = import_node_path59.default.join(this.rootDir, `${snapshotHash}.zip`);
57539
58238
  await import_promises13.default.rm(file, { force: true });
57540
58239
  }
57541
58240
  };
@@ -57560,16 +58259,16 @@ async function startDaemon(config) {
57560
58259
  });
57561
58260
  const logger = createLogger({
57562
58261
  level: config.logLevel,
57563
- file: import_node_path55.default.join(config.dataDir, "clawd.log"),
58262
+ file: import_node_path60.default.join(config.dataDir, "clawd.log"),
57564
58263
  logClient
57565
58264
  });
57566
58265
  logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
57567
58266
  const screenIdleProbeLogger = createFileOnlyLogger({
57568
- file: import_node_path55.default.join(config.dataDir, "screen-idle-probe.log"),
58267
+ file: import_node_path60.default.join(config.dataDir, "screen-idle-probe.log"),
57569
58268
  level: "debug"
57570
58269
  });
57571
58270
  logger.info("screen-idle probe logger enabled", {
57572
- file: import_node_path55.default.join(config.dataDir, "screen-idle-probe.log")
58271
+ file: import_node_path60.default.join(config.dataDir, "screen-idle-probe.log")
57573
58272
  });
57574
58273
  const stateMgr = new StateFileManager({ dataDir: config.dataDir });
57575
58274
  const pre = stateMgr.preflight();
@@ -57707,8 +58406,8 @@ async function startDaemon(config) {
57707
58406
  const agents = new AgentsScanner();
57708
58407
  const history = new ClaudeHistoryReader();
57709
58408
  let transport = null;
57710
- const personaStore = new PersonaStore(import_node_path55.default.join(config.dataDir, "personas"));
57711
- const usersRoot = import_node_path55.default.join(config.dataDir, "users");
58409
+ const personaStore = new PersonaStore(import_node_path60.default.join(config.dataDir, "personas"));
58410
+ const usersRoot = import_node_path60.default.join(config.dataDir, "users");
57712
58411
  const defaultsRoot = findDefaultsRoot(logger);
57713
58412
  if (defaultsRoot) {
57714
58413
  seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
@@ -57728,17 +58427,17 @@ async function startDaemon(config) {
57728
58427
  migrateCodexSandbox({ store: personaStore, logger });
57729
58428
  const groupFileStore = new GroupFileStore({ dataDir: config.dataDir, logger });
57730
58429
  const personaDispatchManager = new PersonaDispatchManager({ genId: () => v4_default() });
57731
- const here = typeof __dirname === "string" ? __dirname : import_node_path55.default.dirname((0, import_node_url4.fileURLToPath)(import_meta6.url));
58430
+ const here = typeof __dirname === "string" ? __dirname : import_node_path60.default.dirname((0, import_node_url4.fileURLToPath)(import_meta6.url));
57732
58431
  const dispatchServerCandidates = [
57733
- import_node_path55.default.join(here, "dispatch", "mcp-server.cjs"),
58432
+ import_node_path60.default.join(here, "dispatch", "mcp-server.cjs"),
57734
58433
  // 生产 dist/index → dist/dispatch/mcp-server.cjs
57735
- import_node_path55.default.join(here, "..", "dist", "dispatch", "mcp-server.cjs")
58434
+ import_node_path60.default.join(here, "..", "dist", "dispatch", "mcp-server.cjs")
57736
58435
  // dev tsx src/index → ../dist/dispatch/mcp-server.cjs
57737
58436
  ];
57738
- const dispatchServerScriptPath = dispatchServerCandidates.find((p2) => import_node_fs42.default.existsSync(p2));
58437
+ const dispatchServerScriptPath = dispatchServerCandidates.find((p2) => import_node_fs47.default.existsSync(p2));
57739
58438
  let dispatchMcpConfigPath2;
57740
58439
  if (dispatchServerScriptPath) {
57741
- const dispatchLogPath = import_node_path55.default.join(config.dataDir, "dispatch-mcp-server.log");
58440
+ const dispatchLogPath = import_node_path60.default.join(config.dataDir, "dispatch-mcp-server.log");
57742
58441
  dispatchMcpConfigPath2 = writeDispatchMcpConfig({
57743
58442
  dataDir: config.dataDir,
57744
58443
  serverScriptPath: dispatchServerScriptPath,
@@ -57755,15 +58454,15 @@ async function startDaemon(config) {
57755
58454
  });
57756
58455
  }
57757
58456
  const ticketServerCandidates = [
57758
- import_node_path55.default.join(here, "ticket", "mcp-server.cjs"),
57759
- import_node_path55.default.join(here, "..", "dist", "ticket", "mcp-server.cjs")
58457
+ import_node_path60.default.join(here, "ticket", "mcp-server.cjs"),
58458
+ import_node_path60.default.join(here, "..", "dist", "ticket", "mcp-server.cjs")
57760
58459
  ];
57761
- const ticketServerScriptPath = ticketServerCandidates.find((p2) => import_node_fs42.default.existsSync(p2));
58460
+ const ticketServerScriptPath = ticketServerCandidates.find((p2) => import_node_fs47.default.existsSync(p2));
57762
58461
  const ticketOwnerUnionId = feishuIdentity?.identity.unionId ?? "";
57763
58462
  const ticketOwnerName = feishuIdentity?.identity.displayName ?? "";
57764
58463
  let ticketMcpConfigPath2;
57765
58464
  if (ticketServerScriptPath && ticketOwnerUnionId) {
57766
- const ticketLogPath = import_node_path55.default.join(config.dataDir, "ticket-mcp-server.log");
58465
+ const ticketLogPath = import_node_path60.default.join(config.dataDir, "ticket-mcp-server.log");
57767
58466
  ticketMcpConfigPath2 = writeTicketMcpConfig({
57768
58467
  dataDir: config.dataDir,
57769
58468
  serverScriptPath: ticketServerScriptPath,
@@ -57784,13 +58483,13 @@ async function startDaemon(config) {
57784
58483
  });
57785
58484
  }
57786
58485
  const shiftServerCandidates = [
57787
- import_node_path55.default.join(here, "shift", "mcp-server.cjs"),
57788
- import_node_path55.default.join(here, "..", "dist", "shift", "mcp-server.cjs")
58486
+ import_node_path60.default.join(here, "shift", "mcp-server.cjs"),
58487
+ import_node_path60.default.join(here, "..", "dist", "shift", "mcp-server.cjs")
57789
58488
  ];
57790
- const shiftServerScriptPath = shiftServerCandidates.find((p2) => import_node_fs42.default.existsSync(p2));
58489
+ const shiftServerScriptPath = shiftServerCandidates.find((p2) => import_node_fs47.default.existsSync(p2));
57791
58490
  let shiftMcpConfigPath2;
57792
58491
  if (shiftServerScriptPath) {
57793
- const shiftLogPath = import_node_path55.default.join(config.dataDir, "shift-mcp-server.log");
58492
+ const shiftLogPath = import_node_path60.default.join(config.dataDir, "shift-mcp-server.log");
57794
58493
  shiftMcpConfigPath2 = await writeShiftMcpConfig({
57795
58494
  dataDir: config.dataDir,
57796
58495
  serverScriptPath: shiftServerScriptPath,
@@ -57808,13 +58507,13 @@ async function startDaemon(config) {
57808
58507
  );
57809
58508
  }
57810
58509
  const inboxServerCandidates = [
57811
- import_node_path55.default.join(here, "inbox", "mcp-server.cjs"),
57812
- import_node_path55.default.join(here, "..", "dist", "inbox", "mcp-server.cjs")
58510
+ import_node_path60.default.join(here, "inbox", "mcp-server.cjs"),
58511
+ import_node_path60.default.join(here, "..", "dist", "inbox", "mcp-server.cjs")
57813
58512
  ];
57814
- const inboxServerScriptPath = inboxServerCandidates.find((p2) => import_node_fs42.default.existsSync(p2));
58513
+ const inboxServerScriptPath = inboxServerCandidates.find((p2) => import_node_fs47.default.existsSync(p2));
57815
58514
  let inboxMcpConfigPath2;
57816
58515
  if (inboxServerScriptPath) {
57817
- const inboxLogPath = import_node_path55.default.join(config.dataDir, "inbox-mcp-server.log");
58516
+ const inboxLogPath = import_node_path60.default.join(config.dataDir, "inbox-mcp-server.log");
57818
58517
  inboxMcpConfigPath2 = await writeInboxMcpConfig({
57819
58518
  dataDir: config.dataDir,
57820
58519
  serverScriptPath: inboxServerScriptPath,
@@ -57832,7 +58531,7 @@ async function startDaemon(config) {
57832
58531
  );
57833
58532
  }
57834
58533
  const shiftStore = createShiftStore({
57835
- filePath: import_node_path55.default.join(config.dataDir, "shift.json"),
58534
+ filePath: import_node_path60.default.join(config.dataDir, "shift.json"),
57836
58535
  ownerIdProvider: () => ownerPrincipalId,
57837
58536
  now: () => Date.now()
57838
58537
  });
@@ -57854,7 +58553,7 @@ async function startDaemon(config) {
57854
58553
  getAdapter,
57855
58554
  historyReader: history,
57856
58555
  dataDir: config.dataDir,
57857
- personaRoot: import_node_path55.default.join(config.dataDir, "personas"),
58556
+ personaRoot: import_node_path60.default.join(config.dataDir, "personas"),
57858
58557
  usersRoot,
57859
58558
  personaStore,
57860
58559
  ownerDisplayName,
@@ -57897,10 +58596,10 @@ async function startDaemon(config) {
57897
58596
  // 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
57898
58597
  attachmentGroup: {
57899
58598
  onFileEdit: (input) => {
57900
- const absPath = import_node_path55.default.isAbsolute(input.relPath) ? input.relPath : import_node_path55.default.join(input.cwd, input.relPath);
58599
+ const absPath = import_node_path60.default.isAbsolute(input.relPath) ? input.relPath : import_node_path60.default.join(input.cwd, input.relPath);
57901
58600
  let size = 0;
57902
58601
  try {
57903
- size = import_node_fs42.default.statSync(absPath).size;
58602
+ size = import_node_fs47.default.statSync(absPath).size;
57904
58603
  } catch (err) {
57905
58604
  logger.warn("attachment.onFileEdit stat failed", {
57906
58605
  sessionId: input.sessionId,
@@ -58098,11 +58797,11 @@ async function startDaemon(config) {
58098
58797
  // 'persona/<pid>/owner',default 走 'default'。
58099
58798
  getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
58100
58799
  // guest path guard:candidate 必须在 personaRoot 子树或调用者自己的 user-dir 下
58101
- personaRoot: import_node_path55.default.join(config.dataDir, "personas"),
58800
+ personaRoot: import_node_path60.default.join(config.dataDir, "personas"),
58102
58801
  usersRoot
58103
58802
  },
58104
58803
  // workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
58105
- personaRoot: import_node_path55.default.join(config.dataDir, "personas"),
58804
+ personaRoot: import_node_path60.default.join(config.dataDir, "personas"),
58106
58805
  // v2 多人 persona 隔离:handler 派生 guest user-dir 放行
58107
58806
  usersRoot,
58108
58807
  // capability:list / delete handler 依赖
@@ -58122,6 +58821,9 @@ async function startDaemon(config) {
58122
58821
  inboxStore,
58123
58822
  // 联系人列表 store(device:connect / 自动反向落同一 store)
58124
58823
  contactStore,
58824
+ // <dataDir>/sshd 绝对路径 —— contact-ssh handlers 用它拼 authorized_keys / keys/ 子路径
58825
+ // Task 10 会加 SshdManager 起 sshd;handlers wire 提前挂 sshdDir 让 typecheck 过
58826
+ sshdDir: import_node_path60.default.join(config.dataDir, "sshd"),
58125
58827
  // inbox:sendDm 用:sessionId → session 出身(复用 attachment 同款 findOwnedSessionScope)
58126
58828
  getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
58127
58829
  // contact:removed broadcast;复用 capability:tokenIssued 同款通路
@@ -58211,11 +58913,11 @@ async function startDaemon(config) {
58211
58913
  // 发布上线脚手架化 (spec 2026-06-03 §5.2):
58212
58914
  // appBuilderPersonaRoot 用于拼 publish.sh 绝对路径(persona-app-builder 安装在
58213
58915
  // dataDir/personas/persona-app-builder 之下,extension-kit/scripts/publish.sh 是相对路径)。
58214
- appBuilderPersonaRoot: import_node_path55.default.join(config.dataDir, "personas", "persona-app-builder"),
58916
+ appBuilderPersonaRoot: import_node_path60.default.join(config.dataDir, "personas", "persona-app-builder"),
58215
58917
  // 共享 deploy-kit 根:scaffold/publish 脚本骨架 + 阿里云凭证单一真源。
58216
- deployKitRoot: import_node_path55.default.join(config.dataDir, "deploy-kit"),
58918
+ deployKitRoot: import_node_path60.default.join(config.dataDir, "deploy-kit"),
58217
58919
  // scaffold/publish 按当前 session 的 persona 解析其安装根,让每个 persona 用自己的模板/注入配置。
58218
- resolvePersonaRoot: (personaId) => import_node_path55.default.join(config.dataDir, "personas", personaId),
58920
+ resolvePersonaRoot: (personaId) => import_node_path60.default.join(config.dataDir, "personas", personaId),
58219
58921
  // 发布上线脚手架化 (spec 2026-06-03 §5.2.2):
58220
58922
  // 复用 SessionManagerDeps.broadcastFrame 同款 dispatch 逻辑 —— runner 调 manager.send
58221
58923
  // 取回 broadcast 帧后逐帧 push 到 transport,跟 manager 自身的 deps 一致。
@@ -58258,7 +58960,7 @@ async function startDaemon(config) {
58258
58960
  }
58259
58961
  let sourceJsonlPath = "(no transcript yet \u2014 operate from the task description alone)";
58260
58962
  if (sourceFile && sourceFile.toolSessionId) {
58261
- sourceJsonlPath = import_node_path55.default.join(
58963
+ sourceJsonlPath = import_node_path60.default.join(
58262
58964
  import_node_os21.default.homedir(),
58263
58965
  ".claude",
58264
58966
  "projects",
@@ -58558,8 +59260,8 @@ async function startDaemon(config) {
58558
59260
  const lines = [
58559
59261
  `Tunnel: ${r.url}`,
58560
59262
  ...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
58561
- `Frpc config: ${import_node_path55.default.join(config.dataDir, "frpc.toml")}`,
58562
- `Frpc log: ${import_node_path55.default.join(config.dataDir, "frpc.log")}`
59263
+ `Frpc config: ${import_node_path60.default.join(config.dataDir, "frpc.toml")}`,
59264
+ `Frpc log: ${import_node_path60.default.join(config.dataDir, "frpc.log")}`
58563
59265
  ];
58564
59266
  const width = Math.max(...lines.map((l) => l.length));
58565
59267
  const bar = "\u2550".repeat(width + 4);
@@ -58572,8 +59274,8 @@ ${bar}
58572
59274
 
58573
59275
  `);
58574
59276
  try {
58575
- const connectPath = import_node_path55.default.join(config.dataDir, "connect.txt");
58576
- import_node_fs42.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
59277
+ const connectPath = import_node_path60.default.join(config.dataDir, "connect.txt");
59278
+ import_node_fs47.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
58577
59279
  } catch {
58578
59280
  }
58579
59281
  } catch (err) {
@@ -58598,6 +59300,22 @@ ${bar}
58598
59300
  logger.warn("tunnel unavailable, degraded to local mode", { reason: tunnelError });
58599
59301
  }
58600
59302
  }
59303
+ const sshdMgr = new SshdManager({
59304
+ dataDir: config.dataDir,
59305
+ port: config.sshdPort,
59306
+ logger,
59307
+ installProcessExitHandlers: true,
59308
+ onSshdExit: (info) => logger.warn("sshd exited unexpectedly", info)
59309
+ });
59310
+ try {
59311
+ await sshdMgr.start();
59312
+ rebuildAuthorizedKeys(contactStore, import_node_path60.default.join(config.dataDir, "sshd"));
59313
+ logger.info("sshd: contact-ssh sandbox ready", { port: config.sshdPort });
59314
+ } catch (err) {
59315
+ logger.warn("sshd start failed; contact SSH grant will not work until fixed", {
59316
+ err: err.message
59317
+ });
59318
+ }
58601
59319
  void reportDevice();
58602
59320
  void fetchServerKey();
58603
59321
  const tickAttachmentGc = () => {
@@ -58632,6 +59350,11 @@ ${bar}
58632
59350
  if (tunnelMgr) {
58633
59351
  await tunnelMgr.stop();
58634
59352
  }
59353
+ await sshdMgr.stop().catch((err) => {
59354
+ logger.warn("shutdown.sshd-stop-failed", {
59355
+ error: err instanceof Error ? err.message : String(err)
59356
+ });
59357
+ });
58635
59358
  await wss.stop();
58636
59359
  stateMgr.delete();
58637
59360
  if (logClient) await logClient.dispose();
@@ -58645,9 +59368,9 @@ ${bar}
58645
59368
  };
58646
59369
  }
58647
59370
  function migrateDropPersonsDir(dataDir) {
58648
- const dir = import_node_path55.default.join(dataDir, "persons");
59371
+ const dir = import_node_path60.default.join(dataDir, "persons");
58649
59372
  try {
58650
- import_node_fs42.default.rmSync(dir, { recursive: true, force: true });
59373
+ import_node_fs47.default.rmSync(dir, { recursive: true, force: true });
58651
59374
  } catch {
58652
59375
  }
58653
59376
  }