@clawos-dev/clawd 0.2.235 → 0.2.237

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,11 @@ 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
+ /** 连接方自报本机 SSH identity pubkey;见 doc/glossary/peer-mesh.md#建联交换清单 */
5330
+ selfSshPublicKey: external_exports.string().min(1).optional(),
5331
+ /** 连接方自报本机 OS 登录账号名;见 doc/glossary/peer-mesh.md#建联交换清单 */
5332
+ selfOsUser: external_exports.string().min(1).optional()
5330
5333
  });
5331
5334
  AuthOkFrameSchema = external_exports.object({
5332
5335
  type: external_exports.literal("auth:ok"),
@@ -5649,7 +5652,11 @@ var init_capability = __esm({
5649
5652
  id: external_exports.string().min(1),
5650
5653
  displayName: external_exports.string()
5651
5654
  }).strict()
5652
- )
5655
+ ),
5656
+ /** 被连方 SSH identity pubkey;见 doc/glossary/peer-mesh.md#建联交换清单 */
5657
+ sshPublicKey: external_exports.string().min(1).optional(),
5658
+ /** 被连方 OS 登录账号名;见 doc/glossary/peer-mesh.md#建联交换清单 */
5659
+ osUser: external_exports.string().min(1).optional()
5653
5660
  }).strict();
