@clawos-dev/clawd 0.2.240 → 0.2.242

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
@@ -167,8 +167,8 @@ var init_methods = __esm({
167
167
  "contact:pin",
168
168
  "contact:remove",
169
169
  // ---- contact:setRemoteAccess (远程访问授权,spec 2026-07-16) ----
170
- // owner UI 授权 contact 反向操作我的设备(exec + SSH 共用开关 remoteAccessAllowed)。
171
- // 详见 protocol/src/contact-ssh.ts.
170
+ // owner UI 授权 contact 反向操作我的设备(跨设备 exec 开关 remoteAccessAllowed)。
171
+ // 详见 protocol/src/contact-remote-access.ts.
172
172
  "contact:setRemoteAccess",
173
173
  // ---- visitor:* (web 访客 · persona web 分享,spec 2026-06-24-persona-web-share-design) ----
174
174
  // owner-only:列出登录访问过本机 public persona 的 web 访客(零安装、飞书登录、daemon 自签
@@ -749,8 +749,8 @@ var init_parseUtil = __esm({
749
749
  init_errors2();
750
750
  init_en();
751
751
  makeIssue = (params) => {
752
- const { data, path: path77, errorMaps, issueData } = params;
753
- const fullPath = [...path77, ...issueData.path || []];
752
+ const { data, path: path70, errorMaps, issueData } = params;
753
+ const fullPath = [...path70, ...issueData.path || []];
754
754
  const fullIssue = {
755
755
  ...issueData,
756
756
  path: fullPath
@@ -1061,11 +1061,11 @@ var init_types = __esm({
1061
1061
  init_parseUtil();
1062
1062
  init_util();
1063
1063
  ParseInputLazyPath = class {
1064
- constructor(parent, value, path77, key) {
1064
+ constructor(parent, value, path70, key) {
1065
1065
  this._cachedPath = [];
1066
1066
  this.parent = parent;
1067
1067
  this.data = value;
1068
- this._path = path77;
1068
+ this._path = path70;
1069
1069
  this._key = key;
1070
1070
  }
1071
1071
  get path() {
@@ -5330,11 +5330,7 @@ var init_schemas = __esm({
5330
5330
  * selfUrl 保留:它只是"可达地址"(auto-reverse 反向连接用),不是身份——填错只影响
5331
5331
  * 反向连通性,不构成伪造风险,且 url 不在 token 里(token 只签身份不签地址)。
5332
5332
  */
5333
- selfUrl: external_exports.string().min(1).optional(),
5334
- /** 连接方自报本机 SSH identity pubkey;见 doc/glossary/peer-mesh.md#建联交换清单 */
5335
- selfSshPublicKey: external_exports.string().min(1).optional(),
5336
- /** 连接方自报本机 OS 登录账号名;见 doc/glossary/peer-mesh.md#建联交换清单 */
5337
- selfOsUser: external_exports.string().min(1).optional()
5333
+ selfUrl: external_exports.string().min(1).optional()
5338
5334
  });
5339
5335
  AuthOkFrameSchema = external_exports.object({
5340
5336
  type: external_exports.literal("auth:ok"),
@@ -5657,11 +5653,7 @@ var init_capability = __esm({
5657
5653
  id: external_exports.string().min(1),
5658
5654
  displayName: external_exports.string()
5659
5655
  }).strict()
5660
- ),
5661
- /** 被连方 SSH identity pubkey;见 doc/glossary/peer-mesh.md#建联交换清单 */
5662
- sshPublicKey: external_exports.string().min(1).optional(),
5663
- /** 被连方 OS 登录账号名;见 doc/glossary/peer-mesh.md#建联交换清单 */
5664
- osUser: external_exports.string().min(1).optional()
5656
+ )
5665
5657
  }).strict();
5666
5658
  PERSONAL_CAP_GRANTS = Object.freeze([
5667
5659
  Object.freeze({
@@ -5751,22 +5743,10 @@ var init_contact = __esm({
5751
5743
  pinnedAt: external_exports.number().int().nullable().default(null),
5752
5744
  /**
5753
5745
  * 远程访问授权(spec 2026-07-16)。owner 视角:允许该 contact 反向操作我的设备——
5754
- * 一个开关两种实现:跨设备 execpeerExec:run handler 判它)+ SSH 反向访问
5755
- * (rebuildAuthorizedKeys 读它)。default=false,owner 显式开。
5746
+ * 跨设备 exec 的总开关(peerExec:run handler 运行时判它)。default=false,owner 显式开。
5756
5747
  * 授权粒度 per-people (per-contact):借用别人 persona 本来就带信任对方本人的前提。
5757
5748
  */
5758
- remoteAccessAllowed: external_exports.boolean().default(false),
5759
- /** 对端设备 SSH identity pubkey(一行 `ssh-ed25519 AAAA... comment`);见 doc/glossary/peer-mesh.md#建联交换清单 */
5760
- sshPublicKey: external_exports.string().optional(),
5761
- /** 对端 OS 登录账号名,SSH 拨号登录名(`<osUser>@127.0.0.1`);见 doc/glossary/peer-mesh.md#建联交换清单 */
5762
- osUser: external_exports.string().optional(),
5763
- /**
5764
- * @deprecated T-29 去沙箱后废弃。原为授权访问的目录白名单(clawd-ssh-jail 生成 sbpl/bwrap
5765
- * policy 用);去掉 OS 沙箱后不再有目录级限制,远程访问授权即 on/off(见 remoteAccessAllowed)。
5766
- * 字段仅为兼容老 contacts.json 保留(default [] 保证 strict parse 不炸),不再有代码读写它,
5767
- * 也不再进 setRemoteAccess RPC / ContactSettingsDrawer / jail。勿新增消费方。
5768
- */
5769
- exposedDirs: external_exports.array(external_exports.string()).default([])
5749
+ remoteAccessAllowed: external_exports.boolean().default(false)
5770
5750
  }).strict();
5771
5751
  ContactWireSchema = ContactSchema;
5772
5752
  ContactRemoveArgsSchema = external_exports.object({
@@ -5805,10 +5785,10 @@ var init_contact = __esm({
5805
5785
  }
5806
5786
  });
5807
5787
 
5808
- // ../protocol/src/contact-ssh.ts
5788
+ // ../protocol/src/contact-remote-access.ts
5809
5789
  var ContactSetRemoteAccessArgsSchema, ContactSetRemoteAccessOkSchema, ContactRemoteAccessUpdatedFrameSchema;
5810
- var init_contact_ssh = __esm({
5811
- "../protocol/src/contact-ssh.ts"() {
5790
+ var init_contact_remote_access = __esm({
5791
+ "../protocol/src/contact-remote-access.ts"() {
5812
5792
  "use strict";
5813
5793
  init_zod();
5814
5794
  ContactSetRemoteAccessArgsSchema = external_exports.object({
@@ -6261,7 +6241,7 @@ var init_runtime = __esm({
6261
6241
  init_capability();
6262
6242
  init_inbox();
6263
6243
  init_contact();
6264
- init_contact_ssh();
6244
+ init_contact_remote_access();
6265
6245
  init_peer_exec();
6266
6246
  init_extension();
6267
6247
  init_feishu_auth();
@@ -6542,8 +6522,8 @@ var require_req = __commonJS({
6542
6522
  if (req.originalUrl) {
6543
6523
  _req.url = req.originalUrl;
6544
6524
  } else {
6545
- const path77 = req.path;
6546
- _req.url = typeof path77 === "string" ? path77 : req.url ? req.url.path || req.url : void 0;
6525
+ const path70 = req.path;
6526
+ _req.url = typeof path70 === "string" ? path70 : req.url ? req.url.path || req.url : void 0;
6547
6527
  }
6548
6528
  if (req.query) {
6549
6529
  _req.query = req.query;
@@ -6708,14 +6688,14 @@ var require_redact = __commonJS({
6708
6688
  }
6709
6689
  return obj;
6710
6690
  }
6711
- function parsePath(path77) {
6691
+ function parsePath(path70) {
6712
6692
  const parts = [];
6713
6693
  let current = "";
6714
6694
  let inBrackets = false;
6715
6695
  let inQuotes = false;
6716
6696
  let quoteChar = "";
6717
- for (let i = 0; i < path77.length; i++) {
6718
- const char = path77[i];
6697
+ for (let i = 0; i < path70.length; i++) {
6698
+ const char = path70[i];
6719
6699
  if (!inBrackets && char === ".") {
6720
6700
  if (current) {
6721
6701
  parts.push(current);
@@ -6846,10 +6826,10 @@ var require_redact = __commonJS({
6846
6826
  return current;
6847
6827
  }
6848
6828
  function redactPaths(obj, paths, censor, remove = false) {
6849
- for (const path77 of paths) {
6850
- const parts = parsePath(path77);
6829
+ for (const path70 of paths) {
6830
+ const parts = parsePath(path70);
6851
6831
  if (parts.includes("*")) {
6852
- redactWildcardPath(obj, parts, censor, path77, remove);
6832
+ redactWildcardPath(obj, parts, censor, path70, remove);
6853
6833
  } else {
6854
6834
  if (remove) {
6855
6835
  removeKey(obj, parts);
@@ -6934,8 +6914,8 @@ var require_redact = __commonJS({
6934
6914
  }
6935
6915
  } else {
6936
6916
  if (afterWildcard.includes("*")) {
6937
- const wrappedCensor = typeof censor === "function" ? (value, path77) => {
6938
- const fullPath = [...pathArray.slice(0, pathLength), ...path77];
6917
+ const wrappedCensor = typeof censor === "function" ? (value, path70) => {
6918
+ const fullPath = [...pathArray.slice(0, pathLength), ...path70];
6939
6919
  return censor(value, fullPath);
6940
6920
  } : censor;
6941
6921
  redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
@@ -6970,8 +6950,8 @@ var require_redact = __commonJS({
6970
6950
  return null;
6971
6951
  }
6972
6952
  const pathStructure = /* @__PURE__ */ new Map();
6973
- for (const path77 of pathsToClone) {
6974
- const parts = parsePath(path77);
6953
+ for (const path70 of pathsToClone) {
6954
+ const parts = parsePath(path70);
6975
6955
  let current = pathStructure;
6976
6956
  for (let i = 0; i < parts.length; i++) {
6977
6957
  const part = parts[i];
@@ -7023,24 +7003,24 @@ var require_redact = __commonJS({
7023
7003
  }
7024
7004
  return cloneSelectively(obj, pathStructure);
7025
7005
  }
7026
- function validatePath(path77) {
7027
- if (typeof path77 !== "string") {
7006
+ function validatePath(path70) {
7007
+ if (typeof path70 !== "string") {
7028
7008
  throw new Error("Paths must be (non-empty) strings");
7029
7009
  }
7030
- if (path77 === "") {
7010
+ if (path70 === "") {
7031
7011
  throw new Error("Invalid redaction path ()");
7032
7012
  }
7033
- if (path77.includes("..")) {
7034
- throw new Error(`Invalid redaction path (${path77})`);
7013
+ if (path70.includes("..")) {
7014
+ throw new Error(`Invalid redaction path (${path70})`);
7035
7015
  }
7036
- if (path77.includes(",")) {
7037
- throw new Error(`Invalid redaction path (${path77})`);
7016
+ if (path70.includes(",")) {
7017
+ throw new Error(`Invalid redaction path (${path70})`);
7038
7018
  }
7039
7019
  let bracketCount = 0;
7040
7020
  let inQuotes = false;
7041
7021
  let quoteChar = "";
7042
- for (let i = 0; i < path77.length; i++) {
7043
- const char = path77[i];
7022
+ for (let i = 0; i < path70.length; i++) {
7023
+ const char = path70[i];
7044
7024
  if ((char === '"' || char === "'") && bracketCount > 0) {
7045
7025
  if (!inQuotes) {
7046
7026
  inQuotes = true;
@@ -7054,20 +7034,20 @@ var require_redact = __commonJS({
7054
7034
  } else if (char === "]" && !inQuotes) {
7055
7035
  bracketCount--;
7056
7036
  if (bracketCount < 0) {
7057
- throw new Error(`Invalid redaction path (${path77})`);
7037
+ throw new Error(`Invalid redaction path (${path70})`);
7058
7038
  }
7059
7039
  }
7060
7040
  }
7061
7041
  if (bracketCount !== 0) {
7062
- throw new Error(`Invalid redaction path (${path77})`);
7042
+ throw new Error(`Invalid redaction path (${path70})`);
7063
7043
  }
7064
7044
  }
7065
7045
  function validatePaths(paths) {
7066
7046
  if (!Array.isArray(paths)) {
7067
7047
  throw new TypeError("paths must be an array");
7068
7048
  }
7069
- for (const path77 of paths) {
7070
- validatePath(path77);
7049
+ for (const path70 of paths) {
7050
+ validatePath(path70);
7071
7051
  }
7072
7052
  }
7073
7053
  function slowRedact(options = {}) {
@@ -7235,8 +7215,8 @@ var require_redaction = __commonJS({
7235
7215
  if (shape[k2] === null) {
7236
7216
  o[k2] = (value) => topCensor(value, [k2]);
7237
7217
  } else {
7238
- const wrappedCensor = typeof censor === "function" ? (value, path77) => {
7239
- return censor(value, [k2, ...path77]);
7218
+ const wrappedCensor = typeof censor === "function" ? (value, path70) => {
7219
+ return censor(value, [k2, ...path70]);
7240
7220
  } : censor;
7241
7221
  o[k2] = Redact({
7242
7222
  paths: shape[k2],
@@ -7454,10 +7434,10 @@ var require_atomic_sleep = __commonJS({
7454
7434
  var require_sonic_boom = __commonJS({
7455
7435
  "../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
7456
7436
  "use strict";
7457
- var fs71 = require("fs");
7437
+ var fs64 = require("fs");
7458
7438
  var EventEmitter3 = require("events");
7459
7439
  var inherits = require("util").inherits;
7460
- var path77 = require("path");
7440
+ var path70 = require("path");
7461
7441
  var sleep2 = require_atomic_sleep();
7462
7442
  var assert = require("assert");
7463
7443
  var BUSY_WRITE_TIMEOUT = 100;
@@ -7511,20 +7491,20 @@ var require_sonic_boom = __commonJS({
7511
7491
  const mode = sonic.mode;
7512
7492
  if (sonic.sync) {
7513
7493
  try {
7514
- if (sonic.mkdir) fs71.mkdirSync(path77.dirname(file), { recursive: true });
7515
- const fd = fs71.openSync(file, flags, mode);
7494
+ if (sonic.mkdir) fs64.mkdirSync(path70.dirname(file), { recursive: true });
7495
+ const fd = fs64.openSync(file, flags, mode);
7516
7496
  fileOpened(null, fd);
7517
7497
  } catch (err) {
7518
7498
  fileOpened(err);
7519
7499
  throw err;
7520
7500
  }
7521
7501
  } else if (sonic.mkdir) {
7522
- fs71.mkdir(path77.dirname(file), { recursive: true }, (err) => {
7502
+ fs64.mkdir(path70.dirname(file), { recursive: true }, (err) => {
7523
7503
  if (err) return fileOpened(err);
7524
- fs71.open(file, flags, mode, fileOpened);
7504
+ fs64.open(file, flags, mode, fileOpened);
7525
7505
  });
7526
7506
  } else {
7527
- fs71.open(file, flags, mode, fileOpened);
7507
+ fs64.open(file, flags, mode, fileOpened);
7528
7508
  }
7529
7509
  }
7530
7510
  function SonicBoom(opts) {
@@ -7565,8 +7545,8 @@ var require_sonic_boom = __commonJS({
7565
7545
  this.flush = flushBuffer;
7566
7546
  this.flushSync = flushBufferSync;
7567
7547
  this._actualWrite = actualWriteBuffer;
7568
- fsWriteSync = () => fs71.writeSync(this.fd, this._writingBuf);
7569
- fsWrite = () => fs71.write(this.fd, this._writingBuf, this.release);
7548
+ fsWriteSync = () => fs64.writeSync(this.fd, this._writingBuf);
7549
+ fsWrite = () => fs64.write(this.fd, this._writingBuf, this.release);
7570
7550
  } else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
7571
7551
  this._writingBuf = "";
7572
7552
  this.write = write;
@@ -7575,15 +7555,15 @@ var require_sonic_boom = __commonJS({
7575
7555
  this._actualWrite = actualWrite;
7576
7556
  fsWriteSync = () => {
7577
7557
  if (Buffer.isBuffer(this._writingBuf)) {
7578
- return fs71.writeSync(this.fd, this._writingBuf);
7558
+ return fs64.writeSync(this.fd, this._writingBuf);
7579
7559
  }
7580
- return fs71.writeSync(this.fd, this._writingBuf, "utf8");
7560
+ return fs64.writeSync(this.fd, this._writingBuf, "utf8");
7581
7561
  };
7582
7562
  fsWrite = () => {
7583
7563
  if (Buffer.isBuffer(this._writingBuf)) {
7584
- return fs71.write(this.fd, this._writingBuf, this.release);
7564
+ return fs64.write(this.fd, this._writingBuf, this.release);
7585
7565
  }
7586
- return fs71.write(this.fd, this._writingBuf, "utf8", this.release);
7566
+ return fs64.write(this.fd, this._writingBuf, "utf8", this.release);
7587
7567
  };
7588
7568
  } else {
7589
7569
  throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
@@ -7640,7 +7620,7 @@ var require_sonic_boom = __commonJS({
7640
7620
  }
7641
7621
  }
7642
7622
  if (this._fsync) {
7643
- fs71.fsyncSync(this.fd);
7623
+ fs64.fsyncSync(this.fd);
7644
7624
  }
7645
7625
  const len = this._len;
7646
7626
  if (this._reopening) {
@@ -7754,7 +7734,7 @@ var require_sonic_boom = __commonJS({
7754
7734
  const onDrain = () => {
7755
7735
  if (!this._fsync) {
7756
7736
  try {
7757
- fs71.fsync(this.fd, (err) => {
7737
+ fs64.fsync(this.fd, (err) => {
7758
7738
  this._flushPending = false;
7759
7739
  cb(err);
7760
7740
  });
@@ -7856,7 +7836,7 @@ var require_sonic_boom = __commonJS({
7856
7836
  const fd = this.fd;
7857
7837
  this.once("ready", () => {
7858
7838
  if (fd !== this.fd) {
7859
- fs71.close(fd, (err) => {
7839
+ fs64.close(fd, (err) => {
7860
7840
  if (err) {
7861
7841
  return this.emit("error", err);
7862
7842
  }
@@ -7905,7 +7885,7 @@ var require_sonic_boom = __commonJS({
7905
7885
  buf = this._bufs[0];
7906
7886
  }
7907
7887
  try {
7908
- const n = Buffer.isBuffer(buf) ? fs71.writeSync(this.fd, buf) : fs71.writeSync(this.fd, buf, "utf8");
7888
+ const n = Buffer.isBuffer(buf) ? fs64.writeSync(this.fd, buf) : fs64.writeSync(this.fd, buf, "utf8");
7909
7889
  const releasedBufObj = releaseWritingBuf(buf, this._len, n);
7910
7890
  buf = releasedBufObj.writingBuf;
7911
7891
  this._len = releasedBufObj.len;
@@ -7921,7 +7901,7 @@ var require_sonic_boom = __commonJS({
7921
7901
  }
7922
7902
  }
7923
7903
  try {
7924
- fs71.fsyncSync(this.fd);
7904
+ fs64.fsyncSync(this.fd);
7925
7905
  } catch {
7926
7906
  }
7927
7907
  }
@@ -7942,7 +7922,7 @@ var require_sonic_boom = __commonJS({
7942
7922
  buf = mergeBuf(this._bufs[0], this._lens[0]);
7943
7923
  }
7944
7924
  try {
7945
- const n = fs71.writeSync(this.fd, buf);
7925
+ const n = fs64.writeSync(this.fd, buf);
7946
7926
  buf = buf.subarray(n);
7947
7927
  this._len = Math.max(this._len - n, 0);
7948
7928
  if (buf.length <= 0) {
@@ -7970,13 +7950,13 @@ var require_sonic_boom = __commonJS({
7970
7950
  this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
7971
7951
  if (this.sync) {
7972
7952
  try {
7973
- const written = Buffer.isBuffer(this._writingBuf) ? fs71.writeSync(this.fd, this._writingBuf) : fs71.writeSync(this.fd, this._writingBuf, "utf8");
7953
+ const written = Buffer.isBuffer(this._writingBuf) ? fs64.writeSync(this.fd, this._writingBuf) : fs64.writeSync(this.fd, this._writingBuf, "utf8");
7974
7954
  release(null, written);
7975
7955
  } catch (err) {
7976
7956
  release(err);
7977
7957
  }
7978
7958
  } else {
7979
- fs71.write(this.fd, this._writingBuf, release);
7959
+ fs64.write(this.fd, this._writingBuf, release);
7980
7960
  }
7981
7961
  }
7982
7962
  function actualWriteBuffer() {
@@ -7985,7 +7965,7 @@ var require_sonic_boom = __commonJS({
7985
7965
  this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
7986
7966
  if (this.sync) {
7987
7967
  try {
7988
- const written = fs71.writeSync(this.fd, this._writingBuf);
7968
+ const written = fs64.writeSync(this.fd, this._writingBuf);
7989
7969
  release(null, written);
7990
7970
  } catch (err) {
7991
7971
  release(err);
@@ -7994,7 +7974,7 @@ var require_sonic_boom = __commonJS({
7994
7974
  if (kCopyBuffer) {
7995
7975
  this._writingBuf = Buffer.from(this._writingBuf);
7996
7976
  }
7997
- fs71.write(this.fd, this._writingBuf, release);
7977
+ fs64.write(this.fd, this._writingBuf, release);
7998
7978
  }
7999
7979
  }
8000
7980
  function actualClose(sonic) {
@@ -8010,12 +7990,12 @@ var require_sonic_boom = __commonJS({
8010
7990
  sonic._lens = [];
8011
7991
  assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
8012
7992
  try {
8013
- fs71.fsync(sonic.fd, closeWrapped);
7993
+ fs64.fsync(sonic.fd, closeWrapped);
8014
7994
  } catch {
8015
7995
  }
8016
7996
  function closeWrapped() {
8017
7997
  if (sonic.fd !== 1 && sonic.fd !== 2) {
8018
- fs71.close(sonic.fd, done);
7998
+ fs64.close(sonic.fd, done);
8019
7999
  } else {
8020
8000
  done();
8021
8001
  }
@@ -11150,11 +11130,11 @@ var init_lib = __esm({
11150
11130
  }
11151
11131
  }
11152
11132
  },
11153
- addToPath: function addToPath(path77, added, removed, oldPosInc, options) {
11154
- var last = path77.lastComponent;
11133
+ addToPath: function addToPath(path70, added, removed, oldPosInc, options) {
11134
+ var last = path70.lastComponent;
11155
11135
  if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
11156
11136
  return {
11157
- oldPos: path77.oldPos + oldPosInc,
11137
+ oldPos: path70.oldPos + oldPosInc,
11158
11138
  lastComponent: {
11159
11139
  count: last.count + 1,
11160
11140
  added,
@@ -11164,7 +11144,7 @@ var init_lib = __esm({
11164
11144
  };
11165
11145
  } else {
11166
11146
  return {
11167
- oldPos: path77.oldPos + oldPosInc,
11147
+ oldPos: path70.oldPos + oldPosInc,
11168
11148
  lastComponent: {
11169
11149
  count: 1,
11170
11150
  added,
@@ -11669,10 +11649,10 @@ function attachmentToHistoryMessage(o, ts) {
11669
11649
  const memories = raw.map((m2) => {
11670
11650
  if (!m2 || typeof m2 !== "object") return null;
11671
11651
  const rec3 = m2;
11672
- const path77 = typeof rec3.path === "string" ? rec3.path : null;
11652
+ const path70 = typeof rec3.path === "string" ? rec3.path : null;
11673
11653
  const content = typeof rec3.content === "string" ? rec3.content : null;
11674
- if (!path77 || content == null) return null;
11675
- const entry = { path: path77, content };
11654
+ if (!path70 || content == null) return null;
11655
+ const entry = { path: path70, content };
11676
11656
  if (typeof rec3.mtimeMs === "number") entry.mtimeMs = rec3.mtimeMs;
11677
11657
  return entry;
11678
11658
  }).filter((m2) => m2 !== null);
@@ -12475,10 +12455,10 @@ function parseAttachment(obj) {
12475
12455
  const memories = raw.map((m2) => {
12476
12456
  if (!m2 || typeof m2 !== "object") return null;
12477
12457
  const rec3 = m2;
12478
- const path77 = typeof rec3.path === "string" ? rec3.path : null;
12458
+ const path70 = typeof rec3.path === "string" ? rec3.path : null;
12479
12459
  const content = typeof rec3.content === "string" ? rec3.content : null;
12480
- if (!path77 || content == null) return null;
12481
- const out = { path: path77, content };
12460
+ if (!path70 || content == null) return null;
12461
+ const out = { path: path70, content };
12482
12462
  if (typeof rec3.mtimeMs === "number") out.mtimeMs = rec3.mtimeMs;
12483
12463
  return out;
12484
12464
  }).filter((m2) => m2 !== null);
@@ -27014,8 +26994,8 @@ var require_buffer_util = __commonJS({
27014
26994
  }
27015
26995
  return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.length);
27016
26996
  }
27017
- function toBuffer2(data) {
27018
- toBuffer2.readOnly = true;
26997
+ function toBuffer(data) {
26998
+ toBuffer.readOnly = true;
27019
26999
  if (Buffer.isBuffer(data)) return data;
27020
27000
  let buf;
27021
27001
  if (data instanceof ArrayBuffer) {
@@ -27024,7 +27004,7 @@ var require_buffer_util = __commonJS({
27024
27004
  buf = new FastBuffer(data.buffer, data.byteOffset, data.byteLength);
27025
27005
  } else {
27026
27006
  buf = Buffer.from(data);
27027
- toBuffer2.readOnly = false;
27007
+ toBuffer.readOnly = false;
27028
27008
  }
27029
27009
  return buf;
27030
27010
  }
@@ -27032,7 +27012,7 @@ var require_buffer_util = __commonJS({
27032
27012
  concat,
27033
27013
  mask: _mask,
27034
27014
  toArrayBuffer,
27035
- toBuffer: toBuffer2,
27015
+ toBuffer,
27036
27016
  unmask: _unmask
27037
27017
  };
27038
27018
  if (!process.env.WS_NO_BUFFER_UTIL) {
@@ -28287,7 +28267,7 @@ var require_sender = __commonJS({
28287
28267
  var PerMessageDeflate2 = require_permessage_deflate();
28288
28268
  var { EMPTY_BUFFER, kWebSocket, NOOP } = require_constants2();
28289
28269
  var { isBlob, isValidStatusCode } = require_validation();
28290
- var { mask: applyMask, toBuffer: toBuffer2 } = require_buffer_util();
28270
+ var { mask: applyMask, toBuffer } = require_buffer_util();
28291
28271
  var kByteLength = /* @__PURE__ */ Symbol("kByteLength");
28292
28272
  var maskBuffer = Buffer.alloc(4);
28293
28273
  var RANDOM_POOL_SIZE = 8 * 1024;
@@ -28475,9 +28455,9 @@ var require_sender = __commonJS({
28475
28455
  byteLength = data.size;
28476
28456
  readOnly = false;
28477
28457
  } else {
28478
- data = toBuffer2(data);
28458
+ data = toBuffer(data);
28479
28459
  byteLength = data.length;
28480
- readOnly = toBuffer2.readOnly;
28460
+ readOnly = toBuffer.readOnly;
28481
28461
  }
28482
28462
  if (byteLength > 125) {
28483
28463
  throw new RangeError("The data size must not be greater than 125 bytes");
@@ -28522,9 +28502,9 @@ var require_sender = __commonJS({
28522
28502
  byteLength = data.size;
28523
28503
  readOnly = false;
28524
28504
  } else {
28525
- data = toBuffer2(data);
28505
+ data = toBuffer(data);
28526
28506
  byteLength = data.length;
28527
- readOnly = toBuffer2.readOnly;
28507
+ readOnly = toBuffer.readOnly;
28528
28508
  }
28529
28509
  if (byteLength > 125) {
28530
28510
  throw new RangeError("The data size must not be greater than 125 bytes");
@@ -28580,9 +28560,9 @@ var require_sender = __commonJS({
28580
28560
  byteLength = data.size;
28581
28561
  readOnly = false;
28582
28562
  } else {
28583
- data = toBuffer2(data);
28563
+ data = toBuffer(data);
28584
28564
  byteLength = data.length;
28585
- readOnly = toBuffer2.readOnly;
28565
+ readOnly = toBuffer.readOnly;
28586
28566
  }
28587
28567
  if (this._firstFragment) {
28588
28568
  this._firstFragment = false;
@@ -28652,7 +28632,7 @@ var require_sender = __commonJS({
28652
28632
  return;
28653
28633
  }
28654
28634
  this._bufferedBytes -= options[kByteLength];
28655
- const data = toBuffer2(arrayBuffer);
28635
+ const data = toBuffer(arrayBuffer);
28656
28636
  if (!compress) {
28657
28637
  this._state = DEFAULT;
28658
28638
  this.sendFrame(_Sender.frame(data, options), cb);
@@ -29155,7 +29135,7 @@ var require_websocket = __commonJS({
29155
29135
  var EventEmitter3 = require("events");
29156
29136
  var https = require("https");
29157
29137
  var http3 = require("http");
29158
- var net4 = require("net");
29138
+ var net3 = require("net");
29159
29139
  var tls = require("tls");
29160
29140
  var { randomBytes, createHash: createHash3 } = require("crypto");
29161
29141
  var { Duplex, Readable: Readable3 } = require("stream");
@@ -29179,7 +29159,7 @@ var require_websocket = __commonJS({
29179
29159
  EventTarget: { addEventListener, removeEventListener }
29180
29160
  } = require_event_target();
29181
29161
  var { format, parse } = require_extension();
29182
- var { toBuffer: toBuffer2 } = require_buffer_util();
29162
+ var { toBuffer } = require_buffer_util();
29183
29163
  var kAborted = /* @__PURE__ */ Symbol("kAborted");
29184
29164
  var protocolVersions = [8, 13];
29185
29165
  var readyStates = ["CONNECTING", "OPEN", "CLOSING", "CLOSED"];
@@ -29889,12 +29869,12 @@ var require_websocket = __commonJS({
29889
29869
  }
29890
29870
  function netConnect(options) {
29891
29871
  options.path = options.socketPath;
29892
- return net4.connect(options);
29872
+ return net3.connect(options);
29893
29873
  }
29894
29874
  function tlsConnect(options) {
29895
29875
  options.path = void 0;
29896
29876
  if (!options.servername && options.servername !== "") {
29897
- options.servername = net4.isIP(options.host) ? "" : options.host;
29877
+ options.servername = net3.isIP(options.host) ? "" : options.host;
29898
29878
  }
29899
29879
  return tls.connect(options);
29900
29880
  }
@@ -29917,7 +29897,7 @@ var require_websocket = __commonJS({
29917
29897
  }
29918
29898
  function sendAfterClose(websocket, data, cb) {
29919
29899
  if (data) {
29920
- const length = isBlob(data) ? data.size : toBuffer2(data).length;
29900
+ const length = isBlob(data) ? data.size : toBuffer(data).length;
29921
29901
  if (websocket._socket) websocket._sender._bufferedBytes += length;
29922
29902
  else websocket._bufferedAmount += length;
29923
29903
  }
@@ -30570,23 +30550,6 @@ var require_websocket_server = __commonJS({
30570
30550
  }
30571
30551
  });
30572
30552
 
30573
- // ../node_modules/.pnpm/ws@8.20.0/node_modules/ws/wrapper.mjs
30574
- var import_stream, import_extension, import_permessage_deflate, import_receiver, import_sender, import_subprotocol, import_websocket, import_websocket_server, wrapper_default;
30575
- var init_wrapper = __esm({
30576
- "../node_modules/.pnpm/ws@8.20.0/node_modules/ws/wrapper.mjs"() {
30577
- "use strict";
30578
- import_stream = __toESM(require_stream(), 1);
30579
- import_extension = __toESM(require_extension(), 1);
30580
- import_permessage_deflate = __toESM(require_permessage_deflate(), 1);
30581
- import_receiver = __toESM(require_receiver(), 1);
30582
- import_sender = __toESM(require_sender(), 1);
30583
- import_subprotocol = __toESM(require_subprotocol(), 1);
30584
- import_websocket = __toESM(require_websocket(), 1);
30585
- import_websocket_server = __toESM(require_websocket_server(), 1);
30586
- wrapper_default = import_websocket.default;
30587
- }
30588
- });
30589
-
30590
30553
  // ../node_modules/.pnpm/process-nextick-args@2.0.1/node_modules/process-nextick-args/index.js
30591
30554
  var require_process_nextick_args = __commonJS({
30592
30555
  "../node_modules/.pnpm/process-nextick-args@2.0.1/node_modules/process-nextick-args/index.js"(exports2, module2) {
@@ -33464,8 +33427,8 @@ var require_utils = __commonJS({
33464
33427
  var result = transform[inputType][outputType](input);
33465
33428
  return result;
33466
33429
  };
33467
- exports2.resolve = function(path77) {
33468
- var parts = path77.split("/");
33430
+ exports2.resolve = function(path70) {
33431
+ var parts = path70.split("/");
33469
33432
  var result = [];
33470
33433
  for (var index = 0; index < parts.length; index++) {
33471
33434
  var part = parts[index];
@@ -39318,18 +39281,18 @@ var require_object = __commonJS({
39318
39281
  var object = new ZipObject(name, zipObjectContent, o);
39319
39282
  this.files[name] = object;
39320
39283
  };
39321
- var parentFolder = function(path77) {
39322
- if (path77.slice(-1) === "/") {
39323
- path77 = path77.substring(0, path77.length - 1);
39284
+ var parentFolder = function(path70) {
39285
+ if (path70.slice(-1) === "/") {
39286
+ path70 = path70.substring(0, path70.length - 1);
39324
39287
  }
39325
- var lastSlash = path77.lastIndexOf("/");
39326
- return lastSlash > 0 ? path77.substring(0, lastSlash) : "";
39288
+ var lastSlash = path70.lastIndexOf("/");
39289
+ return lastSlash > 0 ? path70.substring(0, lastSlash) : "";
39327
39290
  };
39328
- var forceTrailingSlash = function(path77) {
39329
- if (path77.slice(-1) !== "/") {
39330
- path77 += "/";
39291
+ var forceTrailingSlash = function(path70) {
39292
+ if (path70.slice(-1) !== "/") {
39293
+ path70 += "/";
39331
39294
  }
39332
- return path77;
39295
+ return path70;
39333
39296
  };
39334
39297
  var folderAdd = function(name, createFolders) {
39335
39298
  createFolders = typeof createFolders !== "undefined" ? createFolders : defaults.createFolders;
@@ -40331,7 +40294,7 @@ var require_lib3 = __commonJS({
40331
40294
  // src/run-case/recorder.ts
40332
40295
  function startRunCaseRecorder(opts) {
40333
40296
  const now = opts.now ?? Date.now;
40334
- const dir = import_node_path65.default.dirname(opts.recordPath);
40297
+ const dir = import_node_path58.default.dirname(opts.recordPath);
40335
40298
  let stream = null;
40336
40299
  let closing = false;
40337
40300
  let closedSettled = false;
@@ -40345,8 +40308,8 @@ function startRunCaseRecorder(opts) {
40345
40308
  });
40346
40309
  const ensureStream = () => {
40347
40310
  if (stream) return stream;
40348
- import_node_fs53.default.mkdirSync(dir, { recursive: true });
40349
- stream = import_node_fs53.default.createWriteStream(opts.recordPath, { flags: "a" });
40311
+ import_node_fs46.default.mkdirSync(dir, { recursive: true });
40312
+ stream = import_node_fs46.default.createWriteStream(opts.recordPath, { flags: "a" });
40350
40313
  stream.on("close", () => closedResolve());
40351
40314
  return stream;
40352
40315
  };
@@ -40371,12 +40334,12 @@ function startRunCaseRecorder(opts) {
40371
40334
  };
40372
40335
  return { tap, close, closed };
40373
40336
  }
40374
- var import_node_fs53, import_node_path65;
40337
+ var import_node_fs46, import_node_path58;
40375
40338
  var init_recorder = __esm({
40376
40339
  "src/run-case/recorder.ts"() {
40377
40340
  "use strict";
40378
- import_node_fs53 = __toESM(require("fs"), 1);
40379
- import_node_path65 = __toESM(require("path"), 1);
40341
+ import_node_fs46 = __toESM(require("fs"), 1);
40342
+ import_node_path58 = __toESM(require("path"), 1);
40380
40343
  }
40381
40344
  });
40382
40345
 
@@ -40419,7 +40382,7 @@ var init_wire = __esm({
40419
40382
  // src/run-case/controller.ts
40420
40383
  async function runController(opts) {
40421
40384
  const now = opts.now ?? Date.now;
40422
- const cwd = opts.cwd ?? (0, import_node_fs54.mkdtempSync)(import_node_path66.default.join(import_node_os23.default.tmpdir(), "clawd-runcase-"));
40385
+ const cwd = opts.cwd ?? (0, import_node_fs47.mkdtempSync)(import_node_path59.default.join(import_node_os23.default.tmpdir(), "clawd-runcase-"));
40423
40386
  const ownsCwd = opts.cwd === void 0;
40424
40387
  const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
40425
40388
  const spawnCtx = { cwd };
@@ -40580,19 +40543,19 @@ async function runController(opts) {
40580
40543
  if (sigintHandler) process.off("SIGINT", sigintHandler);
40581
40544
  if (ownsCwd) {
40582
40545
  try {
40583
- (0, import_node_fs54.rmSync)(cwd, { recursive: true, force: true });
40546
+ (0, import_node_fs47.rmSync)(cwd, { recursive: true, force: true });
40584
40547
  } catch {
40585
40548
  }
40586
40549
  }
40587
40550
  return exitCode ?? 0;
40588
40551
  }
40589
- var import_node_fs54, import_node_os23, import_node_path66;
40552
+ var import_node_fs47, import_node_os23, import_node_path59;
40590
40553
  var init_controller = __esm({
40591
40554
  "src/run-case/controller.ts"() {
40592
40555
  "use strict";
40593
- import_node_fs54 = require("fs");
40556
+ import_node_fs47 = require("fs");
40594
40557
  import_node_os23 = __toESM(require("os"), 1);
40595
- import_node_path66 = __toESM(require("path"), 1);
40558
+ import_node_path59 = __toESM(require("path"), 1);
40596
40559
  init_claude();
40597
40560
  init_stdout_splitter();
40598
40561
  init_permission_stdio();
@@ -40688,124 +40651,6 @@ stdout \u4E8B\u4EF6\uFF08\u884C JSON\uFF09\uFF1A
40688
40651
  }
40689
40652
  });
40690
40653
 
40691
- // src/sshd/sshd-cli-relay.ts
40692
- var sshd_cli_relay_exports = {};
40693
- __export(sshd_cli_relay_exports, {
40694
- sshRelay: () => sshRelay
40695
- });
40696
- async function sshRelay(argv) {
40697
- const args = parseSshRelayArgs(argv);
40698
- if (args.help) {
40699
- process.stdout.write(SSH_RELAY_HELP);
40700
- return 0;
40701
- }
40702
- if (!args.peerDeviceId) {
40703
- process.stderr.write("clawd ssh-relay: missing <peer-device-id>\n" + SSH_RELAY_HELP);
40704
- return 2;
40705
- }
40706
- const daemonUrl = process.env.CLAWD_DAEMON_URL;
40707
- const sessionId = process.env.CLAWD_SESSION_ID;
40708
- if (!daemonUrl) {
40709
- process.stderr.write(
40710
- "clawd ssh-relay: CLAWD_DAEMON_URL env not set (relay must run inside a clawd CC session)\n"
40711
- );
40712
- return 2;
40713
- }
40714
- if (!sessionId) {
40715
- process.stderr.write(
40716
- "clawd ssh-relay: CLAWD_SESSION_ID env not set (relay must run inside a clawd CC session)\n"
40717
- );
40718
- return 2;
40719
- }
40720
- const wsBase = daemonUrl.replace(/^http/, "ws").replace(/\/+$/, "");
40721
- const url = `${wsBase}/rpc/ssh-tunnel-local?peerDeviceId=${encodeURIComponent(args.peerDeviceId)}`;
40722
- return new Promise((resolve6) => {
40723
- const ws = new import_websocket.default(url, {
40724
- headers: { "X-Clawd-Session-Id": sessionId }
40725
- });
40726
- let settled = false;
40727
- const settle = (code) => {
40728
- if (settled) return;
40729
- settled = true;
40730
- try {
40731
- ws.close();
40732
- } catch {
40733
- }
40734
- resolve6(code);
40735
- };
40736
- ws.on("open", () => {
40737
- process.stdin.on("data", (chunk) => {
40738
- if (ws.readyState === ws.OPEN) ws.send(chunk, { binary: true });
40739
- });
40740
- process.stdin.on("end", () => {
40741
- try {
40742
- ws.close(1e3, "stdin end");
40743
- } catch {
40744
- }
40745
- });
40746
- process.stdin.on("error", () => settle(1));
40747
- });
40748
- ws.on("message", (data) => {
40749
- if (Buffer.isBuffer(data)) process.stdout.write(data);
40750
- else if (Array.isArray(data)) process.stdout.write(Buffer.concat(data));
40751
- else if (data instanceof ArrayBuffer) process.stdout.write(Buffer.from(data));
40752
- });
40753
- ws.on("close", (code) => {
40754
- settle(code === 1e3 ? 0 : 1);
40755
- });
40756
- ws.on("error", (err) => {
40757
- process.stderr.write(`clawd ssh-relay: ws error ${err.message}
40758
- `);
40759
- settle(1);
40760
- });
40761
- const onSignal = () => settle(0);
40762
- process.once("SIGINT", onSignal);
40763
- process.once("SIGTERM", onSignal);
40764
- });
40765
- }
40766
- function parseSshRelayArgs(argv) {
40767
- const out = {};
40768
- for (let i = 0; i < argv.length; i++) {
40769
- const a = argv[i];
40770
- if (a === "--help" || a === "-h") {
40771
- out.help = true;
40772
- continue;
40773
- }
40774
- if (a.startsWith("--")) {
40775
- throw new Error(`unknown flag: ${a}`);
40776
- }
40777
- if (!out.peerDeviceId) {
40778
- out.peerDeviceId = a;
40779
- }
40780
- }
40781
- return out;
40782
- }
40783
- var SSH_RELAY_HELP;
40784
- var init_sshd_cli_relay = __esm({
40785
- "src/sshd/sshd-cli-relay.ts"() {
40786
- "use strict";
40787
- init_wrapper();
40788
- SSH_RELAY_HELP = `clawd ssh-relay <peer-device-id>
40789
-
40790
- WebSocket relay to a peer daemon's /rpc/ssh-tunnel via the local daemon
40791
- (reads CLAWD_DAEMON_URL + CLAWD_SESSION_ID from env). Meant to be used as
40792
- SSH ProxyCommand from inside a clawd CC session.
40793
-
40794
- Options:
40795
- --help / -h \u663E\u793A\u5E2E\u52A9
40796
-
40797
- Example:
40798
- ssh -o ProxyCommand='~/.clawd/bin/clawd ssh-relay <peer-device-id>' \\
40799
- -i ~/.clawd/ssh-identity/id_ed25519 \\
40800
- <peer-os-user>@127.0.0.1
40801
-
40802
- \u767B\u5F55\u540D\u7528\u5BF9\u7AEF\u8D26\u53F7\uFF08Contact.osUser\uFF0C\u5EFA\u8054\u63E1\u624B\u4EA4\u6362\uFF09\u2014\u2014\u4E0D\u662F\u672C\u673A $USER\u3002
40803
- \u4E24\u673A\u8D26\u53F7\u540D\u4E0D\u540C\u65F6\u7528 $USER \u4F1A Permission denied (publickey)\u3002
40804
- \u89C1 doc/glossary/peer-mesh.md#\u5EFA\u8054\u4EA4\u6362\u6E05\u5355
40805
- `;
40806
- }
40807
- });
40808
-
40809
40654
  // src/config.ts
40810
40655
  var import_node_fs = __toESM(require("fs"), 1);
40811
40656
  var import_node_os = __toESM(require("os"), 1);
@@ -40813,7 +40658,6 @@ var import_node_path = __toESM(require("path"), 1);
40813
40658
  init_protocol();
40814
40659
  var DEFAULT_PORT = 18790;
40815
40660
  var DEFAULT_HOST = "127.0.0.1";
40816
- var DEFAULT_SSHD_PORT = 22422;
40817
40661
  var DEFAULT_CLAWOS_API = "https://api.clawos.chat";
40818
40662
  var DEFAULT_LOG_ENDPOINT = "https://clawd-prod.cn-hangzhou.log.aliyuncs.com/logstores/app-logs/track";
40819
40663
  function resolveLogShipping(raw, cliNoShipping) {
@@ -40881,14 +40725,6 @@ function parseArgs(argv) {
40881
40725
  case "--no-log-shipping":
40882
40726
  out.noLogShipping = true;
40883
40727
  break;
40884
- case "--sshd-port": {
40885
- const n = Number.parseInt(next() ?? "", 10);
40886
- if (!Number.isFinite(n) || n <= 0 || n > 65535) {
40887
- throw new Error(`invalid --sshd-port value: ${argv[i]}`);
40888
- }
40889
- out.sshdPort = n;
40890
- break;
40891
- }
40892
40728
  default:
40893
40729
  if (a.startsWith("--")) throw new Error(`unknown flag: ${a}`);
40894
40730
  break;
@@ -40938,7 +40774,6 @@ function resolveConfig(opts) {
40938
40774
  fileCfg.logShipping,
40939
40775
  args.noLogShipping ?? false
40940
40776
  );
40941
- const sshdPort = args.sshdPort ?? (typeof fileCfg.sshdPort === "number" ? fileCfg.sshdPort : DEFAULT_SSHD_PORT);
40942
40777
  return {
40943
40778
  port,
40944
40779
  host,
@@ -40952,8 +40787,7 @@ function resolveConfig(opts) {
40952
40787
  frpcBinary,
40953
40788
  mode,
40954
40789
  previewPorts,
40955
- logShipping,
40956
- sshdPort
40790
+ logShipping
40957
40791
  };
40958
40792
  }
40959
40793
  var HELP_TEXT = `clawd [options]
@@ -40965,7 +40799,6 @@ var HELP_TEXT = `clawd [options]
40965
40799
  --clawos-api <url> tunnel register \u63A5\u53E3\u7684 base url\uFF08\u9ED8\u8BA4 https://api.clawos.chat\uFF09
40966
40800
  --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
40967
40801
  --no-log-shipping \u7981\u7528 SLS \u65E5\u5FD7\u4E0A\u884C\uFF08\u8986\u76D6 config.json logShipping.mode=off\uFF09
40968
- --sshd-port <n> Contact SSH \u53CD\u5411\u8BBF\u95EE loopback \u7AEF\u53E3\uFF0C\u9ED8\u8BA4 22422
40969
40802
  --help / -h \u663E\u793A\u5E2E\u52A9
40970
40803
  --version / -v \u663E\u793A\u7248\u672C
40971
40804
 
@@ -40974,12 +40807,6 @@ Subcommands:
40974
40807
  \u72EC\u7ACB CC \u63A7\u5236\u5668\u8FDB\u7A0B\uFF1A\u5916\u90E8 AI \u901A\u8FC7 stdin/stdout \u884C JSON \u534F\u8BAE
40975
40808
  \u9A71\u52A8 CC \u591A\u8F6E\u5BF9\u8BDD\u4E0E\u6743\u9650\u51B3\u7B56\uFF0CIPC \u5168\u7A0B\u5F55\u5230 --record JSONL\u3002
40976
40809
  \u8BE6\u89C1 'clawd run-case --help'
40977
- ssh-relay <peer-device-id>
40978
- WebSocket relay via local daemon (reads
40979
- CLAWD_DAEMON_URL + CLAWD_SESSION_ID from env)
40980
- to peer /rpc/ssh-tunnel\uFF0C\u4F5C\u4E3A SSH ProxyCommand
40981
- \u4F7F\u7528\uFF08Contact SSH tunnel v2\uFF09\u3002
40982
- \u8BE6\u89C1 'clawd ssh-relay --help'
40983
40810
 
40984
40811
  Env (advanced):
40985
40812
  CLAWD_FRPC_BIN \u81EA\u5E26 frpc \u4E8C\u8FDB\u5236\u8DEF\u5F84\uFF08\u9ED8\u8BA4\u6309\u9700\u4E0B\u8F7D\u5230 ~/.clawd/bin/frpc\uFF09
@@ -40990,8 +40817,8 @@ Env (advanced):
40990
40817
  `;
40991
40818
 
40992
40819
  // src/index.ts
40993
- var import_node_path64 = __toESM(require("path"), 1);
40994
- var import_node_fs52 = __toESM(require("fs"), 1);
40820
+ var import_node_path57 = __toESM(require("path"), 1);
40821
+ var import_node_fs45 = __toESM(require("fs"), 1);
40995
40822
  var import_node_os22 = __toESM(require("os"), 1);
40996
40823
 
40997
40824
  // ../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/stringify.js
@@ -41741,13 +41568,6 @@ function composeGuestSandbox(base, userWorkDir, spawnCwd) {
41741
41568
  fsv.denyWrite = unionArr((fsv.denyWrite ?? []).filter((p2) => p2 !== "~/"), [spawnCwd]);
41742
41569
  fsv.allowRead = unionArr(fsv.allowRead, [userWorkDir]);
41743
41570
  fsv.allowWrite = unionArr(fsv.allowWrite, [userWorkDir]);
41744
- fsv.allowRead = unionArr(fsv.allowRead, [
41745
- "~/.clawd/ssh-identity",
41746
- "~/.clawd/bin",
41747
- "~/Library/Application Support/@clawos/clawd-desktop/ota-bundles",
41748
- "~/.clawd/config.json",
41749
- "~/.clawd/auth.json"
41750
- ]);
41751
41571
  return s;
41752
41572
  }
41753
41573
 
@@ -42015,32 +41835,6 @@ deviceId \u76F4\u63A5\u53D6\u81EA token\uFF0C\u4E0D\u8981\u6539\u5199\u6216\u731
42015
41835
  - \u5931\u8D25\uFF1A\`{ isFailure: true, reason: "..." }\`
42016
41836
  - \u62A5\u544A\u5B8C\u540E\u4F60\u7684 session \u5C31\u7ED3\u675F\u4E86\uFF0C\u4E0D\u8981\u7EE7\u7EED\u8F93\u51FA`;
42017
41837
 
42018
- // src/sshd/contact-ssh-prompt.ts
42019
- var CONTACT_SSH_SYSTEM_PROMPT_HINT = `## \u8DE8\u8BBE\u5907\u6587\u4EF6\u8BBF\u95EE\uFF08Contact SSH\uFF09
42020
-
42021
- \u7528\u6237\u53EF\u80FD\u6709\u591A\u53F0 clawd \u8BBE\u5907\u4E92\u76F8\u6388\u6743\u3002\u4F60\u5728\u8FD9\u53F0\u673A\u5668\u4E0A\u8BBF\u95EE\u5DF2\u6388\u6743\u5BF9\u7AEF\uFF08\u4E0B\u79F0 A\uFF0CdeviceId \u7531\u7528\u6237/\u4EFB\u52A1\u4E0A\u4E0B\u6587\u7ED9\u51FA\uFF09\u7684\u65B9\u6CD5\uFF1A
42022
-
42023
- \u7528 SSH \u62E8\u53F7\uFF08\u628A \`<A>\` \u6362\u6210\u5BF9\u7AEF deviceId\uFF09\uFF1A
42024
- \`\`\`bash
42025
- ssh -o ProxyCommand='~/.clawd/bin/clawd ssh-relay <A>' \\
42026
- -i ~/.clawd/ssh-identity/id_ed25519 \\
42027
- -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \\
42028
- $USER@127.0.0.1 <command>
42029
- # \u4F8B\u5982\uFF1A... $USER@127.0.0.1 cat /Users/xxx/some/file.md
42030
- # ... $USER@127.0.0.1 ls -la /Users/xxx/proj
42031
- # \u8BF4\u660E\uFF1A\`-i\` \u7528\u672C\u8BBE\u5907\u552F\u4E00\u7684 SSH identity key\uFF08\u6240\u6709\u5BF9\u7AEF\u590D\u7528\u4E00\u628A\uFF09\u3002
42032
- # \`~/.clawd/bin/clawd\` \u662F daemon \u542F\u52A8\u65F6 seed \u7684 shim\uFF0C\u786C\u7F16\u7801\u5F53\u524D daemon \u7684
42033
- # execPath+cliPath\uFF0C\u4FDD\u8BC1 relay \u8DDF daemon \u7248\u672C\u540C\u6B65\u3002\u4E0D\u8981\u7528\u88F8 \`clawd\`\uFF08\u8D70 $PATH \u4F1A\u6F02\uFF09\u3002
42034
- \`\`\`
42035
-
42036
- **\u4F60\u80FD\u8BBF\u95EE\u4EC0\u4E48**\uFF1A**\u6CA1\u6709\u76EE\u5F55\u7EA7\u9650\u5236**\u2014\u2014\u4F60\u4EE5 A \u673A\u5668\u4E0A\u8BE5 SSH \u7528\u6237\u7684\u6743\u9650\u8BBF\u95EE\u6587\u4EF6\uFF08\u7B49\u540C\u4E8E A \u7ED9\u4F60\u5F00\u4E86\u4E00\u4E2A shell\uFF09\u3002\u8BF7\u53EA\u5728 A \u6388\u6743/\u9700\u8981\u7684\u8303\u56F4\u5185\u64CD\u4F5C\uFF0C\u4E0D\u8981\u8D8A\u754C\u7FFB\u770B\u65E0\u5173\u76EE\u5F55\u3002
42037
-
42038
- **\u6CE8\u610F**\uFF1A
42039
- - \u6388\u6743\u662F\u5B9E\u65F6\u7684\uFF1AA \u64A4\u9500 remoteAccessAllowed \u540E\u4F60\u4E0B\u4E00\u6B21\u62E8\u53F7\u7ACB\u5373\u5931\u6548\uFF08authorized_keys \u7ACB\u5373\u5237\uFF09
42040
- - \u62E8\u53F7\u88AB\u62D2\uFF08Permission denied\uFF09\u901A\u5E38\u662F A \u5C1A\u672A\u6388\u6743\u672C\u8BBE\u5907\uFF0C\u6216\u53CC\u65B9\u8001\u7248\u672C\u5EFA\u8054\u3001A \u7684 contact \u91CC\u6CA1\u6709\u672C\u673A pubkey\uFF08\u4EFB\u4E00\u65B9\u91CD\u65B0 device:connect \u4E00\u6B21\u5373\u8865\uFF09\uFF0C\u5982\u5B9E\u544A\u8BC9\u7528\u6237
42041
- - \u6392\u969C\uFF1A\u672C\u673A debug \u65E5\u5FD7\u5728 \`~/.clawd/log/contact-ssh.log\`\uFF08\u7528\u6237\u53EF \`tail -f\` \u770B\u6BCF\u4E00\u6B65\uFF09\uFF0C\u4F60\u51FA\u9519\u65F6\u544A\u8BC9\u7528\u6237\u67E5\u8FD9\u4E2A\u6587\u4EF6
42042
- `;
42043
-
42044
41838
  // src/session/reducer.ts
42045
41839
  function cloneState(s) {
42046
41840
  return {
@@ -42176,9 +41970,6 @@ function buildSpawnContext(state, deps) {
42176
41970
  if (daemonUrl) {
42177
41971
  ctx.extraSystemPrompt = (ctx.extraSystemPrompt ? ctx.extraSystemPrompt + "\n\n" : "") + ATTACHMENT_SHARING_HINT;
42178
41972
  }
42179
- if (meta?.personaMode === "guest") {
42180
- ctx.extraSystemPrompt = (ctx.extraSystemPrompt ? ctx.extraSystemPrompt + "\n\n" : "") + CONTACT_SSH_SYSTEM_PROMPT_HINT;
42181
- }
42182
41973
  if (meta?.extraSettings) {
42183
41974
  ctx.extraSettings = meta.extraSettings;
42184
41975
  }
@@ -46352,7 +46143,7 @@ function tryLoadViewerAssets(logger) {
46352
46143
  }
46353
46144
  logger?.warn("share-md-viewer.assets-not-found", {
46354
46145
  candidates,
46355
- hint: "run `pnpm -F @clawos/ext-clawd-ui build && pnpm -F @clawos-dev/clawd build`"
46146
+ hint: "run `pnpm -F @clawos/clawd-ui build && pnpm -F @clawos-dev/clawd build`"
46356
46147
  });
46357
46148
  return null;
46358
46149
  }
@@ -46425,7 +46216,7 @@ function tryLoadShareUi(logger) {
46425
46216
  }
46426
46217
  logger?.warn("share-ui.assets-not-found", {
46427
46218
  candidates,
46428
- hint: "run `pnpm -F @clawos/ext-clawd-ui build && pnpm -F @clawos-dev/clawd build`"
46219
+ hint: "run `pnpm -F @clawos/clawd-ui build && pnpm -F @clawos-dev/clawd build`"
46429
46220
  });
46430
46221
  return null;
46431
46222
  }
@@ -46793,8 +46584,8 @@ function turnStartInput(text) {
46793
46584
  const items = [];
46794
46585
  let leftover = text;
46795
46586
  for (const m2 of text.matchAll(SKILL_RE)) {
46796
- const [marker, name, path77] = m2;
46797
- items.push({ type: "skill", name, path: path77 });
46587
+ const [marker, name, path70] = m2;
46588
+ items.push({ type: "skill", name, path: path70 });
46798
46589
  leftover = leftover.replace(marker, "");
46799
46590
  }
46800
46591
  for (const m2 of text.matchAll(ATTACHMENT_RE2)) {
@@ -49394,13 +49185,13 @@ function mapSkillsListResponse(res) {
49394
49185
  const r = s ?? {};
49395
49186
  const name = str3(r.name);
49396
49187
  if (!name) continue;
49397
- const path77 = str3(r.path);
49188
+ const path70 = str3(r.path);
49398
49189
  const description = str3(r.description);
49399
49190
  const isPlugin = name.includes(":");
49400
49191
  out.push({
49401
49192
  name,
49402
49193
  source: isPlugin ? "plugin" : "project",
49403
- ...path77 ? { path: path77 } : {},
49194
+ ...path70 ? { path: path70 } : {},
49404
49195
  ...description ? { description } : {},
49405
49196
  ...isPlugin ? { plugin: name.split(":")[0] } : {}
49406
49197
  });
@@ -50106,8 +49897,18 @@ function listRegistered() {
50106
49897
  return [...registry.keys()];
50107
49898
  }
50108
49899
 
49900
+ // ../node_modules/.pnpm/ws@8.20.0/node_modules/ws/wrapper.mjs
49901
+ var import_stream = __toESM(require_stream(), 1);
49902
+ var import_extension = __toESM(require_extension(), 1);
49903
+ var import_permessage_deflate = __toESM(require_permessage_deflate(), 1);
49904
+ var import_receiver = __toESM(require_receiver(), 1);
49905
+ var import_sender = __toESM(require_sender(), 1);
49906
+ var import_subprotocol = __toESM(require_subprotocol(), 1);
49907
+ var import_websocket = __toESM(require_websocket(), 1);
49908
+ var import_websocket_server = __toESM(require_websocket_server(), 1);
49909
+ var wrapper_default = import_websocket.default;
49910
+
50109
49911
  // src/transport/local-ws-server.ts
50110
- init_wrapper();
50111
49912
  var import_node_http2 = __toESM(require("http"), 1);
50112
49913
 
50113
49914
  // src/transport/preview-proxy.ts
@@ -50183,30 +49984,6 @@ var LocalWsServer = class {
50183
49984
  const httpServer = import_node_http2.default.createServer((req, res) => this.handleHttpRequest(req, res));
50184
49985
  const wss = new import_websocket_server.default({ noServer: true, clientTracking: true });
50185
49986
  httpServer.on("upgrade", (req, socket, head) => {
50186
- if (this.opts.sshTunnelUpgradeHandler) {
50187
- const [urlPath] = (req.url ?? "").split("?");
50188
- if (urlPath === "/rpc/ssh-tunnel") {
50189
- void this.opts.sshTunnelUpgradeHandler(
50190
- req,
50191
- socket,
50192
- head,
50193
- wss
50194
- );
50195
- return;
50196
- }
50197
- }
50198
- if (this.opts.sshTunnelLocalUpgradeHandler) {
50199
- const [urlPath] = (req.url ?? "").split("?");
50200
- if (urlPath === "/rpc/ssh-tunnel-local") {
50201
- void this.opts.sshTunnelLocalUpgradeHandler(
50202
- req,
50203
- socket,
50204
- head,
50205
- wss
50206
- );
50207
- return;
50208
- }
50209
- }
50210
49987
  if (req.url?.startsWith("/preview/")) {
50211
49988
  const pathname = (() => {
50212
49989
  try {
@@ -50601,10 +50378,7 @@ var EXPOSED_RPC_METHODS = [
50601
50378
  // 查当前 turn caller —— cc 长驻 + owner/guest 交替驱动同一 session 时不能靠启动时 env
50602
50379
  // 写死身份,必须运行时 fetch。header X-Clawd-Session-Id 作 session 定位,handler 内查
50603
50380
  // manager.tryGetState(sid).currentTurnSender 返 SenderInfo | null。
50604
- "session.currentTurnSender",
50605
- // 建联交换(T-32):本机 cc 拨号前查对端 osUser 拼 `<osUser>@127.0.0.1`。窄读——handler
50606
- // 只返 deviceId / displayName / osUser(无 connectToken 等敏感字段),见 handlers/contact.ts dialInfo。
50607
- "contact.dialInfo"
50381
+ "session.currentTurnSender"
50608
50382
  ];
50609
50383
  async function handleRpcRequest(input) {
50610
50384
  const { method, body, sessionId, dispatcher } = input;
@@ -50693,9 +50467,7 @@ var AuthGate = class {
50693
50467
  try {
50694
50468
  r = await this.opts.authenticate(
50695
50469
  parsed.data.token,
50696
- parsed.data.selfUrl,
50697
- parsed.data.selfSshPublicKey,
50698
- parsed.data.selfOsUser
50470
+ parsed.data.selfUrl
50699
50471
  );
50700
50472
  } finally {
50701
50473
  st.pending = false;
@@ -51284,8 +51056,7 @@ var ContactStore = class {
51284
51056
  return true;
51285
51057
  }
51286
51058
  /**
51287
- * 更新单条 contact SSH 授权开关(对齐 setPin pattern)。T-29 去沙箱后只改 remoteAccessAllowed;
51288
- * exposedDirs 已废弃,本方法不再触碰它(保留 contact 原值不动)。
51059
+ * 更新单条 contact 的远程访问授权开关(对齐 setPin pattern)。
51289
51060
  * @returns 是否命中:deviceId 不存在返 false;命中即 flush.
51290
51061
  */
51291
51062
  setRemoteAccess(deviceId, opts) {
@@ -51319,7 +51090,6 @@ var ContactStore = class {
51319
51090
  };
51320
51091
 
51321
51092
  // src/contact/connect-remote.ts
51322
- init_wrapper();
51323
51093
  var crypto6 = __toESM(require("crypto"), 1);
51324
51094
  var HANDSHAKE_TIMEOUT_MS = 5e3;
51325
51095
  var RPC_TIMEOUT_MS = 5e3;
@@ -51341,11 +51111,7 @@ async function connectRemote(args) {
51341
51111
  // 缺省时省略字段(不发空串),对方 schema selfUrl 是 .min(1).optional()。
51342
51112
  // 决策 #16:身份字段(selfPrincipalId/selfDisplayName/selfDeviceId)已删——
51343
51113
  // 对方从 connect token 签名背书取 deviceId/ownerId/provider,不信自报。
51344
- ...args.selfUrl ? { selfUrl: args.selfUrl } : {},
51345
- // 建联交换:自报本机 SSH pubkey + OS 账号名;缺省省略字段(不发空串)。
51346
- // 见 doc/glossary/peer-mesh.md#建联交换清单
51347
- ...args.selfSshPublicKey ? { selfSshPublicKey: args.selfSshPublicKey } : {},
51348
- ...args.selfOsUser ? { selfOsUser: args.selfOsUser } : {}
51114
+ ...args.selfUrl ? { selfUrl: args.selfUrl } : {}
51349
51115
  })
51350
51116
  );
51351
51117
  await new Promise((resolve6, reject) => {
@@ -51420,10 +51186,7 @@ async function connectRemote(args) {
51420
51186
  ownerId: f.owner.ownerId,
51421
51187
  provider: f.owner.provider,
51422
51188
  displayName: f.owner.displayName,
51423
- grants: f.capability.grants,
51424
- // 建联交换:对方老版本不带 → 省略(不落 undefined 占位)。见 …#建联交换清单
51425
- ...f.sshPublicKey ? { sshPublicKey: f.sshPublicKey } : {},
51426
- ...f.osUser ? { osUser: f.osUser } : {}
51189
+ grants: f.capability.grants
51427
51190
  };
51428
51191
  },
51429
51192
  close: () => {
@@ -51445,8 +51208,6 @@ async function autoReverseContact(args) {
51445
51208
  actions: [...g2.actions]
51446
51209
  }));
51447
51210
  const existing = args.store.get(args.deviceId) ?? void 0;
51448
- const sshPublicKey = args.sshPublicKey ?? existing?.sshPublicKey;
51449
- const osUser = args.osUser ?? existing?.osUser;
51450
51211
  const base = {
51451
51212
  deviceId: args.deviceId,
51452
51213
  ownerId: args.ownerId,
@@ -51457,17 +51218,10 @@ async function autoReverseContact(args) {
51457
51218
  grants,
51458
51219
  addedAt: now(),
51459
51220
  pinnedAt: existing?.pinnedAt ?? null,
51460
- remoteAccessAllowed: existing?.remoteAccessAllowed ?? false,
51461
- // 建联交换:缺省不落 undefined 占位(JSON 持久化干净)。见 …#建联交换清单
51462
- ...sshPublicKey ? { sshPublicKey } : {},
51463
- ...osUser ? { osUser } : {},
51464
- exposedDirs: []
51221
+ remoteAccessAllowed: existing?.remoteAccessAllowed ?? false
51465
51222
  };
51466
51223
  args.store.upsert(base);
51467
51224
  args.broadcast({ type: "contact:added", contact: base });
51468
- if (base.remoteAccessAllowed && sshPublicKey && sshPublicKey !== existing?.sshPublicKey) {
51469
- args.rebuildAuthorizedKeys?.();
51470
- }
51471
51225
  if (!args.exchangeToken) return;
51472
51226
  let token;
51473
51227
  try {
@@ -53465,926 +53219,29 @@ async function waitForFrpcReady(proc, timeoutMs) {
53465
53219
  });
53466
53220
  }
53467
53221
 
53468
- // src/sshd/sshd-manager.ts
53469
- var import_node_fs40 = __toESM(require("fs"), 1);
53470
- var import_node_path40 = __toESM(require("path"), 1);
53471
- var import_node_child_process11 = require("child_process");
53472
-
53473
- // src/sshd/sshd-config.ts
53474
- function buildSshdConfig(input) {
53475
- const lines = [
53476
- `ListenAddress ${input.listenAddress}`,
53477
- `Port ${input.port}`,
53478
- `HostKey ${input.hostKeyPath}`,
53479
- `PidFile ${input.pidFilePath}`,
53480
- `AuthorizedKeysFile ${input.authorizedKeysFile}`,
53481
- `PubkeyAuthentication yes`,
53482
- `PasswordAuthentication no`,
53483
- `ChallengeResponseAuthentication no`,
53484
- `KbdInteractiveAuthentication no`,
53485
- `PermitRootLogin no`,
53486
- `StrictModes no`,
53487
- `UsePAM no`,
53488
- `LogLevel INFO`,
53489
- `Subsystem sftp internal-sftp`
53490
- ];
53491
- return lines.join("\n") + "\n";
53492
- }
53493
-
53494
- // src/sshd/sshd-process.ts
53495
- var import_node_fs37 = __toESM(require("fs"), 1);
53496
- var import_node_path37 = __toESM(require("path"), 1);
53497
- var import_node_child_process10 = require("child_process");
53498
- function sshdPidFilePath(dataDir) {
53499
- return import_node_path37.default.join(dataDir, "sshd", "sshd.pid");
53500
- }
53501
- function writeSshdPid(dataDir, pid) {
53502
- try {
53503
- const p2 = sshdPidFilePath(dataDir);
53504
- import_node_fs37.default.mkdirSync(import_node_path37.default.dirname(p2), { recursive: true, mode: 448 });
53505
- import_node_fs37.default.writeFileSync(p2, String(pid), { mode: 384 });
53506
- } catch {
53507
- }
53508
- }
53509
- function clearSshdPid(dataDir) {
53510
- try {
53511
- import_node_fs37.default.unlinkSync(sshdPidFilePath(dataDir));
53512
- } catch {
53513
- }
53514
- }
53515
- function defaultIsPidAlive2(pid) {
53516
- if (!Number.isFinite(pid) || pid <= 0) return false;
53517
- try {
53518
- process.kill(pid, 0);
53519
- return true;
53520
- } catch (err) {
53521
- const code = err.code;
53522
- return code === "EPERM";
53523
- }
53524
- }
53525
- function defaultReadPidFile2(file) {
53526
- try {
53527
- return import_node_fs37.default.readFileSync(file, "utf8");
53528
- } catch {
53529
- return null;
53530
- }
53531
- }
53532
- function defaultKillPid2(pid, signal) {
53533
- try {
53534
- process.kill(pid, signal);
53535
- } catch {
53536
- }
53537
- }
53538
- function defaultSleep2(ms) {
53539
- return new Promise((r) => setTimeout(r, ms));
53540
- }
53541
- async function killStaleSshd(deps) {
53542
- const pidFile = sshdPidFilePath(deps.dataDir);
53543
- const configPath = import_node_path37.default.join(deps.dataDir, "sshd", "sshd_config");
53544
- const readPidFile = deps.readPidFileImpl ?? defaultReadPidFile2;
53545
- const isAlive = deps.isPidAliveImpl ?? defaultIsPidAlive2;
53546
- const killPid = deps.killPidImpl ?? defaultKillPid2;
53547
- const scanPids = deps.scanSshdPidsImpl ?? ((cp) => defaultScanSshdPidsByCmdline(cp, deps.logger));
53548
- const sleep2 = deps.sleepImpl ?? defaultSleep2;
53549
- const victims = /* @__PURE__ */ new Set();
53550
- const raw = readPidFile(pidFile);
53551
- if (raw) {
53552
- const pid = parseInt(raw.trim(), 10);
53553
- if (Number.isFinite(pid) && pid > 0 && pid !== deps.ownPid && isAlive(pid)) {
53554
- victims.add(pid);
53555
- }
53556
- }
53557
- try {
53558
- const scanned = await scanPids(configPath);
53559
- for (const pid of scanned) {
53560
- if (pid > 0 && pid !== deps.ownPid && isAlive(pid)) victims.add(pid);
53561
- }
53562
- } catch (e) {
53563
- deps.logger?.warn("sshd: stale-sshd cmdline scan failed", { err: e.message });
53564
- }
53565
- if (victims.size === 0) {
53566
- try {
53567
- import_node_fs37.default.unlinkSync(pidFile);
53568
- } catch {
53569
- }
53570
- return;
53571
- }
53572
- for (const pid of victims) {
53573
- deps.logger?.warn("sshd: killing stale sshd before respawn", { pid });
53574
- killPid(pid, "SIGKILL");
53575
- }
53576
- await sleep2(deps.reapWaitMs ?? 300);
53577
- try {
53578
- import_node_fs37.default.unlinkSync(pidFile);
53579
- } catch {
53580
- }
53581
- }
53582
- async function defaultScanSshdPidsByCmdline(configPath, logger) {
53583
- if (process.platform === "win32") return [];
53584
- return new Promise((resolve6) => {
53585
- const ps = (0, import_node_child_process10.spawn)("ps", ["-axo", "pid=,command="], { stdio: ["ignore", "pipe", "ignore"] });
53586
- let buf = "";
53587
- ps.stdout.on("data", (c) => {
53588
- buf += c.toString();
53589
- });
53590
- ps.on("exit", () => {
53591
- const pids = [];
53592
- for (const line of buf.split("\n")) {
53593
- const m2 = /^\s*(\d+)\s+(.*)$/.exec(line);
53594
- if (!m2) continue;
53595
- const cmd = m2[2];
53596
- if (!/\bsshd\b/.test(cmd)) continue;
53597
- if (!cmd.includes(configPath)) continue;
53598
- const pid = parseInt(m2[1], 10);
53599
- if (Number.isFinite(pid) && pid > 0) pids.push(pid);
53600
- }
53601
- resolve6(pids);
53602
- });
53603
- ps.on("error", (e) => {
53604
- logger?.warn("sshd: ps scan failed", { err: e.message });
53605
- resolve6([]);
53606
- });
53607
- });
53608
- }
53609
-
53610
- // src/sshd/jail-script.ts
53611
- var import_node_fs38 = __toESM(require("fs"), 1);
53612
- var import_node_path38 = __toESM(require("path"), 1);
53613
- var CLAWD_SSH_JAIL_SCRIPT = String.raw`#!/usr/bin/env bash
53614
- # clawd-ssh-jail — SSH reverse access sandbox wrapper (managed by clawd; do not edit)
53615
- #
53616
- # 由 sshd authorized_keys 的 command= 强制入口调用。
53617
- # 用法: sshd 会以 \`clawd-ssh-jail <deviceId>\` 起本脚本;$SSH_ORIGINAL_COMMAND = client
53618
- # 真实请求(interactive shell 时为空)。
53619
- #
53620
- # 职责:
53621
- # 1. 读 ~/.clawd/contacts.json 校验 contact.remoteAccessAllowed(不通过即拒)
53622
- # 2. 直接 exec shell(去沙箱后无目录级限制)
53623
-
53624
- set -euo pipefail
53625
-
53626
- # 用户可读审计日志:追加到 ~/.clawd/log/contact-ssh.log(跟 daemon TS 侧 contact-ssh-log.ts 共写)
53627
- # bash 侧写死 ~/.clawd/log/,因为 jail 脚本在 authorized_keys command= 里被 sshd 起,
53628
- # 拿不到 daemon --data-dir 覆盖。dev daemon 用 --data-dir=/tmp/x 时 jail 仍写 ~/.clawd/log/,
53629
- # 跟改动前 (~/.clawd/contact-ssh.log) 一样的分歧行为,不做统一。
53630
- CONTACT_SSH_LOG="${"${HOME}"}/.clawd/log/contact-ssh.log"
53631
- log_line() {
53632
- # 参数: level tag msg
53633
- local ts
53634
- ts=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ" 2>/dev/null || date -u +"%Y-%m-%dT%H:%M:%SZ")
53635
- printf '[%s] [%s] [%s] %s\n' "$ts" "$1" "$2" "$3" >> "$CONTACT_SSH_LOG" 2>/dev/null || true
53636
- }
53637
-
53638
- DEVICE_ID="${"${1:-}"}"
53639
- if [ -z "$DEVICE_ID" ]; then
53640
- log_line ERROR jail.entered "clawd-ssh-jail 缺 deviceId 参数"
53641
- echo "clawd-ssh-jail: missing deviceId" >&2
53642
- exit 1
53643
- fi
53644
-
53645
- log_line INFO jail.entered "clawd-ssh-jail 起来 device=${"${DEVICE_ID}"} cmd=${"${SSH_ORIGINAL_COMMAND:-<interactive-shell>}"}"
53646
-
53647
- CONTACTS="${"${HOME}"}/.clawd/contacts.json"
53648
- if [ ! -f "$CONTACTS" ]; then
53649
- log_line ERROR jail.entered "contacts.json 不存在,无法校验 remoteAccessAllowed"
53650
- echo "clawd-ssh-jail: contacts.json missing" >&2
53651
- exit 1
53652
- fi
53653
-
53654
- # 校验该 contact 的 remoteAccessAllowed(mac/linux 都自带 python3)。去沙箱后不再读目录白名单:
53655
- # remoteAccessAllowed=false → DENIED exit 2;deviceId 未命中 → NOT_FOUND exit 3;命中且允许 → exit 0。
53656
- # set -e 下 python 非 0 退出即终止脚本(不进 exec),只有 exit 0 才继续 exec shell。
53657
- python3 -c "
53658
- import json, sys
53659
- with open('$CONTACTS') as f:
53660
- data = json.load(f)
53661
- for c in data.get('contacts', []):
53662
- if c.get('deviceId') == '$DEVICE_ID':
53663
- if not c.get('remoteAccessAllowed'):
53664
- print('DENIED', file=sys.stderr); sys.exit(2)
53665
- sys.exit(0)
53666
- print('NOT_FOUND', file=sys.stderr); sys.exit(3)
53667
- "
53668
-
53669
- log_line INFO jail.entered "remoteAccessAllowed 校验通过,放行 exec shell"
53670
-
53671
- CMD="${"${SSH_ORIGINAL_COMMAND:-}"}"
53672
- if [ -z "$CMD" ]; then
53673
- SHELL_CMD=(bash --login)
53674
- else
53675
- SHELL_CMD=(bash -c "$CMD")
53676
- fi
53677
-
53678
- # 去掉 OS 沙箱层(原本 macOS Seatbelt 策略 / Linux 命名空间隔离):SSH 进来即该用户
53679
- # 权限内的 shell,直接 exec。沙箱只限制文件、拦不住网络/进程,隔离价值有限,且相关 API
53680
- # 在 macOS 已 deprecated。授权即完全信任——remoteAccessAllowed 授权开关 + authorized_keys 即时
53681
- # 踢行是唯一访问闸。要加回沙箱:从 git 历史(T-29 之前)恢复本段的 OS 分支即可。
53682
- exec "${"${SHELL_CMD[@]}"}"
53683
- `;
53684
- function ensureJailScript(dataDir) {
53685
- const binDir = import_node_path38.default.join(dataDir, "bin");
53686
- import_node_fs38.default.mkdirSync(binDir, { recursive: true, mode: 493 });
53687
- const target = import_node_path38.default.join(binDir, "clawd-ssh-jail");
53688
- import_node_fs38.default.writeFileSync(target, CLAWD_SSH_JAIL_SCRIPT, { mode: 493 });
53689
- return target;
53690
- }
53691
-
53692
- // src/sshd/clawd-shim.ts
53693
- var import_node_fs39 = __toESM(require("fs"), 1);
53694
- var import_node_path39 = __toESM(require("path"), 1);
53695
- var DEFAULT_FALLBACK_APPS = ["/Applications/Clawd.app", "$HOME/Applications/Clawd.app"];
53696
- function shellQuote(s) {
53697
- return `'${s.replace(/'/g, "'\\''")}'`;
53698
- }
53699
- function fallbackWord(app) {
53700
- if (app.startsWith("$HOME/")) return `"$HOME"/${shellQuote(app.slice("$HOME/".length))}`;
53701
- return shellQuote(app);
53702
- }
53703
- function buildClawdShim(execPath, cliPath, fallbackApps = DEFAULT_FALLBACK_APPS) {
53704
- const candidates = fallbackApps.map(fallbackWord).join(" ");
53705
- return `#!/usr/bin/env bash
53706
- # clawd shim (managed by clawd; do not edit)
53707
- #
53708
- # primary = seed \u672C\u6587\u4EF6\u7684 daemon \u7684 execPath + cliPath\uFF1Bdaemon \u91CD\u542F\u4F1A\u8986\u5199\u672C\u6587\u4EF6\uFF08\u5E42\u7B49 seed\uFF09\u3002
53709
- # primary \u5931\u6548\uFF08npx \u7F13\u5B58 GC / OTA bundle GC / app \u79FB\u52A8\uFF09\u65F6\u56DE\u9000 installer \u81EA\u5E26 CLI\uFF1B
53710
- # \u5168\u706D\u7ED9\u53EF\u8BFB\u9519\u8BEF\u3002guest CC \u7528 SSH ProxyCommand \u8D70 ~/.clawd/bin/clawd ssh-relay \u65F6\u547D\u4E2D\u672C shim\u3002
53711
- #
53712
- # ELECTRON_RUN_AS_NODE \u663E\u5F0F\u5BFC\u51FA\uFF1Aprimary/fallback \u90FD\u53EF\u80FD\u662F Electron \u4E8C\u8FDB\u5236\uFF08plain node \u5FFD\u7565
53713
- # \u8BE5\u53D8\u91CF\uFF0C\u65E0\u5BB3\uFF09\u2014\u2014\u4E0D\u518D\u4F9D\u8D56 desktop \u73AF\u5883\u53D8\u91CF\u6070\u597D\u6CC4\u6F0F\u5230 CC \u5B50\u8FDB\u7A0B\u3002
53714
- export ELECTRON_RUN_AS_NODE=1
53715
- EXEC=${shellQuote(execPath)}
53716
- CLI=${shellQuote(cliPath)}
53717
- if [ ! -x "$EXEC" ] || [ ! -f "$CLI" ]; then
53718
- for APP in ${candidates}; do
53719
- FCLI="$APP/Contents/Resources/daemon/dist/cli.cjs"
53720
- [ -f "$FCLI" ] || continue
53721
- for FEXE in "$APP"/Contents/MacOS/*; do
53722
- if [ -f "$FEXE" ] && [ -x "$FEXE" ]; then
53723
- EXEC="$FEXE"
53724
- CLI="$FCLI"
53725
- break 2
53726
- fi
53727
- done
53728
- done
53729
- fi
53730
- if [ ! -x "$EXEC" ] || [ ! -f "$CLI" ]; then
53731
- echo "clawd shim: daemon CLI not found ($CLI); \u91CD\u542F clawd daemon \u53EF\u91CD\u65B0\u751F\u6210\u672C\u6587\u4EF6" >&2
53732
- exit 127
53733
- fi
53734
- exec "$EXEC" "$CLI" "$@"
53735
- `;
53736
- }
53737
- function ensureClawdShim(dataDir, execPath, cliPath) {
53738
- const binDir = import_node_path39.default.join(dataDir, "bin");
53739
- import_node_fs39.default.mkdirSync(binDir, { recursive: true, mode: 493 });
53740
- const target = import_node_path39.default.join(binDir, "clawd");
53741
- import_node_fs39.default.writeFileSync(target, buildClawdShim(execPath, cliPath), { mode: 493 });
53742
- return target;
53743
- }
53744
-
53745
- // src/sshd/sshd-manager.ts
53746
- var SshdManager = class {
53747
- constructor(deps) {
53748
- this.deps = deps;
53749
- this.sshdDir = import_node_path40.default.join(deps.dataDir, "sshd");
53750
- this.startupTimeoutMs = deps.startupTimeoutMs ?? 15e3;
53751
- }
53752
- deps;
53753
- proc = null;
53754
- sshdDir;
53755
- stopping = false;
53756
- exitHookInstalled = false;
53757
- startupTimeoutMs;
53758
- // sshd stderr 按行嗌探 pubkey 认证结果时的半行缓冲(防关键字被 chunk 边界切断)
53759
- sniffBuf = "";
53760
- get port() {
53761
- return this.deps.port;
53762
- }
53763
- async start() {
53764
- const { logger } = this.deps;
53765
- await (this.deps.killStaleImpl ?? killStaleSshd)({
53766
- dataDir: this.deps.dataDir,
53767
- ownPid: process.pid,
53768
- logger
53769
- });
53770
- import_node_fs40.default.mkdirSync(this.sshdDir, { recursive: true, mode: 448 });
53771
- import_node_fs40.default.mkdirSync(import_node_path40.default.join(this.sshdDir, "authorized_keys.d"), { recursive: true, mode: 448 });
53772
- ensureJailScript(this.deps.dataDir);
53773
- ensureClawdShim(this.deps.dataDir, process.execPath, process.argv[1] ?? "");
53774
- const hostKeyPath = import_node_path40.default.join(this.sshdDir, "host_key");
53775
- if (!import_node_fs40.default.existsSync(hostKeyPath)) {
53776
- await this.generateHostKey(hostKeyPath);
53777
- }
53778
- const akFile = import_node_path40.default.join(this.sshdDir, "authorized_keys.d", "clawd-contacts");
53779
- if (!import_node_fs40.default.existsSync(akFile)) {
53780
- import_node_fs40.default.writeFileSync(akFile, "", { mode: 384 });
53781
- }
53782
- const configPath = import_node_path40.default.join(this.sshdDir, "sshd_config");
53783
- const config = buildSshdConfig({
53784
- listenAddress: "127.0.0.1",
53785
- port: this.deps.port,
53786
- hostKeyPath,
53787
- authorizedKeysFile: akFile,
53788
- pidFilePath: import_node_path40.default.join(this.sshdDir, "sshd.pid")
53789
- });
53790
- import_node_fs40.default.writeFileSync(configPath, config, { mode: 384 });
53791
- const sshdBin = this.deps.sshdBin ?? "/usr/sbin/sshd";
53792
- const proc = (this.deps.spawnImpl ?? import_node_child_process11.spawn)(sshdBin, ["-D", "-e", "-f", configPath], {
53793
- stdio: ["ignore", "pipe", "pipe"]
53794
- });
53795
- const sshdLogDir = import_node_path40.default.join(this.deps.dataDir, "log");
53796
- import_node_fs40.default.mkdirSync(sshdLogDir, { recursive: true });
53797
- const logStream = import_node_fs40.default.createWriteStream(import_node_path40.default.join(sshdLogDir, "sshd.log"), {
53798
- flags: "a",
53799
- mode: 384
53800
- });
53801
- logStream.on("error", () => {
53802
- });
53803
- const tee = (c) => {
53804
- logStream.write(String(c));
53805
- };
53806
- proc.stdout?.on("data", tee);
53807
- proc.stderr?.on("data", (c) => {
53808
- const s = String(c);
53809
- logStream.write(s);
53810
- this.sniffSshdAuth(s);
53811
- });
53812
- proc.once("exit", () => logStream.end());
53813
- const ready = await waitForSshdReady(proc, this.startupTimeoutMs);
53814
- if (!ready.ok) {
53815
- try {
53816
- proc.kill("SIGTERM");
53817
- } catch {
53818
- }
53819
- const tail = ready.output.slice(-500);
53820
- const msg = tail ? `${ready.error}
53821
- ${tail}` : ready.error;
53822
- throw new Error(msg);
53823
- }
53824
- if (typeof proc.pid === "number") writeSshdPid(this.deps.dataDir, proc.pid);
53825
- this.proc = proc;
53826
- this.installProcessExitHandlersIfNeeded();
53827
- this.attachExitListener(proc);
53828
- logger?.info("sshd: up", { port: this.deps.port, pid: proc.pid ?? null });
53829
- return { port: this.deps.port };
53830
- }
53831
- async stop() {
53832
- this.stopping = true;
53833
- const proc = this.proc;
53834
- this.proc = null;
53835
- if (!proc) {
53836
- clearSshdPid(this.deps.dataDir);
53837
- return;
53838
- }
53839
- proc.kill("SIGTERM");
53840
- await new Promise((resolve6) => {
53841
- const t = setTimeout(() => {
53842
- try {
53843
- proc.kill("SIGKILL");
53844
- } catch {
53845
- }
53846
- resolve6();
53847
- }, 5e3);
53848
- proc.once("exit", () => {
53849
- clearTimeout(t);
53850
- resolve6();
53851
- });
53852
- });
53853
- clearSshdPid(this.deps.dataDir);
53854
- }
53855
- killSync() {
53856
- const proc = this.proc;
53857
- this.proc = null;
53858
- clearSshdPid(this.deps.dataDir);
53859
- if (!proc) return;
53860
- try {
53861
- proc.kill("SIGTERM");
53862
- } catch {
53863
- }
53864
- }
53865
- attachExitListener(proc) {
53866
- proc.on("exit", (code) => {
53867
- this.deps.logger?.warn("sshd exited", { code });
53868
- if (this.stopping) return;
53869
- this.proc = null;
53870
- this.deps.onSshdExit?.({ code });
53871
- });
53872
- }
53873
- installProcessExitHandlersIfNeeded() {
53874
- if (this.exitHookInstalled) return;
53875
- if (this.deps.installProcessExitHandlers !== true) return;
53876
- this.exitHookInstalled = true;
53877
- const sync = () => this.killSync();
53878
- process.once("exit", sync);
53879
- process.once("SIGHUP", sync);
53880
- process.once("uncaughtException", sync);
53881
- }
53882
- async generateHostKey(hostKeyPath) {
53883
- const keygenBin = this.deps.keygenBin ?? "/usr/bin/ssh-keygen";
53884
- await new Promise((resolve6, reject) => {
53885
- const p2 = (this.deps.spawnImpl ?? import_node_child_process11.spawn)(
53886
- keygenBin,
53887
- ["-t", "ed25519", "-f", hostKeyPath, "-N", "", "-q"],
53888
- { stdio: "ignore" }
53889
- );
53890
- p2.on("exit", (code) => code === 0 ? resolve6() : reject(new Error(`ssh-keygen exit ${code}`)));
53891
- p2.on("error", reject);
53892
- });
53893
- try {
53894
- import_node_fs40.default.chmodSync(hostKeyPath, 384);
53895
- } catch {
53896
- }
53897
- }
53898
- // 把 sshd -e 输出里的 pubkey 认证结果转写一条到 contact-ssh.log,让 A 侧审计日志一处即可
53899
- // 看到"隧道连上但 sshd 拒了 key"(否则只有 tunnel.connected + tunnel.disconnected,为什么
53900
- // 被拒要翻另一个 sshd.log 对时间线)。按 \n 切行嗅探,半行留 sniffBuf 等下个 chunk 拼回。
53901
- sniffSshdAuth(chunk) {
53902
- const sshLog = this.deps.sshLog;
53903
- if (!sshLog) return;
53904
- this.sniffBuf += chunk;
53905
- const parts = this.sniffBuf.split("\n");
53906
- this.sniffBuf = parts.pop() ?? "";
53907
- for (const line of parts) {
53908
- if (/Accepted publickey/i.test(line)) {
53909
- sshLog.info(
53910
- "ssh.auth-ok",
53911
- "sshd \u63A5\u53D7\u4E86 pubkey \u8BA4\u8BC1\uFF08\u5BF9\u7AEF key \u547D\u4E2D authorized_keys\uFF09",
53912
- sshdAuthMeta(line)
53913
- );
53914
- } else if (/Failed publickey/i.test(line) || /Connection closed by (authenticating|invalid) user/i.test(line)) {
53915
- sshLog.warn(
53916
- "ssh.auth-rejected",
53917
- "sshd \u62D2\u7EDD\u4E86 pubkey \u8BA4\u8BC1\uFF08\u5BF9\u7AEF key \u4E0D\u5728 authorized_keys / \u672A\u6388\u6743 / key \u672A\u62C9\u5230\uFF09",
53918
- sshdAuthMeta(line)
53919
- );
53920
- }
53921
- }
53922
- }
53923
- };
53924
- function sshdAuthMeta(line) {
53925
- const m2 = /port (\d+)/.exec(line);
53926
- return m2 ? { clientPort: Number(m2[1]) } : {};
53927
- }
53928
- async function waitForSshdReady(proc, timeoutMs) {
53929
- return new Promise((resolve6) => {
53930
- let settled = false;
53931
- let buf = "";
53932
- const finish = (r) => {
53933
- if (settled) return;
53934
- settled = true;
53935
- cleanup();
53936
- resolve6(r);
53937
- };
53938
- const onData = (chunk) => {
53939
- buf += String(chunk);
53940
- if (/Server listening on/i.test(buf)) finish({ ok: true });
53941
- if (/fatal:/i.test(buf) || /error: Bind to port/i.test(buf)) {
53942
- finish({ ok: false, error: "sshd startup failed", output: buf });
53943
- }
53944
- };
53945
- const onExit = (code) => finish({ ok: false, error: `sshd exited before ready (code=${code})`, output: buf });
53946
- const onErr = (err) => finish({ ok: false, error: `sshd spawn error: ${err.message}`, output: buf });
53947
- const cleanup = () => {
53948
- proc.stdout?.off("data", onData);
53949
- proc.stderr?.off("data", onData);
53950
- proc.off("exit", onExit);
53951
- proc.off("error", onErr);
53952
- clearTimeout(timer);
53953
- };
53954
- proc.stdout?.on("data", onData);
53955
- proc.stderr?.on("data", onData);
53956
- proc.on("exit", onExit);
53957
- proc.on("error", onErr);
53958
- const timer = setTimeout(
53959
- () => finish({ ok: false, error: `sshd startup timeout after ${timeoutMs}ms`, output: buf }),
53960
- timeoutMs
53961
- );
53962
- });
53963
- }
53964
-
53965
- // src/sshd/authorized-keys.ts
53966
- var import_node_fs41 = __toESM(require("fs"), 1);
53967
- var import_node_path41 = __toESM(require("path"), 1);
53968
- var JAIL_BIN_PATH_ENV = "CLAWD_JAIL_BIN_PATH";
53969
- var AUTHORIZED_KEYS_FILE = "clawd-contacts";
53970
- function jailBinPath() {
53971
- return process.env[JAIL_BIN_PATH_ENV] ?? import_node_path41.default.join(process.env.HOME ?? "", ".clawd", "bin", "clawd-ssh-jail");
53972
- }
53973
- function rebuildAuthorizedKeys(store, sshdDir) {
53974
- const akDir = import_node_path41.default.join(sshdDir, "authorized_keys.d");
53975
- const target = import_node_path41.default.join(akDir, AUTHORIZED_KEYS_FILE);
53976
- import_node_fs41.default.mkdirSync(akDir, { recursive: true, mode: 448 });
53977
- const lines = ["# managed by clawd; do not edit", ""];
53978
- for (const c of store.list()) {
53979
- if (!c.remoteAccessAllowed) continue;
53980
- const safe = /^[A-Za-z0-9_.-]+$/.test(c.deviceId);
53981
- if (!safe) continue;
53982
- const pubkey = c.sshPublicKey?.trim();
53983
- if (!pubkey || /[\r\n"]/.test(pubkey)) continue;
53984
- const bin = jailBinPath();
53985
- lines.push(`command="${bin} ${c.deviceId}",restrict ${pubkey}`);
53986
- lines.push(`# contact:${c.deviceId}`);
53987
- }
53988
- const body = lines.join("\n") + "\n";
53989
- const tmp = `${target}.tmp-${process.pid}-${Date.now()}`;
53990
- import_node_fs41.default.writeFileSync(tmp, body, { mode: 384 });
53991
- import_node_fs41.default.renameSync(tmp, target);
53992
- }
53993
-
53994
- // src/sshd/ssh-identity.ts
53995
- var import_node_fs42 = __toESM(require("fs"), 1);
53996
- var import_node_path42 = __toESM(require("path"), 1);
53997
- var import_node_child_process12 = require("child_process");
53998
- var IDENTITY_DIR = "ssh-identity";
53999
- var KEY_FILE = "id_ed25519";
54000
- function identityPaths(dataDir) {
54001
- const privPath = import_node_path42.default.join(dataDir, IDENTITY_DIR, KEY_FILE);
54002
- return { privPath, pubPath: `${privPath}.pub` };
54003
- }
54004
- async function ensureSshIdentity(dataDir, opts = {}) {
54005
- const { privPath, pubPath } = identityPaths(dataDir);
54006
- import_node_fs42.default.mkdirSync(import_node_path42.default.dirname(privPath), { recursive: true, mode: 448 });
54007
- if (import_node_fs42.default.existsSync(privPath) && import_node_fs42.default.existsSync(pubPath)) {
54008
- return { privateKeyPath: privPath, publicKeyLine: import_node_fs42.default.readFileSync(pubPath, "utf8").trim() };
54009
- }
54010
- const bin = opts.keygenBin ?? "/usr/bin/ssh-keygen";
54011
- await new Promise((resolve6, reject) => {
54012
- const p2 = (opts.spawnImpl ?? import_node_child_process12.spawn)(
54013
- bin,
54014
- ["-t", "ed25519", "-f", privPath, "-N", "", "-q", "-C", "clawd-ssh-identity"],
54015
- { stdio: "ignore" }
54016
- );
54017
- p2.on("exit", (code) => code === 0 ? resolve6() : reject(new Error(`ssh-keygen exit ${code}`)));
54018
- p2.on("error", reject);
54019
- });
54020
- try {
54021
- import_node_fs42.default.chmodSync(privPath, 384);
54022
- } catch {
54023
- }
54024
- try {
54025
- import_node_fs42.default.chmodSync(pubPath, 420);
54026
- } catch {
54027
- }
54028
- return { privateKeyPath: privPath, publicKeyLine: import_node_fs42.default.readFileSync(pubPath, "utf8").trim() };
54029
- }
54030
- function readSshIdentityPublicKey(dataDir) {
54031
- try {
54032
- return import_node_fs42.default.readFileSync(identityPaths(dataDir).pubPath, "utf8").trim();
54033
- } catch {
54034
- return null;
54035
- }
54036
- }
54037
-
54038
- // src/sshd/ssh-tunnel-relay.ts
54039
- var import_node_net2 = __toESM(require("net"), 1);
54040
-
54041
- // src/sshd/contact-ssh-log.ts
54042
- var import_node_fs43 = __toESM(require("fs"), 1);
54043
- var import_node_path43 = __toESM(require("path"), 1);
54044
- function createContactSshLog(dataDir) {
54045
- const file = import_node_path43.default.join(dataDir, "log", "contact-ssh.log");
54046
- function append(level, tag, message, meta) {
54047
- const time = (/* @__PURE__ */ new Date()).toISOString();
54048
- let line = `[${time}] [${level}] [${tag}] ${message}`;
54049
- if (meta && Object.keys(meta).length > 0) {
54050
- try {
54051
- line += " " + JSON.stringify(meta);
54052
- } catch {
54053
- line += " [meta-serialize-failed]";
54054
- }
54055
- }
54056
- line += "\n";
54057
- try {
54058
- import_node_fs43.default.mkdirSync(import_node_path43.default.dirname(file), { recursive: true });
54059
- import_node_fs43.default.appendFileSync(file, line, { mode: 384 });
54060
- } catch {
54061
- }
54062
- }
54063
- return {
54064
- info: (tag, message, meta) => append("INFO", tag, message, meta),
54065
- warn: (tag, message, meta) => append("WARN", tag, message, meta),
54066
- error: (tag, message, meta) => append("ERROR", tag, message, meta)
54067
- };
54068
- }
54069
- var nullContactSshLog = {
54070
- info: () => {
54071
- },
54072
- warn: () => {
54073
- },
54074
- error: () => {
54075
- }
54076
- };
54077
-
54078
- // src/sshd/ssh-tunnel-relay.ts
54079
- async function handleSshTunnelUpgrade(req, socket, head, deps) {
54080
- const sshLog = deps.sshLog ?? nullContactSshLog;
54081
- const clientAddr = (req.socket && "remoteAddress" in req.socket ? req.socket.remoteAddress : null) ?? "unknown";
54082
- const auth = req.headers.authorization ?? "";
54083
- const m2 = /^Bearer\s+(.+)$/i.exec(auth);
54084
- if (!m2) {
54085
- sshLog.warn("tunnel.auth-failed", "/rpc/ssh-tunnel \u8BF7\u6C42\u7F3A Bearer token", {
54086
- clientAddr
54087
- });
54088
- socket.write(
54089
- "HTTP/1.1 401 Unauthorized\r\nContent-Length: 0\r\n\r\n"
54090
- );
54091
- socket.destroy();
54092
- return;
54093
- }
54094
- const token = m2[1].trim();
54095
- let ok = false;
54096
- try {
54097
- ok = await deps.authorize(token);
54098
- } catch (err) {
54099
- deps.logger?.warn("ssh-tunnel: authorize threw", {
54100
- err: err instanceof Error ? err.message : String(err)
54101
- });
54102
- }
54103
- if (!ok) {
54104
- sshLog.warn("tunnel.auth-failed", "/rpc/ssh-tunnel Bearer token \u9A8C\u8BC1\u5931\u8D25", {
54105
- clientAddr
54106
- });
54107
- socket.write(
54108
- "HTTP/1.1 401 Unauthorized\r\nContent-Length: 0\r\n\r\n"
54109
- );
54110
- socket.destroy();
54111
- return;
54112
- }
54113
- sshLog.info("tunnel.auth-ok", "/rpc/ssh-tunnel \u8BA4\u8BC1\u901A\u8FC7\uFF0C\u5347\u7EA7 WS", {
54114
- clientAddr
54115
- });
54116
- deps.wss.handleUpgrade(req, socket, head, (ws) => {
54117
- pumpWsToSshd(ws, deps, clientAddr);
54118
- });
54119
- }
54120
- function pumpWsToSshd(ws, deps, clientAddr) {
54121
- const sshLog = deps.sshLog ?? nullContactSshLog;
54122
- const tcp = import_node_net2.default.connect(deps.sshdPort, "127.0.0.1");
54123
- let tcpConnected = false;
54124
- let bytesFromWs = 0;
54125
- let bytesToWs = 0;
54126
- const startedAt = Date.now();
54127
- const cleanup = (reason) => {
54128
- try {
54129
- ws.close(1e3, reason);
54130
- } catch {
54131
- }
54132
- try {
54133
- tcp.destroy();
54134
- } catch {
54135
- }
54136
- sshLog.info("tunnel.disconnected", "SSH tunnel \u4F1A\u8BDD\u7ED3\u675F", {
54137
- clientAddr,
54138
- reason,
54139
- bytesFromWs,
54140
- bytesToWs,
54141
- durationMs: Date.now() - startedAt
54142
- });
54143
- };
54144
- tcp.once("connect", () => {
54145
- tcpConnected = true;
54146
- deps.logger?.info("ssh-tunnel: tcp connected to sshd", { port: deps.sshdPort });
54147
- sshLog.info("tunnel.connected", "WS \u2194 \u672C\u673A sshd raw byte relay \u5DF2\u5C31\u7EEA", {
54148
- clientAddr,
54149
- sshdPort: deps.sshdPort
54150
- });
54151
- });
54152
- tcp.on("data", (chunk) => {
54153
- if (ws.readyState === ws.OPEN) {
54154
- bytesToWs += chunk.length;
54155
- ws.send(chunk, { binary: true });
54156
- }
54157
- });
54158
- tcp.on("error", (err) => {
54159
- deps.logger?.warn("ssh-tunnel: tcp error", { err: err.message, tcpConnected });
54160
- sshLog.error(
54161
- tcpConnected ? "tunnel.tcp-error" : "tunnel.tcp-connect-failed",
54162
- tcpConnected ? "WS \u2194 sshd relay \u671F\u95F4 TCP \u4FA7\u51FA\u9519" : "\u65E0\u6CD5\u8FDE\u5230\u672C\u673A sshd\uFF08sshd \u672A\u8D77\uFF1F\u7AEF\u53E3\u9519\uFF1F\uFF09",
54163
- { clientAddr, sshdPort: deps.sshdPort, err: err.message }
54164
- );
54165
- cleanup("tcp error");
54166
- });
54167
- tcp.on("end", () => cleanup("tcp end"));
54168
- tcp.on("close", () => cleanup("tcp close"));
54169
- ws.on("message", (data) => {
54170
- let buf = null;
54171
- if (Buffer.isBuffer(data)) buf = data;
54172
- else if (Array.isArray(data)) buf = Buffer.concat(data);
54173
- else if (data instanceof ArrayBuffer) buf = Buffer.from(data);
54174
- if (buf) {
54175
- bytesFromWs += buf.length;
54176
- tcp.write(buf);
54177
- }
54178
- });
54179
- ws.on("close", () => cleanup("ws close"));
54180
- ws.on("error", (err) => {
54181
- deps.logger?.warn("ssh-tunnel: ws error", { err: err.message });
54182
- cleanup("ws error");
54183
- });
54184
- }
54185
-
54186
- // src/sshd/ssh-tunnel-local-relay.ts
54187
- init_wrapper();
54188
- function parsePeerDeviceId(rawUrl) {
54189
- if (!rawUrl) return null;
54190
- try {
54191
- const u = new URL(rawUrl, "http://127.0.0.1");
54192
- const v2 = u.searchParams.get("peerDeviceId");
54193
- return v2 && v2.length > 0 ? v2 : null;
54194
- } catch {
54195
- return null;
54196
- }
54197
- }
54198
- async function handleSshTunnelLocalUpgrade(req, socket, head, deps) {
54199
- const sshLog = deps.sshLog ?? nullContactSshLog;
54200
- const clientAddr = (req.socket && "remoteAddress" in req.socket ? req.socket.remoteAddress : null) ?? "unknown";
54201
- const sessionId = req.headers["x-clawd-session-id"] ?? "";
54202
- if (!sessionId) {
54203
- sshLog.warn("relay.auth-failed", "/rpc/ssh-tunnel-local \u7F3A X-Clawd-Session-Id header", {
54204
- clientAddr
54205
- });
54206
- respond401(socket);
54207
- return;
54208
- }
54209
- const peerDeviceId = parsePeerDeviceId(req.url);
54210
- if (!peerDeviceId) {
54211
- sshLog.warn("relay.auth-failed", "/rpc/ssh-tunnel-local \u7F3A peerDeviceId query", { clientAddr });
54212
- respond400(socket);
54213
- return;
54214
- }
54215
- const contact = deps.store.get(peerDeviceId);
54216
- if (!contact) {
54217
- sshLog.error("relay.contact-not-found", "ssh-tunnel-local \u627E\u4E0D\u5230 peer contact", {
54218
- peerDeviceId
54219
- });
54220
- respond404(socket);
54221
- return;
54222
- }
54223
- if (!contact.connectToken) {
54224
- sshLog.error(
54225
- "relay.token-missing",
54226
- "contact \u6709\u8BB0\u5F55\u4F46\u7F3A connectToken\uFF08auto-reverse \u672A\u6362\u7968\uFF09",
54227
- { peerDeviceId, peerDisplayName: contact.displayName }
54228
- );
54229
- respond403(socket);
54230
- return;
54231
- }
54232
- const baseHttp = wsUrlToHttp(contact.remoteUrl).replace(/\/+$/, "");
54233
- const wsBase = baseHttp.replace(/^http:/, "ws:").replace(/^https:/, "wss:");
54234
- const outboundUrl = `${wsBase}/rpc/ssh-tunnel`;
54235
- sshLog.info("relay.dial", "\u5F00\u59CB\u62E8 A \u4FA7 /rpc/ssh-tunnel", {
54236
- peerDeviceId,
54237
- peerDisplayName: contact.displayName,
54238
- url: outboundUrl
54239
- });
54240
- deps.wss.handleUpgrade(req, socket, head, (clientWs) => {
54241
- sshLog.info(
54242
- "relay.client-connected",
54243
- "relay CLI \u5DF2\u8FDE\u4E0A\u672C\u5730 daemon\uFF08client WS \u5347\u7EA7\u6210\u529F\uFF09\uFF0C\u5F00\u59CB\u62E8 peer",
54244
- { peerDeviceId, peerDisplayName: contact.displayName }
54245
- );
54246
- const makeOut = deps.makeOutboundWs ?? ((u, o) => new import_websocket.default(u, o));
54247
- const outbound = makeOut(outboundUrl, {
54248
- headers: { Authorization: `Bearer ${contact.connectToken}` }
54249
- });
54250
- pumpClientToOutbound(clientWs, outbound, peerDeviceId, contact.displayName, deps);
54251
- });
54252
- }
54253
- function pumpClientToOutbound(client, outbound, peerDeviceId, peerDisplayName, deps) {
54254
- const sshLog = deps.sshLog ?? nullContactSshLog;
54255
- const startedAt = Date.now();
54256
- let outboundOpened = false;
54257
- let bytesClientToOutbound = 0;
54258
- let bytesOutboundToClient = 0;
54259
- let settled = false;
54260
- const preOpenQueue = [];
54261
- let preOpenQueuedBytes = 0;
54262
- const cleanup = (reason, code = 1e3) => {
54263
- if (settled) return;
54264
- settled = true;
54265
- try {
54266
- client.close(code, reason);
54267
- } catch {
54268
- }
54269
- try {
54270
- outbound.close();
54271
- } catch {
54272
- }
54273
- const level = code === 1e3 ? "info" : "warn";
54274
- sshLog[level](
54275
- code === 1e3 ? "relay.dial-close" : "relay.dial-close-abnormal",
54276
- code === 1e3 ? "tunnel \u4F1A\u8BDD\u6B63\u5E38\u7ED3\u675F" : "tunnel \u4F1A\u8BDD\u975E\u6B63\u5E38\u5173\u95ED",
54277
- {
54278
- peerDeviceId,
54279
- peerDisplayName,
54280
- reason,
54281
- code,
54282
- outboundOpened,
54283
- bytesClientToOutbound,
54284
- bytesOutboundToClient,
54285
- durationMs: Date.now() - startedAt
54286
- }
54287
- );
54288
- };
54289
- outbound.on("open", () => {
54290
- outboundOpened = true;
54291
- const queuedFrames = preOpenQueue.length;
54292
- if (queuedFrames > 0) {
54293
- for (const buf of preOpenQueue) outbound.send(buf, { binary: true });
54294
- preOpenQueue.length = 0;
54295
- sshLog.info(
54296
- "relay.preopen-flush",
54297
- "outbound \u5C31\u7EEA\u524D\u7F13\u51B2\u7684 client \u5B57\u8282\u5DF2\u6309\u5E8F flush\uFF08\u542B SSH banner\uFF09",
54298
- { peerDeviceId, peerDisplayName, queuedFrames, queuedBytes: preOpenQueuedBytes }
54299
- );
54300
- }
54301
- sshLog.info("relay.dial-open", "outbound ws open\uFF0C\u8FDB\u5165\u53CC\u5411 pipe", {
54302
- peerDeviceId,
54303
- peerDisplayName,
54304
- preOpenQueuedFrames: queuedFrames
54305
- });
54306
- });
54307
- outbound.on("message", (data) => {
54308
- const buf = toBuffer(data);
54309
- if (!buf) return;
54310
- bytesOutboundToClient += buf.length;
54311
- if (client.readyState === client.OPEN) client.send(buf, { binary: true });
54312
- });
54313
- outbound.on("error", (err) => {
54314
- deps.logger?.warn("ssh-tunnel-local: outbound ws error", { err: err.message });
54315
- sshLog.error("relay.dial-error", "outbound ws \u8FDE\u63A5\u9519\u8BEF\uFF08peer \u4E0D\u53EF\u8FBE / TLS \u5931\u8D25\uFF09", {
54316
- peerDeviceId,
54317
- peerDisplayName,
54318
- message: err.message
54319
- });
54320
- cleanup("outbound error", 1011);
54321
- });
54322
- outbound.on("close", (code) => {
54323
- if (!settled) cleanup(`outbound close ${code}`, code === 1e3 ? 1e3 : 1011);
54324
- });
54325
- client.on("message", (data) => {
54326
- const buf = toBuffer(data);
54327
- if (!buf) return;
54328
- bytesClientToOutbound += buf.length;
54329
- if (outbound.readyState === outbound.OPEN) {
54330
- outbound.send(buf, { binary: true });
54331
- } else {
54332
- preOpenQueue.push(buf);
54333
- preOpenQueuedBytes += buf.length;
54334
- }
54335
- });
54336
- client.on("close", () => cleanup("client close", 1e3));
54337
- client.on("error", (err) => {
54338
- deps.logger?.warn("ssh-tunnel-local: client ws error", { err: err.message });
54339
- cleanup("client error", 1011);
54340
- });
54341
- }
54342
- function toBuffer(data) {
54343
- if (Buffer.isBuffer(data)) return data;
54344
- if (Array.isArray(data)) return Buffer.concat(data);
54345
- if (data instanceof ArrayBuffer) return Buffer.from(data);
54346
- return null;
54347
- }
54348
- function respond401(socket) {
54349
- socket.write("HTTP/1.1 401 Unauthorized\r\nContent-Length: 0\r\n\r\n");
54350
- socket.destroy();
54351
- }
54352
- function respond400(socket) {
54353
- socket.write("HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n");
54354
- socket.destroy();
54355
- }
54356
- function respond403(socket) {
54357
- socket.write("HTTP/1.1 403 Forbidden\r\nContent-Length: 0\r\n\r\n");
54358
- socket.destroy();
54359
- }
54360
- function respond404(socket) {
54361
- socket.write("HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n");
54362
- socket.destroy();
54363
- }
54364
-
54365
53222
  // src/tunnel/device-key.ts
54366
53223
  var import_node_os14 = __toESM(require("os"), 1);
54367
- var import_node_path44 = __toESM(require("path"), 1);
53224
+ var import_node_path37 = __toESM(require("path"), 1);
54368
53225
  var import_node_crypto11 = __toESM(require("crypto"), 1);
54369
53226
  var DERIVE_SALT = "clawd-tunnel-device-v1";
54370
53227
  function deriveStableDeviceKey(opts = {}) {
54371
53228
  const hostname = opts.hostname ?? import_node_os14.default.hostname();
54372
53229
  const uid = opts.uid ?? (typeof import_node_os14.default.userInfo === "function" ? import_node_os14.default.userInfo().uid : 0);
54373
53230
  const home = opts.home ?? import_node_os14.default.homedir();
54374
- const defaultDataDir = import_node_path44.default.resolve(import_node_path44.default.join(home, ".clawd"));
54375
- const normalizedDataDir = opts.dataDir ? import_node_path44.default.resolve(opts.dataDir) : null;
53231
+ const defaultDataDir = import_node_path37.default.resolve(import_node_path37.default.join(home, ".clawd"));
53232
+ const normalizedDataDir = opts.dataDir ? import_node_path37.default.resolve(opts.dataDir) : null;
54376
53233
  const isDefaultDir = normalizedDataDir == null || normalizedDataDir === defaultDataDir;
54377
53234
  const input = isDefaultDir ? `${hostname}::${uid}` : `${hostname}::${uid}::${normalizedDataDir}`;
54378
53235
  return import_node_crypto11.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
54379
53236
  }
54380
53237
 
54381
53238
  // src/auth-store.ts
54382
- var import_node_fs44 = __toESM(require("fs"), 1);
54383
- var import_node_path45 = __toESM(require("path"), 1);
53239
+ var import_node_fs37 = __toESM(require("fs"), 1);
53240
+ var import_node_path38 = __toESM(require("path"), 1);
54384
53241
  var import_node_crypto12 = __toESM(require("crypto"), 1);
54385
53242
  var AUTH_FILE_NAME = "auth.json";
54386
53243
  function authFilePath(dataDir) {
54387
- return import_node_path45.default.join(dataDir, AUTH_FILE_NAME);
53244
+ return import_node_path38.default.join(dataDir, AUTH_FILE_NAME);
54388
53245
  }
54389
53246
  function loadOrCreateAuthFile(opts) {
54390
53247
  const file = authFilePath(opts.dataDir);
@@ -54420,7 +53277,7 @@ function defaultGenerateOwnerPrincipalId() {
54420
53277
  }
54421
53278
  function readAuthFile(file) {
54422
53279
  try {
54423
- const raw = import_node_fs44.default.readFileSync(file, "utf8");
53280
+ const raw = import_node_fs37.default.readFileSync(file, "utf8");
54424
53281
  const parsed = JSON.parse(raw);
54425
53282
  if (typeof parsed?.token !== "string" || parsed.token.length === 0) {
54426
53283
  return null;
@@ -54440,25 +53297,25 @@ function readAuthFile(file) {
54440
53297
  }
54441
53298
  }
54442
53299
  function writeAuthFile(file, content) {
54443
- import_node_fs44.default.mkdirSync(import_node_path45.default.dirname(file), { recursive: true });
54444
- import_node_fs44.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
53300
+ import_node_fs37.default.mkdirSync(import_node_path38.default.dirname(file), { recursive: true });
53301
+ import_node_fs37.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
54445
53302
  try {
54446
- import_node_fs44.default.chmodSync(file, 384);
53303
+ import_node_fs37.default.chmodSync(file, 384);
54447
53304
  } catch {
54448
53305
  }
54449
53306
  }
54450
53307
 
54451
53308
  // src/owner-profile.ts
54452
- var import_node_fs45 = __toESM(require("fs"), 1);
53309
+ var import_node_fs38 = __toESM(require("fs"), 1);
54453
53310
  var import_node_os15 = __toESM(require("os"), 1);
54454
- var import_node_path46 = __toESM(require("path"), 1);
53311
+ var import_node_path39 = __toESM(require("path"), 1);
54455
53312
  var PROFILE_FILENAME = "profile.json";
54456
53313
  function loadOwnerDisplayName(dataDir) {
54457
53314
  const fallback = import_node_os15.default.userInfo().username;
54458
- const profilePath = import_node_path46.default.join(dataDir, PROFILE_FILENAME);
53315
+ const profilePath = import_node_path39.default.join(dataDir, PROFILE_FILENAME);
54459
53316
  let raw;
54460
53317
  try {
54461
- raw = import_node_fs45.default.readFileSync(profilePath, "utf8");
53318
+ raw = import_node_fs38.default.readFileSync(profilePath, "utf8");
54462
53319
  } catch {
54463
53320
  return fallback;
54464
53321
  }
@@ -54481,18 +53338,18 @@ function loadOwnerDisplayName(dataDir) {
54481
53338
  }
54482
53339
 
54483
53340
  // src/feishu-auth/owner-identity-store.ts
54484
- var import_node_fs46 = __toESM(require("fs"), 1);
54485
- var import_node_path47 = __toESM(require("path"), 1);
53341
+ var import_node_fs39 = __toESM(require("fs"), 1);
53342
+ var import_node_path40 = __toESM(require("path"), 1);
54486
53343
  var OWNER_IDENTITY_FILE_NAME = "owner-identity.json";
54487
53344
  var OwnerIdentityStore = class {
54488
53345
  file;
54489
53346
  constructor(dataDir) {
54490
- this.file = import_node_path47.default.join(dataDir, OWNER_IDENTITY_FILE_NAME);
53347
+ this.file = import_node_path40.default.join(dataDir, OWNER_IDENTITY_FILE_NAME);
54491
53348
  }
54492
53349
  read() {
54493
53350
  let raw;
54494
53351
  try {
54495
- raw = import_node_fs46.default.readFileSync(this.file, "utf8");
53352
+ raw = import_node_fs39.default.readFileSync(this.file, "utf8");
54496
53353
  } catch {
54497
53354
  return null;
54498
53355
  }
@@ -54520,16 +53377,16 @@ var OwnerIdentityStore = class {
54520
53377
  };
54521
53378
  }
54522
53379
  write(record) {
54523
- import_node_fs46.default.mkdirSync(import_node_path47.default.dirname(this.file), { recursive: true });
54524
- import_node_fs46.default.writeFileSync(this.file, JSON.stringify(record, null, 2), { mode: 384 });
53380
+ import_node_fs39.default.mkdirSync(import_node_path40.default.dirname(this.file), { recursive: true });
53381
+ import_node_fs39.default.writeFileSync(this.file, JSON.stringify(record, null, 2), { mode: 384 });
54525
53382
  try {
54526
- import_node_fs46.default.chmodSync(this.file, 384);
53383
+ import_node_fs39.default.chmodSync(this.file, 384);
54527
53384
  } catch {
54528
53385
  }
54529
53386
  }
54530
53387
  clear() {
54531
53388
  try {
54532
- import_node_fs46.default.unlinkSync(this.file);
53389
+ import_node_fs39.default.unlinkSync(this.file);
54533
53390
  } catch (err) {
54534
53391
  const code = err?.code;
54535
53392
  if (code !== "ENOENT") throw err;
@@ -54666,9 +53523,9 @@ var CentralClientError = class extends Error {
54666
53523
  code;
54667
53524
  cause;
54668
53525
  };
54669
- async function centralRequest(opts, path77, init) {
53526
+ async function centralRequest(opts, path70, init) {
54670
53527
  const f = opts.fetchImpl ?? globalThis.fetch;
54671
- const url = `${opts.api.replace(/\/+$/, "")}${path77}`;
53528
+ const url = `${opts.api.replace(/\/+$/, "")}${path70}`;
54672
53529
  const ctrl = new AbortController();
54673
53530
  const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 15e3);
54674
53531
  let res;
@@ -54862,8 +53719,8 @@ function verifyConnectToken(args) {
54862
53719
  }
54863
53720
 
54864
53721
  // src/feishu-auth/server-key.ts
54865
- var fs56 = __toESM(require("fs"), 1);
54866
- var path56 = __toESM(require("path"), 1);
53722
+ var fs49 = __toESM(require("fs"), 1);
53723
+ var path49 = __toESM(require("path"), 1);
54867
53724
  var FILE_NAME2 = "server-signing-key.json";
54868
53725
  var ServerKeyStore = class {
54869
53726
  constructor(dataDir) {
@@ -54871,12 +53728,12 @@ var ServerKeyStore = class {
54871
53728
  }
54872
53729
  dataDir;
54873
53730
  filePath() {
54874
- return path56.join(this.dataDir, FILE_NAME2);
53731
+ return path49.join(this.dataDir, FILE_NAME2);
54875
53732
  }
54876
53733
  /** 读缓存的公钥;无缓存 / 损坏 → null(调用方决定是否触发拉取) */
54877
53734
  read() {
54878
53735
  try {
54879
- const raw = fs56.readFileSync(this.filePath(), "utf8");
53736
+ const raw = fs49.readFileSync(this.filePath(), "utf8");
54880
53737
  const parsed = JSON.parse(raw);
54881
53738
  if (typeof parsed.publicKeyPem === "string" && parsed.publicKeyPem.includes("PUBLIC KEY")) {
54882
53739
  return parsed.publicKeyPem;
@@ -54891,12 +53748,12 @@ var ServerKeyStore = class {
54891
53748
  publicKeyPem,
54892
53749
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
54893
53750
  };
54894
- fs56.mkdirSync(this.dataDir, { recursive: true });
54895
- fs56.writeFileSync(this.filePath(), JSON.stringify(content, null, 2), { mode: 384 });
53751
+ fs49.mkdirSync(this.dataDir, { recursive: true });
53752
+ fs49.writeFileSync(this.filePath(), JSON.stringify(content, null, 2), { mode: 384 });
54896
53753
  }
54897
53754
  clear() {
54898
53755
  try {
54899
- fs56.unlinkSync(this.filePath());
53756
+ fs49.unlinkSync(this.filePath());
54900
53757
  } catch {
54901
53758
  }
54902
53759
  }
@@ -54909,12 +53766,12 @@ init_protocol();
54909
53766
  init_protocol();
54910
53767
 
54911
53768
  // src/session/fork.ts
54912
- var import_node_fs47 = __toESM(require("fs"), 1);
53769
+ var import_node_fs40 = __toESM(require("fs"), 1);
54913
53770
  var import_node_os16 = __toESM(require("os"), 1);
54914
- var import_node_path48 = __toESM(require("path"), 1);
53771
+ var import_node_path41 = __toESM(require("path"), 1);
54915
53772
  init_claude_history();
54916
53773
  function readJsonlEntries(file) {
54917
- const raw = import_node_fs47.default.readFileSync(file, "utf8");
53774
+ const raw = import_node_fs40.default.readFileSync(file, "utf8");
54918
53775
  const out = [];
54919
53776
  for (const line of raw.split("\n")) {
54920
53777
  const t = line.trim();
@@ -54927,19 +53784,19 @@ function readJsonlEntries(file) {
54927
53784
  return out;
54928
53785
  }
54929
53786
  function forkSession(input) {
54930
- const baseDir = input.baseDir ?? import_node_path48.default.join(import_node_os16.default.homedir(), ".claude");
54931
- let projectDir = import_node_path48.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
54932
- let sourceFile = import_node_path48.default.join(projectDir, `${input.toolSessionId}.jsonl`);
54933
- if (!import_node_fs47.default.existsSync(sourceFile)) {
53787
+ const baseDir = input.baseDir ?? import_node_path41.default.join(import_node_os16.default.homedir(), ".claude");
53788
+ let projectDir = import_node_path41.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
53789
+ let sourceFile = import_node_path41.default.join(projectDir, `${input.toolSessionId}.jsonl`);
53790
+ if (!import_node_fs40.default.existsSync(sourceFile)) {
54934
53791
  const found = findTranscriptByToolSessionId(
54935
- import_node_path48.default.join(baseDir, "projects"),
53792
+ import_node_path41.default.join(baseDir, "projects"),
54936
53793
  input.toolSessionId
54937
53794
  );
54938
53795
  if (!found) {
54939
53796
  throw new Error(`fork: source transcript not found: ${sourceFile}`);
54940
53797
  }
54941
53798
  sourceFile = found;
54942
- projectDir = import_node_path48.default.dirname(found);
53799
+ projectDir = import_node_path41.default.dirname(found);
54943
53800
  }
54944
53801
  const entries = readJsonlEntries(sourceFile);
54945
53802
  const mainEntries = entries.filter((e) => e.isSidechain !== true);
@@ -54968,9 +53825,9 @@ function forkSession(input) {
54968
53825
  }
54969
53826
  forkedLines.push(JSON.stringify(forked));
54970
53827
  }
54971
- const forkedFilePath = import_node_path48.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
54972
- import_node_fs47.default.mkdirSync(projectDir, { recursive: true });
54973
- import_node_fs47.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
53828
+ const forkedFilePath = import_node_path41.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
53829
+ import_node_fs40.default.mkdirSync(projectDir, { recursive: true });
53830
+ import_node_fs40.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
54974
53831
  return { forkedToolSessionId, forkedFilePath };
54975
53832
  }
54976
53833
 
@@ -55341,7 +54198,7 @@ function buildPermissionHandlers(deps) {
55341
54198
  }
55342
54199
 
55343
54200
  // src/handlers/history.ts
55344
- var path59 = __toESM(require("path"), 1);
54201
+ var path52 = __toESM(require("path"), 1);
55345
54202
  init_protocol();
55346
54203
 
55347
54204
  // src/session/recent-dirs.ts
@@ -55359,7 +54216,7 @@ function listRecentDirs(store, limit = 50) {
55359
54216
  }
55360
54217
 
55361
54218
  // src/permission/persona-paths.ts
55362
- var path58 = __toESM(require("path"), 1);
54219
+ var path51 = __toESM(require("path"), 1);
55363
54220
  function getAllowedPersonaIds(grants, action) {
55364
54221
  const ids = /* @__PURE__ */ new Set();
55365
54222
  for (const g2 of grants) {
@@ -55372,42 +54229,42 @@ function getAllowedPersonaIds(grants, action) {
55372
54229
  return ids;
55373
54230
  }
55374
54231
  function isGuestPathAllowed(grants, absPath, personaRoot, action = "read", userWorkDir) {
55375
- const target = path58.resolve(absPath);
54232
+ const target = path51.resolve(absPath);
55376
54233
  if (userWorkDir) {
55377
- const u = path58.resolve(userWorkDir);
55378
- const usep = u.endsWith(path58.sep) ? "" : path58.sep;
54234
+ const u = path51.resolve(userWorkDir);
54235
+ const usep = u.endsWith(path51.sep) ? "" : path51.sep;
55379
54236
  if (target === u || target.startsWith(u + usep)) return true;
55380
54237
  }
55381
- const root = path58.resolve(personaRoot);
55382
- const sep3 = root.endsWith(path58.sep) ? "" : path58.sep;
54238
+ const root = path51.resolve(personaRoot);
54239
+ const sep3 = root.endsWith(path51.sep) ? "" : path51.sep;
55383
54240
  if (!target.startsWith(root + sep3)) return false;
55384
- const rel = path58.relative(root, target);
54241
+ const rel = path51.relative(root, target);
55385
54242
  if (!rel || rel.startsWith("..")) return false;
55386
- const personaId = rel.split(path58.sep)[0];
54243
+ const personaId = rel.split(path51.sep)[0];
55387
54244
  if (!personaId) return false;
55388
54245
  const allowed = getAllowedPersonaIds(grants, action);
55389
54246
  if (allowed === "*") return true;
55390
54247
  return allowed.has(personaId);
55391
54248
  }
55392
54249
  function personaIdFromPath(absPath, personaRoot) {
55393
- const root = path58.resolve(personaRoot);
55394
- const target = path58.resolve(absPath);
55395
- const sep3 = root.endsWith(path58.sep) ? "" : path58.sep;
54250
+ const root = path51.resolve(personaRoot);
54251
+ const target = path51.resolve(absPath);
54252
+ const sep3 = root.endsWith(path51.sep) ? "" : path51.sep;
55396
54253
  if (!target.startsWith(root + sep3)) return null;
55397
- const rel = path58.relative(root, target);
54254
+ const rel = path51.relative(root, target);
55398
54255
  if (!rel || rel.startsWith("..")) return null;
55399
- const id = rel.split(path58.sep)[0];
54256
+ const id = rel.split(path51.sep)[0];
55400
54257
  return id || null;
55401
54258
  }
55402
54259
  function isPathWithin(dir, absPath) {
55403
- const d = path58.resolve(dir);
55404
- const t = path58.resolve(absPath);
55405
- const sep3 = d.endsWith(path58.sep) ? "" : path58.sep;
54260
+ const d = path51.resolve(dir);
54261
+ const t = path51.resolve(absPath);
54262
+ const sep3 = d.endsWith(path51.sep) ? "" : path51.sep;
55406
54263
  return t === d || t.startsWith(d + sep3);
55407
54264
  }
55408
54265
  function isPathInGuestBoundary(personaRoot, personaId, userWorkDir, absPath) {
55409
54266
  if (userWorkDir && isPathWithin(userWorkDir, absPath)) return true;
55410
- return personaIdFromPath(path58.resolve(absPath), personaRoot) === personaId;
54267
+ return personaIdFromPath(path51.resolve(absPath), personaRoot) === personaId;
55411
54268
  }
55412
54269
 
55413
54270
  // src/handlers/history.ts
@@ -55433,7 +54290,7 @@ function buildHistoryHandlers(deps) {
55433
54290
  if (!pid) return false;
55434
54291
  return isGuestPathAllowed(
55435
54292
  ctx.grants,
55436
- path59.join(personaRoot, pid),
54293
+ path52.join(personaRoot, pid),
55437
54294
  personaRoot,
55438
54295
  "read",
55439
54296
  userWorkDir
@@ -55445,7 +54302,7 @@ function buildHistoryHandlers(deps) {
55445
54302
  };
55446
54303
  const list = async (frame, _client, ctx) => {
55447
54304
  const args = HistoryListArgs.parse(frame);
55448
- assertGuestPath(ctx, path59.resolve(args.projectPath), personaRoot, "history:list");
54305
+ assertGuestPath(ctx, path52.resolve(args.projectPath), personaRoot, "history:list");
55449
54306
  const sessions = await history.listSessions(args);
55450
54307
  return { response: { type: "history:list", sessions } };
55451
54308
  };
@@ -55477,13 +54334,13 @@ function buildHistoryHandlers(deps) {
55477
54334
  };
55478
54335
  const subagents = async (frame, _client, ctx) => {
55479
54336
  const args = HistorySubagentsArgs.parse(frame);
55480
- assertGuestPath(ctx, path59.resolve(args.cwd), personaRoot, "history:subagents", usersRoot);
54337
+ assertGuestPath(ctx, path52.resolve(args.cwd), personaRoot, "history:subagents", usersRoot);
55481
54338
  const subs = await history.listSubagents(args);
55482
54339
  return { response: { type: "history:subagents", subagents: subs } };
55483
54340
  };
55484
54341
  const subagentRead = async (frame, _client, ctx) => {
55485
54342
  const args = HistorySubagentReadArgs.parse(frame);
55486
- assertGuestPath(ctx, path59.resolve(args.cwd), personaRoot, "history:subagent-read", usersRoot);
54343
+ assertGuestPath(ctx, path52.resolve(args.cwd), personaRoot, "history:subagent-read", usersRoot);
55487
54344
  const res = await history.readSubagent(args);
55488
54345
  return { response: { type: "history:subagent-read", ...res } };
55489
54346
  };
@@ -55492,7 +54349,7 @@ function buildHistoryHandlers(deps) {
55492
54349
  if (ctx?.principal.kind === "guest" && personaRoot) {
55493
54350
  const userWorkDir = usersRoot ? deriveUserWorkDir(ctx.principal.id, usersRoot) : void 0;
55494
54351
  const filtered = dirs.filter(
55495
- (d) => isGuestPathAllowed(ctx.grants, path59.resolve(d.cwd), personaRoot, "read", userWorkDir)
54352
+ (d) => isGuestPathAllowed(ctx.grants, path52.resolve(d.cwd), personaRoot, "read", userWorkDir)
55496
54353
  );
55497
54354
  return { response: { type: "history:recentDirs", dirs: filtered } };
55498
54355
  }
@@ -55509,7 +54366,7 @@ function buildHistoryHandlers(deps) {
55509
54366
  }
55510
54367
 
55511
54368
  // src/handlers/workspace.ts
55512
- var path60 = __toESM(require("path"), 1);
54369
+ var path53 = __toESM(require("path"), 1);
55513
54370
  var os16 = __toESM(require("os"), 1);
55514
54371
  init_protocol();
55515
54372
  init_protocol();
@@ -55551,22 +54408,22 @@ function buildWorkspaceHandlers(deps) {
55551
54408
  const args = WorkspaceListArgs.parse(frame);
55552
54409
  const isGuest = ctx?.principal.kind === "guest";
55553
54410
  const fallbackCwd = isGuest && personaRoot ? personaRoot : os16.homedir();
55554
- const resolvedCwd = path60.resolve(args.cwd ?? fallbackCwd);
55555
- const target = args.path ? path60.resolve(resolvedCwd, args.path) : resolvedCwd;
54411
+ const resolvedCwd = path53.resolve(args.cwd ?? fallbackCwd);
54412
+ const target = args.path ? path53.resolve(resolvedCwd, args.path) : resolvedCwd;
55556
54413
  assertGuestPath2(ctx, target, personaRoot, "workspace:list", usersRoot);
55557
54414
  const res = workspace.list({ ...args, cwd: resolvedCwd });
55558
54415
  return { response: { type: "workspace:list", ...res } };
55559
54416
  };
55560
54417
  const read = async (frame, _client, ctx) => {
55561
54418
  const args = WorkspaceReadArgs.parse(frame);
55562
- const target = path60.isAbsolute(args.path) ? path60.resolve(args.path) : path60.resolve(args.cwd, args.path);
54419
+ const target = path53.isAbsolute(args.path) ? path53.resolve(args.path) : path53.resolve(args.cwd, args.path);
55563
54420
  assertGuestPath2(ctx, target, personaRoot, "workspace:read", usersRoot);
55564
54421
  const res = workspace.read(args);
55565
54422
  return { response: { type: "workspace:read", ...res } };
55566
54423
  };
55567
54424
  const skillsList = async (frame, _client, ctx) => {
55568
54425
  const args = SkillsListArgs.parse(frame);
55569
- const cwdAbs = path60.resolve(args.cwd);
54426
+ const cwdAbs = path53.resolve(args.cwd);
55570
54427
  assertGuestPath2(ctx, cwdAbs, personaRoot, "skills:list", usersRoot);
55571
54428
  const list2 = await getSkillsForTool(args.tool ?? "claude", cwdAbs);
55572
54429
  if (ctx?.principal.kind === "guest" && personaRoot) {
@@ -55578,7 +54435,7 @@ function buildWorkspaceHandlers(deps) {
55578
54435
  };
55579
54436
  const agentsList = async (frame, _client, ctx) => {
55580
54437
  const args = AgentsListArgs.parse(frame);
55581
- const cwdAbs = path60.resolve(args.cwd);
54438
+ const cwdAbs = path53.resolve(args.cwd);
55582
54439
  assertGuestPath2(ctx, cwdAbs, personaRoot, "agents:list", usersRoot);
55583
54440
  if (args.tool === "codex") {
55584
54441
  return { response: { type: "agents:list", agents: [] } };
@@ -55600,20 +54457,20 @@ function buildWorkspaceHandlers(deps) {
55600
54457
  }
55601
54458
 
55602
54459
  // src/handlers/git.ts
55603
- var path62 = __toESM(require("path"), 1);
54460
+ var path55 = __toESM(require("path"), 1);
55604
54461
  init_protocol();
55605
54462
  init_protocol();
55606
54463
 
55607
54464
  // src/workspace/git.ts
55608
- var import_node_child_process13 = require("child_process");
55609
- var import_node_fs48 = __toESM(require("fs"), 1);
55610
- var import_node_path49 = __toESM(require("path"), 1);
54465
+ var import_node_child_process10 = require("child_process");
54466
+ var import_node_fs41 = __toESM(require("fs"), 1);
54467
+ var import_node_path42 = __toESM(require("path"), 1);
55611
54468
  var import_node_util = require("util");
55612
- var pexec = (0, import_node_util.promisify)(import_node_child_process13.execFile);
54469
+ var pexec = (0, import_node_util.promisify)(import_node_child_process10.execFile);
55613
54470
  function normalizePath(p2) {
55614
- const resolved = import_node_path49.default.resolve(p2);
54471
+ const resolved = import_node_path42.default.resolve(p2);
55615
54472
  try {
55616
- return import_node_fs48.default.realpathSync(resolved);
54473
+ return import_node_fs41.default.realpathSync(resolved);
55617
54474
  } catch {
55618
54475
  return resolved;
55619
54476
  }
@@ -55687,7 +54544,7 @@ async function listGitBranches(cwd) {
55687
54544
  function assertGuestCwd(ctx, cwd, personaRoot, method, usersRoot) {
55688
54545
  if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
55689
54546
  const userWorkDir = usersRoot ? deriveUserWorkDir(ctx.principal.id, usersRoot) : void 0;
55690
- if (!isGuestPathAllowed(ctx.grants, path62.resolve(cwd), personaRoot, "read", userWorkDir)) {
54547
+ if (!isGuestPathAllowed(ctx.grants, path55.resolve(cwd), personaRoot, "read", userWorkDir)) {
55691
54548
  throw new ClawdError(
55692
54549
  ERROR_CODES.UNAUTHORIZED,
55693
54550
  `guest ${ctx.principal.id} cannot ${method} cwd ${cwd}`
@@ -55972,18 +54829,6 @@ function buildContactHandlers(deps) {
55972
54829
  }
55973
54830
  };
55974
54831
  };
55975
- const dialInfo = async () => {
55976
- return {
55977
- response: {
55978
- type: "contact:dialInfo:ok",
55979
- contacts: deps.store.list().map((c) => ({
55980
- deviceId: c.deviceId,
55981
- displayName: c.displayName,
55982
- ...c.osUser ? { osUser: c.osUser } : {}
55983
- }))
55984
- }
55985
- };
55986
- };
55987
54832
  const pin = async (frame, _client, ctx) => {
55988
54833
  ensureOwner(ctx);
55989
54834
  const { type: _t, requestId: _r, ...rest } = frame;
@@ -56027,13 +54872,12 @@ function buildContactHandlers(deps) {
56027
54872
  };
56028
54873
  return {
56029
54874
  "contact:list": list,
56030
- "contact:dialInfo": dialInfo,
56031
54875
  "contact:pin": pin,
56032
54876
  "contact:remove": remove
56033
54877
  };
56034
54878
  }
56035
54879
 
56036
- // src/handlers/contact-ssh.ts
54880
+ // src/handlers/contact-remote-access.ts
56037
54881
  init_protocol();
56038
54882
  function ensureOwner2(ctx) {
56039
54883
  if (!ctx || ctx.principal.kind !== "owner") {
@@ -56043,8 +54887,7 @@ function ensureOwner2(ctx) {
56043
54887
  );
56044
54888
  }
56045
54889
  }
56046
- function buildContactSshHandlers(deps) {
56047
- const sshLog = deps.sshLog ?? nullContactSshLog;
54890
+ function buildContactRemoteAccessHandlers(deps) {
56048
54891
  const setRemoteAccess = async (frame, _client, ctx) => {
56049
54892
  ensureOwner2(ctx);
56050
54893
  const { type: _t, requestId: _r, ...rest } = frame;
@@ -56053,19 +54896,11 @@ function buildContactSshHandlers(deps) {
56053
54896
  remoteAccessAllowed: args.remoteAccessAllowed
56054
54897
  });
56055
54898
  if (!hit) {
56056
- sshLog.warn("authz.setRemoteAccess", "owner \u5C1D\u8BD5\u6539 SSH \u6388\u6743\u4F46 contact \u4E0D\u5728 store", {
56057
- peerDeviceId: args.deviceId
56058
- });
56059
54899
  throw new ClawdError(
56060
54900
  ERROR_CODES.CONTACT_NOT_FOUND,
56061
54901
  `CONTACT_NOT_FOUND: contact ${args.deviceId} not in store`
56062
54902
  );
56063
54903
  }
56064
- rebuildAuthorizedKeys(deps.store, deps.sshdDir);
56065
- sshLog.info("authz.setRemoteAccess", "owner \u6539\u4E86 SSH \u6388\u6743 \u2192 authorized_keys \u5DF2\u91CD\u5EFA", {
56066
- peerDeviceId: args.deviceId,
56067
- remoteAccessAllowed: args.remoteAccessAllowed
56068
- });
56069
54904
  deps.broadcast({
56070
54905
  type: "contact:remote-access-updated",
56071
54906
  deviceId: args.deviceId,
@@ -56132,16 +54967,12 @@ function buildWhoamiHandler(deps) {
56132
54967
  grantedPersonas.push({ id: file.personaId, displayName: file.label });
56133
54968
  }
56134
54969
  }
56135
- const sshPublicKey = deps.sshIdentityPublicKey();
56136
- const osUser = deps.selfOsUser();
56137
54970
  return {
56138
54971
  response: {
56139
54972
  type: "whoami:ok",
56140
54973
  owner,
56141
54974
  capability,
56142
- grantedPersonas,
56143
- ...sshPublicKey ? { sshPublicKey } : {},
56144
- ...osUser ? { osUser } : {}
54975
+ grantedPersonas
56145
54976
  }
56146
54977
  };
56147
54978
  };
@@ -56217,10 +55048,7 @@ function buildDeviceHandlers(deps) {
56217
55048
  // 自动反向(spec 决策 #11):自报本机可达地址,让对方反向加我为联系人;
56218
55049
  // 本机无 tunnel → null → connectRemote 省略 selfUrl 字段(不造假数据)。
56219
55050
  // 身份不自报:对方从 token 签名背书取我的 deviceId/ownerId/provider(决策 #16)。
56220
- selfUrl: deps.selfUrl() ?? void 0,
56221
- // 建联交换:自报本机 SSH pubkey + OS 账号名;未生成 → 省略。见 …#建联交换清单
56222
- selfSshPublicKey: deps.selfSshPublicKey() ?? void 0,
56223
- selfOsUser: deps.selfOsUser() ?? void 0
55051
+ selfUrl: deps.selfUrl() ?? void 0
56224
55052
  });
56225
55053
  } catch (e) {
56226
55054
  throw new ClawdError(
@@ -56231,8 +55059,6 @@ function buildDeviceHandlers(deps) {
56231
55059
  try {
56232
55060
  const wh = await conn.whoami();
56233
55061
  const existing = deps.store.get(args.deviceId) ?? void 0;
56234
- const sshPublicKey = wh.sshPublicKey ?? existing?.sshPublicKey;
56235
- const osUser = wh.osUser ?? existing?.osUser;
56236
55062
  const contact = {
56237
55063
  deviceId: args.deviceId,
56238
55064
  ownerId: wh.ownerId,
@@ -56248,18 +55074,10 @@ function buildDeviceHandlers(deps) {
56248
55074
  grants: wh.grants,
56249
55075
  addedAt: now(),
56250
55076
  pinnedAt: existing?.pinnedAt ?? null,
56251
- remoteAccessAllowed: existing?.remoteAccessAllowed ?? false,
56252
- // 建联交换:对方 whoami:ok 下发的 pubkey / osUser 落库;对方老版本不带 →
56253
- // 保留既有值 / 不落 undefined 占位。见 doc/glossary/peer-mesh.md#建联交换清单
56254
- ...sshPublicKey ? { sshPublicKey } : {},
56255
- ...osUser ? { osUser } : {},
56256
- exposedDirs: []
55077
+ remoteAccessAllowed: existing?.remoteAccessAllowed ?? false
56257
55078
  };
56258
55079
  deps.store.upsert(contact);
56259
55080
  deps.broadcast({ type: "contact:added", contact });
56260
- if (contact.remoteAccessAllowed && sshPublicKey && sshPublicKey !== existing?.sshPublicKey) {
56261
- deps.rebuildAuthorizedKeys?.();
56262
- }
56263
55081
  return {
56264
55082
  response: {
56265
55083
  type: "device:connect:ok",
@@ -56412,7 +55230,7 @@ function buildPersonaHandlers(deps) {
56412
55230
  }
56413
55231
 
56414
55232
  // src/handlers/attachment.ts
56415
- var import_node_path50 = __toESM(require("path"), 1);
55233
+ var import_node_path43 = __toESM(require("path"), 1);
56416
55234
  init_protocol();
56417
55235
  init_protocol();
56418
55236
  var DEFAULT_TTL_SECONDS = 24 * 3600;
@@ -56492,12 +55310,12 @@ function buildAttachmentHandlers(deps) {
56492
55310
  `session ${args.sessionId} scope unresolved`
56493
55311
  );
56494
55312
  }
56495
- const cwdAbs = import_node_path50.default.resolve(sessionFile.cwd);
56496
- const candidateAbs = import_node_path50.default.isAbsolute(args.relPath) ? import_node_path50.default.resolve(args.relPath) : import_node_path50.default.resolve(cwdAbs, args.relPath);
55313
+ const cwdAbs = import_node_path43.default.resolve(sessionFile.cwd);
55314
+ const candidateAbs = import_node_path43.default.isAbsolute(args.relPath) ? import_node_path43.default.resolve(args.relPath) : import_node_path43.default.resolve(cwdAbs, args.relPath);
56497
55315
  guardAttachmentPath(ctx, args.sessionId, candidateAbs, "attachment.signUrl", "group-acl");
56498
55316
  const entries = deps.groupFileStore.list(scope, args.sessionId);
56499
55317
  const entry = entries.find((e) => {
56500
- const storedAbs = import_node_path50.default.isAbsolute(e.relPath) ? import_node_path50.default.resolve(e.relPath) : import_node_path50.default.resolve(cwdAbs, e.relPath);
55318
+ const storedAbs = import_node_path43.default.isAbsolute(e.relPath) ? import_node_path43.default.resolve(e.relPath) : import_node_path43.default.resolve(cwdAbs, e.relPath);
56501
55319
  return storedAbs === candidateAbs && !e.stale;
56502
55320
  });
56503
55321
  if (!entry) {
@@ -56522,7 +55340,7 @@ function buildAttachmentHandlers(deps) {
56522
55340
  if (!ctx || ctx.principal.kind !== "guest" || !deps.personaRoot || !deps.sessionStore) return;
56523
55341
  const f = deps.sessionStore.read(sessionId);
56524
55342
  if (!f) return;
56525
- assertGuestAttachmentPath(ctx, import_node_path50.default.resolve(f.cwd), deps.personaRoot, method, deps.usersRoot);
55343
+ assertGuestAttachmentPath(ctx, import_node_path43.default.resolve(f.cwd), deps.personaRoot, method, deps.usersRoot);
56526
55344
  }
56527
55345
  const groupAdd = async (frame, _client, ctx) => {
56528
55346
  if (!deps.groupFileStore || !deps.getSessionScope) {
@@ -56537,8 +55355,8 @@ function buildAttachmentHandlers(deps) {
56537
55355
  if (!scope) {
56538
55356
  throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, `session ${args.sessionId} not found`);
56539
55357
  }
56540
- const cwdAbs = import_node_path50.default.resolve(deps.sessionStore?.read(args.sessionId)?.cwd ?? ".");
56541
- const candidateAbs = import_node_path50.default.isAbsolute(args.relPath) ? import_node_path50.default.resolve(args.relPath) : import_node_path50.default.resolve(cwdAbs, args.relPath);
55358
+ const cwdAbs = import_node_path43.default.resolve(deps.sessionStore?.read(args.sessionId)?.cwd ?? ".");
55359
+ const candidateAbs = import_node_path43.default.isAbsolute(args.relPath) ? import_node_path43.default.resolve(args.relPath) : import_node_path43.default.resolve(cwdAbs, args.relPath);
56542
55360
  guardAttachmentPath(ctx, args.sessionId, candidateAbs, "attachment.groupAdd", "cwd-subtree");
56543
55361
  const from = ctx?.principal.kind === "owner" ? "owner" : "agent";
56544
55362
  const size = 0;
@@ -56597,19 +55415,19 @@ function buildAttachmentHandlers(deps) {
56597
55415
 
56598
55416
  // src/handlers/extension.ts
56599
55417
  var import_promises8 = __toESM(require("fs/promises"), 1);
56600
- var import_node_path55 = __toESM(require("path"), 1);
55418
+ var import_node_path48 = __toESM(require("path"), 1);
56601
55419
  init_protocol();
56602
55420
 
56603
55421
  // src/extension/bundle-zip.ts
56604
55422
  var import_promises5 = __toESM(require("fs/promises"), 1);
56605
- var import_node_path51 = __toESM(require("path"), 1);
55423
+ var import_node_path44 = __toESM(require("path"), 1);
56606
55424
  var import_node_crypto14 = __toESM(require("crypto"), 1);
56607
55425
  var import_jszip2 = __toESM(require_lib3(), 1);
56608
55426
  async function bundleExtensionDir(dir) {
56609
55427
  const entries = await listFilesSorted(dir);
56610
55428
  const zip = new import_jszip2.default();
56611
55429
  for (const rel of entries) {
56612
- const abs = import_node_path51.default.join(dir, rel);
55430
+ const abs = import_node_path44.default.join(dir, rel);
56613
55431
  const content = await import_promises5.default.readFile(abs);
56614
55432
  zip.file(rel, content, { date: FIXED_DATE });
56615
55433
  }
@@ -56630,7 +55448,7 @@ async function listFilesSorted(rootDir) {
56630
55448
  return out;
56631
55449
  }
56632
55450
  async function walk(absRoot, relPrefix, out) {
56633
- const dirAbs = import_node_path51.default.join(absRoot, relPrefix);
55451
+ const dirAbs = import_node_path44.default.join(absRoot, relPrefix);
56634
55452
  const entries = await import_promises5.default.readdir(dirAbs, { withFileTypes: true });
56635
55453
  for (const e of entries) {
56636
55454
  if (IGNORE_BASENAMES.has(e.name)) continue;
@@ -56684,25 +55502,25 @@ function computePublishCheck(args) {
56684
55502
 
56685
55503
  // src/extension/install-flow.ts
56686
55504
  var import_promises6 = __toESM(require("fs/promises"), 1);
56687
- var import_node_path53 = __toESM(require("path"), 1);
55505
+ var import_node_path46 = __toESM(require("path"), 1);
56688
55506
  var import_node_os19 = __toESM(require("os"), 1);
56689
55507
  var import_node_crypto15 = __toESM(require("crypto"), 1);
56690
55508
  var import_jszip3 = __toESM(require_lib3(), 1);
56691
55509
 
56692
55510
  // src/extension/paths.ts
56693
55511
  var import_node_os18 = __toESM(require("os"), 1);
56694
- var import_node_path52 = __toESM(require("path"), 1);
55512
+ var import_node_path45 = __toESM(require("path"), 1);
56695
55513
  function clawdHomeRoot(override) {
56696
- return override ?? process.env.CLAWD_HOME ?? import_node_path52.default.join(import_node_os18.default.homedir(), ".clawd");
55514
+ return override ?? process.env.CLAWD_HOME ?? import_node_path45.default.join(import_node_os18.default.homedir(), ".clawd");
56697
55515
  }
56698
55516
  function extensionsRoot(override) {
56699
- return import_node_path52.default.join(clawdHomeRoot(override), "extensions");
55517
+ return import_node_path45.default.join(clawdHomeRoot(override), "extensions");
56700
55518
  }
56701
55519
  function publishedChannelsFile(override) {
56702
- return import_node_path52.default.join(clawdHomeRoot(override), "extensions-published.json");
55520
+ return import_node_path45.default.join(clawdHomeRoot(override), "extensions-published.json");
56703
55521
  }
56704
55522
  function bundleCacheRoot(override) {
56705
- return import_node_path52.default.join(clawdHomeRoot(override), "extension-bundles");
55523
+ return import_node_path45.default.join(clawdHomeRoot(override), "extension-bundles");
56706
55524
  }
56707
55525
 
56708
55526
  // src/extension/install-flow.ts
@@ -56729,7 +55547,7 @@ async function installFromChannel(args, deps) {
56729
55547
  throw new InstallError("ZIP_INVALID", `failed to load zip: ${e.message}`);
56730
55548
  }
56731
55549
  for (const name of Object.keys(zip.files)) {
56732
- if (name.includes("..") || name.startsWith("/") || import_node_path53.default.isAbsolute(name)) {
55550
+ if (name.includes("..") || name.startsWith("/") || import_node_path46.default.isAbsolute(name)) {
56733
55551
  throw new InstallError("ZIP_INVALID", `unsafe zip entry: ${name}`);
56734
55552
  }
56735
55553
  }
@@ -56761,7 +55579,7 @@ async function installFromChannel(args, deps) {
56761
55579
  );
56762
55580
  }
56763
55581
  const localExtId = namespacedExtId(ownerSlug, channelRef.ownerPrincipalId);
56764
- const destDir = import_node_path53.default.join(deps.extensionsRoot, localExtId);
55582
+ const destDir = import_node_path46.default.join(deps.extensionsRoot, localExtId);
56765
55583
  let destExists = false;
56766
55584
  try {
56767
55585
  await import_promises6.default.access(destDir);
@@ -56775,16 +55593,16 @@ async function installFromChannel(args, deps) {
56775
55593
  );
56776
55594
  }
56777
55595
  const stage = await import_promises6.default.mkdtemp(
56778
- import_node_path53.default.join(import_node_os19.default.tmpdir(), `clawd-ext-install-${localExtId}-`)
55596
+ import_node_path46.default.join(import_node_os19.default.tmpdir(), `clawd-ext-install-${localExtId}-`)
56779
55597
  );
56780
55598
  try {
56781
55599
  for (const [name, entry] of Object.entries(zip.files)) {
56782
- const dest = import_node_path53.default.join(stage, name);
55600
+ const dest = import_node_path46.default.join(stage, name);
56783
55601
  if (entry.dir) {
56784
55602
  await import_promises6.default.mkdir(dest, { recursive: true });
56785
55603
  continue;
56786
55604
  }
56787
- await import_promises6.default.mkdir(import_node_path53.default.dirname(dest), { recursive: true });
55605
+ await import_promises6.default.mkdir(import_node_path46.default.dirname(dest), { recursive: true });
56788
55606
  if (name === "manifest.json") {
56789
55607
  const rewritten = { ...parsed.data, id: localExtId };
56790
55608
  await import_promises6.default.writeFile(dest, JSON.stringify(rewritten, null, 2));
@@ -56805,7 +55623,7 @@ async function installFromChannel(args, deps) {
56805
55623
 
56806
55624
  // src/extension/update-flow.ts
56807
55625
  var import_promises7 = __toESM(require("fs/promises"), 1);
56808
- var import_node_path54 = __toESM(require("path"), 1);
55626
+ var import_node_path47 = __toESM(require("path"), 1);
56809
55627
  var import_node_os20 = __toESM(require("os"), 1);
56810
55628
  var import_node_crypto16 = __toESM(require("crypto"), 1);
56811
55629
  var import_jszip4 = __toESM(require_lib3(), 1);
@@ -56822,11 +55640,11 @@ async function updateFromChannel(args, deps) {
56822
55640
  channelRef.extId,
56823
55641
  channelRef.ownerPrincipalId
56824
55642
  );
56825
- const liveDir = import_node_path54.default.join(deps.extensionsRoot, localExtId);
55643
+ const liveDir = import_node_path47.default.join(deps.extensionsRoot, localExtId);
56826
55644
  const prevDir = `${liveDir}.prev`;
56827
55645
  let existingVersion;
56828
55646
  try {
56829
- const raw = await import_promises7.default.readFile(import_node_path54.default.join(liveDir, "manifest.json"), "utf8");
55647
+ const raw = await import_promises7.default.readFile(import_node_path47.default.join(liveDir, "manifest.json"), "utf8");
56830
55648
  const parsed2 = ExtensionManifestSchema.safeParse(JSON.parse(raw));
56831
55649
  if (!parsed2.success) {
56832
55650
  throw new UpdateError(
@@ -56859,7 +55677,7 @@ async function updateFromChannel(args, deps) {
56859
55677
  throw new UpdateError("ZIP_INVALID", `failed to load zip: ${e.message}`);
56860
55678
  }
56861
55679
  for (const name of Object.keys(zip.files)) {
56862
- if (name.includes("..") || name.startsWith("/") || import_node_path54.default.isAbsolute(name)) {
55680
+ if (name.includes("..") || name.startsWith("/") || import_node_path47.default.isAbsolute(name)) {
56863
55681
  throw new UpdateError("ZIP_INVALID", `unsafe zip entry: ${name}`);
56864
55682
  }
56865
55683
  }
@@ -56894,16 +55712,16 @@ async function updateFromChannel(args, deps) {
56894
55712
  await import_promises7.default.rm(prevDir, { recursive: true, force: true });
56895
55713
  await import_promises7.default.rename(liveDir, prevDir);
56896
55714
  const stage = await import_promises7.default.mkdtemp(
56897
- import_node_path54.default.join(import_node_os20.default.tmpdir(), `clawd-ext-update-${localExtId}-`)
55715
+ import_node_path47.default.join(import_node_os20.default.tmpdir(), `clawd-ext-update-${localExtId}-`)
56898
55716
  );
56899
55717
  try {
56900
55718
  for (const [name, entry] of Object.entries(zip.files)) {
56901
- const dest = import_node_path54.default.join(stage, name);
55719
+ const dest = import_node_path47.default.join(stage, name);
56902
55720
  if (entry.dir) {
56903
55721
  await import_promises7.default.mkdir(dest, { recursive: true });
56904
55722
  continue;
56905
55723
  }
56906
- await import_promises7.default.mkdir(import_node_path54.default.dirname(dest), { recursive: true });
55724
+ await import_promises7.default.mkdir(import_node_path47.default.dirname(dest), { recursive: true });
56907
55725
  if (name === "manifest.json") {
56908
55726
  const rewritten = { ...parsed.data, id: localExtId };
56909
55727
  await import_promises7.default.writeFile(dest, JSON.stringify(rewritten, null, 2));
@@ -56996,7 +55814,7 @@ async function rewriteManifestVersion(root, extId, newVersion, previousPublished
56996
55814
  );
56997
55815
  }
56998
55816
  }
56999
- const manifestPath = import_node_path55.default.join(root, extId, "manifest.json");
55817
+ const manifestPath = import_node_path48.default.join(root, extId, "manifest.json");
57000
55818
  const manifest = await readManifest(root, extId);
57001
55819
  const next = { ...manifest, version: newVersion };
57002
55820
  const tmp = `${manifestPath}.tmp`;
@@ -57004,7 +55822,7 @@ async function rewriteManifestVersion(root, extId, newVersion, previousPublished
57004
55822
  await import_promises8.default.rename(tmp, manifestPath);
57005
55823
  }
57006
55824
  async function readManifest(root, extId) {
57007
- const file = import_node_path55.default.join(root, extId, "manifest.json");
55825
+ const file = import_node_path48.default.join(root, extId, "manifest.json");
57008
55826
  let raw;
57009
55827
  try {
57010
55828
  raw = await import_promises8.default.readFile(file, "utf8");
@@ -57095,7 +55913,7 @@ function buildExtensionHandlers(deps) {
57095
55913
  };
57096
55914
  async function buildSnapshotMeta(extId) {
57097
55915
  const manifest = await readManifest(deps.root, extId);
57098
- const { sha256, buffer } = await bundleExtensionDir(import_node_path55.default.join(deps.root, extId));
55916
+ const { sha256, buffer } = await bundleExtensionDir(import_node_path48.default.join(deps.root, extId));
57099
55917
  return { manifest, contentHash: sha256, buffer };
57100
55918
  }
57101
55919
  const publish = async (frame, _client, ctx) => {
@@ -57276,9 +56094,9 @@ function buildExtensionHandlers(deps) {
57276
56094
  }
57277
56095
 
57278
56096
  // src/app-builder/project-store.ts
57279
- var import_node_fs49 = require("fs");
57280
- var import_node_child_process14 = require("child_process");
57281
- var import_node_path56 = require("path");
56097
+ var import_node_fs42 = require("fs");
56098
+ var import_node_child_process11 = require("child_process");
56099
+ var import_node_path49 = require("path");
57282
56100
  init_protocol();
57283
56101
  var PROJECTS_DIR = "projects";
57284
56102
  var META_FILE = ".clawd-project.json";
@@ -57292,19 +56110,19 @@ var ProjectStore = class {
57292
56110
  root;
57293
56111
  /** projects/<name>/.clawd-project.json 路径 */
57294
56112
  metaPath(name) {
57295
- return (0, import_node_path56.join)(this.projectsRoot(), name, META_FILE);
56113
+ return (0, import_node_path49.join)(this.projectsRoot(), name, META_FILE);
57296
56114
  }
57297
56115
  /** projects/<name>/ 目录路径(cwd 用) */
57298
56116
  projectDir(name) {
57299
- return (0, import_node_path56.join)(this.projectsRoot(), name);
56117
+ return (0, import_node_path49.join)(this.projectsRoot(), name);
57300
56118
  }
57301
56119
  projectsRoot() {
57302
- return (0, import_node_path56.join)(this.root, PROJECTS_DIR);
56120
+ return (0, import_node_path49.join)(this.root, PROJECTS_DIR);
57303
56121
  }
57304
56122
  async list() {
57305
56123
  let entries;
57306
56124
  try {
57307
- entries = await import_node_fs49.promises.readdir(this.projectsRoot());
56125
+ entries = await import_node_fs42.promises.readdir(this.projectsRoot());
57308
56126
  } catch (err) {
57309
56127
  if (err.code === "ENOENT") return [];
57310
56128
  throw err;
@@ -57312,7 +56130,7 @@ var ProjectStore = class {
57312
56130
  const out = [];
57313
56131
  for (const name of entries) {
57314
56132
  try {
57315
- const raw = await import_node_fs49.promises.readFile(this.metaPath(name), "utf8");
56133
+ const raw = await import_node_fs42.promises.readFile(this.metaPath(name), "utf8");
57316
56134
  const json = JSON.parse(raw);
57317
56135
  let migrated = false;
57318
56136
  if (typeof json.devCommand !== "string" || json.devCommand.length === 0) {
@@ -57323,7 +56141,7 @@ var ProjectStore = class {
57323
56141
  if (parsed.success) {
57324
56142
  out.push(parsed.data);
57325
56143
  if (migrated) {
57326
- void import_node_fs49.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
56144
+ void import_node_fs42.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
57327
56145
  });
57328
56146
  }
57329
56147
  }
@@ -57367,8 +56185,8 @@ var ProjectStore = class {
57367
56185
  throw new Error(`invalid name "${name}": ${validated.error.message}`);
57368
56186
  }
57369
56187
  const dir = this.projectDir(name);
57370
- await import_node_fs49.promises.mkdir(dir, { recursive: true });
57371
- await import_node_fs49.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
56188
+ await import_node_fs42.promises.mkdir(dir, { recursive: true });
56189
+ await import_node_fs42.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
57372
56190
  return meta;
57373
56191
  }
57374
56192
  /**
@@ -57382,7 +56200,7 @@ var ProjectStore = class {
57382
56200
  async scaffold(name, templateSrcDir, scaffoldScriptPath) {
57383
56201
  const destDir = this.projectDir(name);
57384
56202
  return await new Promise((resolve6, reject) => {
57385
- const child = (0, import_node_child_process14.spawn)("bash", [scaffoldScriptPath, name, templateSrcDir, destDir], {
56203
+ const child = (0, import_node_child_process11.spawn)("bash", [scaffoldScriptPath, name, templateSrcDir, destDir], {
57386
56204
  env: { ...process.env, PATH: process.env.PATH ?? "" },
57387
56205
  stdio: ["ignore", "pipe", "pipe"]
57388
56206
  });
@@ -57411,7 +56229,7 @@ var ProjectStore = class {
57411
56229
  }
57412
56230
  async delete(name) {
57413
56231
  const dir = this.projectDir(name);
57414
- await import_node_fs49.promises.rm(dir, { recursive: true, force: true });
56232
+ await import_node_fs42.promises.rm(dir, { recursive: true, force: true });
57415
56233
  }
57416
56234
  async updatePort(name, newPort) {
57417
56235
  if (newPort < PROJECT_PORT_MIN || newPort > PROJECT_PORT_MAX) {
@@ -57427,7 +56245,7 @@ var ProjectStore = class {
57427
56245
  throw new Error(`port ${newPort} already used / \u5DF2\u88AB project "${conflict.name}" \u5360\u7528`);
57428
56246
  }
57429
56247
  const updated = { ...target, port: newPort };
57430
- await import_node_fs49.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
56248
+ await import_node_fs42.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
57431
56249
  return updated;
57432
56250
  }
57433
56251
  /**
@@ -57444,7 +56262,7 @@ var ProjectStore = class {
57444
56262
  if (!validated.success) {
57445
56263
  throw new Error(`invalid prodUrl "${url}": ${validated.error.message}`);
57446
56264
  }
57447
- await import_node_fs49.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
56265
+ await import_node_fs42.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
57448
56266
  return validated.data;
57449
56267
  }
57450
56268
  /**
@@ -57465,7 +56283,7 @@ var ProjectStore = class {
57465
56283
  if (!validated.success) {
57466
56284
  throw new Error(`invalid publishJob: ${validated.error.message}`);
57467
56285
  }
57468
- await import_node_fs49.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
56286
+ await import_node_fs42.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
57469
56287
  return validated.data;
57470
56288
  }
57471
56289
  /** 清掉 .clawd-project.json.publishJob 字段。其他字段保持原样。 */
@@ -57480,13 +56298,13 @@ var ProjectStore = class {
57480
56298
  if (!validated.success) {
57481
56299
  throw new Error(`failed to clear publishJob: ${validated.error.message}`);
57482
56300
  }
57483
- await import_node_fs49.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
56301
+ await import_node_fs42.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
57484
56302
  return validated.data;
57485
56303
  }
57486
56304
  };
57487
56305
 
57488
56306
  // src/app-builder/kill-port.ts
57489
- var import_node_child_process15 = require("child_process");
56307
+ var import_node_child_process12 = require("child_process");
57490
56308
  async function killPortOccupants(port, ownedPids, logger) {
57491
56309
  let pids;
57492
56310
  try {
@@ -57528,7 +56346,7 @@ async function killPortOccupants(port, ownedPids, logger) {
57528
56346
  }
57529
56347
  function listPidsOnPort(port) {
57530
56348
  return new Promise((resolve6, reject) => {
57531
- (0, import_node_child_process15.execFile)(
56349
+ (0, import_node_child_process12.execFile)(
57532
56350
  "lsof",
57533
56351
  ["-ti", `:${port}`],
57534
56352
  { timeout: 3e3 },
@@ -57600,9 +56418,9 @@ var PublishJobRegistry = class {
57600
56418
  };
57601
56419
 
57602
56420
  // src/app-builder/publish-job-runner.ts
57603
- var import_node_child_process16 = require("child_process");
57604
- var import_node_fs50 = require("fs");
57605
- var import_node_path57 = require("path");
56421
+ var import_node_child_process13 = require("child_process");
56422
+ var import_node_fs43 = require("fs");
56423
+ var import_node_path50 = require("path");
57606
56424
 
57607
56425
  // src/app-builder/publish-stage-parser.ts
57608
56426
  var STAGE_RE = /^\s*::stage::(build|deploy|verify)\s*$/;
@@ -57629,19 +56447,19 @@ function tailStderrLines(buf, n) {
57629
56447
  // src/app-builder/publish-job-runner.ts
57630
56448
  async function startPublishJob(deps, args) {
57631
56449
  const { registry: registry2, projectDir } = deps;
57632
- const spawn16 = deps.spawnImpl ?? import_node_child_process16.spawn;
56450
+ const spawn13 = deps.spawnImpl ?? import_node_child_process13.spawn;
57633
56451
  if (registry2.has(args.name)) {
57634
56452
  return { jobId: registry2.get(args.name).jobId, status: "already-publishing" };
57635
56453
  }
57636
56454
  const projDir = projectDir(args.name);
57637
- const logPath = (0, import_node_path57.join)(projDir, ".publish.log");
56455
+ const logPath = (0, import_node_path50.join)(projDir, ".publish.log");
57638
56456
  let logStream = null;
57639
56457
  try {
57640
- logStream = (0, import_node_fs50.createWriteStream)(logPath, { flags: "w" });
56458
+ logStream = (0, import_node_fs43.createWriteStream)(logPath, { flags: "w" });
57641
56459
  } catch {
57642
56460
  logStream = null;
57643
56461
  }
57644
- const child = spawn16("bash", [args.scriptPath, projDir, args.personaRoot ?? ""], {
56462
+ const child = spawn13("bash", [args.scriptPath, projDir, args.personaRoot ?? ""], {
57645
56463
  cwd: projDir,
57646
56464
  env: process.env,
57647
56465
  stdio: ["ignore", "pipe", "pipe"]
@@ -57894,8 +56712,8 @@ async function recoverInterruptedJobs(deps) {
57894
56712
 
57895
56713
  // src/handlers/app-builder.ts
57896
56714
  init_protocol();
57897
- var import_node_path58 = require("path");
57898
- var import_node_fs51 = require("fs");
56715
+ var import_node_path51 = require("path");
56716
+ var import_node_fs44 = require("fs");
57899
56717
  var APP_BUILDER_PERSONAS = ["persona-app-builder", "persona-dataclaw-builder"];
57900
56718
  var DEV_SERVER_READY_TIMEOUT_MS = 3e4;
57901
56719
  async function recoverInterruptedPublishJobs(store, logger) {
@@ -57976,7 +56794,7 @@ function buildAppBuilderHandlers(deps) {
57976
56794
  async function listAllUsersProjects() {
57977
56795
  if (!deps.usersRoot || !deps.getStore) return [];
57978
56796
  const getStore = deps.getStore;
57979
- const userIds = await import_node_fs51.promises.readdir(deps.usersRoot).catch(() => []);
56797
+ const userIds = await import_node_fs44.promises.readdir(deps.usersRoot).catch(() => []);
57980
56798
  const perUser = await Promise.all(
57981
56799
  userIds.map((uid) => getStore(uid).list().catch(() => []))
57982
56800
  );
@@ -58052,8 +56870,8 @@ function buildAppBuilderHandlers(deps) {
58052
56870
  const project = await userStore.create(f.name, reservedPorts);
58053
56871
  try {
58054
56872
  const personaRoot = deps.resolvePersonaRoot ? deps.resolvePersonaRoot(session.ownerPersonaId ?? "") : deps.personaRoot;
58055
- const templateSrcDir = (0, import_node_path58.join)(personaRoot, "extension-kit", "examples", DEFAULT_TEMPLATE);
58056
- const scaffoldScript = (0, import_node_path58.join)(deps.deployKitRoot, "scripts", "new-extension.sh");
56873
+ const templateSrcDir = (0, import_node_path51.join)(personaRoot, "extension-kit", "examples", DEFAULT_TEMPLATE);
56874
+ const scaffoldScript = (0, import_node_path51.join)(deps.deployKitRoot, "scripts", "new-extension.sh");
58057
56875
  const scaffoldResult = await userStore.scaffold(project.name, templateSrcDir, scaffoldScript);
58058
56876
  deps.logger?.info("app-builder.scaffold.done", {
58059
56877
  name: project.name,
@@ -58274,7 +57092,7 @@ function buildAppBuilderHandlers(deps) {
58274
57092
  await userStore.clearPublishJob(args.name);
58275
57093
  }
58276
57094
  const personaRoot = deps.resolvePersonaRoot ? deps.resolvePersonaRoot(boundSession.ownerPersonaId ?? "") : deps.personaRoot;
58277
- const scriptPath = (0, import_node_path58.join)(deps.deployKitRoot, "scripts", "publish.sh");
57095
+ const scriptPath = (0, import_node_path51.join)(deps.deployKitRoot, "scripts", "publish.sh");
58278
57096
  deps.logger?.info("app-builder.publish.start", {
58279
57097
  name: args.name,
58280
57098
  sessionId: boundSession.sessionId,
@@ -58450,7 +57268,7 @@ function buildVisitorHandlers(deps) {
58450
57268
 
58451
57269
  // src/extension/registry.ts
58452
57270
  var import_promises9 = __toESM(require("fs/promises"), 1);
58453
- var import_node_path59 = __toESM(require("path"), 1);
57271
+ var import_node_path52 = __toESM(require("path"), 1);
58454
57272
  async function loadAll(root) {
58455
57273
  let entries;
58456
57274
  try {
@@ -58463,13 +57281,13 @@ async function loadAll(root) {
58463
57281
  for (const ent of entries) {
58464
57282
  if (!ent.isDirectory()) continue;
58465
57283
  if (ent.name.startsWith(".")) continue;
58466
- records.push(await loadOne(import_node_path59.default.join(root, ent.name), ent.name));
57284
+ records.push(await loadOne(import_node_path52.default.join(root, ent.name), ent.name));
58467
57285
  }
58468
57286
  records.sort((a, b2) => a.extId < b2.extId ? -1 : a.extId > b2.extId ? 1 : 0);
58469
57287
  return records;
58470
57288
  }
58471
57289
  async function loadOne(dir, dirName) {
58472
- const manifestPath = import_node_path59.default.join(dir, "manifest.json");
57290
+ const manifestPath = import_node_path52.default.join(dir, "manifest.json");
58473
57291
  let raw;
58474
57292
  try {
58475
57293
  raw = await import_promises9.default.readFile(manifestPath, "utf8");
@@ -58514,7 +57332,7 @@ async function loadOne(dir, dirName) {
58514
57332
 
58515
57333
  // src/extension/uninstall.ts
58516
57334
  var import_promises10 = __toESM(require("fs/promises"), 1);
58517
- var import_node_path60 = __toESM(require("path"), 1);
57335
+ var import_node_path53 = __toESM(require("path"), 1);
58518
57336
  var UninstallError = class extends Error {
58519
57337
  constructor(code, message) {
58520
57338
  super(message);
@@ -58523,7 +57341,7 @@ var UninstallError = class extends Error {
58523
57341
  code;
58524
57342
  };
58525
57343
  async function uninstall(deps) {
58526
- const dir = import_node_path60.default.join(deps.root, deps.extId);
57344
+ const dir = import_node_path53.default.join(deps.root, deps.extId);
58527
57345
  try {
58528
57346
  await import_promises10.default.access(dir);
58529
57347
  } catch {
@@ -58540,7 +57358,7 @@ var import_node_crypto18 = require("crypto");
58540
57358
  init_protocol();
58541
57359
 
58542
57360
  // src/peer-ops/run-command.ts
58543
- var import_node_child_process17 = require("child_process");
57361
+ var import_node_child_process14 = require("child_process");
58544
57362
  var import_node_os21 = __toESM(require("os"), 1);
58545
57363
  var DEFAULT_TIMEOUT_MS5 = 3e4;
58546
57364
  var MAX_TIMEOUT_MS = 3e5;
@@ -58548,7 +57366,7 @@ var MAX_OUTPUT_BYTES = 1048576;
58548
57366
  function runCommand(command, opts = {}) {
58549
57367
  const timeoutMs = Math.min(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS5, MAX_TIMEOUT_MS);
58550
57368
  return new Promise((resolve6) => {
58551
- const child = (0, import_node_child_process17.spawn)("bash", ["-c", command], { cwd: import_node_os21.default.homedir() });
57369
+ const child = (0, import_node_child_process14.spawn)("bash", ["-c", command], { cwd: import_node_os21.default.homedir() });
58552
57370
  const out = [];
58553
57371
  const err = [];
58554
57372
  let outBytes = 0;
@@ -58703,11 +57521,9 @@ function buildMethodHandlers(deps) {
58703
57521
  broadcast: deps.broadcastToOwners,
58704
57522
  now: () => Date.now()
58705
57523
  }),
58706
- ...buildContactSshHandlers({
57524
+ ...buildContactRemoteAccessHandlers({
58707
57525
  store: deps.contactStore,
58708
- broadcast: deps.broadcastToOwners,
58709
- sshdDir: deps.sshdDir,
58710
- sshLog: deps.contactSshLog
57526
+ broadcast: deps.broadcastToOwners
58711
57527
  }),
58712
57528
  // 跨设备 exec(spec 2026-07-16):A 侧带 peerDeviceId → forwardPeerExecToPeer;
58713
57529
  // B 侧无 peerDeviceId → 本地 runCommand(handler 判 remoteAccessAllowed)。
@@ -58721,9 +57537,7 @@ function buildMethodHandlers(deps) {
58721
57537
  ownerId: deps.ownerId,
58722
57538
  ownerProvider: deps.ownerProvider,
58723
57539
  personaStore: deps.personaStore,
58724
- capabilityManager: deps.capabilityManager,
58725
- sshIdentityPublicKey: deps.sshIdentityPublicKey,
58726
- selfOsUser: deps.selfOsUser
57540
+ capabilityManager: deps.capabilityManager
58727
57541
  }),
58728
57542
  ...buildFeishuAuthHandlers(deps.feishuAuth),
58729
57543
  ...buildDeviceHandlers(deps.feishuDevice),
@@ -58766,7 +57580,7 @@ function buildMethodHandlers(deps) {
58766
57580
  }
58767
57581
 
58768
57582
  // src/app-builder/dev-server-supervisor.ts
58769
- var import_node_child_process18 = require("child_process");
57583
+ var import_node_child_process15 = require("child_process");
58770
57584
  var import_node_events2 = require("events");
58771
57585
  var DEFAULT_READY_PATTERN = /Local:\s+https?:\/\/|Nest application successfully started|server listening on/i;
58772
57586
  var DevServerSupervisor = class extends import_node_events2.EventEmitter {
@@ -58803,7 +57617,7 @@ var DevServerSupervisor = class extends import_node_events2.EventEmitter {
58803
57617
  tunnelHost: args.tunnelHost,
58804
57618
  devCommand: cmd
58805
57619
  });
58806
- const child = (0, import_node_child_process18.spawn)("sh", ["-c", cmd], {
57620
+ const child = (0, import_node_child_process15.spawn)("sh", ["-c", cmd], {
58807
57621
  cwd: args.cwd,
58808
57622
  env,
58809
57623
  stdio: "pipe",
@@ -59055,7 +57869,7 @@ var METHOD_GRANT_MAP = {
59055
57869
  "contact:list": ADMIN_ANY,
59056
57870
  "contact:pin": ADMIN_ANY,
59057
57871
  "contact:remove": ADMIN_ANY,
59058
- // contact:setRemoteAccess (owner UI 配 SSH 授权):ADMIN_ANY
57872
+ // contact:setRemoteAccess (owner UI 配远程访问授权):ADMIN_ANY
59059
57873
  // (对齐 inbox:postMessage 的"能连上=有 auth,业务在 handler 校"模式)
59060
57874
  "contact:setRemoteAccess": ADMIN_ANY,
59061
57875
  // ---- visitor:* (访客名单,owner-only) ----
@@ -59245,12 +58059,12 @@ async function dispatchRpc(method, frame, client, ctx, deps) {
59245
58059
  }
59246
58060
 
59247
58061
  // src/extension/runtime.ts
59248
- var import_node_child_process19 = require("child_process");
59249
- var import_node_path61 = __toESM(require("path"), 1);
58062
+ var import_node_child_process16 = require("child_process");
58063
+ var import_node_path54 = __toESM(require("path"), 1);
59250
58064
  var import_promises11 = require("timers/promises");
59251
58065
 
59252
58066
  // src/extension/port-allocator.ts
59253
- var import_node_net3 = __toESM(require("net"), 1);
58067
+ var import_node_net2 = __toESM(require("net"), 1);
59254
58068
  var PortExhaustedError = class extends Error {
59255
58069
  constructor(min, max) {
59256
58070
  super(`no free port in [${min},${max}]`);
@@ -59263,7 +58077,7 @@ var PortExhaustedError = class extends Error {
59263
58077
  };
59264
58078
  function probe(port) {
59265
58079
  return new Promise((resolve6) => {
59266
- const srv = import_node_net3.default.createServer();
58080
+ const srv = import_node_net2.default.createServer();
59267
58081
  srv.once("error", () => resolve6(false));
59268
58082
  srv.once("listening", () => {
59269
58083
  srv.close(() => resolve6(true));
@@ -59347,13 +58161,13 @@ var Runtime = class {
59347
58161
  /\$CLAWOS_EXT_PORT/g,
59348
58162
  String(port)
59349
58163
  );
59350
- const dir = import_node_path61.default.join(this.root, extId);
58164
+ const dir = import_node_path54.default.join(this.root, extId);
59351
58165
  const env = {
59352
58166
  ...process.env,
59353
58167
  CLAWOS_EXT_PORT: String(port),
59354
58168
  CLAWOS_EXT_ID: extId
59355
58169
  };
59356
- const child = (0, import_node_child_process19.spawn)("sh", ["-c", cmd], {
58170
+ const child = (0, import_node_child_process16.spawn)("sh", ["-c", cmd], {
59357
58171
  cwd: dir,
59358
58172
  env,
59359
58173
  stdio: ["ignore", "pipe", "pipe"],
@@ -59459,7 +58273,7 @@ ${handle.stderrTail}`
59459
58273
 
59460
58274
  // src/extension/published-channels.ts
59461
58275
  var import_promises12 = __toESM(require("fs/promises"), 1);
59462
- var import_node_path62 = __toESM(require("path"), 1);
58276
+ var import_node_path55 = __toESM(require("path"), 1);
59463
58277
  init_zod();
59464
58278
  var PublishedChannelsError = class extends Error {
59465
58279
  constructor(code, message) {
@@ -59558,7 +58372,7 @@ var PublishedChannelStore = class {
59558
58372
  )
59559
58373
  };
59560
58374
  const tmp = `${this.filePath}.tmp`;
59561
- await import_promises12.default.mkdir(import_node_path62.default.dirname(this.filePath), { recursive: true });
58375
+ await import_promises12.default.mkdir(import_node_path55.default.dirname(this.filePath), { recursive: true });
59562
58376
  await import_promises12.default.writeFile(tmp, JSON.stringify(data, null, 2), { mode: 384 });
59563
58377
  await import_promises12.default.rename(tmp, this.filePath);
59564
58378
  }
@@ -59566,7 +58380,7 @@ var PublishedChannelStore = class {
59566
58380
 
59567
58381
  // src/extension/bundle-cache.ts
59568
58382
  var import_promises13 = __toESM(require("fs/promises"), 1);
59569
- var import_node_path63 = __toESM(require("path"), 1);
58383
+ var import_node_path56 = __toESM(require("path"), 1);
59570
58384
  var BundleCache = class {
59571
58385
  constructor(rootDir) {
59572
58386
  this.rootDir = rootDir;
@@ -59575,14 +58389,14 @@ var BundleCache = class {
59575
58389
  /** Atomic write: stage tmp → rename. Caller passes the hex sha256. */
59576
58390
  async write(snapshotHash, buffer) {
59577
58391
  await import_promises13.default.mkdir(this.rootDir, { recursive: true });
59578
- const file = import_node_path63.default.join(this.rootDir, `${snapshotHash}.zip`);
58392
+ const file = import_node_path56.default.join(this.rootDir, `${snapshotHash}.zip`);
59579
58393
  const tmp = `${file}.tmp`;
59580
58394
  await import_promises13.default.writeFile(tmp, buffer, { mode: 384 });
59581
58395
  await import_promises13.default.rename(tmp, file);
59582
58396
  }
59583
58397
  /** Returns the bundle bytes, or null when the file doesn't exist. */
59584
58398
  async read(snapshotHash) {
59585
- const file = import_node_path63.default.join(this.rootDir, `${snapshotHash}.zip`);
58399
+ const file = import_node_path56.default.join(this.rootDir, `${snapshotHash}.zip`);
59586
58400
  try {
59587
58401
  return await import_promises13.default.readFile(file);
59588
58402
  } catch (e) {
@@ -59592,20 +58406,13 @@ var BundleCache = class {
59592
58406
  }
59593
58407
  /** Idempotent — missing file is not an error. */
59594
58408
  async delete(snapshotHash) {
59595
- const file = import_node_path63.default.join(this.rootDir, `${snapshotHash}.zip`);
58409
+ const file = import_node_path56.default.join(this.rootDir, `${snapshotHash}.zip`);
59596
58410
  await import_promises13.default.rm(file, { force: true });
59597
58411
  }
59598
58412
  };
59599
58413
 
59600
58414
  // src/index.ts
59601
58415
  var import_meta6 = {};
59602
- function currentOsUser() {
59603
- try {
59604
- return import_node_os22.default.userInfo().username;
59605
- } catch {
59606
- return "";
59607
- }
59608
- }
59609
58416
  async function startDaemon(config) {
59610
58417
  const authFile = loadOrCreateAuthFile({ dataDir: config.dataDir });
59611
58418
  const logShippingCfg = config.logShipping ?? { endpoint: null, sampling: {}, mode: "off" };
@@ -59622,24 +58429,24 @@ async function startDaemon(config) {
59622
58429
  sampling: logShippingCfg.sampling,
59623
58430
  homeDir: import_node_os22.default.homedir()
59624
58431
  });
59625
- const logDir = import_node_path64.default.join(config.dataDir, "log");
59626
- import_node_fs52.default.mkdirSync(logDir, { recursive: true });
58432
+ const logDir = import_node_path57.default.join(config.dataDir, "log");
58433
+ import_node_fs45.default.mkdirSync(logDir, { recursive: true });
59627
58434
  const logger = createLogger({
59628
58435
  level: config.logLevel,
59629
- file: import_node_path64.default.join(logDir, "clawd.log"),
58436
+ file: import_node_path57.default.join(logDir, "clawd.log"),
59630
58437
  logClient
59631
58438
  });
59632
58439
  logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
59633
58440
  const screenIdleProbeLogger = createFileOnlyLogger({
59634
- file: import_node_path64.default.join(logDir, "screen-idle-probe.log"),
58441
+ file: import_node_path57.default.join(logDir, "screen-idle-probe.log"),
59635
58442
  level: "debug"
59636
58443
  });
59637
58444
  logger.info("screen-idle probe logger enabled", {
59638
- file: import_node_path64.default.join(logDir, "screen-idle-probe.log")
58445
+ file: import_node_path57.default.join(logDir, "screen-idle-probe.log")
59639
58446
  });
59640
58447
  const CAP_TARGETS = [
59641
- import_node_path64.default.join(logDir, "clawd.log"),
59642
- import_node_path64.default.join(logDir, "screen-idle-probe.log")
58448
+ import_node_path57.default.join(logDir, "clawd.log"),
58449
+ import_node_path57.default.join(logDir, "screen-idle-probe.log")
59643
58450
  ];
59644
58451
  const LOG_CAP_MAX_BYTES = 10 * 1024 * 1024;
59645
58452
  const LOG_CAP_KEEP_BYTES = 5 * 1024 * 1024;
@@ -59735,7 +58542,7 @@ async function startDaemon(config) {
59735
58542
  // Task 1.7:authenticate 注入路径替代 expectedToken 单 token 比对。
59736
58543
  // owner 路径 constantTimeEqual 防侧信道;guest 路径走 capabilityRegistry.
59737
58544
  expectedToken: resolvedAuthToken,
59738
- authenticate: async (t, selfUrl, selfSshPublicKey, selfOsUser) => {
58545
+ authenticate: async (t, selfUrl) => {
59739
58546
  const result = await authenticate(t, buildConnectAuthDeps());
59740
58547
  if (result.ok && result.context.principal.kind === "guest") {
59741
58548
  void autoReverseContact({
@@ -59746,11 +58553,6 @@ async function startDaemon(config) {
59746
58553
  provider: result.context.provider ?? "",
59747
58554
  displayName: result.context.principal.displayName ?? result.context.principal.id,
59748
58555
  selfUrl,
59749
- // 建联交换:guest auth 帧自报的 pubkey / osUser 落 contact;pubkey 变化且已授权时
59750
- // 立即重建 authorized_keys(重新握手 = 迁移路径)。见 …#建联交换清单
59751
- sshPublicKey: selfSshPublicKey,
59752
- osUser: selfOsUser,
59753
- rebuildAuthorizedKeys: () => rebuildAuthorizedKeys(contactStore, import_node_path64.default.join(config.dataDir, "sshd")),
59754
58556
  // 决策 #16 换票补全:用 owner 当前 jwt 换 aud=对方设备 的票(本机主动连对方用)。
59755
58557
  // 现读 ownerIdentityStore(热切换后用新身份);失败 → null → 保持空票。
59756
58558
  exchangeToken: async (targetDeviceId) => {
@@ -59798,8 +58600,8 @@ async function startDaemon(config) {
59798
58600
  const agents = new AgentsScanner();
59799
58601
  const history = new ClaudeHistoryReader();
59800
58602
  let transport = null;
59801
- const personaStore = new PersonaStore(import_node_path64.default.join(config.dataDir, "personas"));
59802
- const usersRoot = import_node_path64.default.join(config.dataDir, "users");
58603
+ const personaStore = new PersonaStore(import_node_path57.default.join(config.dataDir, "personas"));
58604
+ const usersRoot = import_node_path57.default.join(config.dataDir, "users");
59803
58605
  const defaultsRoot = findDefaultsRoot(logger);
59804
58606
  if (defaultsRoot) {
59805
58607
  seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
@@ -59819,17 +58621,17 @@ async function startDaemon(config) {
59819
58621
  migrateCodexSandbox({ store: personaStore, logger });
59820
58622
  const groupFileStore = new GroupFileStore({ dataDir: config.dataDir, logger });
59821
58623
  const personaDispatchManager = new PersonaDispatchManager({ genId: () => v4_default() });
59822
- const here = typeof __dirname === "string" ? __dirname : import_node_path64.default.dirname((0, import_node_url4.fileURLToPath)(import_meta6.url));
58624
+ const here = typeof __dirname === "string" ? __dirname : import_node_path57.default.dirname((0, import_node_url4.fileURLToPath)(import_meta6.url));
59823
58625
  const mcpConfigs = [];
59824
58626
  const dispatchServerCandidates = [
59825
- import_node_path64.default.join(here, "dispatch", "mcp-server.cjs"),
58627
+ import_node_path57.default.join(here, "dispatch", "mcp-server.cjs"),
59826
58628
  // 生产 dist/index → dist/dispatch/mcp-server.cjs
59827
- import_node_path64.default.join(here, "..", "dist", "dispatch", "mcp-server.cjs")
58629
+ import_node_path57.default.join(here, "..", "dist", "dispatch", "mcp-server.cjs")
59828
58630
  // dev tsx src/index → ../dist/dispatch/mcp-server.cjs
59829
58631
  ];
59830
- const dispatchServerScriptPath = dispatchServerCandidates.find((p2) => import_node_fs52.default.existsSync(p2));
58632
+ const dispatchServerScriptPath = dispatchServerCandidates.find((p2) => import_node_fs45.default.existsSync(p2));
59831
58633
  if (dispatchServerScriptPath) {
59832
- const dispatchLogPath = import_node_path64.default.join(logDir, "dispatch-mcp-server.log");
58634
+ const dispatchLogPath = import_node_path57.default.join(logDir, "dispatch-mcp-server.log");
59833
58635
  const dispatchCfgPath = writeDispatchMcpConfig({
59834
58636
  dataDir: config.dataDir,
59835
58637
  serverScriptPath: dispatchServerScriptPath,
@@ -59847,12 +58649,12 @@ async function startDaemon(config) {
59847
58649
  });
59848
58650
  }
59849
58651
  const ticketServerCandidates = [
59850
- import_node_path64.default.join(here, "ticket", "mcp-server.cjs"),
59851
- import_node_path64.default.join(here, "..", "dist", "ticket", "mcp-server.cjs")
58652
+ import_node_path57.default.join(here, "ticket", "mcp-server.cjs"),
58653
+ import_node_path57.default.join(here, "..", "dist", "ticket", "mcp-server.cjs")
59852
58654
  ];
59853
- const ticketServerScriptPath = ticketServerCandidates.find((p2) => import_node_fs52.default.existsSync(p2));
58655
+ const ticketServerScriptPath = ticketServerCandidates.find((p2) => import_node_fs45.default.existsSync(p2));
59854
58656
  if (ticketServerScriptPath) {
59855
- const ticketLogPath = import_node_path64.default.join(logDir, "ticket-mcp-server.log");
58657
+ const ticketLogPath = import_node_path57.default.join(logDir, "ticket-mcp-server.log");
59856
58658
  const ticketCfgPath = writeTicketMcpConfig({
59857
58659
  dataDir: config.dataDir,
59858
58660
  serverScriptPath: ticketServerScriptPath,
@@ -59872,12 +58674,12 @@ async function startDaemon(config) {
59872
58674
  });
59873
58675
  }
59874
58676
  const shiftServerCandidates = [
59875
- import_node_path64.default.join(here, "shift", "mcp-server.cjs"),
59876
- import_node_path64.default.join(here, "..", "dist", "shift", "mcp-server.cjs")
58677
+ import_node_path57.default.join(here, "shift", "mcp-server.cjs"),
58678
+ import_node_path57.default.join(here, "..", "dist", "shift", "mcp-server.cjs")
59877
58679
  ];
59878
- const shiftServerScriptPath = shiftServerCandidates.find((p2) => import_node_fs52.default.existsSync(p2));
58680
+ const shiftServerScriptPath = shiftServerCandidates.find((p2) => import_node_fs45.default.existsSync(p2));
59879
58681
  if (shiftServerScriptPath) {
59880
- const shiftLogPath = import_node_path64.default.join(logDir, "shift-mcp-server.log");
58682
+ const shiftLogPath = import_node_path57.default.join(logDir, "shift-mcp-server.log");
59881
58683
  const shiftCfgPath = await writeShiftMcpConfig({
59882
58684
  dataDir: config.dataDir,
59883
58685
  serverScriptPath: shiftServerScriptPath,
@@ -59896,12 +58698,12 @@ async function startDaemon(config) {
59896
58698
  );
59897
58699
  }
59898
58700
  const inboxServerCandidates = [
59899
- import_node_path64.default.join(here, "inbox", "mcp-server.cjs"),
59900
- import_node_path64.default.join(here, "..", "dist", "inbox", "mcp-server.cjs")
58701
+ import_node_path57.default.join(here, "inbox", "mcp-server.cjs"),
58702
+ import_node_path57.default.join(here, "..", "dist", "inbox", "mcp-server.cjs")
59901
58703
  ];
59902
- const inboxServerScriptPath = inboxServerCandidates.find((p2) => import_node_fs52.default.existsSync(p2));
58704
+ const inboxServerScriptPath = inboxServerCandidates.find((p2) => import_node_fs45.default.existsSync(p2));
59903
58705
  if (inboxServerScriptPath) {
59904
- const inboxLogPath = import_node_path64.default.join(logDir, "inbox-mcp-server.log");
58706
+ const inboxLogPath = import_node_path57.default.join(logDir, "inbox-mcp-server.log");
59905
58707
  const inboxCfgPath = await writeInboxMcpConfig({
59906
58708
  dataDir: config.dataDir,
59907
58709
  serverScriptPath: inboxServerScriptPath,
@@ -59920,10 +58722,10 @@ async function startDaemon(config) {
59920
58722
  );
59921
58723
  }
59922
58724
  const peerOpsServerCandidates = [
59923
- import_node_path64.default.join(here, "peer-ops", "mcp-server.cjs"),
59924
- import_node_path64.default.join(here, "..", "dist", "peer-ops", "mcp-server.cjs")
58725
+ import_node_path57.default.join(here, "peer-ops", "mcp-server.cjs"),
58726
+ import_node_path57.default.join(here, "..", "dist", "peer-ops", "mcp-server.cjs")
59925
58727
  ];
59926
- const peerOpsServerScriptPath = peerOpsServerCandidates.find((p2) => import_node_fs52.default.existsSync(p2));
58728
+ const peerOpsServerScriptPath = peerOpsServerCandidates.find((p2) => import_node_fs45.default.existsSync(p2));
59927
58729
  if (peerOpsServerScriptPath) {
59928
58730
  const peerOpsCfgPath = writePeerOpsMcpConfig({
59929
58731
  dataDir: config.dataDir,
@@ -59941,7 +58743,7 @@ async function startDaemon(config) {
59941
58743
  );
59942
58744
  }
59943
58745
  const shiftStore = createShiftStore({
59944
- filePath: import_node_path64.default.join(config.dataDir, "shift.json"),
58746
+ filePath: import_node_path57.default.join(config.dataDir, "shift.json"),
59945
58747
  ownerIdProvider: () => ownerPrincipalId,
59946
58748
  now: () => Date.now()
59947
58749
  });
@@ -59963,7 +58765,7 @@ async function startDaemon(config) {
59963
58765
  getAdapter,
59964
58766
  historyReader: history,
59965
58767
  dataDir: config.dataDir,
59966
- personaRoot: import_node_path64.default.join(config.dataDir, "personas"),
58768
+ personaRoot: import_node_path57.default.join(config.dataDir, "personas"),
59967
58769
  usersRoot,
59968
58770
  personaStore,
59969
58771
  ownerDisplayName,
@@ -60001,10 +58803,10 @@ async function startDaemon(config) {
60001
58803
  // 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
60002
58804
  attachmentGroup: {
60003
58805
  onFileEdit: (input) => {
60004
- const absPath = import_node_path64.default.isAbsolute(input.relPath) ? input.relPath : import_node_path64.default.join(input.cwd, input.relPath);
58806
+ const absPath = import_node_path57.default.isAbsolute(input.relPath) ? input.relPath : import_node_path57.default.join(input.cwd, input.relPath);
60005
58807
  let size = 0;
60006
58808
  try {
60007
- size = import_node_fs52.default.statSync(absPath).size;
58809
+ size = import_node_fs45.default.statSync(absPath).size;
60008
58810
  } catch (err) {
60009
58811
  logger.warn("attachment.onFileEdit stat failed", {
60010
58812
  sessionId: input.sessionId,
@@ -60190,14 +58992,6 @@ async function startDaemon(config) {
60190
58992
  const effectivePreviewPorts = Array.from(
60191
58993
  /* @__PURE__ */ new Set([...config.previewPorts ?? [], ...appBuilderPortRange])
60192
58994
  );
60193
- const sshLog = createContactSshLog(config.dataDir);
60194
- try {
60195
- await ensureSshIdentity(config.dataDir);
60196
- } catch (e) {
60197
- logger.warn("ssh-identity generate failed; contact SSH pubkey exchange disabled", {
60198
- err: e.message
60199
- });
60200
- }
60201
58995
  const makeHandlers = () => buildMethodHandlers({
60202
58996
  manager,
60203
58997
  workspace,
@@ -60241,11 +59035,11 @@ async function startDaemon(config) {
60241
59035
  // 'persona/<pid>/owner',default 走 'default'。
60242
59036
  getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
60243
59037
  // guest path guard:candidate 必须在 personaRoot 子树或调用者自己的 user-dir 下
60244
- personaRoot: import_node_path64.default.join(config.dataDir, "personas"),
59038
+ personaRoot: import_node_path57.default.join(config.dataDir, "personas"),
60245
59039
  usersRoot
60246
59040
  },
60247
59041
  // workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
60248
- personaRoot: import_node_path64.default.join(config.dataDir, "personas"),
59042
+ personaRoot: import_node_path57.default.join(config.dataDir, "personas"),
60249
59043
  // v2 多人 persona 隔离:handler 派生 guest user-dir 放行
60250
59044
  usersRoot,
60251
59045
  // capability:list / delete handler 依赖
@@ -60258,11 +59052,6 @@ async function startDaemon(config) {
60258
59052
  ownerFeishuUnionId,
60259
59053
  ownerId,
60260
59054
  ownerProvider,
60261
- // ssh identity 互换(spec 2026-07-15):whoami:ok 下发本机 identity pubkey。
60262
- // 惰性读盘,不依赖 ensureSshIdentity 完成时序(未生成 → null → 帧省略字段)。
60263
- sshIdentityPublicKey: () => readSshIdentityPublicKey(config.dataDir),
60264
- // 建联交换:whoami:ok 下发本机 OS 账号名(拨号登录名)。见 doc/glossary/peer-mesh.md#建联交换清单
60265
- selfOsUser: currentOsUser,
60266
59055
  personaStore,
60267
59056
  // capability handler 也用 (capability:issue 走 registry / capability:list)
60268
59057
  capabilityRegistry,
@@ -60272,10 +59061,6 @@ async function startDaemon(config) {
60272
59061
  inboxStore,
60273
59062
  // 联系人列表 store(device:connect / 自动反向落同一 store)
60274
59063
  contactStore,
60275
- // <dataDir>/sshd 绝对路径 —— contact-ssh handlers 用它拼 authorized_keys / keys/ 子路径
60276
- // Task 10 会加 SshdManager 起 sshd;handlers wire 提前挂 sshdDir 让 typecheck 过
60277
- sshdDir: import_node_path64.default.join(config.dataDir, "sshd"),
60278
- contactSshLog: sshLog,
60279
59064
  // inbox:sendDm 用:sessionId → session 出身(复用 attachment 同款 findOwnedSessionScope)
60280
59065
  getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
60281
59066
  // contact:removed broadcast;复用 capability:tokenIssued 同款通路
@@ -60342,13 +59127,7 @@ async function startDaemon(config) {
60342
59127
  // 自动反向(spec 决策 #11):本机可达地址 = tunnel URL(公网),出站连接时
60343
59128
  // 透传让对方反向加我为联系人。无 tunnel(仅本机 ws)→ null,对方不自动反向(不造假数据)。
60344
59129
  // 决策 #16:身份不自报——本机 deviceId/ownerId/provider 由 exchange 签进 token 背书。
60345
- selfUrl: () => currentTunnelUrl,
60346
- // ssh identity 互换(spec 2026-07-15):出站 auth 帧自报本机 identity pubkey;
60347
- // 对方 pubkey 变化且已授权时立即重建 authorized_keys(重新 connect = 迁移路径)
60348
- selfSshPublicKey: () => readSshIdentityPublicKey(config.dataDir),
60349
- // 建联交换:出站自报本机 OS 账号名。见 doc/glossary/peer-mesh.md#建联交换清单
60350
- selfOsUser: currentOsUser,
60351
- rebuildAuthorizedKeys: () => rebuildAuthorizedKeys(contactStore, import_node_path64.default.join(config.dataDir, "sshd"))
59130
+ selfUrl: () => currentTunnelUrl
60352
59131
  },
60353
59132
  // app-builder
60354
59133
  appBuilderStore,
@@ -60371,11 +59150,11 @@ async function startDaemon(config) {
60371
59150
  // 发布上线脚手架化 (spec 2026-06-03 §5.2):
60372
59151
  // appBuilderPersonaRoot 用于拼 publish.sh 绝对路径(persona-app-builder 安装在
60373
59152
  // dataDir/personas/persona-app-builder 之下,extension-kit/scripts/publish.sh 是相对路径)。
60374
- appBuilderPersonaRoot: import_node_path64.default.join(config.dataDir, "personas", "persona-app-builder"),
59153
+ appBuilderPersonaRoot: import_node_path57.default.join(config.dataDir, "personas", "persona-app-builder"),
60375
59154
  // 共享 deploy-kit 根:scaffold/publish 脚本骨架 + 阿里云凭证单一真源。
60376
- deployKitRoot: import_node_path64.default.join(config.dataDir, "deploy-kit"),
59155
+ deployKitRoot: import_node_path57.default.join(config.dataDir, "deploy-kit"),
60377
59156
  // scaffold/publish 按当前 session 的 persona 解析其安装根,让每个 persona 用自己的模板/注入配置。
60378
- resolvePersonaRoot: (personaId) => import_node_path64.default.join(config.dataDir, "personas", personaId),
59157
+ resolvePersonaRoot: (personaId) => import_node_path57.default.join(config.dataDir, "personas", personaId),
60379
59158
  // 发布上线脚手架化 (spec 2026-06-03 §5.2.2):
60380
59159
  // 复用 SessionManagerDeps.broadcastFrame 同款 dispatch 逻辑 —— runner 调 manager.send
60381
59160
  // 取回 broadcast 帧后逐帧 push 到 transport,跟 manager 自身的 deps 一致。
@@ -60418,7 +59197,7 @@ async function startDaemon(config) {
60418
59197
  }
60419
59198
  let sourceJsonlPath = "(no transcript yet \u2014 operate from the task description alone)";
60420
59199
  if (sourceFile && sourceFile.toolSessionId) {
60421
- sourceJsonlPath = import_node_path64.default.join(
59200
+ sourceJsonlPath = import_node_path57.default.join(
60422
59201
  import_node_os22.default.homedir(),
60423
59202
  ".claude",
60424
59203
  "projects",
@@ -60625,48 +59404,6 @@ async function startDaemon(config) {
60625
59404
  httpRequestHandler: httpRouter,
60626
59405
  // app-builder build 模式:/preview/<port>/ HMR websocket upgrade 转发的端口白名单(同 http-router 配置)
60627
59406
  previewPorts: effectivePreviewPorts,
60628
- // Contact SSH tunnel (PR: contact-ssh-tunnel): /rpc/ssh-tunnel WS 前置拦截 →
60629
- // Bearer auth (owner token / connect token / capability token 三选一) → net.connect 到本机 sshd →
60630
- // raw byte relay. 用于 B 侧 `clawd ssh-relay` CLI 拨号(SSH ProxyCommand)
60631
- sshTunnelUpgradeHandler: async (req, socket, head, wss2) => {
60632
- await handleSshTunnelUpgrade(req, socket, head, {
60633
- wss: wss2,
60634
- sshdPort: config.sshdPort,
60635
- logger,
60636
- sshLog,
60637
- authorize: async (token) => {
60638
- if (!token) return false;
60639
- if (resolvedAuthToken && token === resolvedAuthToken) return true;
60640
- const cap = capabilityRegistry.verifyToken(token);
60641
- if (cap.ok) return true;
60642
- const vis = verifyVisitorToken(authFile.visitorTokenSecret, token, Math.floor(Date.now() / 1e3));
60643
- if (vis.ok) return true;
60644
- const publicKeyPem = serverKeyStore.read();
60645
- if (publicKeyPem) {
60646
- const r = verifyConnectToken({
60647
- token,
60648
- publicKeyPem,
60649
- expectedDeviceId: authFile.deviceId
60650
- });
60651
- if (r.ok) return true;
60652
- }
60653
- return false;
60654
- }
60655
- });
60656
- return true;
60657
- },
60658
- // Contact SSH relay v2 (spec 2026-07-06-contact-ssh-relay-via-local-daemon-tunnel):
60659
- // /rpc/ssh-tunnel-local WS 前置拦截。guest CC 沙箱从 localhost 连本地 daemon,daemon
60660
- // 代理起 outbound wss 到 peer /rpc/ssh-tunnel。findContact / connectToken 处理下沉到
60661
- // 本 handler;relay CLI 变纯粹的 stdio ↔ local WS pump(不再读 contacts.json / 持 token)。
60662
- sshTunnelLocalUpgradeHandler: async (req, socket, head, wss2) => {
60663
- await handleSshTunnelLocalUpgrade(req, socket, head, {
60664
- wss: wss2,
60665
- store: contactStore,
60666
- logger,
60667
- sshLog
60668
- });
60669
- },
60670
59407
  // 订阅成功后给该 client 重放 in-flight pendingQuestions(plan: clawd-question-server-truth)。
60671
59408
  // daemon 是 pendingQuestions 的唯一 source of truth;新 client 接入 / 刷新页面时
60672
59409
  // 把当前所有未决 question 以 session:question 帧定向回放,让 UI 不再误显示 Ended。
@@ -60778,8 +59515,8 @@ async function startDaemon(config) {
60778
59515
  const lines = [
60779
59516
  `Tunnel: ${r.url}`,
60780
59517
  ...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
60781
- `Frpc config: ${import_node_path64.default.join(config.dataDir, "frpc.toml")}`,
60782
- `Frpc log: ${import_node_path64.default.join(logDir, "frpc.log")}`
59518
+ `Frpc config: ${import_node_path57.default.join(config.dataDir, "frpc.toml")}`,
59519
+ `Frpc log: ${import_node_path57.default.join(logDir, "frpc.log")}`
60783
59520
  ];
60784
59521
  const width = Math.max(...lines.map((l) => l.length));
60785
59522
  const bar = "\u2550".repeat(width + 4);
@@ -60792,8 +59529,8 @@ ${bar}
60792
59529
 
60793
59530
  `);
60794
59531
  try {
60795
- const connectPath = import_node_path64.default.join(config.dataDir, "connect.txt");
60796
- import_node_fs52.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
59532
+ const connectPath = import_node_path57.default.join(config.dataDir, "connect.txt");
59533
+ import_node_fs45.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
60797
59534
  } catch {
60798
59535
  }
60799
59536
  } catch (err) {
@@ -60818,24 +59555,6 @@ ${bar}
60818
59555
  logger.warn("tunnel unavailable, degraded to local mode", { reason: tunnelError });
60819
59556
  }
60820
59557
  }
60821
- const sshdMgr = new SshdManager({
60822
- dataDir: config.dataDir,
60823
- port: config.sshdPort,
60824
- logger,
60825
- installProcessExitHandlers: true,
60826
- onSshdExit: (info) => logger.warn("sshd exited unexpectedly", info),
60827
- // sshd 的 pubkey 认证结果转写进 contact-ssh.log(补 A 侧「隧道连上但 sshd 拒了 key」盲区)
60828
- sshLog
60829
- });
60830
- try {
60831
- await sshdMgr.start();
60832
- rebuildAuthorizedKeys(contactStore, import_node_path64.default.join(config.dataDir, "sshd"));
60833
- logger.info("sshd: contact-ssh sandbox ready", { port: config.sshdPort });
60834
- } catch (err) {
60835
- logger.warn("sshd start failed; contact SSH grant will not work until fixed", {
60836
- err: err.message
60837
- });
60838
- }
60839
59558
  void reportDevice();
60840
59559
  void fetchServerKey();
60841
59560
  const tickAttachmentGc = () => {
@@ -60870,11 +59589,6 @@ ${bar}
60870
59589
  if (tunnelMgr) {
60871
59590
  await tunnelMgr.stop();
60872
59591
  }
60873
- await sshdMgr.stop().catch((err) => {
60874
- logger.warn("shutdown.sshd-stop-failed", {
60875
- error: err instanceof Error ? err.message : String(err)
60876
- });
60877
- });
60878
59592
  await wss.stop();
60879
59593
  stateMgr.delete();
60880
59594
  if (logClient) await logClient.dispose();
@@ -60888,9 +59602,9 @@ ${bar}
60888
59602
  };
60889
59603
  }
60890
59604
  function migrateDropPersonsDir(dataDir) {
60891
- const dir = import_node_path64.default.join(dataDir, "persons");
59605
+ const dir = import_node_path57.default.join(dataDir, "persons");
60892
59606
  try {
60893
- import_node_fs52.default.rmSync(dir, { recursive: true, force: true });
59607
+ import_node_fs45.default.rmSync(dir, { recursive: true, force: true });
60894
59608
  } catch {
60895
59609
  }
60896
59610
  }
@@ -60903,11 +59617,6 @@ async function main() {
60903
59617
  const code = await runCase2(argv.slice(1));
60904
59618
  process.exit(code);
60905
59619
  }
60906
- if (argv[0] === "ssh-relay") {
60907
- const { sshRelay: sshRelay2 } = await Promise.resolve().then(() => (init_sshd_cli_relay(), sshd_cli_relay_exports));
60908
- const code = await sshRelay2(argv.slice(1));
60909
- process.exit(code);
60910
- }
60911
59620
  const parsed = parseArgs(argv);
60912
59621
  if (parsed.help) {
60913
59622
  process.stdout.write(HELP_TEXT);