@clawos-dev/clawd 0.2.235 → 0.2.236

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
@@ -161,11 +161,10 @@ var init_methods = __esm({
161
161
  "contact:list",
162
162
  "contact:pin",
163
163
  "contact:remove",
164
- // ---- contact:setSshAccess / contact:sshKey:issue (SSH 反向访问,PR: contact-ssh-sandbox) ----
165
- // owner UI 授权 contact 通过 daemon 专用 sshd 反向 SSH 进本机;guest daemon 用 sshKey:issue 拉 privkey.
166
- // 详见 protocol/src/contact-ssh.ts.
164
+ // ---- contact:setSshAccess (SSH 反向访问,PR: contact-ssh-sandbox) ----
165
+ // owner UI 授权 contact 通过 daemon 专用 sshd 反向 SSH 进本机;key 分发走建联握手
166
+ // pubkey 互换(spec 2026-07-15,无签发 RPC)。详见 protocol/src/contact-ssh.ts.
167
167
  "contact:setSshAccess",
168
- "contact:sshKey:issue",
169
168
  // ---- visitor:* (web 访客 · persona web 分享,spec 2026-06-24-persona-web-share-design) ----
170
169
  // owner-only:列出登录访问过本机 public persona 的 web 访客(零安装、飞书登录、daemon 自签
171
170
  // visitor token)。存储 ~/.clawd/visitors.json(VisitorStore),登录(exchange)即 upsert;
@@ -745,8 +744,8 @@ var init_parseUtil = __esm({
745
744
  init_errors2();
746
745
  init_en();
747
746
  makeIssue = (params) => {
748
- const { data, path: path77, errorMaps, issueData } = params;
749
- const fullPath = [...path77, ...issueData.path || []];
747
+ const { data, path: path76, errorMaps, issueData } = params;
748
+ const fullPath = [...path76, ...issueData.path || []];
750
749
  const fullIssue = {
751
750
  ...issueData,
752
751
  path: fullPath
@@ -1057,11 +1056,11 @@ var init_types = __esm({
1057
1056
  init_parseUtil();
1058
1057
  init_util();
1059
1058
  ParseInputLazyPath = class {
1060
- constructor(parent, value, path77, key) {
1059
+ constructor(parent, value, path76, key) {
1061
1060
  this._cachedPath = [];
1062
1061
  this.parent = parent;
1063
1062
  this.data = value;
1064
- this._path = path77;
1063
+ this._path = path76;
1065
1064
  this._key = key;
1066
1065
  }
1067
1066
  get path() {
@@ -5326,7 +5325,14 @@ var init_schemas = __esm({
5326
5325
  * selfUrl 保留:它只是"可达地址"(auto-reverse 反向连接用),不是身份——填错只影响
5327
5326
  * 反向连通性,不构成伪造风险,且 url 不在 token 里(token 只签身份不签地址)。
5328
5327
  */
5329
- selfUrl: external_exports.string().min(1).optional()
5328
+ selfUrl: external_exports.string().min(1).optional(),
5329
+ /**
5330
+ * ssh identity 互换(spec 2026-07-15):连接方自报本机 SSH identity pubkey
5331
+ * (一行 `ssh-ed25519 AAAA... comment`)。跟 selfUrl 同理不是身份——pubkey 是公开
5332
+ * 材料,报假只会让自己连不上(拨号还要对应 privkey 签名验证),被连方 auto-reverse
5333
+ * 落 contact.sshPublicKey。缺省 = 老客户端 / identity 未生成。
5334
+ */
5335
+ selfSshPublicKey: external_exports.string().min(1).optional()
5330
5336
  });
5331
5337
  AuthOkFrameSchema = external_exports.object({
5332
5338
  type: external_exports.literal("auth:ok"),
@@ -5649,7 +5655,12 @@ var init_capability = __esm({
5649
5655
  id: external_exports.string().min(1),
5650
5656
  displayName: external_exports.string()
5651
5657
  }).strict()
5652
- )
5658
+ ),
5659
+ /**
5660
+ * 被连方设备 SSH identity pubkey(spec 2026-07-15 ssh identity 互换):
5661
+ * 连接方 device:connect 读走落 contact.sshPublicKey。identity 未生成时省略。
5662
+ */
5663
+ sshPublicKey: external_exports.string().min(1).optional()
5653
5664
  }).strict();
5654
5665
  PERSONAL_CAP_GRANTS = Object.freeze([
5655
5666
  Object.freeze({
@@ -5744,6 +5755,14 @@ var init_contact = __esm({
5744
5755
  * 极端场景(区别对方多个 persona)留 per-persona override 演进空间。
5745
5756
  */
5746
5757
  sshAllowed: external_exports.boolean().default(false),
5758
+ /**
5759
+ * 对端设备 SSH identity pubkey(一行 `ssh-ed25519 AAAA... comment`,spec 2026-07-15)。
5760
+ * 建联握手双向交换写入(连入方 auth 帧自报 / 被连方 whoami:ok 下发),
5761
+ * setSshAccess=true 时由 rebuildAuthorizedKeys 写进 authorized_keys。
5762
+ * pubkey 公开可自报无伪造风险——拨号还要 privkey 签名验证。
5763
+ * 缺省 = 老 contact / 对方老版本未重连,授权时写不出 authorized_keys 行。
5764
+ */
5765
+ sshPublicKey: external_exports.string().optional(),
5747
5766
  /**
5748
5767
  * @deprecated T-29 去沙箱后废弃。原为授权访问的目录白名单(clawd-ssh-jail 生成 sbpl/bwrap
5749
5768
  * policy 用);去掉 OS 沙箱后不再有目录级限制,SSH 授权即 on/off(见 sshAllowed)。
@@ -5790,7 +5809,7 @@ var init_contact = __esm({
5790
5809
  });
5791
5810
 
5792
5811
  // ../protocol/src/contact-ssh.ts
5793
- var ContactSetSshAccessArgsSchema, ContactSetSshAccessOkSchema, ContactSshKeyIssueArgsSchema, ContactSshKeyIssueOkSchema, ContactSshAccessUpdatedFrameSchema;
5812
+ var ContactSetSshAccessArgsSchema, ContactSetSshAccessOkSchema, ContactSshAccessUpdatedFrameSchema;
5794
5813
  var init_contact_ssh = __esm({
5795
5814
  "../protocol/src/contact-ssh.ts"() {
5796
5815
  "use strict";
@@ -5804,14 +5823,6 @@ var init_contact_ssh = __esm({
5804
5823
  deviceId: external_exports.string().min(1),
5805
5824
  sshAllowed: external_exports.boolean()
5806
5825
  }).strict();
5807
- ContactSshKeyIssueArgsSchema = external_exports.object({
5808
- deviceId: external_exports.string().min(1)
5809
- }).strict();
5810
- ContactSshKeyIssueOkSchema = external_exports.object({
5811
- type: external_exports.literal("contact:sshKey:issue:ok"),
5812
- privateKeyPem: external_exports.string().min(1),
5813
- publicKeyLine: external_exports.string().min(1)
5814
- }).strict();
5815
5826
  ContactSshAccessUpdatedFrameSchema = external_exports.object({
5816
5827
  type: external_exports.literal("contact:ssh-access-updated"),
5817
5828
  deviceId: external_exports.string().min(1),
@@ -6506,8 +6517,8 @@ var require_req = __commonJS({
6506
6517
  if (req.originalUrl) {
6507
6518
  _req.url = req.originalUrl;
6508
6519
  } else {
6509
- const path77 = req.path;
6510
- _req.url = typeof path77 === "string" ? path77 : req.url ? req.url.path || req.url : void 0;
6520
+ const path76 = req.path;
6521
+ _req.url = typeof path76 === "string" ? path76 : req.url ? req.url.path || req.url : void 0;
6511
6522
  }
6512
6523
  if (req.query) {
6513
6524
  _req.query = req.query;
@@ -6672,14 +6683,14 @@ var require_redact = __commonJS({
6672
6683
  }
6673
6684
  return obj;
6674
6685
  }
6675
- function parsePath(path77) {
6686
+ function parsePath(path76) {
6676
6687
  const parts = [];
6677
6688
  let current = "";
6678
6689
  let inBrackets = false;
6679
6690
  let inQuotes = false;
6680
6691
  let quoteChar = "";
6681
- for (let i = 0; i < path77.length; i++) {
6682
- const char = path77[i];
6692
+ for (let i = 0; i < path76.length; i++) {
6693
+ const char = path76[i];
6683
6694
  if (!inBrackets && char === ".") {
6684
6695
  if (current) {
6685
6696
  parts.push(current);
@@ -6810,10 +6821,10 @@ var require_redact = __commonJS({
6810
6821
  return current;
6811
6822
  }
6812
6823
  function redactPaths(obj, paths, censor, remove = false) {
6813
- for (const path77 of paths) {
6814
- const parts = parsePath(path77);
6824
+ for (const path76 of paths) {
6825
+ const parts = parsePath(path76);
6815
6826
  if (parts.includes("*")) {
6816
- redactWildcardPath(obj, parts, censor, path77, remove);
6827
+ redactWildcardPath(obj, parts, censor, path76, remove);
6817
6828
  } else {
6818
6829
  if (remove) {
6819
6830
  removeKey(obj, parts);
@@ -6898,8 +6909,8 @@ var require_redact = __commonJS({
6898
6909
  }
6899
6910
  } else {
6900
6911
  if (afterWildcard.includes("*")) {
6901
- const wrappedCensor = typeof censor === "function" ? (value, path77) => {
6902
- const fullPath = [...pathArray.slice(0, pathLength), ...path77];
6912
+ const wrappedCensor = typeof censor === "function" ? (value, path76) => {
6913
+ const fullPath = [...pathArray.slice(0, pathLength), ...path76];
6903
6914
  return censor(value, fullPath);
6904
6915
  } : censor;
6905
6916
  redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
@@ -6934,8 +6945,8 @@ var require_redact = __commonJS({
6934
6945
  return null;
6935
6946
  }
6936
6947
  const pathStructure = /* @__PURE__ */ new Map();
6937
- for (const path77 of pathsToClone) {
6938
- const parts = parsePath(path77);
6948
+ for (const path76 of pathsToClone) {
6949
+ const parts = parsePath(path76);
6939
6950
  let current = pathStructure;
6940
6951
  for (let i = 0; i < parts.length; i++) {
6941
6952
  const part = parts[i];
@@ -6987,24 +6998,24 @@ var require_redact = __commonJS({
6987
6998
  }
6988
6999
  return cloneSelectively(obj, pathStructure);
6989
7000
  }
6990
- function validatePath(path77) {
6991
- if (typeof path77 !== "string") {
7001
+ function validatePath(path76) {
7002
+ if (typeof path76 !== "string") {
6992
7003
  throw new Error("Paths must be (non-empty) strings");
6993
7004
  }
6994
- if (path77 === "") {
7005
+ if (path76 === "") {
6995
7006
  throw new Error("Invalid redaction path ()");
6996
7007
  }
6997
- if (path77.includes("..")) {
6998
- throw new Error(`Invalid redaction path (${path77})`);
7008
+ if (path76.includes("..")) {
7009
+ throw new Error(`Invalid redaction path (${path76})`);
6999
7010
  }
7000
- if (path77.includes(",")) {
7001
- throw new Error(`Invalid redaction path (${path77})`);
7011
+ if (path76.includes(",")) {
7012
+ throw new Error(`Invalid redaction path (${path76})`);
7002
7013
  }
7003
7014
  let bracketCount = 0;
7004
7015
  let inQuotes = false;
7005
7016
  let quoteChar = "";
7006
- for (let i = 0; i < path77.length; i++) {
7007
- const char = path77[i];
7017
+ for (let i = 0; i < path76.length; i++) {
7018
+ const char = path76[i];
7008
7019
  if ((char === '"' || char === "'") && bracketCount > 0) {
7009
7020
  if (!inQuotes) {
7010
7021
  inQuotes = true;
@@ -7018,20 +7029,20 @@ var require_redact = __commonJS({
7018
7029
  } else if (char === "]" && !inQuotes) {
7019
7030
  bracketCount--;
7020
7031
  if (bracketCount < 0) {
7021
- throw new Error(`Invalid redaction path (${path77})`);
7032
+ throw new Error(`Invalid redaction path (${path76})`);
7022
7033
  }
7023
7034
  }
7024
7035
  }
7025
7036
  if (bracketCount !== 0) {
7026
- throw new Error(`Invalid redaction path (${path77})`);
7037
+ throw new Error(`Invalid redaction path (${path76})`);
7027
7038
  }
7028
7039
  }
7029
7040
  function validatePaths(paths) {
7030
7041
  if (!Array.isArray(paths)) {
7031
7042
  throw new TypeError("paths must be an array");
7032
7043
  }
7033
- for (const path77 of paths) {
7034
- validatePath(path77);
7044
+ for (const path76 of paths) {
7045
+ validatePath(path76);
7035
7046
  }
7036
7047
  }
7037
7048
  function slowRedact(options = {}) {
@@ -7199,8 +7210,8 @@ var require_redaction = __commonJS({
7199
7210
  if (shape[k2] === null) {
7200
7211
  o[k2] = (value) => topCensor(value, [k2]);
7201
7212
  } else {
7202
- const wrappedCensor = typeof censor === "function" ? (value, path77) => {
7203
- return censor(value, [k2, ...path77]);
7213
+ const wrappedCensor = typeof censor === "function" ? (value, path76) => {
7214
+ return censor(value, [k2, ...path76]);
7204
7215
  } : censor;
7205
7216
  o[k2] = Redact({
7206
7217
  paths: shape[k2],
@@ -7418,10 +7429,10 @@ var require_atomic_sleep = __commonJS({
7418
7429
  var require_sonic_boom = __commonJS({
7419
7430
  "../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
7420
7431
  "use strict";
7421
- var fs71 = require("fs");
7432
+ var fs70 = require("fs");
7422
7433
  var EventEmitter3 = require("events");
7423
7434
  var inherits = require("util").inherits;
7424
- var path77 = require("path");
7435
+ var path76 = require("path");
7425
7436
  var sleep2 = require_atomic_sleep();
7426
7437
  var assert = require("assert");
7427
7438
  var BUSY_WRITE_TIMEOUT = 100;
@@ -7475,20 +7486,20 @@ var require_sonic_boom = __commonJS({
7475
7486
  const mode = sonic.mode;
7476
7487
  if (sonic.sync) {
7477
7488
  try {
7478
- if (sonic.mkdir) fs71.mkdirSync(path77.dirname(file), { recursive: true });
7479
- const fd = fs71.openSync(file, flags, mode);
7489
+ if (sonic.mkdir) fs70.mkdirSync(path76.dirname(file), { recursive: true });
7490
+ const fd = fs70.openSync(file, flags, mode);
7480
7491
  fileOpened(null, fd);
7481
7492
  } catch (err) {
7482
7493
  fileOpened(err);
7483
7494
  throw err;
7484
7495
  }
7485
7496
  } else if (sonic.mkdir) {
7486
- fs71.mkdir(path77.dirname(file), { recursive: true }, (err) => {
7497
+ fs70.mkdir(path76.dirname(file), { recursive: true }, (err) => {
7487
7498
  if (err) return fileOpened(err);
7488
- fs71.open(file, flags, mode, fileOpened);
7499
+ fs70.open(file, flags, mode, fileOpened);
7489
7500
  });
7490
7501
  } else {
7491
- fs71.open(file, flags, mode, fileOpened);
7502
+ fs70.open(file, flags, mode, fileOpened);
7492
7503
  }
7493
7504
  }
7494
7505
  function SonicBoom(opts) {
@@ -7529,8 +7540,8 @@ var require_sonic_boom = __commonJS({
7529
7540
  this.flush = flushBuffer;
7530
7541
  this.flushSync = flushBufferSync;
7531
7542
  this._actualWrite = actualWriteBuffer;
7532
- fsWriteSync = () => fs71.writeSync(this.fd, this._writingBuf);
7533
- fsWrite = () => fs71.write(this.fd, this._writingBuf, this.release);
7543
+ fsWriteSync = () => fs70.writeSync(this.fd, this._writingBuf);
7544
+ fsWrite = () => fs70.write(this.fd, this._writingBuf, this.release);
7534
7545
  } else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
7535
7546
  this._writingBuf = "";
7536
7547
  this.write = write;
@@ -7539,15 +7550,15 @@ var require_sonic_boom = __commonJS({
7539
7550
  this._actualWrite = actualWrite;
7540
7551
  fsWriteSync = () => {
7541
7552
  if (Buffer.isBuffer(this._writingBuf)) {
7542
- return fs71.writeSync(this.fd, this._writingBuf);
7553
+ return fs70.writeSync(this.fd, this._writingBuf);
7543
7554
  }
7544
- return fs71.writeSync(this.fd, this._writingBuf, "utf8");
7555
+ return fs70.writeSync(this.fd, this._writingBuf, "utf8");
7545
7556
  };
7546
7557
  fsWrite = () => {
7547
7558
  if (Buffer.isBuffer(this._writingBuf)) {
7548
- return fs71.write(this.fd, this._writingBuf, this.release);
7559
+ return fs70.write(this.fd, this._writingBuf, this.release);
7549
7560
  }
7550
- return fs71.write(this.fd, this._writingBuf, "utf8", this.release);
7561
+ return fs70.write(this.fd, this._writingBuf, "utf8", this.release);
7551
7562
  };
7552
7563
  } else {
7553
7564
  throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
@@ -7604,7 +7615,7 @@ var require_sonic_boom = __commonJS({
7604
7615
  }
7605
7616
  }
7606
7617
  if (this._fsync) {
7607
- fs71.fsyncSync(this.fd);
7618
+ fs70.fsyncSync(this.fd);
7608
7619
  }
7609
7620
  const len = this._len;
7610
7621
  if (this._reopening) {
@@ -7718,7 +7729,7 @@ var require_sonic_boom = __commonJS({
7718
7729
  const onDrain = () => {
7719
7730
  if (!this._fsync) {
7720
7731
  try {
7721
- fs71.fsync(this.fd, (err) => {
7732
+ fs70.fsync(this.fd, (err) => {
7722
7733
  this._flushPending = false;
7723
7734
  cb(err);
7724
7735
  });
@@ -7820,7 +7831,7 @@ var require_sonic_boom = __commonJS({
7820
7831
  const fd = this.fd;
7821
7832
  this.once("ready", () => {
7822
7833
  if (fd !== this.fd) {
7823
- fs71.close(fd, (err) => {
7834
+ fs70.close(fd, (err) => {
7824
7835
  if (err) {
7825
7836
  return this.emit("error", err);
7826
7837
  }
@@ -7869,7 +7880,7 @@ var require_sonic_boom = __commonJS({
7869
7880
  buf = this._bufs[0];
7870
7881
  }
7871
7882
  try {
7872
- const n = Buffer.isBuffer(buf) ? fs71.writeSync(this.fd, buf) : fs71.writeSync(this.fd, buf, "utf8");
7883
+ const n = Buffer.isBuffer(buf) ? fs70.writeSync(this.fd, buf) : fs70.writeSync(this.fd, buf, "utf8");
7873
7884
  const releasedBufObj = releaseWritingBuf(buf, this._len, n);
7874
7885
  buf = releasedBufObj.writingBuf;
7875
7886
  this._len = releasedBufObj.len;
@@ -7885,7 +7896,7 @@ var require_sonic_boom = __commonJS({
7885
7896
  }
7886
7897
  }
7887
7898
  try {
7888
- fs71.fsyncSync(this.fd);
7899
+ fs70.fsyncSync(this.fd);
7889
7900
  } catch {
7890
7901
  }
7891
7902
  }
@@ -7906,7 +7917,7 @@ var require_sonic_boom = __commonJS({
7906
7917
  buf = mergeBuf(this._bufs[0], this._lens[0]);
7907
7918
  }
7908
7919
  try {
7909
- const n = fs71.writeSync(this.fd, buf);
7920
+ const n = fs70.writeSync(this.fd, buf);
7910
7921
  buf = buf.subarray(n);
7911
7922
  this._len = Math.max(this._len - n, 0);
7912
7923
  if (buf.length <= 0) {
@@ -7934,13 +7945,13 @@ var require_sonic_boom = __commonJS({
7934
7945
  this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
7935
7946
  if (this.sync) {
7936
7947
  try {
7937
- const written = Buffer.isBuffer(this._writingBuf) ? fs71.writeSync(this.fd, this._writingBuf) : fs71.writeSync(this.fd, this._writingBuf, "utf8");
7948
+ const written = Buffer.isBuffer(this._writingBuf) ? fs70.writeSync(this.fd, this._writingBuf) : fs70.writeSync(this.fd, this._writingBuf, "utf8");
7938
7949
  release(null, written);
7939
7950
  } catch (err) {
7940
7951
  release(err);
7941
7952
  }
7942
7953
  } else {
7943
- fs71.write(this.fd, this._writingBuf, release);
7954
+ fs70.write(this.fd, this._writingBuf, release);
7944
7955
  }
7945
7956
  }
7946
7957
  function actualWriteBuffer() {
@@ -7949,7 +7960,7 @@ var require_sonic_boom = __commonJS({
7949
7960
  this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
7950
7961
  if (this.sync) {
7951
7962
  try {
7952
- const written = fs71.writeSync(this.fd, this._writingBuf);
7963
+ const written = fs70.writeSync(this.fd, this._writingBuf);
7953
7964
  release(null, written);
7954
7965
  } catch (err) {
7955
7966
  release(err);
@@ -7958,7 +7969,7 @@ var require_sonic_boom = __commonJS({
7958
7969
  if (kCopyBuffer) {
7959
7970
  this._writingBuf = Buffer.from(this._writingBuf);
7960
7971
  }
7961
- fs71.write(this.fd, this._writingBuf, release);
7972
+ fs70.write(this.fd, this._writingBuf, release);
7962
7973
  }
7963
7974
  }
7964
7975
  function actualClose(sonic) {
@@ -7974,12 +7985,12 @@ var require_sonic_boom = __commonJS({
7974
7985
  sonic._lens = [];
7975
7986
  assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
7976
7987
  try {
7977
- fs71.fsync(sonic.fd, closeWrapped);
7988
+ fs70.fsync(sonic.fd, closeWrapped);
7978
7989
  } catch {
7979
7990
  }
7980
7991
  function closeWrapped() {
7981
7992
  if (sonic.fd !== 1 && sonic.fd !== 2) {
7982
- fs71.close(sonic.fd, done);
7993
+ fs70.close(sonic.fd, done);
7983
7994
  } else {
7984
7995
  done();
7985
7996
  }
@@ -11114,11 +11125,11 @@ var init_lib = __esm({
11114
11125
  }
11115
11126
  }
11116
11127
  },
11117
- addToPath: function addToPath(path77, added, removed, oldPosInc, options) {
11118
- var last = path77.lastComponent;
11128
+ addToPath: function addToPath(path76, added, removed, oldPosInc, options) {
11129
+ var last = path76.lastComponent;
11119
11130
  if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
11120
11131
  return {
11121
- oldPos: path77.oldPos + oldPosInc,
11132
+ oldPos: path76.oldPos + oldPosInc,
11122
11133
  lastComponent: {
11123
11134
  count: last.count + 1,
11124
11135
  added,
@@ -11128,7 +11139,7 @@ var init_lib = __esm({
11128
11139
  };
11129
11140
  } else {
11130
11141
  return {
11131
- oldPos: path77.oldPos + oldPosInc,
11142
+ oldPos: path76.oldPos + oldPosInc,
11132
11143
  lastComponent: {
11133
11144
  count: 1,
11134
11145
  added,
@@ -11633,10 +11644,10 @@ function attachmentToHistoryMessage(o, ts) {
11633
11644
  const memories = raw.map((m2) => {
11634
11645
  if (!m2 || typeof m2 !== "object") return null;
11635
11646
  const rec3 = m2;
11636
- const path77 = typeof rec3.path === "string" ? rec3.path : null;
11647
+ const path76 = typeof rec3.path === "string" ? rec3.path : null;
11637
11648
  const content = typeof rec3.content === "string" ? rec3.content : null;
11638
- if (!path77 || content == null) return null;
11639
- const entry = { path: path77, content };
11649
+ if (!path76 || content == null) return null;
11650
+ const entry = { path: path76, content };
11640
11651
  if (typeof rec3.mtimeMs === "number") entry.mtimeMs = rec3.mtimeMs;
11641
11652
  return entry;
11642
11653
  }).filter((m2) => m2 !== null);
@@ -12439,10 +12450,10 @@ function parseAttachment(obj) {
12439
12450
  const memories = raw.map((m2) => {
12440
12451
  if (!m2 || typeof m2 !== "object") return null;
12441
12452
  const rec3 = m2;
12442
- const path77 = typeof rec3.path === "string" ? rec3.path : null;
12453
+ const path76 = typeof rec3.path === "string" ? rec3.path : null;
12443
12454
  const content = typeof rec3.content === "string" ? rec3.content : null;
12444
- if (!path77 || content == null) return null;
12445
- const out = { path: path77, content };
12455
+ if (!path76 || content == null) return null;
12456
+ const out = { path: path76, content };
12446
12457
  if (typeof rec3.mtimeMs === "number") out.mtimeMs = rec3.mtimeMs;
12447
12458
  return out;
12448
12459
  }).filter((m2) => m2 !== null);
@@ -33428,8 +33439,8 @@ var require_utils = __commonJS({
33428
33439
  var result = transform[inputType][outputType](input);
33429
33440
  return result;
33430
33441
  };
33431
- exports2.resolve = function(path77) {
33432
- var parts = path77.split("/");
33442
+ exports2.resolve = function(path76) {
33443
+ var parts = path76.split("/");
33433
33444
  var result = [];
33434
33445
  for (var index = 0; index < parts.length; index++) {
33435
33446
  var part = parts[index];
@@ -39282,18 +39293,18 @@ var require_object = __commonJS({
39282
39293
  var object = new ZipObject(name, zipObjectContent, o);
39283
39294
  this.files[name] = object;
39284
39295
  };
39285
- var parentFolder = function(path77) {
39286
- if (path77.slice(-1) === "/") {
39287
- path77 = path77.substring(0, path77.length - 1);
39296
+ var parentFolder = function(path76) {
39297
+ if (path76.slice(-1) === "/") {
39298
+ path76 = path76.substring(0, path76.length - 1);
39288
39299
  }
39289
- var lastSlash = path77.lastIndexOf("/");
39290
- return lastSlash > 0 ? path77.substring(0, lastSlash) : "";
39300
+ var lastSlash = path76.lastIndexOf("/");
39301
+ return lastSlash > 0 ? path76.substring(0, lastSlash) : "";
39291
39302
  };
39292
- var forceTrailingSlash = function(path77) {
39293
- if (path77.slice(-1) !== "/") {
39294
- path77 += "/";
39303
+ var forceTrailingSlash = function(path76) {
39304
+ if (path76.slice(-1) !== "/") {
39305
+ path76 += "/";
39295
39306
  }
39296
- return path77;
39307
+ return path76;
39297
39308
  };
39298
39309
  var folderAdd = function(name, createFolders) {
39299
39310
  createFolders = typeof createFolders !== "undefined" ? createFolders : defaults.createFolders;
@@ -40295,7 +40306,7 @@ var require_lib3 = __commonJS({
40295
40306
  // src/run-case/recorder.ts
40296
40307
  function startRunCaseRecorder(opts) {
40297
40308
  const now = opts.now ?? Date.now;
40298
- const dir = import_node_path65.default.dirname(opts.recordPath);
40309
+ const dir = import_node_path64.default.dirname(opts.recordPath);
40299
40310
  let stream = null;
40300
40311
  let closing = false;
40301
40312
  let closedSettled = false;
@@ -40309,8 +40320,8 @@ function startRunCaseRecorder(opts) {
40309
40320
  });
40310
40321
  const ensureStream = () => {
40311
40322
  if (stream) return stream;
40312
- import_node_fs53.default.mkdirSync(dir, { recursive: true });
40313
- stream = import_node_fs53.default.createWriteStream(opts.recordPath, { flags: "a" });
40323
+ import_node_fs52.default.mkdirSync(dir, { recursive: true });
40324
+ stream = import_node_fs52.default.createWriteStream(opts.recordPath, { flags: "a" });
40314
40325
  stream.on("close", () => closedResolve());
40315
40326
  return stream;
40316
40327
  };
@@ -40335,12 +40346,12 @@ function startRunCaseRecorder(opts) {
40335
40346
  };
40336
40347
  return { tap, close, closed };
40337
40348
  }
40338
- var import_node_fs53, import_node_path65;
40349
+ var import_node_fs52, import_node_path64;
40339
40350
  var init_recorder = __esm({
40340
40351
  "src/run-case/recorder.ts"() {
40341
40352
  "use strict";
40342
- import_node_fs53 = __toESM(require("fs"), 1);
40343
- import_node_path65 = __toESM(require("path"), 1);
40353
+ import_node_fs52 = __toESM(require("fs"), 1);
40354
+ import_node_path64 = __toESM(require("path"), 1);
40344
40355
  }
40345
40356
  });
40346
40357
 
@@ -40383,7 +40394,7 @@ var init_wire = __esm({
40383
40394
  // src/run-case/controller.ts
40384
40395
  async function runController(opts) {
40385
40396
  const now = opts.now ?? Date.now;
40386
- const cwd = opts.cwd ?? (0, import_node_fs54.mkdtempSync)(import_node_path66.default.join(import_node_os22.default.tmpdir(), "clawd-runcase-"));
40397
+ const cwd = opts.cwd ?? (0, import_node_fs53.mkdtempSync)(import_node_path65.default.join(import_node_os22.default.tmpdir(), "clawd-runcase-"));
40387
40398
  const ownsCwd = opts.cwd === void 0;
40388
40399
  const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
40389
40400
  const spawnCtx = { cwd };
@@ -40544,19 +40555,19 @@ async function runController(opts) {
40544
40555
  if (sigintHandler) process.off("SIGINT", sigintHandler);
40545
40556
  if (ownsCwd) {
40546
40557
  try {
40547
- (0, import_node_fs54.rmSync)(cwd, { recursive: true, force: true });
40558
+ (0, import_node_fs53.rmSync)(cwd, { recursive: true, force: true });
40548
40559
  } catch {
40549
40560
  }
40550
40561
  }
40551
40562
  return exitCode ?? 0;
40552
40563
  }
40553
- var import_node_fs54, import_node_os22, import_node_path66;
40564
+ var import_node_fs53, import_node_os22, import_node_path65;
40554
40565
  var init_controller = __esm({
40555
40566
  "src/run-case/controller.ts"() {
40556
40567
  "use strict";
40557
- import_node_fs54 = require("fs");
40568
+ import_node_fs53 = require("fs");
40558
40569
  import_node_os22 = __toESM(require("os"), 1);
40559
- import_node_path66 = __toESM(require("path"), 1);
40570
+ import_node_path65 = __toESM(require("path"), 1);
40560
40571
  init_claude();
40561
40572
  init_stdout_splitter();
40562
40573
  init_permission_stdio();
@@ -40760,7 +40771,7 @@ Options:
40760
40771
 
40761
40772
  Example:
40762
40773
  ssh -o ProxyCommand='~/.clawd/bin/clawd ssh-relay <peer-device-id>' \\
40763
- -i ~/.clawd/contact-ssh-keys/<peer-device-id>.ed25519 \\
40774
+ -i ~/.clawd/ssh-identity/id_ed25519 \\
40764
40775
  $USER@127.0.0.1
40765
40776
  `;
40766
40777
  }
@@ -40950,8 +40961,8 @@ Env (advanced):
40950
40961
  `;
40951
40962
 
40952
40963
  // src/index.ts
40953
- var import_node_path64 = __toESM(require("path"), 1);
40954
- var import_node_fs52 = __toESM(require("fs"), 1);
40964
+ var import_node_path63 = __toESM(require("path"), 1);
40965
+ var import_node_fs51 = __toESM(require("fs"), 1);
40955
40966
  var import_node_os21 = __toESM(require("os"), 1);
40956
40967
 
40957
40968
  // ../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/stringify.js
@@ -41702,7 +41713,7 @@ function composeGuestSandbox(base, userWorkDir, spawnCwd) {
41702
41713
  fsv.allowRead = unionArr(fsv.allowRead, [userWorkDir]);
41703
41714
  fsv.allowWrite = unionArr(fsv.allowWrite, [userWorkDir]);
41704
41715
  fsv.allowRead = unionArr(fsv.allowRead, [
41705
- "~/.clawd/contact-ssh-keys",
41716
+ "~/.clawd/ssh-identity",
41706
41717
  "~/.clawd/bin",
41707
41718
  "~/Library/Application Support/@clawos/clawd-desktop/ota-bundles",
41708
41719
  "~/.clawd/config.json",
@@ -41978,28 +41989,27 @@ deviceId \u76F4\u63A5\u53D6\u81EA token\uFF0C\u4E0D\u8981\u6539\u5199\u6216\u731
41978
41989
  // src/sshd/contact-ssh-prompt.ts
41979
41990
  var CONTACT_SSH_SYSTEM_PROMPT_HINT = `## \u8DE8\u8BBE\u5907\u6587\u4EF6\u8BBF\u95EE\uFF08Contact SSH\uFF09
41980
41991
 
41981
- \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\uFF09\u7684\u65B9\u6CD5\uFF1A
41982
-
41983
- 1. \u5217\u51FA\u53EF\u62E8\u7684\u5BF9\u7AEF deviceId\uFF1A\`ls ~/.clawd/contact-ssh-keys/\`
41984
- \uFF08\u6BCF\u4E2A \`<deviceId>.ed25519\` \u5C31\u662F\u4E00\u628A privkey\uFF0C\u53BB\u6389\u540E\u7F00\u5C31\u662F deviceId\uFF09
41985
- 2. \u7528 SSH \u62E8\u53F7\uFF08\u628A \`<A>\` \u6362\u6210\u5B9E\u9645 deviceId\uFF09\uFF1A
41986
- \`\`\`bash
41987
- ssh -o ProxyCommand='~/.clawd/bin/clawd ssh-relay <A>' \\
41988
- -i ~/.clawd/contact-ssh-keys/<A>.ed25519 \\
41989
- -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \\
41990
- $USER@127.0.0.1 <command>
41991
- # \u4F8B\u5982\uFF1A... $USER@127.0.0.1 cat /Users/xxx/some/file.md
41992
- # ... $USER@127.0.0.1 ls -la /Users/xxx/proj
41993
- # \u8BF4\u660E\uFF1A\`~/.clawd/bin/clawd\` \u662F daemon \u542F\u52A8\u65F6 seed \u7684 shim\uFF0C\u786C\u7F16\u7801\u5F53\u524D daemon \u7684
41994
- # 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
41995
- \`\`\`
41992
+ \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
41993
+
41994
+ \u7528 SSH \u62E8\u53F7\uFF08\u628A \`<A>\` \u6362\u6210\u5BF9\u7AEF deviceId\uFF09\uFF1A
41995
+ \`\`\`bash
41996
+ ssh -o ProxyCommand='~/.clawd/bin/clawd ssh-relay <A>' \\
41997
+ -i ~/.clawd/ssh-identity/id_ed25519 \\
41998
+ -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \\
41999
+ $USER@127.0.0.1 <command>
42000
+ # \u4F8B\u5982\uFF1A... $USER@127.0.0.1 cat /Users/xxx/some/file.md
42001
+ # ... $USER@127.0.0.1 ls -la /Users/xxx/proj
42002
+ # \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
42003
+ # \`~/.clawd/bin/clawd\` \u662F daemon \u542F\u52A8\u65F6 seed \u7684 shim\uFF0C\u786C\u7F16\u7801\u5F53\u524D daemon \u7684
42004
+ # 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
42005
+ \`\`\`
41996
42006
 
41997
42007
  **\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
41998
42008
 
41999
42009
  **\u6CE8\u610F**\uFF1A
42000
42010
  - \u6388\u6743\u662F\u5B9E\u65F6\u7684\uFF1AA \u64A4\u9500 sshAllowed \u540E\u4F60\u4E0B\u4E00\u6B21\u62E8\u53F7\u7ACB\u5373\u5931\u6548\uFF08authorized_keys \u7ACB\u5373\u5237\uFF09
42011
+ - \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
42001
42012
  - \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
42002
- - \u5982\u679C \`ls ~/.clawd/contact-ssh-keys/\` \u4E3A\u7A7A\uFF0C\u8BF4\u660E\u8FD8\u6CA1\u6709\u5BF9\u7AEF\u6388\u6743\u4F60\u6216 key \u8FD8\u6CA1\u62C9\u5230\uFF08B \u4FA7 daemon \u6BCF 60s \u4E00\u6B21\u81EA\u52A8\u62C9\uFF09\uFF0C\u5982\u5B9E\u544A\u8BC9\u7528\u6237
42003
42013
  `;
42004
42014
 
42005
42015
  // src/session/reducer.ts
@@ -46754,8 +46764,8 @@ function turnStartInput(text) {
46754
46764
  const items = [];
46755
46765
  let leftover = text;
46756
46766
  for (const m2 of text.matchAll(SKILL_RE)) {
46757
- const [marker, name, path77] = m2;
46758
- items.push({ type: "skill", name, path: path77 });
46767
+ const [marker, name, path76] = m2;
46768
+ items.push({ type: "skill", name, path: path76 });
46759
46769
  leftover = leftover.replace(marker, "");
46760
46770
  }
46761
46771
  for (const m2 of text.matchAll(ATTACHMENT_RE2)) {
@@ -49108,43 +49118,6 @@ async function forwardInboxPostToPeer(args) {
49108
49118
  }
49109
49119
  return { ok: true };
49110
49120
  }
49111
- async function forwardContactSshKeyIssueToPeer(args) {
49112
- const f = args.fetchImpl ?? fetch;
49113
- const base = wsUrlToHttp(args.contact.remoteUrl).replace(/\/+$/, "");
49114
- const url = `${base}/rpc/contact:sshKey:issue`;
49115
- let res;
49116
- try {
49117
- res = await f(url, {
49118
- method: "POST",
49119
- headers: {
49120
- "content-type": "application/json",
49121
- authorization: `Bearer ${args.contact.connectToken}`
49122
- },
49123
- body: JSON.stringify({ deviceId: args.selfDeviceIdForRequest })
49124
- });
49125
- } catch (err) {
49126
- return {
49127
- ok: false,
49128
- code: "NETWORK",
49129
- message: err instanceof Error ? err.message : String(err)
49130
- };
49131
- }
49132
- let json = null;
49133
- try {
49134
- json = await res.json();
49135
- } catch {
49136
- return { ok: false, code: "PROTOCOL", message: `peer non-JSON (HTTP ${res.status})` };
49137
- }
49138
- if (!json) {
49139
- return { ok: false, code: "PROTOCOL", message: `peer returned null (HTTP ${res.status})` };
49140
- }
49141
- if (json.ok === false) {
49142
- const j = json;
49143
- return { ok: false, code: j.error ?? "UNKNOWN", message: j.message ?? "" };
49144
- }
49145
- const r = json.result;
49146
- return { ok: true, privateKeyPem: r.privateKeyPem, publicKeyLine: r.publicKeyLine };
49147
- }
49148
49121
 
49149
49122
  // src/tools/codex-history.ts
49150
49123
  var import_node_child_process5 = require("child_process");
@@ -49332,13 +49305,13 @@ function mapSkillsListResponse(res) {
49332
49305
  const r = s ?? {};
49333
49306
  const name = str3(r.name);
49334
49307
  if (!name) continue;
49335
- const path77 = str3(r.path);
49308
+ const path76 = str3(r.path);
49336
49309
  const description = str3(r.description);
49337
49310
  const isPlugin = name.includes(":");
49338
49311
  out.push({
49339
49312
  name,
49340
49313
  source: isPlugin ? "plugin" : "project",
49341
- ...path77 ? { path: path77 } : {},
49314
+ ...path76 ? { path: path76 } : {},
49342
49315
  ...description ? { description } : {},
49343
49316
  ...isPlugin ? { plugin: name.split(":")[0] } : {}
49344
49317
  });
@@ -50624,7 +50597,11 @@ var AuthGate = class {
50624
50597
  st.pending = true;
50625
50598
  let r;
50626
50599
  try {
50627
- r = await this.opts.authenticate(parsed.data.token, parsed.data.selfUrl);
50600
+ r = await this.opts.authenticate(
50601
+ parsed.data.token,
50602
+ parsed.data.selfUrl,
50603
+ parsed.data.selfSshPublicKey
50604
+ );
50628
50605
  } finally {
50629
50606
  st.pending = false;
50630
50607
  }
@@ -51269,7 +51246,10 @@ async function connectRemote(args) {
51269
51246
  // 缺省时省略字段(不发空串),对方 schema selfUrl 是 .min(1).optional()。
51270
51247
  // 决策 #16:身份字段(selfPrincipalId/selfDisplayName/selfDeviceId)已删——
51271
51248
  // 对方从 connect token 签名背书取 deviceId/ownerId/provider,不信自报。
51272
- ...args.selfUrl ? { selfUrl: args.selfUrl } : {}
51249
+ ...args.selfUrl ? { selfUrl: args.selfUrl } : {},
51250
+ // ssh identity 互换(spec 2026-07-15):自报本机 identity pubkey;缺省省略字段
51251
+ // (对方 schema selfSshPublicKey 是 .min(1).optional(),不发空串)。
51252
+ ...args.selfSshPublicKey ? { selfSshPublicKey: args.selfSshPublicKey } : {}
51273
51253
  })
51274
51254
  );
51275
51255
  await new Promise((resolve6, reject) => {
@@ -51344,7 +51324,9 @@ async function connectRemote(args) {
51344
51324
  ownerId: f.owner.ownerId,
51345
51325
  provider: f.owner.provider,
51346
51326
  displayName: f.owner.displayName,
51347
- grants: f.capability.grants
51327
+ grants: f.capability.grants,
51328
+ // ssh identity 互换(spec 2026-07-15):对方老版本不带 → 省略(不落 undefined 占位)
51329
+ ...f.sshPublicKey ? { sshPublicKey: f.sshPublicKey } : {}
51348
51330
  };
51349
51331
  },
51350
51332
  close: () => {
@@ -51365,6 +51347,8 @@ async function autoReverseContact(args) {
51365
51347
  resource: { ...g2.resource },
51366
51348
  actions: [...g2.actions]
51367
51349
  }));
51350
+ const existing = args.store.get(args.deviceId) ?? void 0;
51351
+ const sshPublicKey = args.sshPublicKey ?? existing?.sshPublicKey;
51368
51352
  const base = {
51369
51353
  deviceId: args.deviceId,
51370
51354
  ownerId: args.ownerId,
@@ -51374,12 +51358,17 @@ async function autoReverseContact(args) {
51374
51358
  connectToken: "",
51375
51359
  grants,
51376
51360
  addedAt: now(),
51377
- pinnedAt: null,
51378
- sshAllowed: false,
51361
+ pinnedAt: existing?.pinnedAt ?? null,
51362
+ sshAllowed: existing?.sshAllowed ?? false,
51363
+ // ssh identity 互换(spec 2026-07-15):缺省不落 undefined 占位(JSON 持久化干净)
51364
+ ...sshPublicKey ? { sshPublicKey } : {},
51379
51365
  exposedDirs: []
51380
51366
  };
51381
51367
  args.store.upsert(base);
51382
51368
  args.broadcast({ type: "contact:added", contact: base });
51369
+ if (base.sshAllowed && sshPublicKey && sshPublicKey !== existing?.sshPublicKey) {
51370
+ args.rebuildAuthorizedKeys?.();
51371
+ }
51383
51372
  if (!args.exchangeToken) return;
51384
51373
  let token;
51385
51374
  try {
@@ -53862,10 +53851,10 @@ function rebuildAuthorizedKeys(store, sshdDir) {
53862
53851
  if (!c.sshAllowed) continue;
53863
53852
  const safe = /^[A-Za-z0-9_.-]+$/.test(c.deviceId);
53864
53853
  if (!safe) continue;
53865
- const pubkey = readIssuedPubkey(sshdDir, c.deviceId);
53866
- if (!pubkey) continue;
53854
+ const pubkey = c.sshPublicKey?.trim();
53855
+ if (!pubkey || /[\r\n"]/.test(pubkey)) continue;
53867
53856
  const bin = jailBinPath();
53868
- lines.push(`command="${bin} ${c.deviceId}",restrict ${pubkey.trim()}`);
53857
+ lines.push(`command="${bin} ${c.deviceId}",restrict ${pubkey}`);
53869
53858
  lines.push(`# contact:${c.deviceId}`);
53870
53859
  }
53871
53860
  const body = lines.join("\n") + "\n";
@@ -53873,25 +53862,59 @@ function rebuildAuthorizedKeys(store, sshdDir) {
53873
53862
  import_node_fs40.default.writeFileSync(tmp, body, { mode: 384 });
53874
53863
  import_node_fs40.default.renameSync(tmp, target);
53875
53864
  }
53876
- function readIssuedPubkey(sshdDir, deviceId) {
53877
- const safeId = deviceId.replace(/[\/\\]/g, "_");
53878
- const p2 = import_node_path40.default.join(sshdDir, "keys", `${safeId}.ed25519.pub`);
53865
+
53866
+ // src/sshd/ssh-identity.ts
53867
+ var import_node_fs41 = __toESM(require("fs"), 1);
53868
+ var import_node_path41 = __toESM(require("path"), 1);
53869
+ var import_node_child_process12 = require("child_process");
53870
+ var IDENTITY_DIR = "ssh-identity";
53871
+ var KEY_FILE = "id_ed25519";
53872
+ function identityPaths(dataDir) {
53873
+ const privPath = import_node_path41.default.join(dataDir, IDENTITY_DIR, KEY_FILE);
53874
+ return { privPath, pubPath: `${privPath}.pub` };
53875
+ }
53876
+ async function ensureSshIdentity(dataDir, opts = {}) {
53877
+ const { privPath, pubPath } = identityPaths(dataDir);
53878
+ import_node_fs41.default.mkdirSync(import_node_path41.default.dirname(privPath), { recursive: true, mode: 448 });
53879
+ if (import_node_fs41.default.existsSync(privPath) && import_node_fs41.default.existsSync(pubPath)) {
53880
+ return { privateKeyPath: privPath, publicKeyLine: import_node_fs41.default.readFileSync(pubPath, "utf8").trim() };
53881
+ }
53882
+ const bin = opts.keygenBin ?? "/usr/bin/ssh-keygen";
53883
+ await new Promise((resolve6, reject) => {
53884
+ const p2 = (opts.spawnImpl ?? import_node_child_process12.spawn)(
53885
+ bin,
53886
+ ["-t", "ed25519", "-f", privPath, "-N", "", "-q", "-C", "clawd-ssh-identity"],
53887
+ { stdio: "ignore" }
53888
+ );
53889
+ p2.on("exit", (code) => code === 0 ? resolve6() : reject(new Error(`ssh-keygen exit ${code}`)));
53890
+ p2.on("error", reject);
53891
+ });
53879
53892
  try {
53880
- return import_node_fs40.default.readFileSync(p2, "utf8");
53893
+ import_node_fs41.default.chmodSync(privPath, 384);
53894
+ } catch {
53895
+ }
53896
+ try {
53897
+ import_node_fs41.default.chmodSync(pubPath, 420);
53898
+ } catch {
53899
+ }
53900
+ return { privateKeyPath: privPath, publicKeyLine: import_node_fs41.default.readFileSync(pubPath, "utf8").trim() };
53901
+ }
53902
+ function readSshIdentityPublicKey(dataDir) {
53903
+ try {
53904
+ return import_node_fs41.default.readFileSync(identityPaths(dataDir).pubPath, "utf8").trim();
53881
53905
  } catch {
53882
53906
  return null;
53883
53907
  }
53884
53908
  }
53885
53909
 
53886
- // src/sshd/contact-key-puller.ts
53887
- var import_node_fs42 = __toESM(require("fs"), 1);
53888
- var import_node_path42 = __toESM(require("path"), 1);
53910
+ // src/sshd/ssh-tunnel-relay.ts
53911
+ var import_node_net2 = __toESM(require("net"), 1);
53889
53912
 
53890
53913
  // src/sshd/contact-ssh-log.ts
53891
- var import_node_fs41 = __toESM(require("fs"), 1);
53892
- var import_node_path41 = __toESM(require("path"), 1);
53914
+ var import_node_fs42 = __toESM(require("fs"), 1);
53915
+ var import_node_path42 = __toESM(require("path"), 1);
53893
53916
  function createContactSshLog(dataDir) {
53894
- const file = import_node_path41.default.join(dataDir, "log", "contact-ssh.log");
53917
+ const file = import_node_path42.default.join(dataDir, "log", "contact-ssh.log");
53895
53918
  function append(level, tag, message, meta) {
53896
53919
  const time = (/* @__PURE__ */ new Date()).toISOString();
53897
53920
  let line = `[${time}] [${level}] [${tag}] ${message}`;
@@ -53904,8 +53927,8 @@ function createContactSshLog(dataDir) {
53904
53927
  }
53905
53928
  line += "\n";
53906
53929
  try {
53907
- import_node_fs41.default.mkdirSync(import_node_path41.default.dirname(file), { recursive: true });
53908
- import_node_fs41.default.appendFileSync(file, line, { mode: 384 });
53930
+ import_node_fs42.default.mkdirSync(import_node_path42.default.dirname(file), { recursive: true });
53931
+ import_node_fs42.default.appendFileSync(file, line, { mode: 384 });
53909
53932
  } catch {
53910
53933
  }
53911
53934
  }
@@ -53924,118 +53947,7 @@ var nullContactSshLog = {
53924
53947
  }
53925
53948
  };
53926
53949
 
53927
- // src/sshd/contact-key-puller.ts
53928
- var CONTACT_KEYS_DIR = "contact-ssh-keys";
53929
- function safeContactKeyPath(dataDir, deviceId) {
53930
- const safeId = deviceId.replace(/[\/\\]/g, "_");
53931
- return import_node_path42.default.join(dataDir, CONTACT_KEYS_DIR, `${safeId}.ed25519`);
53932
- }
53933
- async function pullContactSshKeyOnce(deps) {
53934
- const forward = deps.forwardImpl ?? ((c) => forwardContactSshKeyIssueToPeer({
53935
- contact: { remoteUrl: c.remoteUrl, connectToken: c.connectToken },
53936
- selfDeviceIdForRequest: c.deviceId
53937
- }));
53938
- const contacts = deps.store.list().filter((c) => c.connectToken.length > 0);
53939
- const results = await Promise.all(
53940
- contacts.map(async (c) => {
53941
- try {
53942
- const r = await forward(c);
53943
- return { contact: c, result: r };
53944
- } catch (err) {
53945
- return {
53946
- contact: c,
53947
- result: {
53948
- ok: false,
53949
- code: "NETWORK",
53950
- message: err instanceof Error ? err.message : String(err)
53951
- }
53952
- };
53953
- }
53954
- })
53955
- );
53956
- const errors = [];
53957
- let pulled = 0;
53958
- const sshLog = deps.sshLog ?? nullContactSshLog;
53959
- for (const { contact, result } of results) {
53960
- if (result.ok) {
53961
- writeKeyFile(deps.dataDir, contact.deviceId, result.privateKeyPem);
53962
- pulled++;
53963
- deps.logger?.info("contact-key-puller: pulled", { deviceId: contact.deviceId });
53964
- sshLog.info("key.pull.success", "B \u4FA7\u4ECE A \u62C9\u5230 privkey \u5E76\u843D\u76D8", {
53965
- peerDeviceId: contact.deviceId,
53966
- peerDisplayName: contact.displayName
53967
- });
53968
- } else if (result.code === "UNAUTHORIZED" || result.code === "FORBIDDEN") {
53969
- const hadStale = removeKeyFile(deps.dataDir, contact.deviceId);
53970
- sshLog.info("key.pull.rejected", "A \u4FA7\u672A\u6388\u6743\u6211 SSH\uFF08\u6B63\u5E38\u72B6\u6001\uFF1B\u8F6E\u8BE2\u7EE7\u7EED\uFF09", {
53971
- peerDeviceId: contact.deviceId,
53972
- peerDisplayName: contact.displayName,
53973
- clearedStalePrivkey: hadStale
53974
- });
53975
- } else {
53976
- errors.push({
53977
- deviceId: contact.deviceId,
53978
- code: result.code,
53979
- message: result.message
53980
- });
53981
- deps.logger?.warn("contact-key-puller: pull failed", {
53982
- deviceId: contact.deviceId,
53983
- code: result.code,
53984
- message: result.message
53985
- });
53986
- sshLog.warn("key.pull.error", "\u62C9 privkey \u65F6\u7F51\u7EDC/\u534F\u8BAE\u9519\u8BEF", {
53987
- peerDeviceId: contact.deviceId,
53988
- code: result.code,
53989
- message: result.message
53990
- });
53991
- }
53992
- }
53993
- return { pulled, errors };
53994
- }
53995
- function writeKeyFile(dataDir, deviceId, pem) {
53996
- const p2 = safeContactKeyPath(dataDir, deviceId);
53997
- import_node_fs42.default.mkdirSync(import_node_path42.default.dirname(p2), { recursive: true, mode: 448 });
53998
- import_node_fs42.default.writeFileSync(p2, pem, { mode: 384 });
53999
- }
54000
- function removeKeyFile(dataDir, deviceId) {
54001
- try {
54002
- import_node_fs42.default.unlinkSync(safeContactKeyPath(dataDir, deviceId));
54003
- return true;
54004
- } catch {
54005
- return false;
54006
- }
54007
- }
54008
- var ContactKeyPuller = class {
54009
- constructor(deps) {
54010
- this.deps = deps;
54011
- }
54012
- deps;
54013
- timer = null;
54014
- async start() {
54015
- const interval = this.deps.intervalMs ?? 6e4;
54016
- void this.tick();
54017
- this.timer = setInterval(() => void this.tick(), interval);
54018
- this.timer.unref();
54019
- }
54020
- stop() {
54021
- if (this.timer) {
54022
- clearInterval(this.timer);
54023
- this.timer = null;
54024
- }
54025
- }
54026
- async tick() {
54027
- try {
54028
- await pullContactSshKeyOnce(this.deps);
54029
- } catch (err) {
54030
- this.deps.logger?.warn("contact-key-puller: tick failed", {
54031
- err: err instanceof Error ? err.message : String(err)
54032
- });
54033
- }
54034
- }
54035
- };
54036
-
54037
53950
  // src/sshd/ssh-tunnel-relay.ts
54038
- var import_node_net2 = __toESM(require("net"), 1);
54039
53951
  async function handleSshTunnelUpgrade(req, socket, head, deps) {
54040
53952
  const sshLog = deps.sshLog ?? nullContactSshLog;
54041
53953
  const clientAddr = (req.socket && "remoteAddress" in req.socket ? req.socket.remoteAddress : null) ?? "unknown";
@@ -54626,9 +54538,9 @@ var CentralClientError = class extends Error {
54626
54538
  code;
54627
54539
  cause;
54628
54540
  };
54629
- async function centralRequest(opts, path77, init) {
54541
+ async function centralRequest(opts, path76, init) {
54630
54542
  const f = opts.fetchImpl ?? globalThis.fetch;
54631
- const url = `${opts.api.replace(/\/+$/, "")}${path77}`;
54543
+ const url = `${opts.api.replace(/\/+$/, "")}${path76}`;
54632
54544
  const ctrl = new AbortController();
54633
54545
  const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 15e3);
54634
54546
  let res;
@@ -55565,11 +55477,11 @@ init_protocol();
55565
55477
  init_protocol();
55566
55478
 
55567
55479
  // src/workspace/git.ts
55568
- var import_node_child_process12 = require("child_process");
55480
+ var import_node_child_process13 = require("child_process");
55569
55481
  var import_node_fs47 = __toESM(require("fs"), 1);
55570
55482
  var import_node_path48 = __toESM(require("path"), 1);
55571
55483
  var import_node_util = require("util");
55572
- var pexec = (0, import_node_util.promisify)(import_node_child_process12.execFile);
55484
+ var pexec = (0, import_node_util.promisify)(import_node_child_process13.execFile);
55573
55485
  function normalizePath(p2) {
55574
55486
  const resolved = import_node_path48.default.resolve(p2);
55575
55487
  try {
@@ -55982,51 +55894,6 @@ function buildContactHandlers(deps) {
55982
55894
 
55983
55895
  // src/handlers/contact-ssh.ts
55984
55896
  init_protocol();
55985
-
55986
- // src/sshd/key-issue.ts
55987
- var import_node_fs48 = __toESM(require("fs"), 1);
55988
- var import_node_path49 = __toESM(require("path"), 1);
55989
- var import_node_child_process13 = require("child_process");
55990
- function safeDeviceId(deviceId) {
55991
- return deviceId.replace(/[\/\\]/g, "_");
55992
- }
55993
- async function issueContactSshKey(deviceId, sshdDir, opts = {}) {
55994
- const safeId = safeDeviceId(deviceId);
55995
- const keysDir = import_node_path49.default.join(sshdDir, "keys");
55996
- import_node_fs48.default.mkdirSync(keysDir, { recursive: true, mode: 448 });
55997
- const privPath = import_node_path49.default.join(keysDir, `${safeId}.ed25519`);
55998
- const pubPath = `${privPath}.pub`;
55999
- if (import_node_fs48.default.existsSync(privPath) && import_node_fs48.default.existsSync(pubPath)) {
56000
- return {
56001
- privateKeyPem: import_node_fs48.default.readFileSync(privPath, "utf8"),
56002
- publicKeyLine: import_node_fs48.default.readFileSync(pubPath, "utf8").trim()
56003
- };
56004
- }
56005
- const bin = opts.keygenBin ?? "/usr/bin/ssh-keygen";
56006
- await new Promise((resolve6, reject) => {
56007
- const p2 = (opts.spawnImpl ?? import_node_child_process13.spawn)(
56008
- bin,
56009
- ["-t", "ed25519", "-f", privPath, "-N", "", "-q", "-C", `clawd-contact-${safeId}`],
56010
- { stdio: "ignore" }
56011
- );
56012
- p2.on("exit", (code) => code === 0 ? resolve6() : reject(new Error(`ssh-keygen exit ${code}`)));
56013
- p2.on("error", reject);
56014
- });
56015
- try {
56016
- import_node_fs48.default.chmodSync(privPath, 384);
56017
- } catch {
56018
- }
56019
- try {
56020
- import_node_fs48.default.chmodSync(pubPath, 420);
56021
- } catch {
56022
- }
56023
- return {
56024
- privateKeyPem: import_node_fs48.default.readFileSync(privPath, "utf8"),
56025
- publicKeyLine: import_node_fs48.default.readFileSync(pubPath, "utf8").trim()
56026
- };
56027
- }
56028
-
56029
- // src/handlers/contact-ssh.ts
56030
55897
  function ensureOwner2(ctx) {
56031
55898
  if (!ctx || ctx.principal.kind !== "owner") {
56032
55899
  throw new ClawdError(
@@ -56035,15 +55902,6 @@ function ensureOwner2(ctx) {
56035
55902
  );
56036
55903
  }
56037
55904
  }
56038
- function ensureGuest(ctx) {
56039
- if (!ctx || ctx.principal.kind !== "guest") {
56040
- throw new ClawdError(
56041
- ERROR_CODES.UNAUTHORIZED,
56042
- "UNAUTHORIZED: contact:sshKey:issue requires guest ctx"
56043
- );
56044
- }
56045
- return ctx.principal.id;
56046
- }
56047
55905
  function buildContactSshHandlers(deps) {
56048
55906
  const sshLog = deps.sshLog ?? nullContactSshLog;
56049
55907
  const setSshAccess = async (frame, _client, ctx) => {
@@ -56080,47 +55938,8 @@ function buildContactSshHandlers(deps) {
56080
55938
  }
56081
55939
  };
56082
55940
  };
56083
- const sshKeyIssue = async (frame, _client, ctx) => {
56084
- const callerDeviceId = ensureGuest(ctx);
56085
- const { type: _t, requestId: _r, ...rest } = frame;
56086
- ContactSshKeyIssueArgsSchema.parse(rest);
56087
- const contact = deps.store.get(callerDeviceId);
56088
- if (!contact || !contact.sshAllowed) {
56089
- sshLog.info(
56090
- "key.issue.rejected",
56091
- contact ? "guest \u8BF7\u6C42 SSH key \u4F46 owner \u672A\u6388\u6743" : "guest \u8BF7\u6C42 SSH key \u4F46\u4E0D\u5728\u6211\u7684 contactStore",
56092
- {
56093
- peerDeviceId: callerDeviceId,
56094
- peerDisplayName: contact?.displayName
56095
- }
56096
- );
56097
- throw new ClawdError(
56098
- ERROR_CODES.UNAUTHORIZED,
56099
- `UNAUTHORIZED: contact ${callerDeviceId} not authorized for SSH`
56100
- );
56101
- }
56102
- const { privateKeyPem, publicKeyLine } = await issueContactSshKey(callerDeviceId, deps.sshdDir);
56103
- rebuildAuthorizedKeys(deps.store, deps.sshdDir);
56104
- sshLog.info(
56105
- "key.issue.success",
56106
- "A \u4FA7\u53D1 privkey \u7ED9 guest\uFF08\u5E42\u7B49\uFF09\uFF0Cauthorized_keys \u5DF2\u91CD\u5EFA",
56107
- {
56108
- peerDeviceId: callerDeviceId,
56109
- peerDisplayName: contact.displayName,
56110
- publicKeyFingerprint: publicKeyLine.slice(0, 32) + "..."
56111
- }
56112
- );
56113
- return {
56114
- response: {
56115
- type: "contact:sshKey:issue:ok",
56116
- privateKeyPem,
56117
- publicKeyLine
56118
- }
56119
- };
56120
- };
56121
55941
  return {
56122
- "contact:setSshAccess": setSshAccess,
56123
- "contact:sshKey:issue": sshKeyIssue
55942
+ "contact:setSshAccess": setSshAccess
56124
55943
  };
56125
55944
  }
56126
55945
 
@@ -56172,12 +55991,14 @@ function buildWhoamiHandler(deps) {
56172
55991
  grantedPersonas.push({ id: file.personaId, displayName: file.label });
56173
55992
  }
56174
55993
  }
55994
+ const sshPublicKey = deps.sshIdentityPublicKey();
56175
55995
  return {
56176
55996
  response: {
56177
55997
  type: "whoami:ok",
56178
55998
  owner,
56179
55999
  capability,
56180
- grantedPersonas
56000
+ grantedPersonas,
56001
+ ...sshPublicKey ? { sshPublicKey } : {}
56181
56002
  }
56182
56003
  };
56183
56004
  };
@@ -56253,7 +56074,9 @@ function buildDeviceHandlers(deps) {
56253
56074
  // 自动反向(spec 决策 #11):自报本机可达地址,让对方反向加我为联系人;
56254
56075
  // 本机无 tunnel → null → connectRemote 省略 selfUrl 字段(不造假数据)。
56255
56076
  // 身份不自报:对方从 token 签名背书取我的 deviceId/ownerId/provider(决策 #16)。
56256
- selfUrl: deps.selfUrl() ?? void 0
56077
+ selfUrl: deps.selfUrl() ?? void 0,
56078
+ // ssh identity 互换(spec 2026-07-15):自报本机 pubkey;未生成 → 省略
56079
+ selfSshPublicKey: deps.selfSshPublicKey() ?? void 0
56257
56080
  });
56258
56081
  } catch (e) {
56259
56082
  throw new ClawdError(
@@ -56263,6 +56086,8 @@ function buildDeviceHandlers(deps) {
56263
56086
  }
56264
56087
  try {
56265
56088
  const wh = await conn.whoami();
56089
+ const existing = deps.store.get(args.deviceId) ?? void 0;
56090
+ const sshPublicKey = wh.sshPublicKey ?? existing?.sshPublicKey;
56266
56091
  const contact = {
56267
56092
  deviceId: args.deviceId,
56268
56093
  ownerId: wh.ownerId,
@@ -56277,12 +56102,18 @@ function buildDeviceHandlers(deps) {
56277
56102
  connectToken: exchanged.token,
56278
56103
  grants: wh.grants,
56279
56104
  addedAt: now(),
56280
- pinnedAt: null,
56281
- sshAllowed: false,
56105
+ pinnedAt: existing?.pinnedAt ?? null,
56106
+ sshAllowed: existing?.sshAllowed ?? false,
56107
+ // ssh identity 互换(spec 2026-07-15):对方 whoami:ok 下发的 pubkey 落库;
56108
+ // 对方老版本不带 → 保留既有值 / 不落 undefined 占位
56109
+ ...sshPublicKey ? { sshPublicKey } : {},
56282
56110
  exposedDirs: []
56283
56111
  };
56284
56112
  deps.store.upsert(contact);
56285
56113
  deps.broadcast({ type: "contact:added", contact });
56114
+ if (contact.sshAllowed && sshPublicKey && sshPublicKey !== existing?.sshPublicKey) {
56115
+ deps.rebuildAuthorizedKeys?.();
56116
+ }
56286
56117
  return {
56287
56118
  response: {
56288
56119
  type: "device:connect:ok",
@@ -56435,7 +56266,7 @@ function buildPersonaHandlers(deps) {
56435
56266
  }
56436
56267
 
56437
56268
  // src/handlers/attachment.ts
56438
- var import_node_path50 = __toESM(require("path"), 1);
56269
+ var import_node_path49 = __toESM(require("path"), 1);
56439
56270
  init_protocol();
56440
56271
  init_protocol();
56441
56272
  var DEFAULT_TTL_SECONDS = 24 * 3600;
@@ -56515,12 +56346,12 @@ function buildAttachmentHandlers(deps) {
56515
56346
  `session ${args.sessionId} scope unresolved`
56516
56347
  );
56517
56348
  }
56518
- const cwdAbs = import_node_path50.default.resolve(sessionFile.cwd);
56519
- const candidateAbs = import_node_path50.default.isAbsolute(args.relPath) ? import_node_path50.default.resolve(args.relPath) : import_node_path50.default.resolve(cwdAbs, args.relPath);
56349
+ const cwdAbs = import_node_path49.default.resolve(sessionFile.cwd);
56350
+ const candidateAbs = import_node_path49.default.isAbsolute(args.relPath) ? import_node_path49.default.resolve(args.relPath) : import_node_path49.default.resolve(cwdAbs, args.relPath);
56520
56351
  guardAttachmentPath(ctx, args.sessionId, candidateAbs, "attachment.signUrl", "group-acl");
56521
56352
  const entries = deps.groupFileStore.list(scope, args.sessionId);
56522
56353
  const entry = entries.find((e) => {
56523
- const storedAbs = import_node_path50.default.isAbsolute(e.relPath) ? import_node_path50.default.resolve(e.relPath) : import_node_path50.default.resolve(cwdAbs, e.relPath);
56354
+ const storedAbs = import_node_path49.default.isAbsolute(e.relPath) ? import_node_path49.default.resolve(e.relPath) : import_node_path49.default.resolve(cwdAbs, e.relPath);
56524
56355
  return storedAbs === candidateAbs && !e.stale;
56525
56356
  });
56526
56357
  if (!entry) {
@@ -56545,7 +56376,7 @@ function buildAttachmentHandlers(deps) {
56545
56376
  if (!ctx || ctx.principal.kind !== "guest" || !deps.personaRoot || !deps.sessionStore) return;
56546
56377
  const f = deps.sessionStore.read(sessionId);
56547
56378
  if (!f) return;
56548
- assertGuestAttachmentPath(ctx, import_node_path50.default.resolve(f.cwd), deps.personaRoot, method, deps.usersRoot);
56379
+ assertGuestAttachmentPath(ctx, import_node_path49.default.resolve(f.cwd), deps.personaRoot, method, deps.usersRoot);
56549
56380
  }
56550
56381
  const groupAdd = async (frame, _client, ctx) => {
56551
56382
  if (!deps.groupFileStore || !deps.getSessionScope) {
@@ -56560,8 +56391,8 @@ function buildAttachmentHandlers(deps) {
56560
56391
  if (!scope) {
56561
56392
  throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, `session ${args.sessionId} not found`);
56562
56393
  }
56563
- const cwdAbs = import_node_path50.default.resolve(deps.sessionStore?.read(args.sessionId)?.cwd ?? ".");
56564
- const candidateAbs = import_node_path50.default.isAbsolute(args.relPath) ? import_node_path50.default.resolve(args.relPath) : import_node_path50.default.resolve(cwdAbs, args.relPath);
56394
+ const cwdAbs = import_node_path49.default.resolve(deps.sessionStore?.read(args.sessionId)?.cwd ?? ".");
56395
+ const candidateAbs = import_node_path49.default.isAbsolute(args.relPath) ? import_node_path49.default.resolve(args.relPath) : import_node_path49.default.resolve(cwdAbs, args.relPath);
56565
56396
  guardAttachmentPath(ctx, args.sessionId, candidateAbs, "attachment.groupAdd", "cwd-subtree");
56566
56397
  const from = ctx?.principal.kind === "owner" ? "owner" : "agent";
56567
56398
  const size = 0;
@@ -56620,19 +56451,19 @@ function buildAttachmentHandlers(deps) {
56620
56451
 
56621
56452
  // src/handlers/extension.ts
56622
56453
  var import_promises8 = __toESM(require("fs/promises"), 1);
56623
- var import_node_path55 = __toESM(require("path"), 1);
56454
+ var import_node_path54 = __toESM(require("path"), 1);
56624
56455
  init_protocol();
56625
56456
 
56626
56457
  // src/extension/bundle-zip.ts
56627
56458
  var import_promises5 = __toESM(require("fs/promises"), 1);
56628
- var import_node_path51 = __toESM(require("path"), 1);
56459
+ var import_node_path50 = __toESM(require("path"), 1);
56629
56460
  var import_node_crypto14 = __toESM(require("crypto"), 1);
56630
56461
  var import_jszip2 = __toESM(require_lib3(), 1);
56631
56462
  async function bundleExtensionDir(dir) {
56632
56463
  const entries = await listFilesSorted(dir);
56633
56464
  const zip = new import_jszip2.default();
56634
56465
  for (const rel of entries) {
56635
- const abs = import_node_path51.default.join(dir, rel);
56466
+ const abs = import_node_path50.default.join(dir, rel);
56636
56467
  const content = await import_promises5.default.readFile(abs);
56637
56468
  zip.file(rel, content, { date: FIXED_DATE });
56638
56469
  }
@@ -56653,7 +56484,7 @@ async function listFilesSorted(rootDir) {
56653
56484
  return out;
56654
56485
  }
56655
56486
  async function walk(absRoot, relPrefix, out) {
56656
- const dirAbs = import_node_path51.default.join(absRoot, relPrefix);
56487
+ const dirAbs = import_node_path50.default.join(absRoot, relPrefix);
56657
56488
  const entries = await import_promises5.default.readdir(dirAbs, { withFileTypes: true });
56658
56489
  for (const e of entries) {
56659
56490
  if (IGNORE_BASENAMES.has(e.name)) continue;
@@ -56707,25 +56538,25 @@ function computePublishCheck(args) {
56707
56538
 
56708
56539
  // src/extension/install-flow.ts
56709
56540
  var import_promises6 = __toESM(require("fs/promises"), 1);
56710
- var import_node_path53 = __toESM(require("path"), 1);
56541
+ var import_node_path52 = __toESM(require("path"), 1);
56711
56542
  var import_node_os19 = __toESM(require("os"), 1);
56712
56543
  var import_node_crypto15 = __toESM(require("crypto"), 1);
56713
56544
  var import_jszip3 = __toESM(require_lib3(), 1);
56714
56545
 
56715
56546
  // src/extension/paths.ts
56716
56547
  var import_node_os18 = __toESM(require("os"), 1);
56717
- var import_node_path52 = __toESM(require("path"), 1);
56548
+ var import_node_path51 = __toESM(require("path"), 1);
56718
56549
  function clawdHomeRoot(override) {
56719
- return override ?? process.env.CLAWD_HOME ?? import_node_path52.default.join(import_node_os18.default.homedir(), ".clawd");
56550
+ return override ?? process.env.CLAWD_HOME ?? import_node_path51.default.join(import_node_os18.default.homedir(), ".clawd");
56720
56551
  }
56721
56552
  function extensionsRoot(override) {
56722
- return import_node_path52.default.join(clawdHomeRoot(override), "extensions");
56553
+ return import_node_path51.default.join(clawdHomeRoot(override), "extensions");
56723
56554
  }
56724
56555
  function publishedChannelsFile(override) {
56725
- return import_node_path52.default.join(clawdHomeRoot(override), "extensions-published.json");
56556
+ return import_node_path51.default.join(clawdHomeRoot(override), "extensions-published.json");
56726
56557
  }
56727
56558
  function bundleCacheRoot(override) {
56728
- return import_node_path52.default.join(clawdHomeRoot(override), "extension-bundles");
56559
+ return import_node_path51.default.join(clawdHomeRoot(override), "extension-bundles");
56729
56560
  }
56730
56561
 
56731
56562
  // src/extension/install-flow.ts
@@ -56752,7 +56583,7 @@ async function installFromChannel(args, deps) {
56752
56583
  throw new InstallError("ZIP_INVALID", `failed to load zip: ${e.message}`);
56753
56584
  }
56754
56585
  for (const name of Object.keys(zip.files)) {
56755
- if (name.includes("..") || name.startsWith("/") || import_node_path53.default.isAbsolute(name)) {
56586
+ if (name.includes("..") || name.startsWith("/") || import_node_path52.default.isAbsolute(name)) {
56756
56587
  throw new InstallError("ZIP_INVALID", `unsafe zip entry: ${name}`);
56757
56588
  }
56758
56589
  }
@@ -56784,7 +56615,7 @@ async function installFromChannel(args, deps) {
56784
56615
  );
56785
56616
  }
56786
56617
  const localExtId = namespacedExtId(ownerSlug, channelRef.ownerPrincipalId);
56787
- const destDir = import_node_path53.default.join(deps.extensionsRoot, localExtId);
56618
+ const destDir = import_node_path52.default.join(deps.extensionsRoot, localExtId);
56788
56619
  let destExists = false;
56789
56620
  try {
56790
56621
  await import_promises6.default.access(destDir);
@@ -56798,16 +56629,16 @@ async function installFromChannel(args, deps) {
56798
56629
  );
56799
56630
  }
56800
56631
  const stage = await import_promises6.default.mkdtemp(
56801
- import_node_path53.default.join(import_node_os19.default.tmpdir(), `clawd-ext-install-${localExtId}-`)
56632
+ import_node_path52.default.join(import_node_os19.default.tmpdir(), `clawd-ext-install-${localExtId}-`)
56802
56633
  );
56803
56634
  try {
56804
56635
  for (const [name, entry] of Object.entries(zip.files)) {
56805
- const dest = import_node_path53.default.join(stage, name);
56636
+ const dest = import_node_path52.default.join(stage, name);
56806
56637
  if (entry.dir) {
56807
56638
  await import_promises6.default.mkdir(dest, { recursive: true });
56808
56639
  continue;
56809
56640
  }
56810
- await import_promises6.default.mkdir(import_node_path53.default.dirname(dest), { recursive: true });
56641
+ await import_promises6.default.mkdir(import_node_path52.default.dirname(dest), { recursive: true });
56811
56642
  if (name === "manifest.json") {
56812
56643
  const rewritten = { ...parsed.data, id: localExtId };
56813
56644
  await import_promises6.default.writeFile(dest, JSON.stringify(rewritten, null, 2));
@@ -56828,7 +56659,7 @@ async function installFromChannel(args, deps) {
56828
56659
 
56829
56660
  // src/extension/update-flow.ts
56830
56661
  var import_promises7 = __toESM(require("fs/promises"), 1);
56831
- var import_node_path54 = __toESM(require("path"), 1);
56662
+ var import_node_path53 = __toESM(require("path"), 1);
56832
56663
  var import_node_os20 = __toESM(require("os"), 1);
56833
56664
  var import_node_crypto16 = __toESM(require("crypto"), 1);
56834
56665
  var import_jszip4 = __toESM(require_lib3(), 1);
@@ -56845,11 +56676,11 @@ async function updateFromChannel(args, deps) {
56845
56676
  channelRef.extId,
56846
56677
  channelRef.ownerPrincipalId
56847
56678
  );
56848
- const liveDir = import_node_path54.default.join(deps.extensionsRoot, localExtId);
56679
+ const liveDir = import_node_path53.default.join(deps.extensionsRoot, localExtId);
56849
56680
  const prevDir = `${liveDir}.prev`;
56850
56681
  let existingVersion;
56851
56682
  try {
56852
- const raw = await import_promises7.default.readFile(import_node_path54.default.join(liveDir, "manifest.json"), "utf8");
56683
+ const raw = await import_promises7.default.readFile(import_node_path53.default.join(liveDir, "manifest.json"), "utf8");
56853
56684
  const parsed2 = ExtensionManifestSchema.safeParse(JSON.parse(raw));
56854
56685
  if (!parsed2.success) {
56855
56686
  throw new UpdateError(
@@ -56882,7 +56713,7 @@ async function updateFromChannel(args, deps) {
56882
56713
  throw new UpdateError("ZIP_INVALID", `failed to load zip: ${e.message}`);
56883
56714
  }
56884
56715
  for (const name of Object.keys(zip.files)) {
56885
- if (name.includes("..") || name.startsWith("/") || import_node_path54.default.isAbsolute(name)) {
56716
+ if (name.includes("..") || name.startsWith("/") || import_node_path53.default.isAbsolute(name)) {
56886
56717
  throw new UpdateError("ZIP_INVALID", `unsafe zip entry: ${name}`);
56887
56718
  }
56888
56719
  }
@@ -56917,16 +56748,16 @@ async function updateFromChannel(args, deps) {
56917
56748
  await import_promises7.default.rm(prevDir, { recursive: true, force: true });
56918
56749
  await import_promises7.default.rename(liveDir, prevDir);
56919
56750
  const stage = await import_promises7.default.mkdtemp(
56920
- import_node_path54.default.join(import_node_os20.default.tmpdir(), `clawd-ext-update-${localExtId}-`)
56751
+ import_node_path53.default.join(import_node_os20.default.tmpdir(), `clawd-ext-update-${localExtId}-`)
56921
56752
  );
56922
56753
  try {
56923
56754
  for (const [name, entry] of Object.entries(zip.files)) {
56924
- const dest = import_node_path54.default.join(stage, name);
56755
+ const dest = import_node_path53.default.join(stage, name);
56925
56756
  if (entry.dir) {
56926
56757
  await import_promises7.default.mkdir(dest, { recursive: true });
56927
56758
  continue;
56928
56759
  }
56929
- await import_promises7.default.mkdir(import_node_path54.default.dirname(dest), { recursive: true });
56760
+ await import_promises7.default.mkdir(import_node_path53.default.dirname(dest), { recursive: true });
56930
56761
  if (name === "manifest.json") {
56931
56762
  const rewritten = { ...parsed.data, id: localExtId };
56932
56763
  await import_promises7.default.writeFile(dest, JSON.stringify(rewritten, null, 2));
@@ -57019,7 +56850,7 @@ async function rewriteManifestVersion(root, extId, newVersion, previousPublished
57019
56850
  );
57020
56851
  }
57021
56852
  }
57022
- const manifestPath = import_node_path55.default.join(root, extId, "manifest.json");
56853
+ const manifestPath = import_node_path54.default.join(root, extId, "manifest.json");
57023
56854
  const manifest = await readManifest(root, extId);
57024
56855
  const next = { ...manifest, version: newVersion };
57025
56856
  const tmp = `${manifestPath}.tmp`;
@@ -57027,7 +56858,7 @@ async function rewriteManifestVersion(root, extId, newVersion, previousPublished
57027
56858
  await import_promises8.default.rename(tmp, manifestPath);
57028
56859
  }
57029
56860
  async function readManifest(root, extId) {
57030
- const file = import_node_path55.default.join(root, extId, "manifest.json");
56861
+ const file = import_node_path54.default.join(root, extId, "manifest.json");
57031
56862
  let raw;
57032
56863
  try {
57033
56864
  raw = await import_promises8.default.readFile(file, "utf8");
@@ -57118,7 +56949,7 @@ function buildExtensionHandlers(deps) {
57118
56949
  };
57119
56950
  async function buildSnapshotMeta(extId) {
57120
56951
  const manifest = await readManifest(deps.root, extId);
57121
- const { sha256, buffer } = await bundleExtensionDir(import_node_path55.default.join(deps.root, extId));
56952
+ const { sha256, buffer } = await bundleExtensionDir(import_node_path54.default.join(deps.root, extId));
57122
56953
  return { manifest, contentHash: sha256, buffer };
57123
56954
  }
57124
56955
  const publish = async (frame, _client, ctx) => {
@@ -57299,9 +57130,9 @@ function buildExtensionHandlers(deps) {
57299
57130
  }
57300
57131
 
57301
57132
  // src/app-builder/project-store.ts
57302
- var import_node_fs49 = require("fs");
57133
+ var import_node_fs48 = require("fs");
57303
57134
  var import_node_child_process14 = require("child_process");
57304
- var import_node_path56 = require("path");
57135
+ var import_node_path55 = require("path");
57305
57136
  init_protocol();
57306
57137
  var PROJECTS_DIR = "projects";
57307
57138
  var META_FILE = ".clawd-project.json";
@@ -57315,19 +57146,19 @@ var ProjectStore = class {
57315
57146
  root;
57316
57147
  /** projects/<name>/.clawd-project.json 路径 */
57317
57148
  metaPath(name) {
57318
- return (0, import_node_path56.join)(this.projectsRoot(), name, META_FILE);
57149
+ return (0, import_node_path55.join)(this.projectsRoot(), name, META_FILE);
57319
57150
  }
57320
57151
  /** projects/<name>/ 目录路径(cwd 用) */
57321
57152
  projectDir(name) {
57322
- return (0, import_node_path56.join)(this.projectsRoot(), name);
57153
+ return (0, import_node_path55.join)(this.projectsRoot(), name);
57323
57154
  }
57324
57155
  projectsRoot() {
57325
- return (0, import_node_path56.join)(this.root, PROJECTS_DIR);
57156
+ return (0, import_node_path55.join)(this.root, PROJECTS_DIR);
57326
57157
  }
57327
57158
  async list() {
57328
57159
  let entries;
57329
57160
  try {
57330
- entries = await import_node_fs49.promises.readdir(this.projectsRoot());
57161
+ entries = await import_node_fs48.promises.readdir(this.projectsRoot());
57331
57162
  } catch (err) {
57332
57163
  if (err.code === "ENOENT") return [];
57333
57164
  throw err;
@@ -57335,7 +57166,7 @@ var ProjectStore = class {
57335
57166
  const out = [];
57336
57167
  for (const name of entries) {
57337
57168
  try {
57338
- const raw = await import_node_fs49.promises.readFile(this.metaPath(name), "utf8");
57169
+ const raw = await import_node_fs48.promises.readFile(this.metaPath(name), "utf8");
57339
57170
  const json = JSON.parse(raw);
57340
57171
  let migrated = false;
57341
57172
  if (typeof json.devCommand !== "string" || json.devCommand.length === 0) {
@@ -57346,7 +57177,7 @@ var ProjectStore = class {
57346
57177
  if (parsed.success) {
57347
57178
  out.push(parsed.data);
57348
57179
  if (migrated) {
57349
- void import_node_fs49.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
57180
+ void import_node_fs48.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
57350
57181
  });
57351
57182
  }
57352
57183
  }
@@ -57390,8 +57221,8 @@ var ProjectStore = class {
57390
57221
  throw new Error(`invalid name "${name}": ${validated.error.message}`);
57391
57222
  }
57392
57223
  const dir = this.projectDir(name);
57393
- await import_node_fs49.promises.mkdir(dir, { recursive: true });
57394
- await import_node_fs49.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
57224
+ await import_node_fs48.promises.mkdir(dir, { recursive: true });
57225
+ await import_node_fs48.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
57395
57226
  return meta;
57396
57227
  }
57397
57228
  /**
@@ -57434,7 +57265,7 @@ var ProjectStore = class {
57434
57265
  }
57435
57266
  async delete(name) {
57436
57267
  const dir = this.projectDir(name);
57437
- await import_node_fs49.promises.rm(dir, { recursive: true, force: true });
57268
+ await import_node_fs48.promises.rm(dir, { recursive: true, force: true });
57438
57269
  }
57439
57270
  async updatePort(name, newPort) {
57440
57271
  if (newPort < PROJECT_PORT_MIN || newPort > PROJECT_PORT_MAX) {
@@ -57450,7 +57281,7 @@ var ProjectStore = class {
57450
57281
  throw new Error(`port ${newPort} already used / \u5DF2\u88AB project "${conflict.name}" \u5360\u7528`);
57451
57282
  }
57452
57283
  const updated = { ...target, port: newPort };
57453
- await import_node_fs49.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
57284
+ await import_node_fs48.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
57454
57285
  return updated;
57455
57286
  }
57456
57287
  /**
@@ -57467,7 +57298,7 @@ var ProjectStore = class {
57467
57298
  if (!validated.success) {
57468
57299
  throw new Error(`invalid prodUrl "${url}": ${validated.error.message}`);
57469
57300
  }
57470
- await import_node_fs49.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
57301
+ await import_node_fs48.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
57471
57302
  return validated.data;
57472
57303
  }
57473
57304
  /**
@@ -57488,7 +57319,7 @@ var ProjectStore = class {
57488
57319
  if (!validated.success) {
57489
57320
  throw new Error(`invalid publishJob: ${validated.error.message}`);
57490
57321
  }
57491
- await import_node_fs49.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
57322
+ await import_node_fs48.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
57492
57323
  return validated.data;
57493
57324
  }
57494
57325
  /** 清掉 .clawd-project.json.publishJob 字段。其他字段保持原样。 */
@@ -57503,7 +57334,7 @@ var ProjectStore = class {
57503
57334
  if (!validated.success) {
57504
57335
  throw new Error(`failed to clear publishJob: ${validated.error.message}`);
57505
57336
  }
57506
- await import_node_fs49.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
57337
+ await import_node_fs48.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
57507
57338
  return validated.data;
57508
57339
  }
57509
57340
  };
@@ -57624,8 +57455,8 @@ var PublishJobRegistry = class {
57624
57455
 
57625
57456
  // src/app-builder/publish-job-runner.ts
57626
57457
  var import_node_child_process16 = require("child_process");
57627
- var import_node_fs50 = require("fs");
57628
- var import_node_path57 = require("path");
57458
+ var import_node_fs49 = require("fs");
57459
+ var import_node_path56 = require("path");
57629
57460
 
57630
57461
  // src/app-builder/publish-stage-parser.ts
57631
57462
  var STAGE_RE = /^\s*::stage::(build|deploy|verify)\s*$/;
@@ -57657,10 +57488,10 @@ async function startPublishJob(deps, args) {
57657
57488
  return { jobId: registry2.get(args.name).jobId, status: "already-publishing" };
57658
57489
  }
57659
57490
  const projDir = projectDir(args.name);
57660
- const logPath = (0, import_node_path57.join)(projDir, ".publish.log");
57491
+ const logPath = (0, import_node_path56.join)(projDir, ".publish.log");
57661
57492
  let logStream = null;
57662
57493
  try {
57663
- logStream = (0, import_node_fs50.createWriteStream)(logPath, { flags: "w" });
57494
+ logStream = (0, import_node_fs49.createWriteStream)(logPath, { flags: "w" });
57664
57495
  } catch {
57665
57496
  logStream = null;
57666
57497
  }
@@ -57917,8 +57748,8 @@ async function recoverInterruptedJobs(deps) {
57917
57748
 
57918
57749
  // src/handlers/app-builder.ts
57919
57750
  init_protocol();
57920
- var import_node_path58 = require("path");
57921
- var import_node_fs51 = require("fs");
57751
+ var import_node_path57 = require("path");
57752
+ var import_node_fs50 = require("fs");
57922
57753
  var APP_BUILDER_PERSONAS = ["persona-app-builder", "persona-dataclaw-builder"];
57923
57754
  var DEV_SERVER_READY_TIMEOUT_MS = 3e4;
57924
57755
  async function recoverInterruptedPublishJobs(store, logger) {
@@ -57999,7 +57830,7 @@ function buildAppBuilderHandlers(deps) {
57999
57830
  async function listAllUsersProjects() {
58000
57831
  if (!deps.usersRoot || !deps.getStore) return [];
58001
57832
  const getStore = deps.getStore;
58002
- const userIds = await import_node_fs51.promises.readdir(deps.usersRoot).catch(() => []);
57833
+ const userIds = await import_node_fs50.promises.readdir(deps.usersRoot).catch(() => []);
58003
57834
  const perUser = await Promise.all(
58004
57835
  userIds.map((uid) => getStore(uid).list().catch(() => []))
58005
57836
  );
@@ -58075,8 +57906,8 @@ function buildAppBuilderHandlers(deps) {
58075
57906
  const project = await userStore.create(f.name, reservedPorts);
58076
57907
  try {
58077
57908
  const personaRoot = deps.resolvePersonaRoot ? deps.resolvePersonaRoot(session.ownerPersonaId ?? "") : deps.personaRoot;
58078
- const templateSrcDir = (0, import_node_path58.join)(personaRoot, "extension-kit", "examples", DEFAULT_TEMPLATE);
58079
- const scaffoldScript = (0, import_node_path58.join)(deps.deployKitRoot, "scripts", "new-extension.sh");
57909
+ const templateSrcDir = (0, import_node_path57.join)(personaRoot, "extension-kit", "examples", DEFAULT_TEMPLATE);
57910
+ const scaffoldScript = (0, import_node_path57.join)(deps.deployKitRoot, "scripts", "new-extension.sh");
58080
57911
  const scaffoldResult = await userStore.scaffold(project.name, templateSrcDir, scaffoldScript);
58081
57912
  deps.logger?.info("app-builder.scaffold.done", {
58082
57913
  name: project.name,
@@ -58297,7 +58128,7 @@ function buildAppBuilderHandlers(deps) {
58297
58128
  await userStore.clearPublishJob(args.name);
58298
58129
  }
58299
58130
  const personaRoot = deps.resolvePersonaRoot ? deps.resolvePersonaRoot(boundSession.ownerPersonaId ?? "") : deps.personaRoot;
58300
- const scriptPath = (0, import_node_path58.join)(deps.deployKitRoot, "scripts", "publish.sh");
58131
+ const scriptPath = (0, import_node_path57.join)(deps.deployKitRoot, "scripts", "publish.sh");
58301
58132
  deps.logger?.info("app-builder.publish.start", {
58302
58133
  name: args.name,
58303
58134
  sessionId: boundSession.sessionId,
@@ -58473,7 +58304,7 @@ function buildVisitorHandlers(deps) {
58473
58304
 
58474
58305
  // src/extension/registry.ts
58475
58306
  var import_promises9 = __toESM(require("fs/promises"), 1);
58476
- var import_node_path59 = __toESM(require("path"), 1);
58307
+ var import_node_path58 = __toESM(require("path"), 1);
58477
58308
  async function loadAll(root) {
58478
58309
  let entries;
58479
58310
  try {
@@ -58486,13 +58317,13 @@ async function loadAll(root) {
58486
58317
  for (const ent of entries) {
58487
58318
  if (!ent.isDirectory()) continue;
58488
58319
  if (ent.name.startsWith(".")) continue;
58489
- records.push(await loadOne(import_node_path59.default.join(root, ent.name), ent.name));
58320
+ records.push(await loadOne(import_node_path58.default.join(root, ent.name), ent.name));
58490
58321
  }
58491
58322
  records.sort((a, b2) => a.extId < b2.extId ? -1 : a.extId > b2.extId ? 1 : 0);
58492
58323
  return records;
58493
58324
  }
58494
58325
  async function loadOne(dir, dirName) {
58495
- const manifestPath = import_node_path59.default.join(dir, "manifest.json");
58326
+ const manifestPath = import_node_path58.default.join(dir, "manifest.json");
58496
58327
  let raw;
58497
58328
  try {
58498
58329
  raw = await import_promises9.default.readFile(manifestPath, "utf8");
@@ -58537,7 +58368,7 @@ async function loadOne(dir, dirName) {
58537
58368
 
58538
58369
  // src/extension/uninstall.ts
58539
58370
  var import_promises10 = __toESM(require("fs/promises"), 1);
58540
- var import_node_path60 = __toESM(require("path"), 1);
58371
+ var import_node_path59 = __toESM(require("path"), 1);
58541
58372
  var UninstallError = class extends Error {
58542
58373
  constructor(code, message) {
58543
58374
  super(message);
@@ -58546,7 +58377,7 @@ var UninstallError = class extends Error {
58546
58377
  code;
58547
58378
  };
58548
58379
  async function uninstall(deps) {
58549
- const dir = import_node_path60.default.join(deps.root, deps.extId);
58380
+ const dir = import_node_path59.default.join(deps.root, deps.extId);
58550
58381
  try {
58551
58382
  await import_promises10.default.access(dir);
58552
58383
  } catch {
@@ -58613,7 +58444,8 @@ function buildMethodHandlers(deps) {
58613
58444
  ownerId: deps.ownerId,
58614
58445
  ownerProvider: deps.ownerProvider,
58615
58446
  personaStore: deps.personaStore,
58616
- capabilityManager: deps.capabilityManager
58447
+ capabilityManager: deps.capabilityManager,
58448
+ sshIdentityPublicKey: deps.sshIdentityPublicKey
58617
58449
  }),
58618
58450
  ...buildFeishuAuthHandlers(deps.feishuAuth),
58619
58451
  ...buildDeviceHandlers(deps.feishuDevice),
@@ -58946,11 +58778,8 @@ var METHOD_GRANT_MAP = {
58946
58778
  "contact:pin": ADMIN_ANY,
58947
58779
  "contact:remove": ADMIN_ANY,
58948
58780
  // contact:setSshAccess (owner UI 配 SSH 授权):ADMIN_ANY
58949
- // contact:sshKey:issue (guest daemon 拉自己的 privkey):public — handler 内校
58950
- // ctx.principal.kind==='guest' + store.get(callerId).sshAllowed
58951
58781
  // (对齐 inbox:postMessage 的"能连上=有 auth,业务在 handler 校"模式)
58952
58782
  "contact:setSshAccess": ADMIN_ANY,
58953
- "contact:sshKey:issue": { kind: "public" },
58954
58783
  // ---- visitor:* (访客名单,owner-only) ----
58955
58784
  // owner 看完整访客名单(含没开会话的);guest 不可调(handler 内再 assertOwner 兜底)。
58956
58785
  "visitor:list": ADMIN_ANY,
@@ -59136,7 +58965,7 @@ async function dispatchRpc(method, frame, client, ctx, deps) {
59136
58965
 
59137
58966
  // src/extension/runtime.ts
59138
58967
  var import_node_child_process18 = require("child_process");
59139
- var import_node_path61 = __toESM(require("path"), 1);
58968
+ var import_node_path60 = __toESM(require("path"), 1);
59140
58969
  var import_promises11 = require("timers/promises");
59141
58970
 
59142
58971
  // src/extension/port-allocator.ts
@@ -59237,7 +59066,7 @@ var Runtime = class {
59237
59066
  /\$CLAWOS_EXT_PORT/g,
59238
59067
  String(port)
59239
59068
  );
59240
- const dir = import_node_path61.default.join(this.root, extId);
59069
+ const dir = import_node_path60.default.join(this.root, extId);
59241
59070
  const env = {
59242
59071
  ...process.env,
59243
59072
  CLAWOS_EXT_PORT: String(port),
@@ -59349,7 +59178,7 @@ ${handle.stderrTail}`
59349
59178
 
59350
59179
  // src/extension/published-channels.ts
59351
59180
  var import_promises12 = __toESM(require("fs/promises"), 1);
59352
- var import_node_path62 = __toESM(require("path"), 1);
59181
+ var import_node_path61 = __toESM(require("path"), 1);
59353
59182
  init_zod();
59354
59183
  var PublishedChannelsError = class extends Error {
59355
59184
  constructor(code, message) {
@@ -59448,7 +59277,7 @@ var PublishedChannelStore = class {
59448
59277
  )
59449
59278
  };
59450
59279
  const tmp = `${this.filePath}.tmp`;
59451
- await import_promises12.default.mkdir(import_node_path62.default.dirname(this.filePath), { recursive: true });
59280
+ await import_promises12.default.mkdir(import_node_path61.default.dirname(this.filePath), { recursive: true });
59452
59281
  await import_promises12.default.writeFile(tmp, JSON.stringify(data, null, 2), { mode: 384 });
59453
59282
  await import_promises12.default.rename(tmp, this.filePath);
59454
59283
  }
@@ -59456,7 +59285,7 @@ var PublishedChannelStore = class {
59456
59285
 
59457
59286
  // src/extension/bundle-cache.ts
59458
59287
  var import_promises13 = __toESM(require("fs/promises"), 1);
59459
- var import_node_path63 = __toESM(require("path"), 1);
59288
+ var import_node_path62 = __toESM(require("path"), 1);
59460
59289
  var BundleCache = class {
59461
59290
  constructor(rootDir) {
59462
59291
  this.rootDir = rootDir;
@@ -59465,14 +59294,14 @@ var BundleCache = class {
59465
59294
  /** Atomic write: stage tmp → rename. Caller passes the hex sha256. */
59466
59295
  async write(snapshotHash, buffer) {
59467
59296
  await import_promises13.default.mkdir(this.rootDir, { recursive: true });
59468
- const file = import_node_path63.default.join(this.rootDir, `${snapshotHash}.zip`);
59297
+ const file = import_node_path62.default.join(this.rootDir, `${snapshotHash}.zip`);
59469
59298
  const tmp = `${file}.tmp`;
59470
59299
  await import_promises13.default.writeFile(tmp, buffer, { mode: 384 });
59471
59300
  await import_promises13.default.rename(tmp, file);
59472
59301
  }
59473
59302
  /** Returns the bundle bytes, or null when the file doesn't exist. */
59474
59303
  async read(snapshotHash) {
59475
- const file = import_node_path63.default.join(this.rootDir, `${snapshotHash}.zip`);
59304
+ const file = import_node_path62.default.join(this.rootDir, `${snapshotHash}.zip`);
59476
59305
  try {
59477
59306
  return await import_promises13.default.readFile(file);
59478
59307
  } catch (e) {
@@ -59482,7 +59311,7 @@ var BundleCache = class {
59482
59311
  }
59483
59312
  /** Idempotent — missing file is not an error. */
59484
59313
  async delete(snapshotHash) {
59485
- const file = import_node_path63.default.join(this.rootDir, `${snapshotHash}.zip`);
59314
+ const file = import_node_path62.default.join(this.rootDir, `${snapshotHash}.zip`);
59486
59315
  await import_promises13.default.rm(file, { force: true });
59487
59316
  }
59488
59317
  };
@@ -59505,24 +59334,24 @@ async function startDaemon(config) {
59505
59334
  sampling: logShippingCfg.sampling,
59506
59335
  homeDir: import_node_os21.default.homedir()
59507
59336
  });
59508
- const logDir = import_node_path64.default.join(config.dataDir, "log");
59509
- import_node_fs52.default.mkdirSync(logDir, { recursive: true });
59337
+ const logDir = import_node_path63.default.join(config.dataDir, "log");
59338
+ import_node_fs51.default.mkdirSync(logDir, { recursive: true });
59510
59339
  const logger = createLogger({
59511
59340
  level: config.logLevel,
59512
- file: import_node_path64.default.join(logDir, "clawd.log"),
59341
+ file: import_node_path63.default.join(logDir, "clawd.log"),
59513
59342
  logClient
59514
59343
  });
59515
59344
  logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
59516
59345
  const screenIdleProbeLogger = createFileOnlyLogger({
59517
- file: import_node_path64.default.join(logDir, "screen-idle-probe.log"),
59346
+ file: import_node_path63.default.join(logDir, "screen-idle-probe.log"),
59518
59347
  level: "debug"
59519
59348
  });
59520
59349
  logger.info("screen-idle probe logger enabled", {
59521
- file: import_node_path64.default.join(logDir, "screen-idle-probe.log")
59350
+ file: import_node_path63.default.join(logDir, "screen-idle-probe.log")
59522
59351
  });
59523
59352
  const CAP_TARGETS = [
59524
- import_node_path64.default.join(logDir, "clawd.log"),
59525
- import_node_path64.default.join(logDir, "screen-idle-probe.log")
59353
+ import_node_path63.default.join(logDir, "clawd.log"),
59354
+ import_node_path63.default.join(logDir, "screen-idle-probe.log")
59526
59355
  ];
59527
59356
  const LOG_CAP_MAX_BYTES = 10 * 1024 * 1024;
59528
59357
  const LOG_CAP_KEEP_BYTES = 5 * 1024 * 1024;
@@ -59618,7 +59447,7 @@ async function startDaemon(config) {
59618
59447
  // Task 1.7:authenticate 注入路径替代 expectedToken 单 token 比对。
59619
59448
  // owner 路径 constantTimeEqual 防侧信道;guest 路径走 capabilityRegistry.
59620
59449
  expectedToken: resolvedAuthToken,
59621
- authenticate: async (t, selfUrl) => {
59450
+ authenticate: async (t, selfUrl, selfSshPublicKey) => {
59622
59451
  const result = await authenticate(t, buildConnectAuthDeps());
59623
59452
  if (result.ok && result.context.principal.kind === "guest") {
59624
59453
  void autoReverseContact({
@@ -59629,6 +59458,10 @@ async function startDaemon(config) {
59629
59458
  provider: result.context.provider ?? "",
59630
59459
  displayName: result.context.principal.displayName ?? result.context.principal.id,
59631
59460
  selfUrl,
59461
+ // ssh identity 互换(spec 2026-07-15):guest auth 帧自报的 pubkey 落 contact;
59462
+ // pubkey 变化且已授权时立即重建 authorized_keys(重新握手 = 迁移路径)
59463
+ sshPublicKey: selfSshPublicKey,
59464
+ rebuildAuthorizedKeys: () => rebuildAuthorizedKeys(contactStore, import_node_path63.default.join(config.dataDir, "sshd")),
59632
59465
  // 决策 #16 换票补全:用 owner 当前 jwt 换 aud=对方设备 的票(本机主动连对方用)。
59633
59466
  // 现读 ownerIdentityStore(热切换后用新身份);失败 → null → 保持空票。
59634
59467
  exchangeToken: async (targetDeviceId) => {
@@ -59676,8 +59509,8 @@ async function startDaemon(config) {
59676
59509
  const agents = new AgentsScanner();
59677
59510
  const history = new ClaudeHistoryReader();
59678
59511
  let transport = null;
59679
- const personaStore = new PersonaStore(import_node_path64.default.join(config.dataDir, "personas"));
59680
- const usersRoot = import_node_path64.default.join(config.dataDir, "users");
59512
+ const personaStore = new PersonaStore(import_node_path63.default.join(config.dataDir, "personas"));
59513
+ const usersRoot = import_node_path63.default.join(config.dataDir, "users");
59681
59514
  const defaultsRoot = findDefaultsRoot(logger);
59682
59515
  if (defaultsRoot) {
59683
59516
  seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
@@ -59697,17 +59530,17 @@ async function startDaemon(config) {
59697
59530
  migrateCodexSandbox({ store: personaStore, logger });
59698
59531
  const groupFileStore = new GroupFileStore({ dataDir: config.dataDir, logger });
59699
59532
  const personaDispatchManager = new PersonaDispatchManager({ genId: () => v4_default() });
59700
- const here = typeof __dirname === "string" ? __dirname : import_node_path64.default.dirname((0, import_node_url4.fileURLToPath)(import_meta6.url));
59533
+ const here = typeof __dirname === "string" ? __dirname : import_node_path63.default.dirname((0, import_node_url4.fileURLToPath)(import_meta6.url));
59701
59534
  const mcpConfigs = [];
59702
59535
  const dispatchServerCandidates = [
59703
- import_node_path64.default.join(here, "dispatch", "mcp-server.cjs"),
59536
+ import_node_path63.default.join(here, "dispatch", "mcp-server.cjs"),
59704
59537
  // 生产 dist/index → dist/dispatch/mcp-server.cjs
59705
- import_node_path64.default.join(here, "..", "dist", "dispatch", "mcp-server.cjs")
59538
+ import_node_path63.default.join(here, "..", "dist", "dispatch", "mcp-server.cjs")
59706
59539
  // dev tsx src/index → ../dist/dispatch/mcp-server.cjs
59707
59540
  ];
59708
- const dispatchServerScriptPath = dispatchServerCandidates.find((p2) => import_node_fs52.default.existsSync(p2));
59541
+ const dispatchServerScriptPath = dispatchServerCandidates.find((p2) => import_node_fs51.default.existsSync(p2));
59709
59542
  if (dispatchServerScriptPath) {
59710
- const dispatchLogPath = import_node_path64.default.join(logDir, "dispatch-mcp-server.log");
59543
+ const dispatchLogPath = import_node_path63.default.join(logDir, "dispatch-mcp-server.log");
59711
59544
  const dispatchCfgPath = writeDispatchMcpConfig({
59712
59545
  dataDir: config.dataDir,
59713
59546
  serverScriptPath: dispatchServerScriptPath,
@@ -59725,12 +59558,12 @@ async function startDaemon(config) {
59725
59558
  });
59726
59559
  }
59727
59560
  const ticketServerCandidates = [
59728
- import_node_path64.default.join(here, "ticket", "mcp-server.cjs"),
59729
- import_node_path64.default.join(here, "..", "dist", "ticket", "mcp-server.cjs")
59561
+ import_node_path63.default.join(here, "ticket", "mcp-server.cjs"),
59562
+ import_node_path63.default.join(here, "..", "dist", "ticket", "mcp-server.cjs")
59730
59563
  ];
59731
- const ticketServerScriptPath = ticketServerCandidates.find((p2) => import_node_fs52.default.existsSync(p2));
59564
+ const ticketServerScriptPath = ticketServerCandidates.find((p2) => import_node_fs51.default.existsSync(p2));
59732
59565
  if (ticketServerScriptPath) {
59733
- const ticketLogPath = import_node_path64.default.join(logDir, "ticket-mcp-server.log");
59566
+ const ticketLogPath = import_node_path63.default.join(logDir, "ticket-mcp-server.log");
59734
59567
  const ticketCfgPath = writeTicketMcpConfig({
59735
59568
  dataDir: config.dataDir,
59736
59569
  serverScriptPath: ticketServerScriptPath,
@@ -59750,12 +59583,12 @@ async function startDaemon(config) {
59750
59583
  });
59751
59584
  }
59752
59585
  const shiftServerCandidates = [
59753
- import_node_path64.default.join(here, "shift", "mcp-server.cjs"),
59754
- import_node_path64.default.join(here, "..", "dist", "shift", "mcp-server.cjs")
59586
+ import_node_path63.default.join(here, "shift", "mcp-server.cjs"),
59587
+ import_node_path63.default.join(here, "..", "dist", "shift", "mcp-server.cjs")
59755
59588
  ];
59756
- const shiftServerScriptPath = shiftServerCandidates.find((p2) => import_node_fs52.default.existsSync(p2));
59589
+ const shiftServerScriptPath = shiftServerCandidates.find((p2) => import_node_fs51.default.existsSync(p2));
59757
59590
  if (shiftServerScriptPath) {
59758
- const shiftLogPath = import_node_path64.default.join(logDir, "shift-mcp-server.log");
59591
+ const shiftLogPath = import_node_path63.default.join(logDir, "shift-mcp-server.log");
59759
59592
  const shiftCfgPath = await writeShiftMcpConfig({
59760
59593
  dataDir: config.dataDir,
59761
59594
  serverScriptPath: shiftServerScriptPath,
@@ -59774,12 +59607,12 @@ async function startDaemon(config) {
59774
59607
  );
59775
59608
  }
59776
59609
  const inboxServerCandidates = [
59777
- import_node_path64.default.join(here, "inbox", "mcp-server.cjs"),
59778
- import_node_path64.default.join(here, "..", "dist", "inbox", "mcp-server.cjs")
59610
+ import_node_path63.default.join(here, "inbox", "mcp-server.cjs"),
59611
+ import_node_path63.default.join(here, "..", "dist", "inbox", "mcp-server.cjs")
59779
59612
  ];
59780
- const inboxServerScriptPath = inboxServerCandidates.find((p2) => import_node_fs52.default.existsSync(p2));
59613
+ const inboxServerScriptPath = inboxServerCandidates.find((p2) => import_node_fs51.default.existsSync(p2));
59781
59614
  if (inboxServerScriptPath) {
59782
- const inboxLogPath = import_node_path64.default.join(logDir, "inbox-mcp-server.log");
59615
+ const inboxLogPath = import_node_path63.default.join(logDir, "inbox-mcp-server.log");
59783
59616
  const inboxCfgPath = await writeInboxMcpConfig({
59784
59617
  dataDir: config.dataDir,
59785
59618
  serverScriptPath: inboxServerScriptPath,
@@ -59798,7 +59631,7 @@ async function startDaemon(config) {
59798
59631
  );
59799
59632
  }
59800
59633
  const shiftStore = createShiftStore({
59801
- filePath: import_node_path64.default.join(config.dataDir, "shift.json"),
59634
+ filePath: import_node_path63.default.join(config.dataDir, "shift.json"),
59802
59635
  ownerIdProvider: () => ownerPrincipalId,
59803
59636
  now: () => Date.now()
59804
59637
  });
@@ -59820,7 +59653,7 @@ async function startDaemon(config) {
59820
59653
  getAdapter,
59821
59654
  historyReader: history,
59822
59655
  dataDir: config.dataDir,
59823
- personaRoot: import_node_path64.default.join(config.dataDir, "personas"),
59656
+ personaRoot: import_node_path63.default.join(config.dataDir, "personas"),
59824
59657
  usersRoot,
59825
59658
  personaStore,
59826
59659
  ownerDisplayName,
@@ -59858,10 +59691,10 @@ async function startDaemon(config) {
59858
59691
  // 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
59859
59692
  attachmentGroup: {
59860
59693
  onFileEdit: (input) => {
59861
- const absPath = import_node_path64.default.isAbsolute(input.relPath) ? input.relPath : import_node_path64.default.join(input.cwd, input.relPath);
59694
+ const absPath = import_node_path63.default.isAbsolute(input.relPath) ? input.relPath : import_node_path63.default.join(input.cwd, input.relPath);
59862
59695
  let size = 0;
59863
59696
  try {
59864
- size = import_node_fs52.default.statSync(absPath).size;
59697
+ size = import_node_fs51.default.statSync(absPath).size;
59865
59698
  } catch (err) {
59866
59699
  logger.warn("attachment.onFileEdit stat failed", {
59867
59700
  sessionId: input.sessionId,
@@ -60048,6 +59881,13 @@ async function startDaemon(config) {
60048
59881
  /* @__PURE__ */ new Set([...config.previewPorts ?? [], ...appBuilderPortRange])
60049
59882
  );
60050
59883
  const sshLog = createContactSshLog(config.dataDir);
59884
+ try {
59885
+ await ensureSshIdentity(config.dataDir);
59886
+ } catch (e) {
59887
+ logger.warn("ssh-identity generate failed; contact SSH pubkey exchange disabled", {
59888
+ err: e.message
59889
+ });
59890
+ }
60051
59891
  const makeHandlers = () => buildMethodHandlers({
60052
59892
  manager,
60053
59893
  workspace,
@@ -60091,11 +59931,11 @@ async function startDaemon(config) {
60091
59931
  // 'persona/<pid>/owner',default 走 'default'。
60092
59932
  getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
60093
59933
  // guest path guard:candidate 必须在 personaRoot 子树或调用者自己的 user-dir 下
60094
- personaRoot: import_node_path64.default.join(config.dataDir, "personas"),
59934
+ personaRoot: import_node_path63.default.join(config.dataDir, "personas"),
60095
59935
  usersRoot
60096
59936
  },
60097
59937
  // workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
60098
- personaRoot: import_node_path64.default.join(config.dataDir, "personas"),
59938
+ personaRoot: import_node_path63.default.join(config.dataDir, "personas"),
60099
59939
  // v2 多人 persona 隔离:handler 派生 guest user-dir 放行
60100
59940
  usersRoot,
60101
59941
  // capability:list / delete handler 依赖
@@ -60108,6 +59948,9 @@ async function startDaemon(config) {
60108
59948
  ownerFeishuUnionId,
60109
59949
  ownerId,
60110
59950
  ownerProvider,
59951
+ // ssh identity 互换(spec 2026-07-15):whoami:ok 下发本机 identity pubkey。
59952
+ // 惰性读盘,不依赖 ensureSshIdentity 完成时序(未生成 → null → 帧省略字段)。
59953
+ sshIdentityPublicKey: () => readSshIdentityPublicKey(config.dataDir),
60111
59954
  personaStore,
60112
59955
  // capability handler 也用 (capability:issue 走 registry / capability:list)
60113
59956
  capabilityRegistry,
@@ -60119,7 +59962,7 @@ async function startDaemon(config) {
60119
59962
  contactStore,
60120
59963
  // <dataDir>/sshd 绝对路径 —— contact-ssh handlers 用它拼 authorized_keys / keys/ 子路径
60121
59964
  // Task 10 会加 SshdManager 起 sshd;handlers wire 提前挂 sshdDir 让 typecheck 过
60122
- sshdDir: import_node_path64.default.join(config.dataDir, "sshd"),
59965
+ sshdDir: import_node_path63.default.join(config.dataDir, "sshd"),
60123
59966
  contactSshLog: sshLog,
60124
59967
  // inbox:sendDm 用:sessionId → session 出身(复用 attachment 同款 findOwnedSessionScope)
60125
59968
  getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
@@ -60187,7 +60030,11 @@ async function startDaemon(config) {
60187
60030
  // 自动反向(spec 决策 #11):本机可达地址 = tunnel URL(公网),出站连接时
60188
60031
  // 透传让对方反向加我为联系人。无 tunnel(仅本机 ws)→ null,对方不自动反向(不造假数据)。
60189
60032
  // 决策 #16:身份不自报——本机 deviceId/ownerId/provider 由 exchange 签进 token 背书。
60190
- selfUrl: () => currentTunnelUrl
60033
+ selfUrl: () => currentTunnelUrl,
60034
+ // ssh identity 互换(spec 2026-07-15):出站 auth 帧自报本机 identity pubkey;
60035
+ // 对方 pubkey 变化且已授权时立即重建 authorized_keys(重新 connect = 迁移路径)
60036
+ selfSshPublicKey: () => readSshIdentityPublicKey(config.dataDir),
60037
+ rebuildAuthorizedKeys: () => rebuildAuthorizedKeys(contactStore, import_node_path63.default.join(config.dataDir, "sshd"))
60191
60038
  },
60192
60039
  // app-builder
60193
60040
  appBuilderStore,
@@ -60210,11 +60057,11 @@ async function startDaemon(config) {
60210
60057
  // 发布上线脚手架化 (spec 2026-06-03 §5.2):
60211
60058
  // appBuilderPersonaRoot 用于拼 publish.sh 绝对路径(persona-app-builder 安装在
60212
60059
  // dataDir/personas/persona-app-builder 之下,extension-kit/scripts/publish.sh 是相对路径)。
60213
- appBuilderPersonaRoot: import_node_path64.default.join(config.dataDir, "personas", "persona-app-builder"),
60060
+ appBuilderPersonaRoot: import_node_path63.default.join(config.dataDir, "personas", "persona-app-builder"),
60214
60061
  // 共享 deploy-kit 根:scaffold/publish 脚本骨架 + 阿里云凭证单一真源。
60215
- deployKitRoot: import_node_path64.default.join(config.dataDir, "deploy-kit"),
60062
+ deployKitRoot: import_node_path63.default.join(config.dataDir, "deploy-kit"),
60216
60063
  // scaffold/publish 按当前 session 的 persona 解析其安装根,让每个 persona 用自己的模板/注入配置。
60217
- resolvePersonaRoot: (personaId) => import_node_path64.default.join(config.dataDir, "personas", personaId),
60064
+ resolvePersonaRoot: (personaId) => import_node_path63.default.join(config.dataDir, "personas", personaId),
60218
60065
  // 发布上线脚手架化 (spec 2026-06-03 §5.2.2):
60219
60066
  // 复用 SessionManagerDeps.broadcastFrame 同款 dispatch 逻辑 —— runner 调 manager.send
60220
60067
  // 取回 broadcast 帧后逐帧 push 到 transport,跟 manager 自身的 deps 一致。
@@ -60257,7 +60104,7 @@ async function startDaemon(config) {
60257
60104
  }
60258
60105
  let sourceJsonlPath = "(no transcript yet \u2014 operate from the task description alone)";
60259
60106
  if (sourceFile && sourceFile.toolSessionId) {
60260
- sourceJsonlPath = import_node_path64.default.join(
60107
+ sourceJsonlPath = import_node_path63.default.join(
60261
60108
  import_node_os21.default.homedir(),
60262
60109
  ".claude",
60263
60110
  "projects",
@@ -60617,8 +60464,8 @@ async function startDaemon(config) {
60617
60464
  const lines = [
60618
60465
  `Tunnel: ${r.url}`,
60619
60466
  ...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
60620
- `Frpc config: ${import_node_path64.default.join(config.dataDir, "frpc.toml")}`,
60621
- `Frpc log: ${import_node_path64.default.join(logDir, "frpc.log")}`
60467
+ `Frpc config: ${import_node_path63.default.join(config.dataDir, "frpc.toml")}`,
60468
+ `Frpc log: ${import_node_path63.default.join(logDir, "frpc.log")}`
60622
60469
  ];
60623
60470
  const width = Math.max(...lines.map((l) => l.length));
60624
60471
  const bar = "\u2550".repeat(width + 4);
@@ -60631,8 +60478,8 @@ ${bar}
60631
60478
 
60632
60479
  `);
60633
60480
  try {
60634
- const connectPath = import_node_path64.default.join(config.dataDir, "connect.txt");
60635
- import_node_fs52.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
60481
+ const connectPath = import_node_path63.default.join(config.dataDir, "connect.txt");
60482
+ import_node_fs51.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
60636
60483
  } catch {
60637
60484
  }
60638
60485
  } catch (err) {
@@ -60668,21 +60515,13 @@ ${bar}
60668
60515
  });
60669
60516
  try {
60670
60517
  await sshdMgr.start();
60671
- rebuildAuthorizedKeys(contactStore, import_node_path64.default.join(config.dataDir, "sshd"));
60518
+ rebuildAuthorizedKeys(contactStore, import_node_path63.default.join(config.dataDir, "sshd"));
60672
60519
  logger.info("sshd: contact-ssh sandbox ready", { port: config.sshdPort });
60673
60520
  } catch (err) {
60674
60521
  logger.warn("sshd start failed; contact SSH grant will not work until fixed", {
60675
60522
  err: err.message
60676
60523
  });
60677
60524
  }
60678
- const contactKeyPuller = new ContactKeyPuller({
60679
- store: contactStore,
60680
- dataDir: config.dataDir,
60681
- logger,
60682
- intervalMs: 6e4,
60683
- sshLog
60684
- });
60685
- void contactKeyPuller.start();
60686
60525
  void reportDevice();
60687
60526
  void fetchServerKey();
60688
60527
  const tickAttachmentGc = () => {
@@ -60717,7 +60556,6 @@ ${bar}
60717
60556
  if (tunnelMgr) {
60718
60557
  await tunnelMgr.stop();
60719
60558
  }
60720
- contactKeyPuller.stop();
60721
60559
  await sshdMgr.stop().catch((err) => {
60722
60560
  logger.warn("shutdown.sshd-stop-failed", {
60723
60561
  error: err instanceof Error ? err.message : String(err)
@@ -60736,9 +60574,9 @@ ${bar}
60736
60574
  };
60737
60575
  }
60738
60576
  function migrateDropPersonsDir(dataDir) {
60739
- const dir = import_node_path64.default.join(dataDir, "persons");
60577
+ const dir = import_node_path63.default.join(dataDir, "persons");
60740
60578
  try {
60741
- import_node_fs52.default.rmSync(dir, { recursive: true, force: true });
60579
+ import_node_fs51.default.rmSync(dir, { recursive: true, force: true });
60742
60580
  } catch {
60743
60581
  }
60744
60582
  }