5654
5661
  PERSONAL_CAP_GRANTS = Object.freeze([
5655
5662
  Object.freeze({
@@ -5744,6 +5751,10 @@ var init_contact = __esm({
5744
5751
  * 极端场景(区别对方多个 persona)留 per-persona override 演进空间。
5745
5752
  */
5746
5753
  sshAllowed: external_exports.boolean().default(false),
5754
+ /** 对端设备 SSH identity pubkey(一行 `ssh-ed25519 AAAA... comment`);见 doc/glossary/peer-mesh.md#建联交换清单 */
5755
+ sshPublicKey: external_exports.string().optional(),
5756
+ /** 对端 OS 登录账号名,SSH 拨号登录名(`<osUser>@127.0.0.1`);见 doc/glossary/peer-mesh.md#建联交换清单 */
5757
+ osUser: external_exports.string().optional(),
5747
5758
  /**
5748
5759
  * @deprecated T-29 去沙箱后废弃。原为授权访问的目录白名单(clawd-ssh-jail 生成 sbpl/bwrap
5749
5760
  * policy 用);去掉 OS 沙箱后不再有目录级限制,SSH 授权即 on/off(见 sshAllowed)。
@@ -5790,7 +5801,7 @@ var init_contact = __esm({
5790
5801
  });
5791
5802
 
5792
5803
  // ../protocol/src/contact-ssh.ts
5793
- var ContactSetSshAccessArgsSchema, ContactSetSshAccessOkSchema, ContactSshKeyIssueArgsSchema, ContactSshKeyIssueOkSchema, ContactSshAccessUpdatedFrameSchema;
5804
+ var ContactSetSshAccessArgsSchema, ContactSetSshAccessOkSchema, ContactSshAccessUpdatedFrameSchema;
5794
5805
  var init_contact_ssh = __esm({
5795
5806
  "../protocol/src/contact-ssh.ts"() {
5796
5807
  "use strict";
@@ -5804,14 +5815,6 @@ var init_contact_ssh = __esm({
5804
5815
  deviceId: external_exports.string().min(1),
5805
5816
  sshAllowed: external_exports.boolean()
5806
5817
  }).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
5818
  ContactSshAccessUpdatedFrameSchema = external_exports.object({
5816
5819
  type: external_exports.literal("contact:ssh-access-updated"),
5817
5820
  deviceId: external_exports.string().min(1),
@@ -6506,8 +6509,8 @@ var require_req = __commonJS({
6506
6509
  if (req.originalUrl) {
6507
6510
  _req.url = req.originalUrl;
6508
6511
  } else {
6509
- const path77 = req.path;
6510
- _req.url = typeof path77 === "string" ? path77 : req.url ? req.url.path || req.url : void 0;
6512
+ const path76 = req.path;
6513
+ _req.url = typeof path76 === "string" ? path76 : req.url ? req.url.path || req.url : void 0;
6511
6514
  }
6512
6515
  if (req.query) {
6513
6516
  _req.query = req.query;
@@ -6672,14 +6675,14 @@ var require_redact = __commonJS({
6672
6675
  }
6673
6676
  return obj;
6674
6677
  }
6675
- function parsePath(path77) {
6678
+ function parsePath(path76) {
6676
6679
  const parts = [];
6677
6680
  let current = "";
6678
6681
  let inBrackets = false;
6679
6682
  let inQuotes = false;
6680
6683
  let quoteChar = "";
6681
- for (let i = 0; i < path77.length; i++) {
6682
- const char = path77[i];
6684
+ for (let i = 0; i < path76.length; i++) {
6685
+ const char = path76[i];
6683
6686
  if (!inBrackets && char === ".") {
6684
6687
  if (current) {
6685
6688
  parts.push(current);
@@ -6810,10 +6813,10 @@ var require_redact = __commonJS({
6810
6813
  return current;
6811
6814
  }
6812
6815
  function redactPaths(obj, paths, censor, remove = false) {
6813
- for (const path77 of paths) {
6814
- const parts = parsePath(path77);
6816
+ for (const path76 of paths) {
6817
+ const parts = parsePath(path76);
6815
6818
  if (parts.includes("*")) {
6816
- redactWildcardPath(obj, parts, censor, path77, remove);
6819
+ redactWildcardPath(obj, parts, censor, path76, remove);
6817
6820
  } else {
6818
6821
  if (remove) {
6819
6822
  removeKey(obj, parts);
@@ -6898,8 +6901,8 @@ var require_redact = __commonJS({
6898
6901
  }
6899
6902
  } else {
6900
6903
  if (afterWildcard.includes("*")) {
6901
- const wrappedCensor = typeof censor === "function" ? (value, path77) => {
6902
- const fullPath = [...pathArray.slice(0, pathLength), ...path77];
6904
+ const wrappedCensor = typeof censor === "function" ? (value, path76) => {
6905
+ const fullPath = [...pathArray.slice(0, pathLength), ...path76];
6903
6906
  return censor(value, fullPath);
6904
6907
  } : censor;
6905
6908
  redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
@@ -6934,8 +6937,8 @@ var require_redact = __commonJS({
6934
6937
  return null;
6935
6938
  }
6936
6939
  const pathStructure = /* @__PURE__ */ new Map();
6937
- for (const path77 of pathsToClone) {
6938
- const parts = parsePath(path77);
6940
+ for (const path76 of pathsToClone) {
6941
+ const parts = parsePath(path76);
6939
6942
  let current = pathStructure;
6940
6943
  for (let i = 0; i < parts.length; i++) {
6941
6944
  const part = parts[i];
@@ -6987,24 +6990,24 @@ var require_redact = __commonJS({
6987
6990
  }
6988
6991
  return cloneSelectively(obj, pathStructure);
6989
6992
  }
6990
- function validatePath(path77) {
6991
- if (typeof path77 !== "string") {
6993
+ function validatePath(path76) {
6994
+ if (typeof path76 !== "string") {
6992
6995
  throw new Error("Paths must be (non-empty) strings");
6993
6996
  }
6994
- if (path77 === "") {
6997
+ if (path76 === "") {
6995
6998
  throw new Error("Invalid redaction path ()");
6996
6999
  }
6997
- if (path77.includes("..")) {
6998
- throw new Error(`Invalid redaction path (${path77})`);
7000
+ if (path76.includes("..")) {
7001
+ throw new Error(`Invalid redaction path (${path76})`);
6999
7002
  }
7000
- if (path77.includes(",")) {
7001
- throw new Error(`Invalid redaction path (${path77})`);
7003
+ if (path76.includes(",")) {
7004
+ throw new Error(`Invalid redaction path (${path76})`);
7002
7005
  }
7003
7006
  let bracketCount = 0;
7004
7007
  let inQuotes = false;
7005
7008
  let quoteChar = "";
7006
- for (let i = 0; i < path77.length; i++) {
7007
- const char = path77[i];
7009
+ for (let i = 0; i < path76.length; i++) {
7010
+ const char = path76[i];
7008
7011
  if ((char === '"' || char === "'") && bracketCount > 0) {
7009
7012
  if (!inQuotes) {
7010
7013
  inQuotes = true;
@@ -7018,20 +7021,20 @@ var require_redact = __commonJS({
7018
7021
  } else if (char === "]" && !inQuotes) {
7019
7022
  bracketCount--;
7020
7023
  if (bracketCount < 0) {
7021
- throw new Error(`Invalid redaction path (${path77})`);
7024
+ throw new Error(`Invalid redaction path (${path76})`);
7022
7025
  }
7023
7026
  }
7024
7027
  }
7025
7028
  if (bracketCount !== 0) {
7026
- throw new Error(`Invalid redaction path (${path77})`);
7029
+ throw new Error(`Invalid redaction path (${path76})`);
7027
7030
  }
7028
7031
  }
7029
7032
  function validatePaths(paths) {
7030
7033
  if (!Array.isArray(paths)) {
7031
7034
  throw new TypeError("paths must be an array");
7032
7035
  }
7033
- for (const path77 of paths) {
7034
- validatePath(path77);
7036
+ for (const path76 of paths) {
7037
+ validatePath(path76);
7035
7038
  }
7036
7039
  }
7037
7040
  function slowRedact(options = {}) {
@@ -7199,8 +7202,8 @@ var require_redaction = __commonJS({
7199
7202
  if (shape[k2] === null) {
7200
7203
  o[k2] = (value) => topCensor(value, [k2]);
7201
7204
  } else {
7202
- const wrappedCensor = typeof censor === "function" ? (value, path77) => {
7203
- return censor(value, [k2, ...path77]);
7205
+ const wrappedCensor = typeof censor === "function" ? (value, path76) => {
7206
+ return censor(value, [k2, ...path76]);
7204
7207
  } : censor;
7205
7208
  o[k2] = Redact({
7206
7209
  paths: shape[k2],
@@ -7418,10 +7421,10 @@ var require_atomic_sleep = __commonJS({
7418
7421
  var require_sonic_boom = __commonJS({
7419
7422
  "../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
7420
7423
  "use strict";
7421
- var fs71 = require("fs");
7424
+ var fs70 = require("fs");
7422
7425
  var EventEmitter3 = require("events");
7423
7426
  var inherits = require("util").inherits;
7424
- var path77 = require("path");
7427
+ var path76 = require("path");
7425
7428
  var sleep2 = require_atomic_sleep();
7426
7429
  var assert = require("assert");
7427
7430
  var BUSY_WRITE_TIMEOUT = 100;
@@ -7475,20 +7478,20 @@ var require_sonic_boom = __commonJS({
7475
7478
  const mode = sonic.mode;
7476
7479
  if (sonic.sync) {
7477
7480
  try {
7478
- if (sonic.mkdir) fs71.mkdirSync(path77.dirname(file), { recursive: true });
7479
- const fd = fs71.openSync(file, flags, mode);
7481
+ if (sonic.mkdir) fs70.mkdirSync(path76.dirname(file), { recursive: true });
7482
+ const fd = fs70.openSync(file, flags, mode);
7480
7483
  fileOpened(null, fd);
7481
7484
  } catch (err) {
7482
7485
  fileOpened(err);
7483
7486
  throw err;
7484
7487
  }
7485
7488
  } else if (sonic.mkdir) {
7486
- fs71.mkdir(path77.dirname(file), { recursive: true }, (err) => {
7489
+ fs70.mkdir(path76.dirname(file), { recursive: true }, (err) => {
7487
7490
  if (err) return fileOpened(err);
7488
- fs71.open(file, flags, mode, fileOpened);
7491
+ fs70.open(file, flags, mode, fileOpened);
7489
7492
  });
7490
7493
  } else {
7491
- fs71.open(file, flags, mode, fileOpened);
7494
+ fs70.open(file, flags, mode, fileOpened);
7492
7495
  }
7493
7496
  }
7494
7497
  function SonicBoom(opts) {
@@ -7529,8 +7532,8 @@ var require_sonic_boom = __commonJS({
7529
7532
  this.flush = flushBuffer;
7530
7533
  this.flushSync = flushBufferSync;
7531
7534
  this._actualWrite = actualWriteBuffer;
7532
- fsWriteSync = () => fs71.writeSync(this.fd, this._writingBuf);
7533
- fsWrite = () => fs71.write(this.fd, this._writingBuf, this.release);
7535
+ fsWriteSync = () => fs70.writeSync(this.fd, this._writingBuf);
7536
+ fsWrite = () => fs70.write(this.fd, this._writingBuf, this.release);
7534
7537
  } else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
7535
7538
  this._writingBuf = "";
7536
7539
  this.write = write;
@@ -7539,15 +7542,15 @@ var require_sonic_boom = __commonJS({
7539
7542
  this._actualWrite = actualWrite;
7540
7543
  fsWriteSync = () => {
7541
7544
  if (Buffer.isBuffer(this._writingBuf)) {
7542
- return fs71.writeSync(this.fd, this._writingBuf);
7545
+ return fs70.writeSync(this.fd, this._writingBuf);
7543
7546
  }
7544
- return fs71.writeSync(this.fd, this._writingBuf, "utf8");
7547
+ return fs70.writeSync(this.fd, this._writingBuf, "utf8");
7545
7548
  };
7546
7549
  fsWrite = () => {
7547
7550
  if (Buffer.isBuffer(this._writingBuf)) {
7548
- return fs71.write(this.fd, this._writingBuf, this.release);
7551
+ return fs70.write(this.fd, this._writingBuf, this.release);
7549
7552
  }
7550
- return fs71.write(this.fd, this._writingBuf, "utf8", this.release);
7553
+ return fs70.write(this.fd, this._writingBuf, "utf8", this.release);
7551
7554
  };
7552
7555
  } else {
7553
7556
  throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
@@ -7604,7 +7607,7 @@ var require_sonic_boom = __commonJS({
7604
7607
  }
7605
7608
  }
7606
7609
  if (this._fsync) {
7607
- fs71.fsyncSync(this.fd);
7610
+ fs70.fsyncSync(this.fd);
7608
7611
  }
7609
7612
  const len = this._len;
7610
7613
  if (this._reopening) {
@@ -7718,7 +7721,7 @@ var require_sonic_boom = __commonJS({
7718
7721
  const onDrain = () => {
7719
7722
  if (!this._fsync) {
7720
7723
  try {
7721
- fs71.fsync(this.fd, (err) => {
7724
+ fs70.fsync(this.fd, (err) => {
7722
7725
  this._flushPending = false;
7723
7726
  cb(err);
7724
7727
  });
@@ -7820,7 +7823,7 @@ var require_sonic_boom = __commonJS({
7820
7823
  const fd = this.fd;
7821
7824
  this.once("ready", () => {
7822
7825
  if (fd !== this.fd) {
7823
- fs71.close(fd, (err) => {
7826
+ fs70.close(fd, (err) => {
7824
7827
  if (err) {
7825
7828
  return this.emit("error", err);
7826
7829
  }
@@ -7869,7 +7872,7 @@ var require_sonic_boom = __commonJS({
7869
7872
  buf = this._bufs[0];
7870
7873
  }
7871
7874
  try {
7872
- const n = Buffer.isBuffer(buf) ? fs71.writeSync(this.fd, buf) : fs71.writeSync(this.fd, buf, "utf8");
7875
+ const n = Buffer.isBuffer(buf) ? fs70.writeSync(this.fd, buf) : fs70.writeSync(this.fd, buf, "utf8");
7873
7876
  const releasedBufObj = releaseWritingBuf(buf, this._len, n);
7874
7877
  buf = releasedBufObj.writingBuf;
7875
7878
  this._len = releasedBufObj.len;
@@ -7885,7 +7888,7 @@ var require_sonic_boom = __commonJS({
7885
7888
  }
7886
7889
  }
7887
7890
  try {
7888
- fs71.fsyncSync(this.fd);
7891
+ fs70.fsyncSync(this.fd);
7889
7892
  } catch {
7890
7893
  }
7891
7894
  }
@@ -7906,7 +7909,7 @@ var require_sonic_boom = __commonJS({
7906
7909
  buf = mergeBuf(this._bufs[0], this._lens[0]);
7907
7910
  }
7908
7911
  try {
7909
- const n = fs71.writeSync(this.fd, buf);
7912
+ const n = fs70.writeSync(this.fd, buf);
7910
7913
  buf = buf.subarray(n);
7911
7914
  this._len = Math.max(this._len - n, 0);
7912
7915
  if (buf.length <= 0) {
@@ -7934,13 +7937,13 @@ var require_sonic_boom = __commonJS({
7934
7937
  this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
7935
7938
  if (this.sync) {
7936
7939
  try {
7937
- const written = Buffer.isBuffer(this._writingBuf) ? fs71.writeSync(this.fd, this._writingBuf) : fs71.writeSync(this.fd, this._writingBuf, "utf8");
7940
+ const written = Buffer.isBuffer(this._writingBuf) ? fs70.writeSync(this.fd, this._writingBuf) : fs70.writeSync(this.fd, this._writingBuf, "utf8");
7938
7941
  release(null, written);
7939
7942
  } catch (err) {
7940
7943
  release(err);
7941
7944
  }
7942
7945
  } else {
7943
- fs71.write(this.fd, this._writingBuf, release);
7946
+ fs70.write(this.fd, this._writingBuf, release);
7944
7947
  }
7945
7948
  }
7946
7949
  function actualWriteBuffer() {
@@ -7949,7 +7952,7 @@ var require_sonic_boom = __commonJS({
7949
7952
  this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
7950
7953
  if (this.sync) {
7951
7954
  try {
7952
- const written = fs71.writeSync(this.fd, this._writingBuf);
7955
+ const written = fs70.writeSync(this.fd, this._writingBuf);
7953
7956
  release(null, written);
7954
7957
  } catch (err) {
7955
7958
  release(err);
@@ -7958,7 +7961,7 @@ var require_sonic_boom = __commonJS({
7958
7961
  if (kCopyBuffer) {
7959
7962
  this._writingBuf = Buffer.from(this._writingBuf);
7960
7963
  }
7961
- fs71.write(this.fd, this._writingBuf, release);
7964
+ fs70.write(this.fd, this._writingBuf, release);
7962
7965
  }
7963
7966
  }
7964
7967
  function actualClose(sonic) {
@@ -7974,12 +7977,12 @@ var require_sonic_boom = __commonJS({
7974
7977
  sonic._lens = [];
7975
7978
  assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
7976
7979
  try {
7977
- fs71.fsync(sonic.fd, closeWrapped);
7980
+ fs70.fsync(sonic.fd, closeWrapped);
7978
7981
  } catch {
7979
7982
  }
7980
7983
  function closeWrapped() {
7981
7984
  if (sonic.fd !== 1 && sonic.fd !== 2) {
7982
- fs71.close(sonic.fd, done);
7985
+ fs70.close(sonic.fd, done);
7983
7986
  } else {
7984
7987
  done();
7985
7988
  }
@@ -11114,11 +11117,11 @@ var init_lib = __esm({
11114
11117
  }
11115
11118
  }
11116
11119
  },
11117
- addToPath: function addToPath(path77, added, removed, oldPosInc, options) {
11118
- var last = path77.lastComponent;
11120
+ addToPath: function addToPath(path76, added, removed, oldPosInc, options) {
11121
+ var last = path76.lastComponent;
11119
11122
  if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
11120
11123
  return {
11121
- oldPos: path77.oldPos + oldPosInc,
11124
+ oldPos: path76.oldPos + oldPosInc,
11122
11125
  lastComponent: {
11123
11126
  count: last.count + 1,
11124
11127
  added,
@@ -11128,7 +11131,7 @@ var init_lib = __esm({
11128
11131
  };
11129
11132
  } else {
11130
11133
  return {
11131
- oldPos: path77.oldPos + oldPosInc,
11134
+ oldPos: path76.oldPos + oldPosInc,
11132
11135
  lastComponent: {
11133
11136
  count: 1,
11134
11137
  added,
@@ -11633,10 +11636,10 @@ function attachmentToHistoryMessage(o, ts) {
11633
11636
  const memories = raw.map((m2) => {
11634
11637
  if (!m2 || typeof m2 !== "object") return null;
11635
11638
  const rec3 = m2;
11636
- const path77 = typeof rec3.path === "string" ? rec3.path : null;
11639
+ const path76 = typeof rec3.path === "string" ? rec3.path : null;
11637
11640
  const content = typeof rec3.content === "string" ? rec3.content : null;
11638
- if (!path77 || content == null) return null;
11639
- const entry = { path: path77, content };
11641
+ if (!path76 || content == null) return null;
11642
+ const entry = { path: path76, content };
11640
11643
  if (typeof rec3.mtimeMs === "number") entry.mtimeMs = rec3.mtimeMs;
11641
11644
  return entry;
11642
11645
  }).filter((m2) => m2 !== null);
@@ -12439,10 +12442,10 @@ function parseAttachment(obj) {
12439
12442
  const memories = raw.map((m2) => {
12440
12443
  if (!m2 || typeof m2 !== "object") return null;
12441
12444
  const rec3 = m2;
12442
- const path77 = typeof rec3.path === "string" ? rec3.path : null;
12445
+ const path76 = typeof rec3.path === "string" ? rec3.path : null;
12443
12446
  const content = typeof rec3.content === "string" ? rec3.content : null;
12444
- if (!path77 || content == null) return null;
12445
- const out = { path: path77, content };
12447
+ if (!path76 || content == null) return null;
12448
+ const out = { path: path76, content };
12446
12449
  if (typeof rec3.mtimeMs === "number") out.mtimeMs = rec3.mtimeMs;
12447
12450
  return out;
12448
12451
  }).filter((m2) => m2 !== null);
@@ -33428,8 +33431,8 @@ var require_utils = __commonJS({
33428
33431
  var result = transform[inputType][outputType](input);
33429
33432
  return result;
33430
33433
  };
33431
- exports2.resolve = function(path77) {
33432
- var parts = path77.split("/");
33434
+ exports2.resolve = function(path76) {
33435
+ var parts = path76.split("/");
33433
33436
  var result = [];
33434
33437
  for (var index = 0; index < parts.length; index++) {
33435
33438
  var part = parts[index];
@@ -39282,18 +39285,18 @@ var require_object = __commonJS({
39282
39285
  var object = new ZipObject(name, zipObjectContent, o);
39283
39286
  this.files[name] = object;
39284
39287
  };
39285
- var parentFolder = function(path77) {
39286
- if (path77.slice(-1) === "/") {
39287
- path77 = path77.substring(0, path77.length - 1);
39288
+ var parentFolder = function(path76) {
39289
+ if (path76.slice(-1) === "/") {
39290
+ path76 = path76.substring(0, path76.length - 1);
39288
39291
  }
39289
- var lastSlash = path77.lastIndexOf("/");
39290
- return lastSlash > 0 ? path77.substring(0, lastSlash) : "";
39292
+ var lastSlash = path76.lastIndexOf("/");
39293
+ return lastSlash > 0 ? path76.substring(0, lastSlash) : "";
39291
39294
  };
39292
- var forceTrailingSlash = function(path77) {
39293
- if (path77.slice(-1) !== "/") {
39294
- path77 += "/";
39295
+ var forceTrailingSlash = function(path76) {
39296
+ if (path76.slice(-1) !== "/") {
39297
+ path76 += "/";
39295
39298
  }
39296
- return path77;
39299
+ return path76;
39297
39300
  };
39298
39301
  var folderAdd = function(name, createFolders) {
39299
39302
  createFolders = typeof createFolders !== "undefined" ? createFolders : defaults.createFolders;
@@ -40295,7 +40298,7 @@ var require_lib3 = __commonJS({
40295
40298
  // src/run-case/recorder.ts
40296
40299
  function startRunCaseRecorder(opts) {
40297
40300
  const now = opts.now ?? Date.now;
40298
- const dir = import_node_path65.default.dirname(opts.recordPath);
40301
+ const dir = import_node_path64.default.dirname(opts.recordPath);
40299
40302
  let stream = null;
40300
40303
  let closing = false;
40301
40304
  let closedSettled = false;
@@ -40309,8 +40312,8 @@ function startRunCaseRecorder(opts) {
40309
40312
  });
40310
40313
  const ensureStream = () => {
40311
40314
  if (stream) return stream;
40312
- import_node_fs53.default.mkdirSync(dir, { recursive: true });
40313
- stream = import_node_fs53.default.createWriteStream(opts.recordPath, { flags: "a" });
40315
+ import_node_fs52.default.mkdirSync(dir, { recursive: true });
40316
+ stream = import_node_fs52.default.createWriteStream(opts.recordPath, { flags: "a" });
40314
40317
  stream.on("close", () => closedResolve());
40315
40318
  return stream;
40316
40319
  };
@@ -40335,12 +40338,12 @@ function startRunCaseRecorder(opts) {
40335
40338
  };
40336
40339
  return { tap, close, closed };
40337
40340
  }
40338
- var import_node_fs53, import_node_path65;
40341
+ var import_node_fs52, import_node_path64;
40339
40342
  var init_recorder = __esm({
40340
40343
  "src/run-case/recorder.ts"() {
40341
40344
  "use strict";
40342
- import_node_fs53 = __toESM(require("fs"), 1);
40343
- import_node_path65 = __toESM(require("path"), 1);
40345
+ import_node_fs52 = __toESM(require("fs"), 1);
40346
+ import_node_path64 = __toESM(require("path"), 1);
40344
40347
  }
40345
40348
  });
40346
40349
 
@@ -40383,7 +40386,7 @@ var init_wire = __esm({
40383
40386
  // src/run-case/controller.ts
40384
40387
  async function runController(opts) {
40385
40388
  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-"));
40389
+ const cwd = opts.cwd ?? (0, import_node_fs53.mkdtempSync)(import_node_path65.default.join(import_node_os22.default.tmpdir(), "clawd-runcase-"));
40387
40390
  const ownsCwd = opts.cwd === void 0;
40388
40391
  const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
40389
40392
  const spawnCtx = { cwd };
@@ -40544,19 +40547,19 @@ async function runController(opts) {
40544
40547
  if (sigintHandler) process.off("SIGINT", sigintHandler);
40545
40548
  if (ownsCwd) {
40546
40549
  try {
40547
- (0, import_node_fs54.rmSync)(cwd, { recursive: true, force: true });
40550
+ (0, import_node_fs53.rmSync)(cwd, { recursive: true, force: true });
40548
40551
  } catch {
40549
40552
  }
40550
40553
  }
40551
40554
  return exitCode ?? 0;
40552
40555
  }
40553
- var import_node_fs54, import_node_os22, import_node_path66;
40556
+ var import_node_fs53, import_node_os22, import_node_path65;
40554
40557
  var init_controller = __esm({
40555
40558
  "src/run-case/controller.ts"() {
40556
40559
  "use strict";
40557
- import_node_fs54 = require("fs");
40560
+ import_node_fs53 = require("fs");
40558
40561
  import_node_os22 = __toESM(require("os"), 1);
40559
- import_node_path66 = __toESM(require("path"), 1);
40562
+ import_node_path65 = __toESM(require("path"), 1);
40560
40563
  init_claude();
40561
40564
  init_stdout_splitter();
40562
40565
  init_permission_stdio();
@@ -40760,8 +40763,12 @@ Options:
40760
40763
 
40761
40764
  Example:
40762
40765
  ssh -o ProxyCommand='~/.clawd/bin/clawd ssh-relay <peer-device-id>' \\
40763
- -i ~/.clawd/contact-ssh-keys/<peer-device-id>.ed25519 \\
40764
- $USER@127.0.0.1
40766
+ -i ~/.clawd/ssh-identity/id_ed25519 \\
40767
+ <peer-os-user>@127.0.0.1
40768
+
40769
+ \u767B\u5F55\u540D\u7528\u5BF9\u7AEF\u8D26\u53F7\uFF08Contact.osUser\uFF0C\u5EFA\u8054\u63E1\u624B\u4EA4\u6362\uFF09\u2014\u2014\u4E0D\u662F\u672C\u673A $USER\u3002
40770
+ \u4E24\u673A\u8D26\u53F7\u540D\u4E0D\u540C\u65F6\u7528 $USER \u4F1A Permission denied (publickey)\u3002
40771
+ \u89C1 doc/glossary/peer-mesh.md#\u5EFA\u8054\u4EA4\u6362\u6E05\u5355
40765
40772
  `;
40766
40773
  }
40767
40774
  });
@@ -40950,8 +40957,8 @@ Env (advanced):
40950
40957
  `;
40951
40958
 
40952
40959
  // src/index.ts
40953
- var import_node_path64 = __toESM(require("path"), 1);
40954
- var import_node_fs52 = __toESM(require("fs"), 1);
40960
+ var import_node_path63 = __toESM(require("path"), 1);
40961
+ var import_node_fs51 = __toESM(require("fs"), 1);
40955
40962
  var import_node_os21 = __toESM(require("os"), 1);
40956
40963
 
40957
40964
  // ../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/stringify.js
@@ -41702,7 +41709,7 @@ function composeGuestSandbox(base, userWorkDir, spawnCwd) {
41702
41709
  fsv.allowRead = unionArr(fsv.allowRead, [userWorkDir]);
41703
41710
  fsv.allowWrite = unionArr(fsv.allowWrite, [userWorkDir]);
41704
41711
  fsv.allowRead = unionArr(fsv.allowRead, [
41705
- "~/.clawd/contact-ssh-keys",
41712
+ "~/.clawd/ssh-identity",
41706
41713
  "~/.clawd/bin",
41707
41714
  "~/Library/Application Support/@clawos/clawd-desktop/ota-bundles",
41708
41715
  "~/.clawd/config.json",
@@ -41978,28 +41985,27 @@ deviceId \u76F4\u63A5\u53D6\u81EA token\uFF0C\u4E0D\u8981\u6539\u5199\u6216\u731
41978
41985
  // src/sshd/contact-ssh-prompt.ts
41979
41986
  var CONTACT_SSH_SYSTEM_PROMPT_HINT = `## \u8DE8\u8BBE\u5907\u6587\u4EF6\u8BBF\u95EE\uFF08Contact SSH\uFF09
41980
41987
 
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
- \`\`\`
41988
+ \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
41989
+
41990
+ \u7528 SSH \u62E8\u53F7\uFF08\u628A \`<A>\` \u6362\u6210\u5BF9\u7AEF deviceId\uFF09\uFF1A
41991
+ \`\`\`bash
41992
+ ssh -o ProxyCommand='~/.clawd/bin/clawd ssh-relay <A>' \\
41993
+ -i ~/.clawd/ssh-identity/id_ed25519 \\
41994
+ -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \\
41995
+ $USER@127.0.0.1 <command>
41996
+ # \u4F8B\u5982\uFF1A... $USER@127.0.0.1 cat /Users/xxx/some/file.md
41997
+ # ... $USER@127.0.0.1 ls -la /Users/xxx/proj
41998
+ # \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
41999
+ # \`~/.clawd/bin/clawd\` \u662F daemon \u542F\u52A8\u65F6 seed \u7684 shim\uFF0C\u786C\u7F16\u7801\u5F53\u524D daemon \u7684
42000
+ # 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
42001
+ \`\`\`
41996
42002
 
41997
42003
  **\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
42004
 
41999
42005
  **\u6CE8\u610F**\uFF1A
42000
42006
  - \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
42007
+ - \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
42008
  - \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
42009
  `;
42004
42010
 
42005
42011
  // src/session/reducer.ts
@@ -46754,8 +46760,8 @@ function turnStartInput(text) {
46754
46760
  const items = [];
46755
46761
  let leftover = text;
46756
46762
  for (const m2 of text.matchAll(SKILL_RE)) {
46757
- const [marker, name, path77] = m2;
46758
- items.push({ type: "skill", name, path: path77 });
46763
+ const [marker, name, path76] = m2;
46764
+ items.push({ type: "skill", name, path: path76 });
46759
46765
  leftover = leftover.replace(marker, "");
46760
46766
  }
46761
46767
  for (const m2 of text.matchAll(ATTACHMENT_RE2)) {
@@ -49108,43 +49114,6 @@ async function forwardInboxPostToPeer(args) {
49108
49114
  }
49109
49115
  return { ok: true };
49110
49116
  }
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
49117
 
49149
49118
  // src/tools/codex-history.ts
49150
49119
  var import_node_child_process5 = require("child_process");
@@ -49332,13 +49301,13 @@ function mapSkillsListResponse(res) {
49332
49301
  const r = s ?? {};
49333
49302
  const name = str3(r.name);
49334
49303
  if (!name) continue;
49335
- const path77 = str3(r.path);
49304
+ const path76 = str3(r.path);
49336
49305
  const description = str3(r.description);
49337
49306
  const isPlugin = name.includes(":");
49338
49307
  out.push({
49339
49308
  name,
49340
49309
  source: isPlugin ? "plugin" : "project",
49341
- ...path77 ? { path: path77 } : {},
49310
+ ...path76 ? { path: path76 } : {},
49342
49311
  ...description ? { description } : {},
49343
49312
  ...isPlugin ? { plugin: name.split(":")[0] } : {}
49344
49313
  });
@@ -50624,7 +50593,12 @@ var AuthGate = class {
50624
50593
  st.pending = true;
50625
50594
  let r;
50626
50595
  try {
50627
- r = await this.opts.authenticate(parsed.data.token, parsed.data.selfUrl);
50596
+ r = await this.opts.authenticate(
50597
+ parsed.data.token,
50598
+ parsed.data.selfUrl,
50599
+ parsed.data.selfSshPublicKey,
50600
+ parsed.data.selfOsUser
50601
+ );
50628
50602
  } finally {
50629
50603
  st.pending = false;
50630
50604
  }
@@ -51269,7 +51243,11 @@ async function connectRemote(args) {
51269
51243
  // 缺省时省略字段(不发空串),对方 schema selfUrl 是 .min(1).optional()。
51270
51244
  // 决策 #16:身份字段(selfPrincipalId/selfDisplayName/selfDeviceId)已删——
51271
51245
  // 对方从 connect token 签名背书取 deviceId/ownerId/provider,不信自报。
51272
- ...args.selfUrl ? { selfUrl: args.selfUrl } : {}
51246
+ ...args.selfUrl ? { selfUrl: args.selfUrl } : {},
51247
+ // 建联交换:自报本机 SSH pubkey + OS 账号名;缺省省略字段(不发空串)。
51248
+ // 见 doc/glossary/peer-mesh.md#建联交换清单
51249
+ ...args.selfSshPublicKey ? { selfSshPublicKey: args.selfSshPublicKey } : {},
51250
+ ...args.selfOsUser ? { selfOsUser: args.selfOsUser } : {}
51273
51251
  })
51274
51252
  );
51275
51253
  await new Promise((resolve6, reject) => {
@@ -51344,7 +51322,10 @@ async function connectRemote(args) {
51344
51322
  ownerId: f.owner.ownerId,
51345
51323
  provider: f.owner.provider,
51346
51324
  displayName: f.owner.displayName,
51347
- grants: f.capability.grants
51325
+ grants: f.capability.grants,
51326
+ // 建联交换:对方老版本不带 → 省略(不落 undefined 占位)。见 …#建联交换清单
51327
+ ...f.sshPublicKey ? { sshPublicKey: f.sshPublicKey } : {},
51328
+ ...f.osUser ? { osUser: f.osUser } : {}
51348
51329
  };
51349
51330
  },
51350
51331
  close: () => {
@@ -51365,6 +51346,9 @@ async function autoReverseContact(args) {
51365
51346
  resource: { ...g2.resource },
51366
51347
  actions: [...g2.actions]
51367
51348
  }));
51349
+ const existing = args.store.get(args.deviceId) ?? void 0;
51350
+ const sshPublicKey = args.sshPublicKey ?? existing?.sshPublicKey;
51351
+ const osUser = args.osUser ?? existing?.osUser;
51368
51352
  const base = {
51369
51353
  deviceId: args.deviceId,
51370
51354
  ownerId: args.ownerId,
@@ -51374,12 +51358,18 @@ 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
+ // 建联交换:缺省不落 undefined 占位(JSON 持久化干净)。见 …#建联交换清单
51364
+ ...sshPublicKey ? { sshPublicKey } : {},
51365
+ ...osUser ? { osUser } : {},
51379
51366
  exposedDirs: []
51380
51367
  };
51381
51368
  args.store.upsert(base);
51382
51369
  args.broadcast({ type: "contact:added", contact: base });
51370
+ if (base.sshAllowed && sshPublicKey && sshPublicKey !== existing?.sshPublicKey) {
51371
+ args.rebuildAuthorizedKeys?.();
51372
+ }
51383
51373
  if (!args.exchangeToken) return;
51384
51374
  let token;
51385
51375
  try {
@@ -53862,10 +53852,10 @@ function rebuildAuthorizedKeys(store, sshdDir) {
53862
53852
  if (!c.sshAllowed) continue;
53863
53853
  const safe = /^[A-Za-z0-9_.-]+$/.test(c.deviceId);
53864
53854
  if (!safe) continue;
53865
- const pubkey = readIssuedPubkey(sshdDir, c.deviceId);
53866
- if (!pubkey) continue;
53855
+ const pubkey = c.sshPublicKey?.trim();
53856
+ if (!pubkey || /[\r\n"]/.test(pubkey)) continue;
53867
53857
  const bin = jailBinPath();
53868
- lines.push(`command="${bin} ${c.deviceId}",restrict ${pubkey.trim()}`);
53858
+ lines.push(`command="${bin} ${c.deviceId}",restrict ${pubkey}`);
53869
53859
  lines.push(`# contact:${c.deviceId}`);
53870
53860
  }
53871
53861
  const body = lines.join("\n") + "\n";
@@ -53873,25 +53863,59 @@ function rebuildAuthorizedKeys(store, sshdDir) {
53873
53863
  import_node_fs40.default.writeFileSync(tmp, body, { mode: 384 });
53874
53864
  import_node_fs40.default.renameSync(tmp, target);
53875
53865
  }
53876
- function readIssuedPubkey(sshdDir, deviceId) {
53877
- const safeId = deviceId.replace(/[\/\\]/g, "_");
53878
- const p2 = import_node_path40.default.join(sshdDir, "keys", `${safeId}.ed25519.pub`);
53866
+
53867
+ // src/sshd/ssh-identity.ts
53868
+ var import_node_fs41 = __toESM(require("fs"), 1);
53869
+ var import_node_path41 = __toESM(require("path"), 1);
53870
+ var import_node_child_process12 = require("child_process");
53871
+ var IDENTITY_DIR = "ssh-identity";
53872
+ var KEY_FILE = "id_ed25519";
53873
+ function identityPaths(dataDir) {
53874
+ const privPath = import_node_path41.default.join(dataDir, IDENTITY_DIR, KEY_FILE);
53875
+ return { privPath, pubPath: `${privPath}.pub` };
53876
+ }
53877
+ async function ensureSshIdentity(dataDir, opts = {}) {
53878
+ const { privPath, pubPath } = identityPaths(dataDir);
53879
+ import_node_fs41.default.mkdirSync(import_node_path41.default.dirname(privPath), { recursive: true, mode: 448 });
53880
+ if (import_node_fs41.default.existsSync(privPath) && import_node_fs41.default.existsSync(pubPath)) {
53881
+ return { privateKeyPath: privPath, publicKeyLine: import_node_fs41.default.readFileSync(pubPath, "utf8").trim() };
53882
+ }
53883
+ const bin = opts.keygenBin ?? "/usr/bin/ssh-keygen";
53884
+ await new Promise((resolve6, reject) => {
53885
+ const p2 = (opts.spawnImpl ?? import_node_child_process12.spawn)(
53886
+ bin,
53887
+ ["-t", "ed25519", "-f", privPath, "-N", "", "-q", "-C", "clawd-ssh-identity"],
53888
+ { stdio: "ignore" }
53889
+ );
53890
+ p2.on("exit", (code) => code === 0 ? resolve6() : reject(new Error(`ssh-keygen exit ${code}`)));
53891
+ p2.on("error", reject);
53892
+ });
53879
53893
  try {
53880
- return import_node_fs40.default.readFileSync(p2, "utf8");
53894
+ import_node_fs41.default.chmodSync(privPath, 384);
53895
+ } catch {
53896
+ }
53897
+ try {
53898
+ import_node_fs41.default.chmodSync(pubPath, 420);
53899
+ } catch {
53900
+ }
53901
+ return { privateKeyPath: privPath, publicKeyLine: import_node_fs41.default.readFileSync(pubPath, "utf8").trim() };
53902
+ }
53903
+ function readSshIdentityPublicKey(dataDir) {
53904
+ try {
53905
+ return import_node_fs41.default.readFileSync(identityPaths(dataDir).pubPath, "utf8").trim();
53881
53906
  } catch {
53882
53907
  return null;
53883
53908
  }
53884
53909
  }
53885
53910
 
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);
53911
+ // src/sshd/ssh-tunnel-relay.ts
53912
+ var import_node_net2 = __toESM(require("net"), 1);
53889
53913
 
53890
53914
  // src/sshd/contact-ssh-log.ts
53891
- var import_node_fs41 = __toESM(require("fs"), 1);
53892
- var import_node_path41 = __toESM(require("path"), 1);
53915
+ var import_node_fs42 = __toESM(require("fs"), 1);
53916
+ var import_node_path42 = __toESM(require("path"), 1);
53893
53917
  function createContactSshLog(dataDir) {
53894
- const file = import_node_path41.default.join(dataDir, "log", "contact-ssh.log");
53918
+ const file = import_node_path42.default.join(dataDir, "log", "contact-ssh.log");
53895
53919
  function append(level, tag, message, meta) {
53896
53920
  const time = (/* @__PURE__ */ new Date()).toISOString();
53897
53921
  let line = `[${time}] [${level}] [${tag}] ${message}`;
@@ -53904,8 +53928,8 @@ function createContactSshLog(dataDir) {
53904
53928
  }
53905
53929
  line += "\n";
53906
53930
  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 });
53931
+ import_node_fs42.default.mkdirSync(import_node_path42.default.dirname(file), { recursive: true });
53932
+ import_node_fs42.default.appendFileSync(file, line, { mode: 384 });
53909
53933
  } catch {
53910
53934
  }
53911
53935
  }
@@ -53924,118 +53948,7 @@ var nullContactSshLog = {
53924
53948
  }
53925
53949
  };
53926
53950
 
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
53951
  // src/sshd/ssh-tunnel-relay.ts
54038
- var import_node_net2 = __toESM(require("net"), 1);
54039
53952
  async function handleSshTunnelUpgrade(req, socket, head, deps) {
54040
53953
  const sshLog = deps.sshLog ?? nullContactSshLog;
54041
53954
  const clientAddr = (req.socket && "remoteAddress" in req.socket ? req.socket.remoteAddress : null) ?? "unknown";
@@ -54626,9 +54539,9 @@ var CentralClientError = class extends Error {
54626
54539
  code;
54627
54540
  cause;
54628
54541
  };
54629
- async function centralRequest(opts, path77, init) {
54542
+ async function centralRequest(opts, path76, init) {
54630
54543
  const f = opts.fetchImpl ?? globalThis.fetch;
54631
- const url = `${opts.api.replace(/\/+$/, "")}${path77}`;
54544
+ const url = `${opts.api.replace(/\/+$/, "")}${path76}`;
54632
54545
  const ctrl = new AbortController();
54633
54546
  const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 15e3);
54634
54547
  let res;
@@ -55565,11 +55478,11 @@ init_protocol();
55565
55478
  init_protocol();
55566
55479
 
55567
55480
  // src/workspace/git.ts
55568
- var import_node_child_process12 = require("child_process");
55481
+ var import_node_child_process13 = require("child_process");
55569
55482
  var import_node_fs47 = __toESM(require("fs"), 1);
55570
55483
  var import_node_path48 = __toESM(require("path"), 1);
55571
55484
  var import_node_util = require("util");
55572
- var pexec = (0, import_node_util.promisify)(import_node_child_process12.execFile);
55485
+ var pexec = (0, import_node_util.promisify)(import_node_child_process13.execFile);
55573
55486
  function normalizePath(p2) {
55574
55487
  const resolved = import_node_path48.default.resolve(p2);
55575
55488
  try {
@@ -55982,51 +55895,6 @@ function buildContactHandlers(deps) {
55982
55895
 
55983
55896
  // src/handlers/contact-ssh.ts
55984
55897
  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
55898
  function ensureOwner2(ctx) {
56031
55899
  if (!ctx || ctx.principal.kind !== "owner") {
56032
55900
  throw new ClawdError(
@@ -56035,15 +55903,6 @@ function ensureOwner2(ctx) {
56035
55903
  );
56036
55904
  }
56037
55905
  }
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
55906
  function buildContactSshHandlers(deps) {
56048
55907
  const sshLog = deps.sshLog ?? nullContactSshLog;
56049
55908
  const setSshAccess = async (frame, _client, ctx) => {
@@ -56080,47 +55939,8 @@ function buildContactSshHandlers(deps) {
56080
55939
  }
56081
55940
  };
56082
55941
  };
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
55942
  return {
56122
- "contact:setSshAccess": setSshAccess,
56123
- "contact:sshKey:issue": sshKeyIssue
55943
+ "contact:setSshAccess": setSshAccess
56124
55944
  };
56125
55945
  }
56126
55946
 
@@ -56172,12 +55992,16 @@ function buildWhoamiHandler(deps) {
56172
55992
  grantedPersonas.push({ id: file.personaId, displayName: file.label });
56173
55993
  }
56174
55994
  }
55995
+ const sshPublicKey = deps.sshIdentityPublicKey();
55996
+ const osUser = deps.selfOsUser();
56175
55997
  return {
56176
55998
  response: {
56177
55999
  type: "whoami:ok",
56178
56000
  owner,
56179
56001
  capability,
56180
- grantedPersonas
56002
+ grantedPersonas,
56003
+ ...sshPublicKey ? { sshPublicKey } : {},
56004
+ ...osUser ? { osUser } : {}
56181
56005
  }
56182
56006
  };
56183
56007
  };
@@ -56253,7 +56077,10 @@ function buildDeviceHandlers(deps) {
56253
56077
  // 自动反向(spec 决策 #11):自报本机可达地址,让对方反向加我为联系人;
56254
56078
  // 本机无 tunnel → null → connectRemote 省略 selfUrl 字段(不造假数据)。
56255
56079
  // 身份不自报:对方从 token 签名背书取我的 deviceId/ownerId/provider(决策 #16)。
56256
- selfUrl: deps.selfUrl() ?? void 0
56080
+ selfUrl: deps.selfUrl() ?? void 0,
56081
+ // 建联交换:自报本机 SSH pubkey + OS 账号名;未生成 → 省略。见 …#建联交换清单
56082
+ selfSshPublicKey: deps.selfSshPublicKey() ?? void 0,
56083
+ selfOsUser: deps.selfOsUser() ?? void 0
56257
56084
  });
56258
56085
  } catch (e) {
56259
56086
  throw new ClawdError(
@@ -56263,6 +56090,9 @@ function buildDeviceHandlers(deps) {
56263
56090
  }
56264
56091
  try {
56265
56092
  const wh = await conn.whoami();
56093
+ const existing = deps.store.get(args.deviceId) ?? void 0;
56094
+ const sshPublicKey = wh.sshPublicKey ?? existing?.sshPublicKey;
56095
+ const osUser = wh.osUser ?? existing?.osUser;
56266
56096
  const contact = {
56267
56097
  deviceId: args.deviceId,
56268
56098
  ownerId: wh.ownerId,
@@ -56277,12 +56107,19 @@ function buildDeviceHandlers(deps) {
56277
56107
  connectToken: exchanged.token,
56278
56108
  grants: wh.grants,
56279
56109
  addedAt: now(),
56280
- pinnedAt: null,
56281
- sshAllowed: false,
56110
+ pinnedAt: existing?.pinnedAt ?? null,
56111
+ sshAllowed: existing?.sshAllowed ?? false,
56112
+ // 建联交换:对方 whoami:ok 下发的 pubkey / osUser 落库;对方老版本不带 →
56113
+ // 保留既有值 / 不落 undefined 占位。见 doc/glossary/peer-mesh.md#建联交换清单
56114
+ ...sshPublicKey ? { sshPublicKey } : {},
56115
+ ...osUser ? { osUser } : {},
56282
56116
  exposedDirs: []
56283
56117
  };
56284
56118
  deps.store.upsert(contact);
56285
56119
  deps.broadcast({ type: "contact:added", contact });
56120
+ if (contact.sshAllowed && sshPublicKey && sshPublicKey !== existing?.sshPublicKey) {
56121
+ deps.rebuildAuthorizedKeys?.();
56122
+ }
56286
56123
  return {
56287
56124
  response: {
56288
56125
  type: "device:connect:ok",
@@ -56435,7 +56272,7 @@ function buildPersonaHandlers(deps) {
56435
56272
  }
56436
56273
 
56437
56274
  // src/handlers/attachment.ts
56438
- var import_node_path50 = __toESM(require("path"), 1);
56275
+ var import_node_path49 = __toESM(require("path"), 1);
56439
56276
  init_protocol();
56440
56277
  init_protocol();
56441
56278
  var DEFAULT_TTL_SECONDS = 24 * 3600;
@@ -56515,12 +56352,12 @@ function buildAttachmentHandlers(deps) {
56515
56352
  `session ${args.sessionId} scope unresolved`
56516
56353
  );
56517
56354
  }
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);
56355
+ const cwdAbs = import_node_path49.default.resolve(sessionFile.cwd);
56356
+ 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
56357
  guardAttachmentPath(ctx, args.sessionId, candidateAbs, "attachment.signUrl", "group-acl");
56521
56358
  const entries = deps.groupFileStore.list(scope, args.sessionId);
56522
56359
  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);
56360
+ 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
56361
  return storedAbs === candidateAbs && !e.stale;
56525
56362
  });
56526
56363
  if (!entry) {
@@ -56545,7 +56382,7 @@ function buildAttachmentHandlers(deps) {
56545
56382
  if (!ctx || ctx.principal.kind !== "guest" || !deps.personaRoot || !deps.sessionStore) return;
56546
56383
  const f = deps.sessionStore.read(sessionId);
56547
56384
  if (!f) return;
56548
- assertGuestAttachmentPath(ctx, import_node_path50.default.resolve(f.cwd), deps.personaRoot, method, deps.usersRoot);
56385
+ assertGuestAttachmentPath(ctx, import_node_path49.default.resolve(f.cwd), deps.personaRoot, method, deps.usersRoot);
56549
56386
  }
56550
56387
  const groupAdd = async (frame, _client, ctx) => {
56551
56388
  if (!deps.groupFileStore || !deps.getSessionScope) {
@@ -56560,8 +56397,8 @@ function buildAttachmentHandlers(deps) {
56560
56397
  if (!scope) {
56561
56398
  throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, `session ${args.sessionId} not found`);
56562
56399
  }
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);
56400
+ const cwdAbs = import_node_path49.default.resolve(deps.sessionStore?.read(args.sessionId)?.cwd ?? ".");
56401
+ 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
56402
  guardAttachmentPath(ctx, args.sessionId, candidateAbs, "attachment.groupAdd", "cwd-subtree");
56566
56403
  const from = ctx?.principal.kind === "owner" ? "owner" : "agent";
56567
56404
  const size = 0;
@@ -56620,19 +56457,19 @@ function buildAttachmentHandlers(deps) {
56620
56457
 
56621
56458
  // src/handlers/extension.ts
56622
56459
  var import_promises8 = __toESM(require("fs/promises"), 1);
56623
- var import_node_path55 = __toESM(require("path"), 1);
56460
+ var import_node_path54 = __toESM(require("path"), 1);
56624
56461
  init_protocol();
56625
56462
 
56626
56463
  // src/extension/bundle-zip.ts
56627
56464
  var import_promises5 = __toESM(require("fs/promises"), 1);
56628
- var import_node_path51 = __toESM(require("path"), 1);
56465
+ var import_node_path50 = __toESM(require("path"), 1);
56629
56466
  var import_node_crypto14 = __toESM(require("crypto"), 1);
56630
56467
  var import_jszip2 = __toESM(require_lib3(), 1);
56631
56468
  async function bundleExtensionDir(dir) {
56632
56469
  const entries = await listFilesSorted(dir);
56633
56470
  const zip = new import_jszip2.default();
56634
56471
  for (const rel of entries) {
56635
- const abs = import_node_path51.default.join(dir, rel);
56472
+ const abs = import_node_path50.default.join(dir, rel);
56636
56473
  const content = await import_promises5.default.readFile(abs);
56637
56474
  zip.file(rel, content, { date: FIXED_DATE });
56638
56475
  }
@@ -56653,7 +56490,7 @@ async function listFilesSorted(rootDir) {
56653
56490
  return out;
56654
56491
  }
56655
56492
  async function walk(absRoot, relPrefix, out) {
56656
- const dirAbs = import_node_path51.default.join(absRoot, relPrefix);
56493
+ const dirAbs = import_node_path50.default.join(absRoot, relPrefix);
56657
56494
  const entries = await import_promises5.default.readdir(dirAbs, { withFileTypes: true });
56658
56495
  for (const e of entries) {
56659
56496
  if (IGNORE_BASENAMES.has(e.name)) continue;
@@ -56707,25 +56544,25 @@ function computePublishCheck(args) {
56707
56544
 
56708
56545
  // src/extension/install-flow.ts
56709
56546
  var import_promises6 = __toESM(require("fs/promises"), 1);
56710
- var import_node_path53 = __toESM(require("path"), 1);
56547
+ var import_node_path52 = __toESM(require("path"), 1);
56711
56548
  var import_node_os19 = __toESM(require("os"), 1);
56712
56549
  var import_node_crypto15 = __toESM(require("crypto"), 1);
56713
56550
  var import_jszip3 = __toESM(require_lib3(), 1);
56714
56551
 
56715
56552
  // src/extension/paths.ts
56716
56553
  var import_node_os18 = __toESM(require("os"), 1);
56717
- var import_node_path52 = __toESM(require("path"), 1);
56554
+ var import_node_path51 = __toESM(require("path"), 1);
56718
56555
  function clawdHomeRoot(override) {
56719
- return override ?? process.env.CLAWD_HOME ?? import_node_path52.default.join(import_node_os18.default.homedir(), ".clawd");
56556
+ return override ?? process.env.CLAWD_HOME ?? import_node_path51.default.join(import_node_os18.default.homedir(), ".clawd");
56720
56557
  }
56721
56558
  function extensionsRoot(override) {
56722
- return import_node_path52.default.join(clawdHomeRoot(override), "extensions");
56559
+ return import_node_path51.default.join(clawdHomeRoot(override), "extensions");
56723
56560
  }
56724
56561
  function publishedChannelsFile(override) {
56725
- return import_node_path52.default.join(clawdHomeRoot(override), "extensions-published.json");
56562
+ return import_node_path51.default.join(clawdHomeRoot(override), "extensions-published.json");
56726
56563
  }
56727
56564
  function bundleCacheRoot(override) {
56728
- return import_node_path52.default.join(clawdHomeRoot(override), "extension-bundles");
56565
+ return import_node_path51.default.join(clawdHomeRoot(override), "extension-bundles");
56729
56566
  }
56730
56567
 
56731
56568
  // src/extension/install-flow.ts
@@ -56752,7 +56589,7 @@ async function installFromChannel(args, deps) {
56752
56589
  throw new InstallError("ZIP_INVALID", `failed to load zip: ${e.message}`);
56753
56590
  }
56754
56591
  for (const name of Object.keys(zip.files)) {
56755
- if (name.includes("..") || name.startsWith("/") || import_node_path53.default.isAbsolute(name)) {
56592
+ if (name.includes("..") || name.startsWith("/") || import_node_path52.default.isAbsolute(name)) {
56756
56593
  throw new InstallError("ZIP_INVALID", `unsafe zip entry: ${name}`);
56757
56594
  }
56758
56595
  }
@@ -56784,7 +56621,7 @@ async function installFromChannel(args, deps) {
56784
56621
  );
56785
56622
  }
56786
56623
  const localExtId = namespacedExtId(ownerSlug, channelRef.ownerPrincipalId);
56787
- const destDir = import_node_path53.default.join(deps.extensionsRoot, localExtId);
56624
+ const destDir = import_node_path52.default.join(deps.extensionsRoot, localExtId);
56788
56625
  let destExists = false;
56789
56626
  try {
56790
56627
  await import_promises6.default.access(destDir);
@@ -56798,16 +56635,16 @@ async function installFromChannel(args, deps) {
56798
56635
  );
56799
56636
  }
56800
56637
  const stage = await import_promises6.default.mkdtemp(
56801
- import_node_path53.default.join(import_node_os19.default.tmpdir(), `clawd-ext-install-${localExtId}-`)
56638
+ import_node_path52.default.join(import_node_os19.default.tmpdir(), `clawd-ext-install-${localExtId}-`)
56802
56639
  );
56803
56640
  try {
56804
56641
  for (const [name, entry] of Object.entries(zip.files)) {
56805
- const dest = import_node_path53.default.join(stage, name);
56642
+ const dest = import_node_path52.default.join(stage, name);
56806
56643
  if (entry.dir) {
56807
56644
  await import_promises6.default.mkdir(dest, { recursive: true });
56808
56645
  continue;
56809
56646
  }
56810
- await import_promises6.default.mkdir(import_node_path53.default.dirname(dest), { recursive: true });
56647
+ await import_promises6.default.mkdir(import_node_path52.default.dirname(dest), { recursive: true });
56811
56648
  if (name === "manifest.json") {
56812
56649
  const rewritten = { ...parsed.data, id: localExtId };
56813
56650
  await import_promises6.default.writeFile(dest, JSON.stringify(rewritten, null, 2));
@@ -56828,7 +56665,7 @@ async function installFromChannel(args, deps) {
56828
56665
 
56829
56666
  // src/extension/update-flow.ts
56830
56667
  var import_promises7 = __toESM(require("fs/promises"), 1);
56831
- var import_node_path54 = __toESM(require("path"), 1);
56668
+ var import_node_path53 = __toESM(require("path"), 1);
56832
56669
  var import_node_os20 = __toESM(require("os"), 1);
56833
56670
  var import_node_crypto16 = __toESM(require("crypto"), 1);
56834
56671
  var import_jszip4 = __toESM(require_lib3(), 1);
@@ -56845,11 +56682,11 @@ async function updateFromChannel(args, deps) {
56845
56682
  channelRef.extId,
56846
56683
  channelRef.ownerPrincipalId
56847
56684
  );
56848
- const liveDir = import_node_path54.default.join(deps.extensionsRoot, localExtId);
56685
+ const liveDir = import_node_path53.default.join(deps.extensionsRoot, localExtId);
56849
56686
  const prevDir = `${liveDir}.prev`;
56850
56687
  let existingVersion;
56851
56688
  try {
56852
- const raw = await import_promises7.default.readFile(import_node_path54.default.join(liveDir, "manifest.json"), "utf8");
56689
+ const raw = await import_promises7.default.readFile(import_node_path53.default.join(liveDir, "manifest.json"), "utf8");
56853
56690
  const parsed2 = ExtensionManifestSchema.safeParse(JSON.parse(raw));
56854
56691
  if (!parsed2.success) {
56855
56692
  throw new UpdateError(
@@ -56882,7 +56719,7 @@ async function updateFromChannel(args, deps) {
56882
56719
  throw new UpdateError("ZIP_INVALID", `failed to load zip: ${e.message}`);
56883
56720
  }
56884
56721
  for (const name of Object.keys(zip.files)) {
56885
- if (name.includes("..") || name.startsWith("/") || import_node_path54.default.isAbsolute(name)) {
56722
+ if (name.includes("..") || name.startsWith("/") || import_node_path53.default.isAbsolute(name)) {
56886
56723
  throw new UpdateError("ZIP_INVALID", `unsafe zip entry: ${name}`);
56887
56724
  }
56888
56725
  }
@@ -56917,16 +56754,16 @@ async function updateFromChannel(args, deps) {
56917
56754
  await import_promises7.default.rm(prevDir, { recursive: true, force: true });
56918
56755
  await import_promises7.default.rename(liveDir, prevDir);
56919
56756
  const stage = await import_promises7.default.mkdtemp(
56920
- import_node_path54.default.join(import_node_os20.default.tmpdir(), `clawd-ext-update-${localExtId}-`)
56757
+ import_node_path53.default.join(import_node_os20.default.tmpdir(), `clawd-ext-update-${localExtId}-`)
56921
56758
  );
56922
56759
  try {
56923
56760
  for (const [name, entry] of Object.entries(zip.files)) {
56924
- const dest = import_node_path54.default.join(stage, name);
56761
+ const dest = import_node_path53.default.join(stage, name);
56925
56762
  if (entry.dir) {
56926
56763
  await import_promises7.default.mkdir(dest, { recursive: true });
56927
56764
  continue;
56928
56765
  }
56929
- await import_promises7.default.mkdir(import_node_path54.default.dirname(dest), { recursive: true });
56766
+ await import_promises7.default.mkdir(import_node_path53.default.dirname(dest), { recursive: true });
56930
56767
  if (name === "manifest.json") {
56931
56768
  const rewritten = { ...parsed.data, id: localExtId };
56932
56769
  await import_promises7.default.writeFile(dest, JSON.stringify(rewritten, null, 2));
@@ -57019,7 +56856,7 @@ async function rewriteManifestVersion(root, extId, newVersion, previousPublished
57019
56856
  );
57020
56857
  }
57021
56858
  }
57022
- const manifestPath = import_node_path55.default.join(root, extId, "manifest.json");
56859
+ const manifestPath = import_node_path54.default.join(root, extId, "manifest.json");
57023
56860
  const manifest = await readManifest(root, extId);
57024
56861
  const next = { ...manifest, version: newVersion };
57025
56862
  const tmp = `${manifestPath}.tmp`;
@@ -57027,7 +56864,7 @@ async function rewriteManifestVersion(root, extId, newVersion, previousPublished
57027
56864
  await import_promises8.default.rename(tmp, manifestPath);
57028
56865
  }
57029
56866
  async function readManifest(root, extId) {
57030
- const file = import_node_path55.default.join(root, extId, "manifest.json");
56867
+ const file = import_node_path54.default.join(root, extId, "manifest.json");
57031
56868
  let raw;
57032
56869
  try {
57033
56870
  raw = await import_promises8.default.readFile(file, "utf8");
@@ -57118,7 +56955,7 @@ function buildExtensionHandlers(deps) {
57118
56955
  };
57119
56956
  async function buildSnapshotMeta(extId) {
57120
56957
  const manifest = await readManifest(deps.root, extId);
57121
- const { sha256, buffer } = await bundleExtensionDir(import_node_path55.default.join(deps.root, extId));
56958
+ const { sha256, buffer } = await bundleExtensionDir(import_node_path54.default.join(deps.root, extId));
57122
56959
  return { manifest, contentHash: sha256, buffer };
57123
56960
  }
57124
56961
  const publish = async (frame, _client, ctx) => {
@@ -57299,9 +57136,9 @@ function buildExtensionHandlers(deps) {
57299
57136
  }
57300
57137
 
57301
57138
  // src/app-builder/project-store.ts
57302
- var import_node_fs49 = require("fs");
57139
+ var import_node_fs48 = require("fs");
57303
57140
  var import_node_child_process14 = require("child_process");
57304
- var import_node_path56 = require("path");
57141
+ var import_node_path55 = require("path");
57305
57142
  init_protocol();
57306
57143
  var PROJECTS_DIR = "projects";
57307
57144
  var META_FILE = ".clawd-project.json";
@@ -57315,19 +57152,19 @@ var ProjectStore = class {
57315
57152
  root;
57316
57153
  /** projects/<name>/.clawd-project.json 路径 */
57317
57154
  metaPath(name) {
57318
- return (0, import_node_path56.join)(this.projectsRoot(), name, META_FILE);
57155
+ return (0, import_node_path55.join)(this.projectsRoot(), name, META_FILE);
57319
57156
  }
57320
57157
  /** projects/<name>/ 目录路径(cwd 用) */
57321
57158
  projectDir(name) {
57322
- return (0, import_node_path56.join)(this.projectsRoot(), name);
57159
+ return (0, import_node_path55.join)(this.projectsRoot(), name);
57323
57160
  }
57324
57161
  projectsRoot() {
57325
- return (0, import_node_path56.join)(this.root, PROJECTS_DIR);
57162
+ return (0, import_node_path55.join)(this.root, PROJECTS_DIR);
57326
57163
  }
57327
57164
  async list() {
57328
57165
  let entries;
57329
57166
  try {
57330
- entries = await import_node_fs49.promises.readdir(this.projectsRoot());
57167
+ entries = await import_node_fs48.promises.readdir(this.projectsRoot());
57331
57168
  } catch (err) {
57332
57169
  if (err.code === "ENOENT") return [];
57333
57170
  throw err;
@@ -57335,7 +57172,7 @@ var ProjectStore = class {
57335
57172
  const out = [];
57336
57173
  for (const name of entries) {
57337
57174
  try {
57338
- const raw = await import_node_fs49.promises.readFile(this.metaPath(name), "utf8");
57175
+ const raw = await import_node_fs48.promises.readFile(this.metaPath(name), "utf8");
57339
57176
  const json = JSON.parse(raw);
57340
57177
  let migrated = false;
57341
57178
  if (typeof json.devCommand !== "string" || json.devCommand.length === 0) {
@@ -57346,7 +57183,7 @@ var ProjectStore = class {
57346
57183
  if (parsed.success) {
57347
57184
  out.push(parsed.data);
57348
57185
  if (migrated) {
57349
- void import_node_fs49.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
57186
+ void import_node_fs48.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
57350
57187
  });
57351
57188
  }
57352
57189
  }
@@ -57390,8 +57227,8 @@ var ProjectStore = class {
57390
57227
  throw new Error(`invalid name "${name}": ${validated.error.message}`);
57391
57228
  }
57392
57229
  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");
57230
+ await import_node_fs48.promises.mkdir(dir, { recursive: true });
57231
+ await import_node_fs48.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
57395
57232
  return meta;
57396
57233
  }
57397
57234
  /**
@@ -57434,7 +57271,7 @@ var ProjectStore = class {
57434
57271
  }
57435
57272
  async delete(name) {
57436
57273
  const dir = this.projectDir(name);
57437
- await import_node_fs49.promises.rm(dir, { recursive: true, force: true });
57274
+ await import_node_fs48.promises.rm(dir, { recursive: true, force: true });
57438
57275
  }
57439
57276
  async updatePort(name, newPort) {
57440
57277
  if (newPort < PROJECT_PORT_MIN || newPort > PROJECT_PORT_MAX) {
@@ -57450,7 +57287,7 @@ var ProjectStore = class {
57450
57287
  throw new Error(`port ${newPort} already used / \u5DF2\u88AB project "${conflict.name}" \u5360\u7528`);
57451
57288
  }
57452
57289
  const updated = { ...target, port: newPort };
57453
- await import_node_fs49.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
57290
+ await import_node_fs48.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
57454
57291
  return updated;
57455
57292
  }
57456
57293
  /**
@@ -57467,7 +57304,7 @@ var ProjectStore = class {
57467
57304
  if (!validated.success) {
57468
57305
  throw new Error(`invalid prodUrl "${url}": ${validated.error.message}`);
57469
57306
  }
57470
- await import_node_fs49.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
57307
+ await import_node_fs48.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
57471
57308
  return validated.data;
57472
57309
  }
57473
57310
  /**
@@ -57488,7 +57325,7 @@ var ProjectStore = class {
57488
57325
  if (!validated.success) {
57489
57326
  throw new Error(`invalid publishJob: ${validated.error.message}`);
57490
57327
  }
57491
- await import_node_fs49.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
57328
+ await import_node_fs48.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
57492
57329
  return validated.data;
57493
57330
  }
57494
57331
  /** 清掉 .clawd-project.json.publishJob 字段。其他字段保持原样。 */
@@ -57503,7 +57340,7 @@ var ProjectStore = class {
57503
57340
  if (!validated.success) {
57504
57341
  throw new Error(`failed to clear publishJob: ${validated.error.message}`);
57505
57342
  }
57506
- await import_node_fs49.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
57343
+ await import_node_fs48.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
57507
57344
  return validated.data;
57508
57345
  }
57509
57346
  };
@@ -57624,8 +57461,8 @@ var PublishJobRegistry = class {
57624
57461
 
57625
57462
  // src/app-builder/publish-job-runner.ts
57626
57463
  var import_node_child_process16 = require("child_process");
57627
- var import_node_fs50 = require("fs");
57628
- var import_node_path57 = require("path");
57464
+ var import_node_fs49 = require("fs");
57465
+ var import_node_path56 = require("path");
57629
57466
 
57630
57467
  // src/app-builder/publish-stage-parser.ts
57631
57468
  var STAGE_RE = /^\s*::stage::(build|deploy|verify)\s*$/;
@@ -57657,10 +57494,10 @@ async function startPublishJob(deps, args) {
57657
57494
  return { jobId: registry2.get(args.name).jobId, status: "already-publishing" };
57658
57495
  }
57659
57496
  const projDir = projectDir(args.name);
57660
- const logPath = (0, import_node_path57.join)(projDir, ".publish.log");
57497
+ const logPath = (0, import_node_path56.join)(projDir, ".publish.log");
57661
57498
  let logStream = null;
57662
57499
  try {
57663
- logStream = (0, import_node_fs50.createWriteStream)(logPath, { flags: "w" });
57500
+ logStream = (0, import_node_fs49.createWriteStream)(logPath, { flags: "w" });
57664
57501
  } catch {
57665
57502
  logStream = null;
57666
57503
  }
@@ -57917,8 +57754,8 @@ async function recoverInterruptedJobs(deps) {
57917
57754
 
57918
57755
  // src/handlers/app-builder.ts
57919
57756
  init_protocol();
57920
- var import_node_path58 = require("path");
57921
- var import_node_fs51 = require("fs");
57757
+ var import_node_path57 = require("path");
57758
+ var import_node_fs50 = require("fs");
57922
57759
  var APP_BUILDER_PERSONAS = ["persona-app-builder", "persona-dataclaw-builder"];
57923
57760
  var DEV_SERVER_READY_TIMEOUT_MS = 3e4;
57924
57761
  async function recoverInterruptedPublishJobs(store, logger) {
@@ -57999,7 +57836,7 @@ function buildAppBuilderHandlers(deps) {
57999
57836
  async function listAllUsersProjects() {
58000
57837
  if (!deps.usersRoot || !deps.getStore) return [];
58001
57838
  const getStore = deps.getStore;
58002
- const userIds = await import_node_fs51.promises.readdir(deps.usersRoot).catch(() => []);
57839
+ const userIds = await import_node_fs50.promises.readdir(deps.usersRoot).catch(() => []);
58003
57840
  const perUser = await Promise.all(
58004
57841
  userIds.map((uid) => getStore(uid).list().catch(() => []))
58005
57842
  );
@@ -58075,8 +57912,8 @@ function buildAppBuilderHandlers(deps) {
58075
57912
  const project = await userStore.create(f.name, reservedPorts);
58076
57913
  try {
58077
57914
  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");
57915
+ const templateSrcDir = (0, import_node_path57.join)(personaRoot, "extension-kit", "examples", DEFAULT_TEMPLATE);
57916
+ const scaffoldScript = (0, import_node_path57.join)(deps.deployKitRoot, "scripts", "new-extension.sh");
58080
57917
  const scaffoldResult = await userStore.scaffold(project.name, templateSrcDir, scaffoldScript);
58081
57918
  deps.logger?.info("app-builder.scaffold.done", {
58082
57919
  name: project.name,
@@ -58297,7 +58134,7 @@ function buildAppBuilderHandlers(deps) {
58297
58134
  await userStore.clearPublishJob(args.name);
58298
58135
  }
58299
58136
  const personaRoot = deps.resolvePersonaRoot ? deps.resolvePersonaRoot(boundSession.ownerPersonaId ?? "") : deps.personaRoot;
58300
- const scriptPath = (0, import_node_path58.join)(deps.deployKitRoot, "scripts", "publish.sh");
58137
+ const scriptPath = (0, import_node_path57.join)(deps.deployKitRoot, "scripts", "publish.sh");
58301
58138
  deps.logger?.info("app-builder.publish.start", {
58302
58139
  name: args.name,
58303
58140
  sessionId: boundSession.sessionId,
@@ -58473,7 +58310,7 @@ function buildVisitorHandlers(deps) {
58473
58310
 
58474
58311
  // src/extension/registry.ts
58475
58312
  var import_promises9 = __toESM(require("fs/promises"), 1);
58476
- var import_node_path59 = __toESM(require("path"), 1);
58313
+ var import_node_path58 = __toESM(require("path"), 1);
58477
58314
  async function loadAll(root) {
58478
58315
  let entries;
58479
58316
  try {
@@ -58486,13 +58323,13 @@ async function loadAll(root) {
58486
58323
  for (const ent of entries) {
58487
58324
  if (!ent.isDirectory()) continue;
58488
58325
  if (ent.name.startsWith(".")) continue;
58489
- records.push(await loadOne(import_node_path59.default.join(root, ent.name), ent.name));
58326
+ records.push(await loadOne(import_node_path58.default.join(root, ent.name), ent.name));
58490
58327
  }
58491
58328
  records.sort((a, b2) => a.extId < b2.extId ? -1 : a.extId > b2.extId ? 1 : 0);
58492
58329
  return records;
58493
58330
  }
58494
58331
  async function loadOne(dir, dirName) {
58495
- const manifestPath = import_node_path59.default.join(dir, "manifest.json");
58332
+ const manifestPath = import_node_path58.default.join(dir, "manifest.json");
58496
58333
  let raw;
58497
58334
  try {
58498
58335
  raw = await import_promises9.default.readFile(manifestPath, "utf8");
@@ -58537,7 +58374,7 @@ async function loadOne(dir, dirName) {
58537
58374
 
58538
58375
  // src/extension/uninstall.ts
58539
58376
  var import_promises10 = __toESM(require("fs/promises"), 1);
58540
- var import_node_path60 = __toESM(require("path"), 1);
58377
+ var import_node_path59 = __toESM(require("path"), 1);
58541
58378
  var UninstallError = class extends Error {
58542
58379
  constructor(code, message) {
58543
58380
  super(message);
@@ -58546,7 +58383,7 @@ var UninstallError = class extends Error {
58546
58383
  code;
58547
58384
  };
58548
58385
  async function uninstall(deps) {
58549
- const dir = import_node_path60.default.join(deps.root, deps.extId);
58386
+ const dir = import_node_path59.default.join(deps.root, deps.extId);
58550
58387
  try {
58551
58388
  await import_promises10.default.access(dir);
58552
58389
  } catch {
@@ -58613,7 +58450,9 @@ function buildMethodHandlers(deps) {
58613
58450
  ownerId: deps.ownerId,
58614
58451
  ownerProvider: deps.ownerProvider,
58615
58452
  personaStore: deps.personaStore,
58616
- capabilityManager: deps.capabilityManager
58453
+ capabilityManager: deps.capabilityManager,
58454
+ sshIdentityPublicKey: deps.sshIdentityPublicKey,
58455
+ selfOsUser: deps.selfOsUser
58617
58456
  }),
58618
58457
  ...buildFeishuAuthHandlers(deps.feishuAuth),
58619
58458
  ...buildDeviceHandlers(deps.feishuDevice),
@@ -58946,11 +58785,8 @@ var METHOD_GRANT_MAP = {
58946
58785
  "contact:pin": ADMIN_ANY,
58947
58786
  "contact:remove": ADMIN_ANY,
58948
58787
  // 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
58788
  // (对齐 inbox:postMessage 的"能连上=有 auth,业务在 handler 校"模式)
58952
58789
  "contact:setSshAccess": ADMIN_ANY,
58953
- "contact:sshKey:issue": { kind: "public" },
58954
58790
  // ---- visitor:* (访客名单,owner-only) ----
58955
58791
  // owner 看完整访客名单(含没开会话的);guest 不可调(handler 内再 assertOwner 兜底)。
58956
58792
  "visitor:list": ADMIN_ANY,
@@ -59136,7 +58972,7 @@ async function dispatchRpc(method, frame, client, ctx, deps) {
59136
58972
 
59137
58973
  // src/extension/runtime.ts
59138
58974
  var import_node_child_process18 = require("child_process");
59139
- var import_node_path61 = __toESM(require("path"), 1);
58975
+ var import_node_path60 = __toESM(require("path"), 1);
59140
58976
  var import_promises11 = require("timers/promises");
59141
58977
 
59142
58978
  // src/extension/port-allocator.ts
@@ -59237,7 +59073,7 @@ var Runtime = class {
59237
59073
  /\$CLAWOS_EXT_PORT/g,
59238
59074
  String(port)
59239
59075
  );
59240
- const dir = import_node_path61.default.join(this.root, extId);
59076
+ const dir = import_node_path60.default.join(this.root, extId);
59241
59077
  const env = {
59242
59078
  ...process.env,
59243
59079
  CLAWOS_EXT_PORT: String(port),
@@ -59349,7 +59185,7 @@ ${handle.stderrTail}`
59349
59185
 
59350
59186
  // src/extension/published-channels.ts
59351
59187
  var import_promises12 = __toESM(require("fs/promises"), 1);
59352
- var import_node_path62 = __toESM(require("path"), 1);
59188
+ var import_node_path61 = __toESM(require("path"), 1);
59353
59189
  init_zod();
59354
59190
  var PublishedChannelsError = class extends Error {
59355
59191
  constructor(code, message) {
@@ -59448,7 +59284,7 @@ var PublishedChannelStore = class {
59448
59284
  )
59449
59285
  };
59450
59286
  const tmp = `${this.filePath}.tmp`;
59451
- await import_promises12.default.mkdir(import_node_path62.default.dirname(this.filePath), { recursive: true });
59287
+ await import_promises12.default.mkdir(import_node_path61.default.dirname(this.filePath), { recursive: true });
59452
59288
  await import_promises12.default.writeFile(tmp, JSON.stringify(data, null, 2), { mode: 384 });
59453
59289
  await import_promises12.default.rename(tmp, this.filePath);
59454
59290
  }
@@ -59456,7 +59292,7 @@ var PublishedChannelStore = class {
59456
59292
 
59457
59293
  // src/extension/bundle-cache.ts
59458
59294
  var import_promises13 = __toESM(require("fs/promises"), 1);
59459
- var import_node_path63 = __toESM(require("path"), 1);
59295
+ var import_node_path62 = __toESM(require("path"), 1);
59460
59296
  var BundleCache = class {
59461
59297
  constructor(rootDir) {
59462
59298
  this.rootDir = rootDir;
@@ -59465,14 +59301,14 @@ var BundleCache = class {
59465
59301
  /** Atomic write: stage tmp → rename. Caller passes the hex sha256. */
59466
59302
  async write(snapshotHash, buffer) {
59467
59303
  await import_promises13.default.mkdir(this.rootDir, { recursive: true });
59468
- const file = import_node_path63.default.join(this.rootDir, `${snapshotHash}.zip`);
59304
+ const file = import_node_path62.default.join(this.rootDir, `${snapshotHash}.zip`);
59469
59305
  const tmp = `${file}.tmp`;
59470
59306
  await import_promises13.default.writeFile(tmp, buffer, { mode: 384 });
59471
59307
  await import_promises13.default.rename(tmp, file);
59472
59308
  }
59473
59309
  /** Returns the bundle bytes, or null when the file doesn't exist. */
59474
59310
  async read(snapshotHash) {
59475
- const file = import_node_path63.default.join(this.rootDir, `${snapshotHash}.zip`);
59311
+ const file = import_node_path62.default.join(this.rootDir, `${snapshotHash}.zip`);
59476
59312
  try {
59477
59313
  return await import_promises13.default.readFile(file);
59478
59314
  } catch (e) {
@@ -59482,13 +59318,20 @@ var BundleCache = class {
59482
59318
  }
59483
59319
  /** Idempotent — missing file is not an error. */
59484
59320
  async delete(snapshotHash) {
59485
- const file = import_node_path63.default.join(this.rootDir, `${snapshotHash}.zip`);
59321
+ const file = import_node_path62.default.join(this.rootDir, `${snapshotHash}.zip`);
59486
59322
  await import_promises13.default.rm(file, { force: true });
59487
59323
  }
59488
59324
  };
59489
59325
 
59490
59326
  // src/index.ts
59491
59327
  var import_meta6 = {};
59328
+ function currentOsUser() {
59329
+ try {
59330
+ return import_node_os21.default.userInfo().username;
59331
+ } catch {
59332
+ return "";
59333
+ }
59334
+ }
59492
59335
  async function startDaemon(config) {
59493
59336
  const authFile = loadOrCreateAuthFile({ dataDir: config.dataDir });
59494
59337
  const logShippingCfg = config.logShipping ?? { endpoint: null, sampling: {}, mode: "off" };
@@ -59505,24 +59348,24 @@ async function startDaemon(config) {
59505
59348
  sampling: logShippingCfg.sampling,
59506
59349
  homeDir: import_node_os21.default.homedir()
59507
59350
  });
59508
- const logDir = import_node_path64.default.join(config.dataDir, "log");
59509
- import_node_fs52.default.mkdirSync(logDir, { recursive: true });
59351
+ const logDir = import_node_path63.default.join(config.dataDir, "log");
59352
+ import_node_fs51.default.mkdirSync(logDir, { recursive: true });
59510
59353
  const logger = createLogger({
59511
59354
  level: config.logLevel,
59512
- file: import_node_path64.default.join(logDir, "clawd.log"),
59355
+ file: import_node_path63.default.join(logDir, "clawd.log"),
59513
59356
  logClient
59514
59357
  });
59515
59358
  logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
59516
59359
  const screenIdleProbeLogger = createFileOnlyLogger({
59517
- file: import_node_path64.default.join(logDir, "screen-idle-probe.log"),
59360
+ file: import_node_path63.default.join(logDir, "screen-idle-probe.log"),
59518
59361
  level: "debug"
59519
59362
  });
59520
59363
  logger.info("screen-idle probe logger enabled", {
59521
- file: import_node_path64.default.join(logDir, "screen-idle-probe.log")
59364
+ file: import_node_path63.default.join(logDir, "screen-idle-probe.log")
59522
59365
  });
59523
59366
  const CAP_TARGETS = [
59524
- import_node_path64.default.join(logDir, "clawd.log"),
59525
- import_node_path64.default.join(logDir, "screen-idle-probe.log")
59367
+ import_node_path63.default.join(logDir, "clawd.log"),
59368
+ import_node_path63.default.join(logDir, "screen-idle-probe.log")
59526
59369
  ];
59527
59370
  const LOG_CAP_MAX_BYTES = 10 * 1024 * 1024;
59528
59371
  const LOG_CAP_KEEP_BYTES = 5 * 1024 * 1024;
@@ -59618,7 +59461,7 @@ async function startDaemon(config) {
59618
59461
  // Task 1.7:authenticate 注入路径替代 expectedToken 单 token 比对。
59619
59462
  // owner 路径 constantTimeEqual 防侧信道;guest 路径走 capabilityRegistry.
59620
59463
  expectedToken: resolvedAuthToken,
59621
- authenticate: async (t, selfUrl) => {
59464
+ authenticate: async (t, selfUrl, selfSshPublicKey, selfOsUser) => {
59622
59465
  const result = await authenticate(t, buildConnectAuthDeps());
59623
59466
  if (result.ok && result.context.principal.kind === "guest") {
59624
59467
  void autoReverseContact({
@@ -59629,6 +59472,11 @@ async function startDaemon(config) {
59629
59472
  provider: result.context.provider ?? "",
59630
59473
  displayName: result.context.principal.displayName ?? result.context.principal.id,
59631
59474
  selfUrl,
59475
+ // 建联交换:guest auth 帧自报的 pubkey / osUser 落 contact;pubkey 变化且已授权时
59476
+ // 立即重建 authorized_keys(重新握手 = 迁移路径)。见 …#建联交换清单
59477
+ sshPublicKey: selfSshPublicKey,
59478
+ osUser: selfOsUser,
59479
+ rebuildAuthorizedKeys: () => rebuildAuthorizedKeys(contactStore, import_node_path63.default.join(config.dataDir, "sshd")),
59632
59480
  // 决策 #16 换票补全:用 owner 当前 jwt 换 aud=对方设备 的票(本机主动连对方用)。
59633
59481
  // 现读 ownerIdentityStore(热切换后用新身份);失败 → null → 保持空票。
59634
59482
  exchangeToken: async (targetDeviceId) => {
@@ -59676,8 +59524,8 @@ async function startDaemon(config) {
59676
59524
  const agents = new AgentsScanner();
59677
59525
  const history = new ClaudeHistoryReader();
59678
59526
  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");
59527
+ const personaStore = new PersonaStore(import_node_path63.default.join(config.dataDir, "personas"));
59528
+ const usersRoot = import_node_path63.default.join(config.dataDir, "users");
59681
59529
  const defaultsRoot = findDefaultsRoot(logger);
59682
59530
  if (defaultsRoot) {
59683
59531
  seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
@@ -59697,17 +59545,17 @@ async function startDaemon(config) {
59697
59545
  migrateCodexSandbox({ store: personaStore, logger });
59698
59546
  const groupFileStore = new GroupFileStore({ dataDir: config.dataDir, logger });
59699
59547
  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));
59548
+ const here = typeof __dirname === "string" ? __dirname : import_node_path63.default.dirname((0, import_node_url4.fileURLToPath)(import_meta6.url));
59701
59549
  const mcpConfigs = [];
59702
59550
  const dispatchServerCandidates = [
59703
- import_node_path64.default.join(here, "dispatch", "mcp-server.cjs"),
59551
+ import_node_path63.default.join(here, "dispatch", "mcp-server.cjs"),
59704
59552
  // 生产 dist/index → dist/dispatch/mcp-server.cjs
59705
- import_node_path64.default.join(here, "..", "dist", "dispatch", "mcp-server.cjs")
59553
+ import_node_path63.default.join(here, "..", "dist", "dispatch", "mcp-server.cjs")
59706
59554
  // dev tsx src/index → ../dist/dispatch/mcp-server.cjs
59707
59555
  ];
59708
- const dispatchServerScriptPath = dispatchServerCandidates.find((p2) => import_node_fs52.default.existsSync(p2));
59556
+ const dispatchServerScriptPath = dispatchServerCandidates.find((p2) => import_node_fs51.default.existsSync(p2));
59709
59557
  if (dispatchServerScriptPath) {
59710
- const dispatchLogPath = import_node_path64.default.join(logDir, "dispatch-mcp-server.log");
59558
+ const dispatchLogPath = import_node_path63.default.join(logDir, "dispatch-mcp-server.log");
59711
59559
  const dispatchCfgPath = writeDispatchMcpConfig({
59712
59560
  dataDir: config.dataDir,
59713
59561
  serverScriptPath: dispatchServerScriptPath,
@@ -59725,12 +59573,12 @@ async function startDaemon(config) {
59725
59573
  });
59726
59574
  }
59727
59575
  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")
59576
+ import_node_path63.default.join(here, "ticket", "mcp-server.cjs"),
59577
+ import_node_path63.default.join(here, "..", "dist", "ticket", "mcp-server.cjs")
59730
59578
  ];
59731
- const ticketServerScriptPath = ticketServerCandidates.find((p2) => import_node_fs52.default.existsSync(p2));
59579
+ const ticketServerScriptPath = ticketServerCandidates.find((p2) => import_node_fs51.default.existsSync(p2));
59732
59580
  if (ticketServerScriptPath) {
59733
- const ticketLogPath = import_node_path64.default.join(logDir, "ticket-mcp-server.log");
59581
+ const ticketLogPath = import_node_path63.default.join(logDir, "ticket-mcp-server.log");
59734
59582
  const ticketCfgPath = writeTicketMcpConfig({
59735
59583
  dataDir: config.dataDir,
59736
59584
  serverScriptPath: ticketServerScriptPath,
@@ -59750,12 +59598,12 @@ async function startDaemon(config) {
59750
59598
  });
59751
59599
  }
59752
59600
  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")
59601
+ import_node_path63.default.join(here, "shift", "mcp-server.cjs"),
59602
+ import_node_path63.default.join(here, "..", "dist", "shift", "mcp-server.cjs")
59755
59603
  ];
59756
- const shiftServerScriptPath = shiftServerCandidates.find((p2) => import_node_fs52.default.existsSync(p2));
59604
+ const shiftServerScriptPath = shiftServerCandidates.find((p2) => import_node_fs51.default.existsSync(p2));
59757
59605
  if (shiftServerScriptPath) {
59758
- const shiftLogPath = import_node_path64.default.join(logDir, "shift-mcp-server.log");
59606
+ const shiftLogPath = import_node_path63.default.join(logDir, "shift-mcp-server.log");
59759
59607
  const shiftCfgPath = await writeShiftMcpConfig({
59760
59608
  dataDir: config.dataDir,
59761
59609
  serverScriptPath: shiftServerScriptPath,
@@ -59774,12 +59622,12 @@ async function startDaemon(config) {
59774
59622
  );
59775
59623
  }
59776
59624
  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")
59625
+ import_node_path63.default.join(here, "inbox", "mcp-server.cjs"),
59626
+ import_node_path63.default.join(here, "..", "dist", "inbox", "mcp-server.cjs")
59779
59627
  ];
59780
- const inboxServerScriptPath = inboxServerCandidates.find((p2) => import_node_fs52.default.existsSync(p2));
59628
+ const inboxServerScriptPath = inboxServerCandidates.find((p2) => import_node_fs51.default.existsSync(p2));
59781
59629
  if (inboxServerScriptPath) {
59782
- const inboxLogPath = import_node_path64.default.join(logDir, "inbox-mcp-server.log");
59630
+ const inboxLogPath = import_node_path63.default.join(logDir, "inbox-mcp-server.log");
59783
59631
  const inboxCfgPath = await writeInboxMcpConfig({
59784
59632
  dataDir: config.dataDir,
59785
59633
  serverScriptPath: inboxServerScriptPath,
@@ -59798,7 +59646,7 @@ async function startDaemon(config) {
59798
59646
  );
59799
59647
  }
59800
59648
  const shiftStore = createShiftStore({
59801
- filePath: import_node_path64.default.join(config.dataDir, "shift.json"),
59649
+ filePath: import_node_path63.default.join(config.dataDir, "shift.json"),
59802
59650
  ownerIdProvider: () => ownerPrincipalId,
59803
59651
  now: () => Date.now()
59804
59652
  });
@@ -59820,7 +59668,7 @@ async function startDaemon(config) {
59820
59668
  getAdapter,
59821
59669
  historyReader: history,
59822
59670
  dataDir: config.dataDir,
59823
- personaRoot: import_node_path64.default.join(config.dataDir, "personas"),
59671
+ personaRoot: import_node_path63.default.join(config.dataDir, "personas"),
59824
59672
  usersRoot,
59825
59673
  personaStore,
59826
59674
  ownerDisplayName,
@@ -59858,10 +59706,10 @@ async function startDaemon(config) {
59858
59706
  // 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
59859
59707
  attachmentGroup: {
59860
59708
  onFileEdit: (input) => {
59861
- const absPath = import_node_path64.default.isAbsolute(input.relPath) ? input.relPath : import_node_path64.default.join(input.cwd, input.relPath);
59709
+ const absPath = import_node_path63.default.isAbsolute(input.relPath) ? input.relPath : import_node_path63.default.join(input.cwd, input.relPath);
59862
59710
  let size = 0;
59863
59711
  try {
59864
- size = import_node_fs52.default.statSync(absPath).size;
59712
+ size = import_node_fs51.default.statSync(absPath).size;
59865
59713
  } catch (err) {
59866
59714
  logger.warn("attachment.onFileEdit stat failed", {
59867
59715
  sessionId: input.sessionId,
@@ -60048,6 +59896,13 @@ async function startDaemon(config) {
60048
59896
  /* @__PURE__ */ new Set([...config.previewPorts ?? [], ...appBuilderPortRange])
60049
59897
  );
60050
59898
  const sshLog = createContactSshLog(config.dataDir);
59899
+ try {
59900
+ await ensureSshIdentity(config.dataDir);
59901
+ } catch (e) {
59902
+ logger.warn("ssh-identity generate failed; contact SSH pubkey exchange disabled", {
59903
+ err: e.message
59904
+ });
59905
+ }
60051
59906
  const makeHandlers = () => buildMethodHandlers({
60052
59907
  manager,
60053
59908
  workspace,
@@ -60091,11 +59946,11 @@ async function startDaemon(config) {
60091
59946
  // 'persona/<pid>/owner',default 走 'default'。
60092
59947
  getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
60093
59948
  // guest path guard:candidate 必须在 personaRoot 子树或调用者自己的 user-dir 下
60094
- personaRoot: import_node_path64.default.join(config.dataDir, "personas"),
59949
+ personaRoot: import_node_path63.default.join(config.dataDir, "personas"),
60095
59950
  usersRoot
60096
59951
  },
60097
59952
  // workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
60098
- personaRoot: import_node_path64.default.join(config.dataDir, "personas"),
59953
+ personaRoot: import_node_path63.default.join(config.dataDir, "personas"),
60099
59954
  // v2 多人 persona 隔离:handler 派生 guest user-dir 放行
60100
59955
  usersRoot,
60101
59956
  // capability:list / delete handler 依赖
@@ -60108,6 +59963,11 @@ async function startDaemon(config) {
60108
59963
  ownerFeishuUnionId,
60109
59964
  ownerId,
60110
59965
  ownerProvider,
59966
+ // ssh identity 互换(spec 2026-07-15):whoami:ok 下发本机 identity pubkey。
59967
+ // 惰性读盘,不依赖 ensureSshIdentity 完成时序(未生成 → null → 帧省略字段)。
59968
+ sshIdentityPublicKey: () => readSshIdentityPublicKey(config.dataDir),
59969
+ // 建联交换:whoami:ok 下发本机 OS 账号名(拨号登录名)。见 doc/glossary/peer-mesh.md#建联交换清单
59970
+ selfOsUser: currentOsUser,
60111
59971
  personaStore,
60112
59972
  // capability handler 也用 (capability:issue 走 registry / capability:list)
60113
59973
  capabilityRegistry,
@@ -60119,7 +59979,7 @@ async function startDaemon(config) {
60119
59979
  contactStore,
60120
59980
  // <dataDir>/sshd 绝对路径 —— contact-ssh handlers 用它拼 authorized_keys / keys/ 子路径
60121
59981
  // Task 10 会加 SshdManager 起 sshd;handlers wire 提前挂 sshdDir 让 typecheck 过
60122
- sshdDir: import_node_path64.default.join(config.dataDir, "sshd"),
59982
+ sshdDir: import_node_path63.default.join(config.dataDir, "sshd"),
60123
59983
  contactSshLog: sshLog,
60124
59984
  // inbox:sendDm 用:sessionId → session 出身(复用 attachment 同款 findOwnedSessionScope)
60125
59985
  getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
@@ -60187,7 +60047,13 @@ async function startDaemon(config) {
60187
60047
  // 自动反向(spec 决策 #11):本机可达地址 = tunnel URL(公网),出站连接时
60188
60048
  // 透传让对方反向加我为联系人。无 tunnel(仅本机 ws)→ null,对方不自动反向(不造假数据)。
60189
60049
  // 决策 #16:身份不自报——本机 deviceId/ownerId/provider 由 exchange 签进 token 背书。
60190
- selfUrl: () => currentTunnelUrl
60050
+ selfUrl: () => currentTunnelUrl,
60051
+ // ssh identity 互换(spec 2026-07-15):出站 auth 帧自报本机 identity pubkey;
60052
+ // 对方 pubkey 变化且已授权时立即重建 authorized_keys(重新 connect = 迁移路径)
60053
+ selfSshPublicKey: () => readSshIdentityPublicKey(config.dataDir),
60054
+ // 建联交换:出站自报本机 OS 账号名。见 doc/glossary/peer-mesh.md#建联交换清单
60055
+ selfOsUser: currentOsUser,
60056
+ rebuildAuthorizedKeys: () => rebuildAuthorizedKeys(contactStore, import_node_path63.default.join(config.dataDir, "sshd"))
60191
60057
  },
60192
60058
  // app-builder
60193
60059
  appBuilderStore,
@@ -60210,11 +60076,11 @@ async function startDaemon(config) {
60210
60076
  // 发布上线脚手架化 (spec 2026-06-03 §5.2):
60211
60077
  // appBuilderPersonaRoot 用于拼 publish.sh 绝对路径(persona-app-builder 安装在
60212
60078
  // dataDir/personas/persona-app-builder 之下,extension-kit/scripts/publish.sh 是相对路径)。
60213
- appBuilderPersonaRoot: import_node_path64.default.join(config.dataDir, "personas", "persona-app-builder"),
60079
+ appBuilderPersonaRoot: import_node_path63.default.join(config.dataDir, "personas", "persona-app-builder"),
60214
60080
  // 共享 deploy-kit 根:scaffold/publish 脚本骨架 + 阿里云凭证单一真源。
60215
- deployKitRoot: import_node_path64.default.join(config.dataDir, "deploy-kit"),
60081
+ deployKitRoot: import_node_path63.default.join(config.dataDir, "deploy-kit"),
60216
60082
  // scaffold/publish 按当前 session 的 persona 解析其安装根,让每个 persona 用自己的模板/注入配置。
60217
- resolvePersonaRoot: (personaId) => import_node_path64.default.join(config.dataDir, "personas", personaId),
60083
+ resolvePersonaRoot: (personaId) => import_node_path63.default.join(config.dataDir, "personas", personaId),
60218
60084
  // 发布上线脚手架化 (spec 2026-06-03 §5.2.2):
60219
60085
  // 复用 SessionManagerDeps.broadcastFrame 同款 dispatch 逻辑 —— runner 调 manager.send
60220
60086
  // 取回 broadcast 帧后逐帧 push 到 transport,跟 manager 自身的 deps 一致。
@@ -60257,7 +60123,7 @@ async function startDaemon(config) {
60257
60123
  }
60258
60124
  let sourceJsonlPath = "(no transcript yet \u2014 operate from the task description alone)";
60259
60125
  if (sourceFile && sourceFile.toolSessionId) {
60260
- sourceJsonlPath = import_node_path64.default.join(
60126
+ sourceJsonlPath = import_node_path63.default.join(
60261
60127
  import_node_os21.default.homedir(),
60262
60128
  ".claude",
60263
60129
  "projects",
@@ -60617,8 +60483,8 @@ async function startDaemon(config) {
60617
60483
  const lines = [
60618
60484
  `Tunnel: ${r.url}`,
60619
60485
  ...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")}`
60486
+ `Frpc config: ${import_node_path63.default.join(config.dataDir, "frpc.toml")}`,
60487
+ `Frpc log: ${import_node_path63.default.join(logDir, "frpc.log")}`
60622
60488
  ];
60623
60489
  const width = Math.max(...lines.map((l) => l.length));
60624
60490
  const bar = "\u2550".repeat(width + 4);
@@ -60631,8 +60497,8 @@ ${bar}
60631
60497
 
60632
60498
  `);
60633
60499
  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 });
60500
+ const connectPath = import_node_path63.default.join(config.dataDir, "connect.txt");
60501
+ import_node_fs51.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
60636
60502
  } catch {
60637
60503
  }
60638
60504
  } catch (err) {
@@ -60668,21 +60534,13 @@ ${bar}
60668
60534
  });
60669
60535
  try {
60670
60536
  await sshdMgr.start();
60671
- rebuildAuthorizedKeys(contactStore, import_node_path64.default.join(config.dataDir, "sshd"));
60537
+ rebuildAuthorizedKeys(contactStore, import_node_path63.default.join(config.dataDir, "sshd"));
60672
60538
  logger.info("sshd: contact-ssh sandbox ready", { port: config.sshdPort });
60673
60539
  } catch (err) {
60674
60540
  logger.warn("sshd start failed; contact SSH grant will not work until fixed", {
60675
60541
  err: err.message
60676
60542
  });
60677
60543
  }
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
60544
  void reportDevice();
60687
60545
  void fetchServerKey();
60688
60546
  const tickAttachmentGc = () => {
@@ -60717,7 +60575,6 @@ ${bar}
60717
60575
  if (tunnelMgr) {
60718
60576
  await tunnelMgr.stop();
60719
60577
  }
60720
- contactKeyPuller.stop();
60721
60578
  await sshdMgr.stop().catch((err) => {
60722
60579
  logger.warn("shutdown.sshd-stop-failed", {
60723
60580
  error: err instanceof Error ? err.message : String(err)
@@ -60736,9 +60593,9 @@ ${bar}
60736
60593
  };
60737
60594
  }
60738
60595
  function migrateDropPersonsDir(dataDir) {
60739
- const dir = import_node_path64.default.join(dataDir, "persons");
60596
+ const dir = import_node_path63.default.join(dataDir, "persons");
60740
60597
  try {
60741
- import_node_fs52.default.rmSync(dir, { recursive: true, force: true });
60598
+ import_node_fs51.default.rmSync(dir, { recursive: true, force: true });
60742
60599
  } catch {
60743
60600
  }
60744
60601
  }