@clawos-dev/clawd 0.2.238 → 0.2.240

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
@@ -115,6 +115,11 @@ var init_methods = __esm({
115
115
  // METHOD_GRANT_MAP capability-scoped(handler 自检 persona.public + assertGrant(send))。
116
116
  // personaDispatch:complete 仅 B 本地 cc 调(/api/rpc),不跨设备,故不列入 WS method。
117
117
  "personaDispatch:run",
118
+ // ---- peerExec:run (跨设备 exec,spec 2026-07-16-cross-device-exec-mcp) ----
119
+ // A 侧本地 cc 经 /api/rpc(EXPOSED_RPC_METHODS)带 peerDeviceId → daemon forward;
120
+ // B 侧对端经 /rpc(Bearer connect token)无 peerDeviceId → 本地 spawn。METHOD_GRANT_MAP
121
+ // 设 public,handler 内判 remoteAccessAllowed(跟 inbox:postMessage 同款)。
122
+ "peerExec:run",
118
123
  // ---- session:pty 双向透传(仅 CLAWD_CC_MODE=tui 才生效,对应 session:pty push 帧的上行) ----
119
124
  // session:pty:input — UI 把用户键盘字节(base64 UTF-8)发给 daemon,daemon 直写 pty stdin。
120
125
  // 高频帧(每次按键 1 帧),不入 reducer / 不广播。response 是 fire-and-forget 风格 ack。
@@ -161,10 +166,10 @@ var init_methods = __esm({
161
166
  "contact:list",
162
167
  "contact:pin",
163
168
  "contact:remove",
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
- "contact:setSshAccess",
169
+ // ---- contact:setRemoteAccess (远程访问授权,spec 2026-07-16) ----
170
+ // owner UI 授权 contact 反向操作我的设备(exec + SSH 共用开关 remoteAccessAllowed)。
171
+ // 详见 protocol/src/contact-ssh.ts.
172
+ "contact:setRemoteAccess",
168
173
  // ---- visitor:* (web 访客 · persona web 分享,spec 2026-06-24-persona-web-share-design) ----
169
174
  // owner-only:列出登录访问过本机 public persona 的 web 访客(零安装、飞书登录、daemon 自签
170
175
  // visitor token)。存储 ~/.clawd/visitors.json(VisitorStore),登录(exchange)即 upsert;
@@ -744,8 +749,8 @@ var init_parseUtil = __esm({
744
749
  init_errors2();
745
750
  init_en();
746
751
  makeIssue = (params) => {
747
- const { data, path: path76, errorMaps, issueData } = params;
748
- const fullPath = [...path76, ...issueData.path || []];
752
+ const { data, path: path77, errorMaps, issueData } = params;
753
+ const fullPath = [...path77, ...issueData.path || []];
749
754
  const fullIssue = {
750
755
  ...issueData,
751
756
  path: fullPath
@@ -1056,11 +1061,11 @@ var init_types = __esm({
1056
1061
  init_parseUtil();
1057
1062
  init_util();
1058
1063
  ParseInputLazyPath = class {
1059
- constructor(parent, value, path76, key) {
1064
+ constructor(parent, value, path77, key) {
1060
1065
  this._cachedPath = [];
1061
1066
  this.parent = parent;
1062
1067
  this.data = value;
1063
- this._path = path76;
1068
+ this._path = path77;
1064
1069
  this._key = key;
1065
1070
  }
1066
1071
  get path() {
@@ -5745,21 +5750,21 @@ var init_contact = __esm({
5745
5750
  */
5746
5751
  pinnedAt: external_exports.number().int().nullable().default(null),
5747
5752
  /**
5748
- * SSH 反向访问授权(PR: contact-ssh-sandbox)。owner 视角:允许该 contact 通过 daemon
5749
- * 专用 sshd 反向 SSH 进本机。default=false 保证老 contacts.json 兼容(zod default 补齐)。
5750
- * 授权粒度 per-people (per-contact):借用别人 persona 本来就带信任对方本人的前提;
5751
- * 极端场景(区别对方多个 persona)留 per-persona override 演进空间。
5753
+ * 远程访问授权(spec 2026-07-16)。owner 视角:允许该 contact 反向操作我的设备——
5754
+ * 一个开关两种实现:跨设备 exec(peerExec:run handler 判它)+ SSH 反向访问
5755
+ * (rebuildAuthorizedKeys 读它)。default=false,owner 显式开。
5756
+ * 授权粒度 per-people (per-contact):借用别人 persona 本来就带信任对方本人的前提。
5752
5757
  */
5753
- sshAllowed: external_exports.boolean().default(false),
5758
+ remoteAccessAllowed: external_exports.boolean().default(false),
5754
5759
  /** 对端设备 SSH identity pubkey(一行 `ssh-ed25519 AAAA... comment`);见 doc/glossary/peer-mesh.md#建联交换清单 */
5755
5760
  sshPublicKey: external_exports.string().optional(),
5756
5761
  /** 对端 OS 登录账号名,SSH 拨号登录名(`<osUser>@127.0.0.1`);见 doc/glossary/peer-mesh.md#建联交换清单 */
5757
5762
  osUser: external_exports.string().optional(),
5758
5763
  /**
5759
5764
  * @deprecated T-29 去沙箱后废弃。原为授权访问的目录白名单(clawd-ssh-jail 生成 sbpl/bwrap
5760
- * policy 用);去掉 OS 沙箱后不再有目录级限制,SSH 授权即 on/off(见 sshAllowed)。
5765
+ * policy 用);去掉 OS 沙箱后不再有目录级限制,远程访问授权即 on/off(见 remoteAccessAllowed)。
5761
5766
  * 字段仅为兼容老 contacts.json 保留(default [] 保证 strict parse 不炸),不再有代码读写它,
5762
- * 也不再进 setSshAccess RPC / ContactSettingsDrawer / jail。勿新增消费方。
5767
+ * 也不再进 setRemoteAccess RPC / ContactSettingsDrawer / jail。勿新增消费方。
5763
5768
  */
5764
5769
  exposedDirs: external_exports.array(external_exports.string()).default([])
5765
5770
  }).strict();
@@ -5801,24 +5806,51 @@ var init_contact = __esm({
5801
5806
  });
5802
5807
 
5803
5808
  // ../protocol/src/contact-ssh.ts
5804
- var ContactSetSshAccessArgsSchema, ContactSetSshAccessOkSchema, ContactSshAccessUpdatedFrameSchema;
5809
+ var ContactSetRemoteAccessArgsSchema, ContactSetRemoteAccessOkSchema, ContactRemoteAccessUpdatedFrameSchema;
5805
5810
  var init_contact_ssh = __esm({
5806
5811
  "../protocol/src/contact-ssh.ts"() {
5807
5812
  "use strict";
5808
5813
  init_zod();
5809
- ContactSetSshAccessArgsSchema = external_exports.object({
5814
+ ContactSetRemoteAccessArgsSchema = external_exports.object({
5810
5815
  deviceId: external_exports.string().min(1),
5811
- sshAllowed: external_exports.boolean()
5816
+ remoteAccessAllowed: external_exports.boolean()
5812
5817
  }).strict();
5813
- ContactSetSshAccessOkSchema = external_exports.object({
5814
- type: external_exports.literal("contact:setSshAccess:ok"),
5818
+ ContactSetRemoteAccessOkSchema = external_exports.object({
5819
+ type: external_exports.literal("contact:setRemoteAccess:ok"),
5815
5820
  deviceId: external_exports.string().min(1),
5816
- sshAllowed: external_exports.boolean()
5821
+ remoteAccessAllowed: external_exports.boolean()
5817
5822
  }).strict();
5818
- ContactSshAccessUpdatedFrameSchema = external_exports.object({
5819
- type: external_exports.literal("contact:ssh-access-updated"),
5823
+ ContactRemoteAccessUpdatedFrameSchema = external_exports.object({
5824
+ type: external_exports.literal("contact:remote-access-updated"),
5820
5825
  deviceId: external_exports.string().min(1),
5821
- sshAllowed: external_exports.boolean()
5826
+ remoteAccessAllowed: external_exports.boolean()
5827
+ }).strict();
5828
+ }
5829
+ });
5830
+
5831
+ // ../protocol/src/peer-exec.ts
5832
+ var PeerExecRunArgsSchema, PeerExecRunOkSchema;
5833
+ var init_peer_exec = __esm({
5834
+ "../protocol/src/peer-exec.ts"() {
5835
+ "use strict";
5836
+ init_zod();
5837
+ PeerExecRunArgsSchema = external_exports.object({
5838
+ /** A 侧发起时带:目标设备 deviceId。peer-forward 转发前 strip 掉,B 侧本地执行时无此字段。 */
5839
+ peerDeviceId: external_exports.string().min(1).optional(),
5840
+ /** 交给对端 `bash -c` 的命令;cc 自拼完整路径(一发一收,无跨命令会话状态)。 */
5841
+ command: external_exports.string().min(1),
5842
+ /** 超时上限(ms);缺省用 handler 默认 30_000,clamp 300_000。 */
5843
+ timeoutMs: external_exports.number().int().positive().optional()
5844
+ }).strict();
5845
+ PeerExecRunOkSchema = external_exports.object({
5846
+ type: external_exports.literal("peerExec:run:ok"),
5847
+ stdout: external_exports.string(),
5848
+ stderr: external_exports.string(),
5849
+ /** 进程退出码;被超时 kill 时 null。 */
5850
+ exitCode: external_exports.number().int().nullable(),
5851
+ timedOut: external_exports.boolean(),
5852
+ stdoutTruncated: external_exports.boolean(),
5853
+ stderrTruncated: external_exports.boolean()
5822
5854
  }).strict();
5823
5855
  }
5824
5856
  });
@@ -6230,6 +6262,7 @@ var init_runtime = __esm({
6230
6262
  init_inbox();
6231
6263
  init_contact();
6232
6264
  init_contact_ssh();
6265
+ init_peer_exec();
6233
6266
  init_extension();
6234
6267
  init_feishu_auth();
6235
6268
  init_dispatch();
@@ -6509,8 +6542,8 @@ var require_req = __commonJS({
6509
6542
  if (req.originalUrl) {
6510
6543
  _req.url = req.originalUrl;
6511
6544
  } else {
6512
- const path76 = req.path;
6513
- _req.url = typeof path76 === "string" ? path76 : req.url ? req.url.path || req.url : void 0;
6545
+ const path77 = req.path;
6546
+ _req.url = typeof path77 === "string" ? path77 : req.url ? req.url.path || req.url : void 0;
6514
6547
  }
6515
6548
  if (req.query) {
6516
6549
  _req.query = req.query;
@@ -6675,14 +6708,14 @@ var require_redact = __commonJS({
6675
6708
  }
6676
6709
  return obj;
6677
6710
  }
6678
- function parsePath(path76) {
6711
+ function parsePath(path77) {
6679
6712
  const parts = [];
6680
6713
  let current = "";
6681
6714
  let inBrackets = false;
6682
6715
  let inQuotes = false;
6683
6716
  let quoteChar = "";
6684
- for (let i = 0; i < path76.length; i++) {
6685
- const char = path76[i];
6717
+ for (let i = 0; i < path77.length; i++) {
6718
+ const char = path77[i];
6686
6719
  if (!inBrackets && char === ".") {
6687
6720
  if (current) {
6688
6721
  parts.push(current);
@@ -6813,10 +6846,10 @@ var require_redact = __commonJS({
6813
6846
  return current;
6814
6847
  }
6815
6848
  function redactPaths(obj, paths, censor, remove = false) {
6816
- for (const path76 of paths) {
6817
- const parts = parsePath(path76);
6849
+ for (const path77 of paths) {
6850
+ const parts = parsePath(path77);
6818
6851
  if (parts.includes("*")) {
6819
- redactWildcardPath(obj, parts, censor, path76, remove);
6852
+ redactWildcardPath(obj, parts, censor, path77, remove);
6820
6853
  } else {
6821
6854
  if (remove) {
6822
6855
  removeKey(obj, parts);
@@ -6901,8 +6934,8 @@ var require_redact = __commonJS({
6901
6934
  }
6902
6935
  } else {
6903
6936
  if (afterWildcard.includes("*")) {
6904
- const wrappedCensor = typeof censor === "function" ? (value, path76) => {
6905
- const fullPath = [...pathArray.slice(0, pathLength), ...path76];
6937
+ const wrappedCensor = typeof censor === "function" ? (value, path77) => {
6938
+ const fullPath = [...pathArray.slice(0, pathLength), ...path77];
6906
6939
  return censor(value, fullPath);
6907
6940
  } : censor;
6908
6941
  redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
@@ -6937,8 +6970,8 @@ var require_redact = __commonJS({
6937
6970
  return null;
6938
6971
  }
6939
6972
  const pathStructure = /* @__PURE__ */ new Map();
6940
- for (const path76 of pathsToClone) {
6941
- const parts = parsePath(path76);
6973
+ for (const path77 of pathsToClone) {
6974
+ const parts = parsePath(path77);
6942
6975
  let current = pathStructure;
6943
6976
  for (let i = 0; i < parts.length; i++) {
6944
6977
  const part = parts[i];
@@ -6990,24 +7023,24 @@ var require_redact = __commonJS({
6990
7023
  }
6991
7024
  return cloneSelectively(obj, pathStructure);
6992
7025
  }
6993
- function validatePath(path76) {
6994
- if (typeof path76 !== "string") {
7026
+ function validatePath(path77) {
7027
+ if (typeof path77 !== "string") {
6995
7028
  throw new Error("Paths must be (non-empty) strings");
6996
7029
  }
6997
- if (path76 === "") {
7030
+ if (path77 === "") {
6998
7031
  throw new Error("Invalid redaction path ()");
6999
7032
  }
7000
- if (path76.includes("..")) {
7001
- throw new Error(`Invalid redaction path (${path76})`);
7033
+ if (path77.includes("..")) {
7034
+ throw new Error(`Invalid redaction path (${path77})`);
7002
7035
  }
7003
- if (path76.includes(",")) {
7004
- throw new Error(`Invalid redaction path (${path76})`);
7036
+ if (path77.includes(",")) {
7037
+ throw new Error(`Invalid redaction path (${path77})`);
7005
7038
  }
7006
7039
  let bracketCount = 0;
7007
7040
  let inQuotes = false;
7008
7041
  let quoteChar = "";
7009
- for (let i = 0; i < path76.length; i++) {
7010
- const char = path76[i];
7042
+ for (let i = 0; i < path77.length; i++) {
7043
+ const char = path77[i];
7011
7044
  if ((char === '"' || char === "'") && bracketCount > 0) {
7012
7045
  if (!inQuotes) {
7013
7046
  inQuotes = true;
@@ -7021,20 +7054,20 @@ var require_redact = __commonJS({
7021
7054
  } else if (char === "]" && !inQuotes) {
7022
7055
  bracketCount--;
7023
7056
  if (bracketCount < 0) {
7024
- throw new Error(`Invalid redaction path (${path76})`);
7057
+ throw new Error(`Invalid redaction path (${path77})`);
7025
7058
  }
7026
7059
  }
7027
7060
  }
7028
7061
  if (bracketCount !== 0) {
7029
- throw new Error(`Invalid redaction path (${path76})`);
7062
+ throw new Error(`Invalid redaction path (${path77})`);
7030
7063
  }
7031
7064
  }
7032
7065
  function validatePaths(paths) {
7033
7066
  if (!Array.isArray(paths)) {
7034
7067
  throw new TypeError("paths must be an array");
7035
7068
  }
7036
- for (const path76 of paths) {
7037
- validatePath(path76);
7069
+ for (const path77 of paths) {
7070
+ validatePath(path77);
7038
7071
  }
7039
7072
  }
7040
7073
  function slowRedact(options = {}) {
@@ -7202,8 +7235,8 @@ var require_redaction = __commonJS({
7202
7235
  if (shape[k2] === null) {
7203
7236
  o[k2] = (value) => topCensor(value, [k2]);
7204
7237
  } else {
7205
- const wrappedCensor = typeof censor === "function" ? (value, path76) => {
7206
- return censor(value, [k2, ...path76]);
7238
+ const wrappedCensor = typeof censor === "function" ? (value, path77) => {
7239
+ return censor(value, [k2, ...path77]);
7207
7240
  } : censor;
7208
7241
  o[k2] = Redact({
7209
7242
  paths: shape[k2],
@@ -7421,10 +7454,10 @@ var require_atomic_sleep = __commonJS({
7421
7454
  var require_sonic_boom = __commonJS({
7422
7455
  "../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
7423
7456
  "use strict";
7424
- var fs70 = require("fs");
7457
+ var fs71 = require("fs");
7425
7458
  var EventEmitter3 = require("events");
7426
7459
  var inherits = require("util").inherits;
7427
- var path76 = require("path");
7460
+ var path77 = require("path");
7428
7461
  var sleep2 = require_atomic_sleep();
7429
7462
  var assert = require("assert");
7430
7463
  var BUSY_WRITE_TIMEOUT = 100;
@@ -7478,20 +7511,20 @@ var require_sonic_boom = __commonJS({
7478
7511
  const mode = sonic.mode;
7479
7512
  if (sonic.sync) {
7480
7513
  try {
7481
- if (sonic.mkdir) fs70.mkdirSync(path76.dirname(file), { recursive: true });
7482
- const fd = fs70.openSync(file, flags, mode);
7514
+ if (sonic.mkdir) fs71.mkdirSync(path77.dirname(file), { recursive: true });
7515
+ const fd = fs71.openSync(file, flags, mode);
7483
7516
  fileOpened(null, fd);
7484
7517
  } catch (err) {
7485
7518
  fileOpened(err);
7486
7519
  throw err;
7487
7520
  }
7488
7521
  } else if (sonic.mkdir) {
7489
- fs70.mkdir(path76.dirname(file), { recursive: true }, (err) => {
7522
+ fs71.mkdir(path77.dirname(file), { recursive: true }, (err) => {
7490
7523
  if (err) return fileOpened(err);
7491
- fs70.open(file, flags, mode, fileOpened);
7524
+ fs71.open(file, flags, mode, fileOpened);
7492
7525
  });
7493
7526
  } else {
7494
- fs70.open(file, flags, mode, fileOpened);
7527
+ fs71.open(file, flags, mode, fileOpened);
7495
7528
  }
7496
7529
  }
7497
7530
  function SonicBoom(opts) {
@@ -7532,8 +7565,8 @@ var require_sonic_boom = __commonJS({
7532
7565
  this.flush = flushBuffer;
7533
7566
  this.flushSync = flushBufferSync;
7534
7567
  this._actualWrite = actualWriteBuffer;
7535
- fsWriteSync = () => fs70.writeSync(this.fd, this._writingBuf);
7536
- fsWrite = () => fs70.write(this.fd, this._writingBuf, this.release);
7568
+ fsWriteSync = () => fs71.writeSync(this.fd, this._writingBuf);
7569
+ fsWrite = () => fs71.write(this.fd, this._writingBuf, this.release);
7537
7570
  } else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
7538
7571
  this._writingBuf = "";
7539
7572
  this.write = write;
@@ -7542,15 +7575,15 @@ var require_sonic_boom = __commonJS({
7542
7575
  this._actualWrite = actualWrite;
7543
7576
  fsWriteSync = () => {
7544
7577
  if (Buffer.isBuffer(this._writingBuf)) {
7545
- return fs70.writeSync(this.fd, this._writingBuf);
7578
+ return fs71.writeSync(this.fd, this._writingBuf);
7546
7579
  }
7547
- return fs70.writeSync(this.fd, this._writingBuf, "utf8");
7580
+ return fs71.writeSync(this.fd, this._writingBuf, "utf8");
7548
7581
  };
7549
7582
  fsWrite = () => {
7550
7583
  if (Buffer.isBuffer(this._writingBuf)) {
7551
- return fs70.write(this.fd, this._writingBuf, this.release);
7584
+ return fs71.write(this.fd, this._writingBuf, this.release);
7552
7585
  }
7553
- return fs70.write(this.fd, this._writingBuf, "utf8", this.release);
7586
+ return fs71.write(this.fd, this._writingBuf, "utf8", this.release);
7554
7587
  };
7555
7588
  } else {
7556
7589
  throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
@@ -7607,7 +7640,7 @@ var require_sonic_boom = __commonJS({
7607
7640
  }
7608
7641
  }
7609
7642
  if (this._fsync) {
7610
- fs70.fsyncSync(this.fd);
7643
+ fs71.fsyncSync(this.fd);
7611
7644
  }
7612
7645
  const len = this._len;
7613
7646
  if (this._reopening) {
@@ -7721,7 +7754,7 @@ var require_sonic_boom = __commonJS({
7721
7754
  const onDrain = () => {
7722
7755
  if (!this._fsync) {
7723
7756
  try {
7724
- fs70.fsync(this.fd, (err) => {
7757
+ fs71.fsync(this.fd, (err) => {
7725
7758
  this._flushPending = false;
7726
7759
  cb(err);
7727
7760
  });
@@ -7823,7 +7856,7 @@ var require_sonic_boom = __commonJS({
7823
7856
  const fd = this.fd;
7824
7857
  this.once("ready", () => {
7825
7858
  if (fd !== this.fd) {
7826
- fs70.close(fd, (err) => {
7859
+ fs71.close(fd, (err) => {
7827
7860
  if (err) {
7828
7861
  return this.emit("error", err);
7829
7862
  }
@@ -7872,7 +7905,7 @@ var require_sonic_boom = __commonJS({
7872
7905
  buf = this._bufs[0];
7873
7906
  }
7874
7907
  try {
7875
- const n = Buffer.isBuffer(buf) ? fs70.writeSync(this.fd, buf) : fs70.writeSync(this.fd, buf, "utf8");
7908
+ const n = Buffer.isBuffer(buf) ? fs71.writeSync(this.fd, buf) : fs71.writeSync(this.fd, buf, "utf8");
7876
7909
  const releasedBufObj = releaseWritingBuf(buf, this._len, n);
7877
7910
  buf = releasedBufObj.writingBuf;
7878
7911
  this._len = releasedBufObj.len;
@@ -7888,7 +7921,7 @@ var require_sonic_boom = __commonJS({
7888
7921
  }
7889
7922
  }
7890
7923
  try {
7891
- fs70.fsyncSync(this.fd);
7924
+ fs71.fsyncSync(this.fd);
7892
7925
  } catch {
7893
7926
  }
7894
7927
  }
@@ -7909,7 +7942,7 @@ var require_sonic_boom = __commonJS({
7909
7942
  buf = mergeBuf(this._bufs[0], this._lens[0]);
7910
7943
  }
7911
7944
  try {
7912
- const n = fs70.writeSync(this.fd, buf);
7945
+ const n = fs71.writeSync(this.fd, buf);
7913
7946
  buf = buf.subarray(n);
7914
7947
  this._len = Math.max(this._len - n, 0);
7915
7948
  if (buf.length <= 0) {
@@ -7937,13 +7970,13 @@ var require_sonic_boom = __commonJS({
7937
7970
  this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
7938
7971
  if (this.sync) {
7939
7972
  try {
7940
- const written = Buffer.isBuffer(this._writingBuf) ? fs70.writeSync(this.fd, this._writingBuf) : fs70.writeSync(this.fd, this._writingBuf, "utf8");
7973
+ const written = Buffer.isBuffer(this._writingBuf) ? fs71.writeSync(this.fd, this._writingBuf) : fs71.writeSync(this.fd, this._writingBuf, "utf8");
7941
7974
  release(null, written);
7942
7975
  } catch (err) {
7943
7976
  release(err);
7944
7977
  }
7945
7978
  } else {
7946
- fs70.write(this.fd, this._writingBuf, release);
7979
+ fs71.write(this.fd, this._writingBuf, release);
7947
7980
  }
7948
7981
  }
7949
7982
  function actualWriteBuffer() {
@@ -7952,7 +7985,7 @@ var require_sonic_boom = __commonJS({
7952
7985
  this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
7953
7986
  if (this.sync) {
7954
7987
  try {
7955
- const written = fs70.writeSync(this.fd, this._writingBuf);
7988
+ const written = fs71.writeSync(this.fd, this._writingBuf);
7956
7989
  release(null, written);
7957
7990
  } catch (err) {
7958
7991
  release(err);
@@ -7961,7 +7994,7 @@ var require_sonic_boom = __commonJS({
7961
7994
  if (kCopyBuffer) {
7962
7995
  this._writingBuf = Buffer.from(this._writingBuf);
7963
7996
  }
7964
- fs70.write(this.fd, this._writingBuf, release);
7997
+ fs71.write(this.fd, this._writingBuf, release);
7965
7998
  }
7966
7999
  }
7967
8000
  function actualClose(sonic) {
@@ -7977,12 +8010,12 @@ var require_sonic_boom = __commonJS({
7977
8010
  sonic._lens = [];
7978
8011
  assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
7979
8012
  try {
7980
- fs70.fsync(sonic.fd, closeWrapped);
8013
+ fs71.fsync(sonic.fd, closeWrapped);
7981
8014
  } catch {
7982
8015
  }
7983
8016
  function closeWrapped() {
7984
8017
  if (sonic.fd !== 1 && sonic.fd !== 2) {
7985
- fs70.close(sonic.fd, done);
8018
+ fs71.close(sonic.fd, done);
7986
8019
  } else {
7987
8020
  done();
7988
8021
  }
@@ -10346,7 +10379,7 @@ var require_multistream = __commonJS({
10346
10379
  var require_pino = __commonJS({
10347
10380
  "../node_modules/.pnpm/pino@9.14.0/node_modules/pino/pino.js"(exports2, module2) {
10348
10381
  "use strict";
10349
- var os23 = require("os");
10382
+ var os24 = require("os");
10350
10383
  var stdSerializers = require_pino_std_serializers();
10351
10384
  var caller = require_caller();
10352
10385
  var redaction = require_redaction();
@@ -10393,7 +10426,7 @@ var require_pino = __commonJS({
10393
10426
  } = symbols;
10394
10427
  var { epochTime, nullTime } = time;
10395
10428
  var { pid } = process;
10396
- var hostname = os23.hostname();
10429
+ var hostname = os24.hostname();
10397
10430
  var defaultErrorSerializer = stdSerializers.err;
10398
10431
  var defaultOptions = {
10399
10432
  level: "info",
@@ -11117,11 +11150,11 @@ var init_lib = __esm({
11117
11150
  }
11118
11151
  }
11119
11152
  },
11120
- addToPath: function addToPath(path76, added, removed, oldPosInc, options) {
11121
- var last = path76.lastComponent;
11153
+ addToPath: function addToPath(path77, added, removed, oldPosInc, options) {
11154
+ var last = path77.lastComponent;
11122
11155
  if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
11123
11156
  return {
11124
- oldPos: path76.oldPos + oldPosInc,
11157
+ oldPos: path77.oldPos + oldPosInc,
11125
11158
  lastComponent: {
11126
11159
  count: last.count + 1,
11127
11160
  added,
@@ -11131,7 +11164,7 @@ var init_lib = __esm({
11131
11164
  };
11132
11165
  } else {
11133
11166
  return {
11134
- oldPos: path76.oldPos + oldPosInc,
11167
+ oldPos: path77.oldPos + oldPosInc,
11135
11168
  lastComponent: {
11136
11169
  count: 1,
11137
11170
  added,
@@ -11636,10 +11669,10 @@ function attachmentToHistoryMessage(o, ts) {
11636
11669
  const memories = raw.map((m2) => {
11637
11670
  if (!m2 || typeof m2 !== "object") return null;
11638
11671
  const rec3 = m2;
11639
- const path76 = typeof rec3.path === "string" ? rec3.path : null;
11672
+ const path77 = typeof rec3.path === "string" ? rec3.path : null;
11640
11673
  const content = typeof rec3.content === "string" ? rec3.content : null;
11641
- if (!path76 || content == null) return null;
11642
- const entry = { path: path76, content };
11674
+ if (!path77 || content == null) return null;
11675
+ const entry = { path: path77, content };
11643
11676
  if (typeof rec3.mtimeMs === "number") entry.mtimeMs = rec3.mtimeMs;
11644
11677
  return entry;
11645
11678
  }).filter((m2) => m2 !== null);
@@ -12442,10 +12475,10 @@ function parseAttachment(obj) {
12442
12475
  const memories = raw.map((m2) => {
12443
12476
  if (!m2 || typeof m2 !== "object") return null;
12444
12477
  const rec3 = m2;
12445
- const path76 = typeof rec3.path === "string" ? rec3.path : null;
12478
+ const path77 = typeof rec3.path === "string" ? rec3.path : null;
12446
12479
  const content = typeof rec3.content === "string" ? rec3.content : null;
12447
- if (!path76 || content == null) return null;
12448
- const out = { path: path76, content };
12480
+ if (!path77 || content == null) return null;
12481
+ const out = { path: path77, content };
12449
12482
  if (typeof rec3.mtimeMs === "number") out.mtimeMs = rec3.mtimeMs;
12450
12483
  return out;
12451
12484
  }).filter((m2) => m2 !== null);
@@ -33431,8 +33464,8 @@ var require_utils = __commonJS({
33431
33464
  var result = transform[inputType][outputType](input);
33432
33465
  return result;
33433
33466
  };
33434
- exports2.resolve = function(path76) {
33435
- var parts = path76.split("/");
33467
+ exports2.resolve = function(path77) {
33468
+ var parts = path77.split("/");
33436
33469
  var result = [];
33437
33470
  for (var index = 0; index < parts.length; index++) {
33438
33471
  var part = parts[index];
@@ -39285,18 +39318,18 @@ var require_object = __commonJS({
39285
39318
  var object = new ZipObject(name, zipObjectContent, o);
39286
39319
  this.files[name] = object;
39287
39320
  };
39288
- var parentFolder = function(path76) {
39289
- if (path76.slice(-1) === "/") {
39290
- path76 = path76.substring(0, path76.length - 1);
39321
+ var parentFolder = function(path77) {
39322
+ if (path77.slice(-1) === "/") {
39323
+ path77 = path77.substring(0, path77.length - 1);
39291
39324
  }
39292
- var lastSlash = path76.lastIndexOf("/");
39293
- return lastSlash > 0 ? path76.substring(0, lastSlash) : "";
39325
+ var lastSlash = path77.lastIndexOf("/");
39326
+ return lastSlash > 0 ? path77.substring(0, lastSlash) : "";
39294
39327
  };
39295
- var forceTrailingSlash = function(path76) {
39296
- if (path76.slice(-1) !== "/") {
39297
- path76 += "/";
39328
+ var forceTrailingSlash = function(path77) {
39329
+ if (path77.slice(-1) !== "/") {
39330
+ path77 += "/";
39298
39331
  }
39299
- return path76;
39332
+ return path77;
39300
39333
  };
39301
39334
  var folderAdd = function(name, createFolders) {
39302
39335
  createFolders = typeof createFolders !== "undefined" ? createFolders : defaults.createFolders;
@@ -40298,7 +40331,7 @@ var require_lib3 = __commonJS({
40298
40331
  // src/run-case/recorder.ts
40299
40332
  function startRunCaseRecorder(opts) {
40300
40333
  const now = opts.now ?? Date.now;
40301
- const dir = import_node_path64.default.dirname(opts.recordPath);
40334
+ const dir = import_node_path65.default.dirname(opts.recordPath);
40302
40335
  let stream = null;
40303
40336
  let closing = false;
40304
40337
  let closedSettled = false;
@@ -40312,8 +40345,8 @@ function startRunCaseRecorder(opts) {
40312
40345
  });
40313
40346
  const ensureStream = () => {
40314
40347
  if (stream) return stream;
40315
- import_node_fs52.default.mkdirSync(dir, { recursive: true });
40316
- stream = import_node_fs52.default.createWriteStream(opts.recordPath, { flags: "a" });
40348
+ import_node_fs53.default.mkdirSync(dir, { recursive: true });
40349
+ stream = import_node_fs53.default.createWriteStream(opts.recordPath, { flags: "a" });
40317
40350
  stream.on("close", () => closedResolve());
40318
40351
  return stream;
40319
40352
  };
@@ -40338,12 +40371,12 @@ function startRunCaseRecorder(opts) {
40338
40371
  };
40339
40372
  return { tap, close, closed };
40340
40373
  }
40341
- var import_node_fs52, import_node_path64;
40374
+ var import_node_fs53, import_node_path65;
40342
40375
  var init_recorder = __esm({
40343
40376
  "src/run-case/recorder.ts"() {
40344
40377
  "use strict";
40345
- import_node_fs52 = __toESM(require("fs"), 1);
40346
- import_node_path64 = __toESM(require("path"), 1);
40378
+ import_node_fs53 = __toESM(require("fs"), 1);
40379
+ import_node_path65 = __toESM(require("path"), 1);
40347
40380
  }
40348
40381
  });
40349
40382
 
@@ -40386,7 +40419,7 @@ var init_wire = __esm({
40386
40419
  // src/run-case/controller.ts
40387
40420
  async function runController(opts) {
40388
40421
  const now = opts.now ?? Date.now;
40389
- const cwd = opts.cwd ?? (0, import_node_fs53.mkdtempSync)(import_node_path65.default.join(import_node_os22.default.tmpdir(), "clawd-runcase-"));
40422
+ const cwd = opts.cwd ?? (0, import_node_fs54.mkdtempSync)(import_node_path66.default.join(import_node_os23.default.tmpdir(), "clawd-runcase-"));
40390
40423
  const ownsCwd = opts.cwd === void 0;
40391
40424
  const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
40392
40425
  const spawnCtx = { cwd };
@@ -40547,19 +40580,19 @@ async function runController(opts) {
40547
40580
  if (sigintHandler) process.off("SIGINT", sigintHandler);
40548
40581
  if (ownsCwd) {
40549
40582
  try {
40550
- (0, import_node_fs53.rmSync)(cwd, { recursive: true, force: true });
40583
+ (0, import_node_fs54.rmSync)(cwd, { recursive: true, force: true });
40551
40584
  } catch {
40552
40585
  }
40553
40586
  }
40554
40587
  return exitCode ?? 0;
40555
40588
  }
40556
- var import_node_fs53, import_node_os22, import_node_path65;
40589
+ var import_node_fs54, import_node_os23, import_node_path66;
40557
40590
  var init_controller = __esm({
40558
40591
  "src/run-case/controller.ts"() {
40559
40592
  "use strict";
40560
- import_node_fs53 = require("fs");
40561
- import_node_os22 = __toESM(require("os"), 1);
40562
- import_node_path65 = __toESM(require("path"), 1);
40593
+ import_node_fs54 = require("fs");
40594
+ import_node_os23 = __toESM(require("os"), 1);
40595
+ import_node_path66 = __toESM(require("path"), 1);
40563
40596
  init_claude();
40564
40597
  init_stdout_splitter();
40565
40598
  init_permission_stdio();
@@ -40957,9 +40990,9 @@ Env (advanced):
40957
40990
  `;
40958
40991
 
40959
40992
  // src/index.ts
40960
- var import_node_path63 = __toESM(require("path"), 1);
40961
- var import_node_fs51 = __toESM(require("fs"), 1);
40962
- var import_node_os21 = __toESM(require("os"), 1);
40993
+ var import_node_path64 = __toESM(require("path"), 1);
40994
+ var import_node_fs52 = __toESM(require("fs"), 1);
40995
+ var import_node_os22 = __toESM(require("os"), 1);
40963
40996
 
40964
40997
  // ../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/stringify.js
40965
40998
  var byteToHex = [];
@@ -42003,7 +42036,7 @@ ssh -o ProxyCommand='~/.clawd/bin/clawd ssh-relay <A>' \\
42003
42036
  **\u4F60\u80FD\u8BBF\u95EE\u4EC0\u4E48**\uFF1A**\u6CA1\u6709\u76EE\u5F55\u7EA7\u9650\u5236**\u2014\u2014\u4F60\u4EE5 A \u673A\u5668\u4E0A\u8BE5 SSH \u7528\u6237\u7684\u6743\u9650\u8BBF\u95EE\u6587\u4EF6\uFF08\u7B49\u540C\u4E8E A \u7ED9\u4F60\u5F00\u4E86\u4E00\u4E2A shell\uFF09\u3002\u8BF7\u53EA\u5728 A \u6388\u6743/\u9700\u8981\u7684\u8303\u56F4\u5185\u64CD\u4F5C\uFF0C\u4E0D\u8981\u8D8A\u754C\u7FFB\u770B\u65E0\u5173\u76EE\u5F55\u3002
42004
42037
 
42005
42038
  **\u6CE8\u610F**\uFF1A
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
42039
+ - \u6388\u6743\u662F\u5B9E\u65F6\u7684\uFF1AA \u64A4\u9500 remoteAccessAllowed \u540E\u4F60\u4E0B\u4E00\u6B21\u62E8\u53F7\u7ACB\u5373\u5931\u6548\uFF08authorized_keys \u7ACB\u5373\u5237\uFF09
42007
42040
  - \u62E8\u53F7\u88AB\u62D2\uFF08Permission denied\uFF09\u901A\u5E38\u662F A \u5C1A\u672A\u6388\u6743\u672C\u8BBE\u5907\uFF0C\u6216\u53CC\u65B9\u8001\u7248\u672C\u5EFA\u8054\u3001A \u7684 contact \u91CC\u6CA1\u6709\u672C\u673A pubkey\uFF08\u4EFB\u4E00\u65B9\u91CD\u65B0 device:connect \u4E00\u6B21\u5373\u8865\uFF09\uFF0C\u5982\u5B9E\u544A\u8BC9\u7528\u6237
42008
42041
  - \u6392\u969C\uFF1A\u672C\u673A debug \u65E5\u5FD7\u5728 \`~/.clawd/log/contact-ssh.log\`\uFF08\u7528\u6237\u53EF \`tail -f\` \u770B\u6BCF\u4E00\u6B65\uFF09\uFF0C\u4F60\u51FA\u9519\u65F6\u544A\u8BC9\u7528\u6237\u67E5\u8FD9\u4E2A\u6587\u4EF6
42009
42042
  `;
@@ -46760,8 +46793,8 @@ function turnStartInput(text) {
46760
46793
  const items = [];
46761
46794
  let leftover = text;
46762
46795
  for (const m2 of text.matchAll(SKILL_RE)) {
46763
- const [marker, name, path76] = m2;
46764
- items.push({ type: "skill", name, path: path76 });
46796
+ const [marker, name, path77] = m2;
46797
+ items.push({ type: "skill", name, path: path77 });
46765
46798
  leftover = leftover.replace(marker, "");
46766
46799
  }
46767
46800
  for (const m2 of text.matchAll(ATTACHMENT_RE2)) {
@@ -48400,9 +48433,31 @@ async function writeInboxMcpConfig(args) {
48400
48433
  return cfgPath;
48401
48434
  }
48402
48435
 
48436
+ // src/peer-ops/mcp-config.ts
48437
+ var import_node_fs23 = __toESM(require("fs"), 1);
48438
+ var import_node_path20 = __toESM(require("path"), 1);
48439
+ function peerOpsMcpConfigPath(dataDir) {
48440
+ return import_node_path20.default.join(dataDir, "peer-ops.mcp.json");
48441
+ }
48442
+ function writePeerOpsMcpConfig(args) {
48443
+ const cfgPath = peerOpsMcpConfigPath(args.dataDir);
48444
+ const cfg = {
48445
+ _comment: "daemon \u542F\u52A8\u81EA\u52A8\u751F\u6210\uFF1Bcc spawn \u901A\u8FC7 --mcp-config \u6CE8\u5165\u3002peer-ops-mcp-server.cjs \u662F daemon \u81EA\u5E26 stdio MCP server\uFF08\u8DE8\u8BBE\u5907 exec\uFF09\u3002",
48446
+ mcpServers: {
48447
+ "peer-ops": {
48448
+ command: "node",
48449
+ args: [args.serverScriptPath]
48450
+ }
48451
+ }
48452
+ };
48453
+ import_node_fs23.default.mkdirSync(args.dataDir, { recursive: true });
48454
+ import_node_fs23.default.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
48455
+ return cfgPath;
48456
+ }
48457
+
48403
48458
  // src/shift/store.ts
48404
48459
  var import_promises = __toESM(require("fs/promises"), 1);
48405
- var import_node_path20 = __toESM(require("path"), 1);
48460
+ var import_node_path21 = __toESM(require("path"), 1);
48406
48461
  var import_node_crypto6 = require("crypto");
48407
48462
 
48408
48463
  // src/shift/constants.ts
@@ -48485,7 +48540,7 @@ function createShiftStore(deps) {
48485
48540
  flushTimer = null;
48486
48541
  }
48487
48542
  const content = { version: 1, shifts };
48488
- await import_promises.default.mkdir(import_node_path20.default.dirname(deps.filePath), { recursive: true });
48543
+ await import_promises.default.mkdir(import_node_path21.default.dirname(deps.filePath), { recursive: true });
48489
48544
  const tmp = `${deps.filePath}.tmp-${deps.now()}-${Math.floor(Math.random() * 1e6)}`;
48490
48545
  await import_promises.default.writeFile(tmp, JSON.stringify(content, null, 2), "utf8");
48491
48546
  await import_promises.default.rename(tmp, deps.filePath);
@@ -49038,6 +49093,7 @@ function buildPersonaDispatchHandlers(deps) {
49038
49093
  }
49039
49094
 
49040
49095
  // src/dispatch/peer-forward.ts
49096
+ init_protocol();
49041
49097
  function wsUrlToHttp(url) {
49042
49098
  if (url.startsWith("wss://")) return "https://" + url.slice("wss://".length);
49043
49099
  if (url.startsWith("ws://")) return "http://" + url.slice("ws://".length);
@@ -49114,6 +49170,43 @@ async function forwardInboxPostToPeer(args) {
49114
49170
  }
49115
49171
  return { ok: true };
49116
49172
  }
49173
+ async function forwardPeerExecToPeer(args) {
49174
+ const f = args.fetchImpl ?? fetch;
49175
+ const base = wsUrlToHttp(args.contact.remoteUrl).replace(/\/+$/, "");
49176
+ const url = `${base}/rpc/peerExec:run`;
49177
+ const tunnelFail = (msg) => ({
49178
+ type: "peerExec:run:ok",
49179
+ stdout: "",
49180
+ stderr: msg,
49181
+ exitCode: null,
49182
+ timedOut: false,
49183
+ stdoutTruncated: false,
49184
+ stderrTruncated: false
49185
+ });
49186
+ let res;
49187
+ try {
49188
+ res = await f(url, {
49189
+ method: "POST",
49190
+ headers: {
49191
+ "content-type": "application/json",
49192
+ authorization: `Bearer ${args.contact.connectToken}`
49193
+ },
49194
+ body: JSON.stringify({ command: args.command, ...args.timeoutMs ? { timeoutMs: args.timeoutMs } : {} })
49195
+ });
49196
+ } catch (err) {
49197
+ return tunnelFail(`forward to peer failed: ${err instanceof Error ? err.message : String(err)}`);
49198
+ }
49199
+ let json;
49200
+ try {
49201
+ json = await res.json();
49202
+ } catch {
49203
+ return tunnelFail(`peer returned non-JSON response (HTTP ${res.status})`);
49204
+ }
49205
+ if (json.ok === false) {
49206
+ throw new ClawdError(json.error || ERROR_CODES.INTERNAL, json.message || json.error);
49207
+ }
49208
+ return json.result;
49209
+ }
49117
49210
 
49118
49211
  // src/tools/codex-history.ts
49119
49212
  var import_node_child_process5 = require("child_process");
@@ -49301,13 +49394,13 @@ function mapSkillsListResponse(res) {
49301
49394
  const r = s ?? {};
49302
49395
  const name = str3(r.name);
49303
49396
  if (!name) continue;
49304
- const path76 = str3(r.path);
49397
+ const path77 = str3(r.path);
49305
49398
  const description = str3(r.description);
49306
49399
  const isPlugin = name.includes(":");
49307
49400
  out.push({
49308
49401
  name,
49309
49402
  source: isPlugin ? "plugin" : "project",
49310
- ...path76 ? { path: path76 } : {},
49403
+ ...path77 ? { path: path77 } : {},
49311
49404
  ...description ? { description } : {},
49312
49405
  ...isPlugin ? { plugin: name.split(":")[0] } : {}
49313
49406
  });
@@ -49346,23 +49439,23 @@ async function listCodexSkills(cwd, deps = {}) {
49346
49439
  }
49347
49440
 
49348
49441
  // src/workspace/browser.ts
49349
- var import_node_fs23 = __toESM(require("fs"), 1);
49442
+ var import_node_fs24 = __toESM(require("fs"), 1);
49350
49443
  var import_node_os8 = __toESM(require("os"), 1);
49351
- var import_node_path21 = __toESM(require("path"), 1);
49444
+ var import_node_path22 = __toESM(require("path"), 1);
49352
49445
  init_protocol();
49353
49446
  var MAX_FILE_BYTES = 2 * 1024 * 1024;
49354
49447
  function resolveInsideCwd(cwd, subpath) {
49355
- const absCwd = import_node_path21.default.resolve(cwd);
49356
- const joined = import_node_path21.default.resolve(absCwd, subpath ?? ".");
49357
- const rel = import_node_path21.default.relative(absCwd, joined);
49358
- if (rel.startsWith("..") || import_node_path21.default.isAbsolute(rel)) {
49448
+ const absCwd = import_node_path22.default.resolve(cwd);
49449
+ const joined = import_node_path22.default.resolve(absCwd, subpath ?? ".");
49450
+ const rel = import_node_path22.default.relative(absCwd, joined);
49451
+ if (rel.startsWith("..") || import_node_path22.default.isAbsolute(rel)) {
49359
49452
  throw new ClawdError(ERROR_CODES.INVALID_PATH, `path escapes cwd: ${subpath}`);
49360
49453
  }
49361
49454
  return joined;
49362
49455
  }
49363
49456
  function ensureCwd(cwd) {
49364
49457
  try {
49365
- const stat = import_node_fs23.default.statSync(cwd);
49458
+ const stat = import_node_fs24.default.statSync(cwd);
49366
49459
  if (!stat.isDirectory()) {
49367
49460
  throw new ClawdError(ERROR_CODES.INVALID_CWD, `not a directory: ${cwd}`);
49368
49461
  }
@@ -49376,7 +49469,7 @@ var WorkspaceBrowser = class {
49376
49469
  const cwd = args.cwd && args.cwd.length > 0 ? args.cwd : import_node_os8.default.homedir();
49377
49470
  ensureCwd(cwd);
49378
49471
  const full = resolveInsideCwd(cwd, args.path);
49379
- const dirents = import_node_fs23.default.readdirSync(full, { withFileTypes: true });
49472
+ const dirents = import_node_fs24.default.readdirSync(full, { withFileTypes: true });
49380
49473
  const entries = [];
49381
49474
  for (const d of dirents) {
49382
49475
  if (!args.showHidden && d.name.startsWith(".")) continue;
@@ -49386,7 +49479,7 @@ var WorkspaceBrowser = class {
49386
49479
  mtime: ""
49387
49480
  };
49388
49481
  try {
49389
- const st = import_node_fs23.default.statSync(import_node_path21.default.join(full, d.name));
49482
+ const st = import_node_fs24.default.statSync(import_node_path22.default.join(full, d.name));
49390
49483
  entry.mtime = new Date(st.mtimeMs).toISOString();
49391
49484
  if (d.isFile()) entry.size = st.size;
49392
49485
  } catch {
@@ -49402,14 +49495,14 @@ var WorkspaceBrowser = class {
49402
49495
  read(args) {
49403
49496
  ensureCwd(args.cwd);
49404
49497
  const full = resolveInsideCwd(args.cwd, args.path);
49405
- const st = import_node_fs23.default.statSync(full);
49498
+ const st = import_node_fs24.default.statSync(full);
49406
49499
  if (!st.isFile()) {
49407
49500
  throw new ClawdError(ERROR_CODES.INVALID_PATH, `not a file: ${args.path}`);
49408
49501
  }
49409
49502
  if (st.size > MAX_FILE_BYTES) {
49410
49503
  throw new ClawdError(ERROR_CODES.FILE_TOO_LARGE, `file > ${MAX_FILE_BYTES} bytes`);
49411
49504
  }
49412
- const buf = import_node_fs23.default.readFileSync(full);
49505
+ const buf = import_node_fs24.default.readFileSync(full);
49413
49506
  const isBinary = buf.includes(0);
49414
49507
  if (isBinary) {
49415
49508
  return {
@@ -49431,20 +49524,20 @@ var WorkspaceBrowser = class {
49431
49524
  };
49432
49525
 
49433
49526
  // src/skills/agents-scanner.ts
49434
- var import_node_fs24 = __toESM(require("fs"), 1);
49527
+ var import_node_fs25 = __toESM(require("fs"), 1);
49435
49528
  var import_node_os9 = __toESM(require("os"), 1);
49436
- var import_node_path22 = __toESM(require("path"), 1);
49529
+ var import_node_path23 = __toESM(require("path"), 1);
49437
49530
  var DEFAULT_POLICY_DIR_DARWIN = "/Library/Application Support/ClaudeCode/.claude/agents";
49438
49531
  function isDirLikeSync2(p2) {
49439
49532
  try {
49440
- return import_node_fs24.default.statSync(p2).isDirectory();
49533
+ return import_node_fs25.default.statSync(p2).isDirectory();
49441
49534
  } catch {
49442
49535
  return false;
49443
49536
  }
49444
49537
  }
49445
49538
  function fileExistsSync(p2) {
49446
49539
  try {
49447
- return import_node_fs24.default.statSync(p2).isFile();
49540
+ return import_node_fs25.default.statSync(p2).isFile();
49448
49541
  } catch {
49449
49542
  return false;
49450
49543
  }
@@ -49452,7 +49545,7 @@ function fileExistsSync(p2) {
49452
49545
  function parseAgentFile(filePath) {
49453
49546
  let content;
49454
49547
  try {
49455
- content = import_node_fs24.default.readFileSync(filePath, "utf8");
49548
+ content = import_node_fs25.default.readFileSync(filePath, "utf8");
49456
49549
  } catch {
49457
49550
  return {};
49458
49551
  }
@@ -49465,16 +49558,16 @@ function parseAgentFile(filePath) {
49465
49558
  function scanAgentsDir(dir, source, seen, out) {
49466
49559
  let entries;
49467
49560
  try {
49468
- entries = import_node_fs24.default.readdirSync(dir, { withFileTypes: true });
49561
+ entries = import_node_fs25.default.readdirSync(dir, { withFileTypes: true });
49469
49562
  } catch {
49470
49563
  return;
49471
49564
  }
49472
49565
  for (const ent of entries) {
49473
49566
  if (!ent.name.endsWith(".md")) continue;
49474
- if (!ent.isFile() && !(ent.isSymbolicLink() && fileExistsSync(import_node_path22.default.join(dir, ent.name)))) {
49567
+ if (!ent.isFile() && !(ent.isSymbolicLink() && fileExistsSync(import_node_path23.default.join(dir, ent.name)))) {
49475
49568
  continue;
49476
49569
  }
49477
- const filePath = import_node_path22.default.join(dir, ent.name);
49570
+ const filePath = import_node_path23.default.join(dir, ent.name);
49478
49571
  const baseName = ent.name.replace(/\.md$/, "");
49479
49572
  if (seen.has(baseName)) continue;
49480
49573
  seen.add(baseName);
@@ -49492,12 +49585,12 @@ function scanPluginAgentsTree(root, pluginName, seen, out) {
49492
49585
  function walk2(dir, namespaces) {
49493
49586
  let entries;
49494
49587
  try {
49495
- entries = import_node_fs24.default.readdirSync(dir, { withFileTypes: true });
49588
+ entries = import_node_fs25.default.readdirSync(dir, { withFileTypes: true });
49496
49589
  } catch {
49497
49590
  return;
49498
49591
  }
49499
49592
  for (const ent of entries) {
49500
- const childPath = import_node_path22.default.join(dir, ent.name);
49593
+ const childPath = import_node_path23.default.join(dir, ent.name);
49501
49594
  if (ent.isDirectory() || ent.isSymbolicLink() && isDirLikeSync2(childPath)) {
49502
49595
  walk2(childPath, [...namespaces, ent.name]);
49503
49596
  continue;
@@ -49522,13 +49615,13 @@ function scanPluginAgentsTree(root, pluginName, seen, out) {
49522
49615
  walk2(root, []);
49523
49616
  }
49524
49617
  function readInstalledPlugins2(home) {
49525
- const pluginsDir = import_node_path22.default.join(home, ".claude", "plugins");
49526
- const v2 = import_node_path22.default.join(pluginsDir, "installed_plugins_v2.json");
49527
- const v1 = import_node_path22.default.join(pluginsDir, "installed_plugins.json");
49618
+ const pluginsDir = import_node_path23.default.join(home, ".claude", "plugins");
49619
+ const v2 = import_node_path23.default.join(pluginsDir, "installed_plugins_v2.json");
49620
+ const v1 = import_node_path23.default.join(pluginsDir, "installed_plugins.json");
49528
49621
  let raw = null;
49529
49622
  for (const candidate of [v2, v1]) {
49530
49623
  try {
49531
- raw = import_node_fs24.default.readFileSync(candidate, "utf8");
49624
+ raw = import_node_fs25.default.readFileSync(candidate, "utf8");
49532
49625
  break;
49533
49626
  } catch {
49534
49627
  }
@@ -49553,19 +49646,19 @@ function readInstalledPlugins2(home) {
49553
49646
  return out;
49554
49647
  }
49555
49648
  function walkUpProjectAgentsDirs(startCwd, home, seen, out) {
49556
- let cur = import_node_path22.default.resolve(startCwd);
49557
- const fsRoot = import_node_path22.default.parse(cur).root;
49649
+ let cur = import_node_path23.default.resolve(startCwd);
49650
+ const fsRoot = import_node_path23.default.parse(cur).root;
49558
49651
  while (true) {
49559
- scanAgentsDir(import_node_path22.default.join(cur, ".claude", "agents"), "project", seen, out);
49652
+ scanAgentsDir(import_node_path23.default.join(cur, ".claude", "agents"), "project", seen, out);
49560
49653
  let hasGit = false;
49561
49654
  try {
49562
- hasGit = import_node_fs24.default.existsSync(import_node_path22.default.join(cur, ".git"));
49655
+ hasGit = import_node_fs25.default.existsSync(import_node_path23.default.join(cur, ".git"));
49563
49656
  } catch {
49564
49657
  }
49565
49658
  if (hasGit) return;
49566
49659
  if (cur === home) return;
49567
49660
  if (cur === fsRoot) return;
49568
- const parent = import_node_path22.default.dirname(cur);
49661
+ const parent = import_node_path23.default.dirname(cur);
49569
49662
  if (parent === cur) return;
49570
49663
  cur = parent;
49571
49664
  }
@@ -49600,7 +49693,7 @@ var AgentsScanner = class {
49600
49693
  }
49601
49694
  const fsBlock = [];
49602
49695
  scanAgentsDir(
49603
- import_node_path22.default.join(this.home, ".claude", "agents"),
49696
+ import_node_path23.default.join(this.home, ".claude", "agents"),
49604
49697
  "global",
49605
49698
  seen,
49606
49699
  fsBlock
@@ -49614,7 +49707,7 @@ var AgentsScanner = class {
49614
49707
  ...this.extraPluginRoots
49615
49708
  ];
49616
49709
  for (const { name, root } of plugins) {
49617
- const agentsRoot = import_node_path22.default.join(root, "agents");
49710
+ const agentsRoot = import_node_path23.default.join(root, "agents");
49618
49711
  scanPluginAgentsTree(agentsRoot, name, seen, fsBlock);
49619
49712
  }
49620
49713
  return [...builtinBlock, ...fsBlock];
@@ -49622,15 +49715,15 @@ var AgentsScanner = class {
49622
49715
  };
49623
49716
 
49624
49717
  // src/observer/session-observer.ts
49625
- var import_node_fs26 = __toESM(require("fs"), 1);
49718
+ var import_node_fs27 = __toESM(require("fs"), 1);
49626
49719
  var import_node_os11 = __toESM(require("os"), 1);
49627
- var import_node_path24 = __toESM(require("path"), 1);
49720
+ var import_node_path25 = __toESM(require("path"), 1);
49628
49721
  init_claude_history();
49629
49722
 
49630
49723
  // src/observer/subagent-meta-observer.ts
49631
- var import_node_fs25 = __toESM(require("fs"), 1);
49724
+ var import_node_fs26 = __toESM(require("fs"), 1);
49632
49725
  var import_node_os10 = __toESM(require("os"), 1);
49633
- var import_node_path23 = __toESM(require("path"), 1);
49726
+ var import_node_path24 = __toESM(require("path"), 1);
49634
49727
  init_claude_history();
49635
49728
  var META_RE = /^agent-([A-Za-z0-9_-]+)\.meta\.json$/;
49636
49729
  var SubagentMetaObserver = class {
@@ -49643,7 +49736,7 @@ var SubagentMetaObserver = class {
49643
49736
  watches = /* @__PURE__ */ new Map();
49644
49737
  // public for spec only:测试直接拼路径写假 meta.json;生产 start() 内部自己解析
49645
49738
  resolveSubagentDir(cwd, toolSessionId) {
49646
- return import_node_path23.default.join(
49739
+ return import_node_path24.default.join(
49647
49740
  this.home,
49648
49741
  ".claude",
49649
49742
  "projects",
@@ -49673,7 +49766,7 @@ var SubagentMetaObserver = class {
49673
49766
  attachWatcher(w2) {
49674
49767
  if (w2.watcher) return;
49675
49768
  try {
49676
- w2.watcher = import_node_fs25.default.watch(w2.dirPath, { persistent: false }, (_evt, name) => {
49769
+ w2.watcher = import_node_fs26.default.watch(w2.dirPath, { persistent: false }, (_evt, name) => {
49677
49770
  if (!name) return;
49678
49771
  const m2 = META_RE.exec(String(name));
49679
49772
  if (!m2) return;
@@ -49685,7 +49778,7 @@ var SubagentMetaObserver = class {
49685
49778
  scan(w2) {
49686
49779
  let entries;
49687
49780
  try {
49688
- entries = import_node_fs25.default.readdirSync(w2.dirPath);
49781
+ entries = import_node_fs26.default.readdirSync(w2.dirPath);
49689
49782
  } catch {
49690
49783
  return;
49691
49784
  }
@@ -49699,10 +49792,10 @@ var SubagentMetaObserver = class {
49699
49792
  if (!m2) return;
49700
49793
  const agentId = m2[1];
49701
49794
  if (w2.emitted.has(agentId)) return;
49702
- const file = import_node_path23.default.join(w2.dirPath, name);
49795
+ const file = import_node_path24.default.join(w2.dirPath, name);
49703
49796
  let raw;
49704
49797
  try {
49705
- raw = import_node_fs25.default.readFileSync(file, "utf8");
49798
+ raw = import_node_fs26.default.readFileSync(file, "utf8");
49706
49799
  } catch {
49707
49800
  return;
49708
49801
  }
@@ -49759,14 +49852,14 @@ var SessionObserver = class {
49759
49852
  metaObserver;
49760
49853
  resolveJsonlPath(cwd, toolSessionId, override) {
49761
49854
  if (override) return override;
49762
- return import_node_path24.default.join(this.home, ".claude", "projects", cwdToHashDir(cwd), `${toolSessionId}.jsonl`);
49855
+ return import_node_path25.default.join(this.home, ".claude", "projects", cwdToHashDir(cwd), `${toolSessionId}.jsonl`);
49763
49856
  }
49764
49857
  start(args) {
49765
49858
  this.stop(args.sessionId);
49766
49859
  const filePath = this.resolveJsonlPath(args.cwd, args.toolSessionId, args.jsonlPath);
49767
49860
  let size = 0;
49768
49861
  try {
49769
- size = import_node_fs26.default.statSync(filePath).size;
49862
+ size = import_node_fs27.default.statSync(filePath).size;
49770
49863
  } catch {
49771
49864
  }
49772
49865
  const w2 = {
@@ -49779,16 +49872,16 @@ var SessionObserver = class {
49779
49872
  adapter: args.adapter,
49780
49873
  prevIsRejectSentinel: false,
49781
49874
  toolSessionId: args.toolSessionId,
49782
- sawFile: import_node_fs26.default.existsSync(filePath),
49875
+ sawFile: import_node_fs27.default.existsSync(filePath),
49783
49876
  lastRelocateScanMs: 0,
49784
49877
  pendingCwdDerive: false,
49785
49878
  lastDeriveMs: 0
49786
49879
  };
49787
49880
  try {
49788
- import_node_fs26.default.mkdirSync(import_node_path24.default.dirname(filePath), { recursive: true });
49881
+ import_node_fs27.default.mkdirSync(import_node_path25.default.dirname(filePath), { recursive: true });
49789
49882
  } catch {
49790
49883
  }
49791
- w2.watcher = import_node_fs26.default.watch(import_node_path24.default.dirname(filePath), { persistent: false }, (_event, changedName) => {
49884
+ w2.watcher = import_node_fs27.default.watch(import_node_path25.default.dirname(filePath), { persistent: false }, (_event, changedName) => {
49792
49885
  if (!changedName || !w2.filePath.endsWith(changedName)) return;
49793
49886
  this.poll(w2);
49794
49887
  });
@@ -49811,7 +49904,7 @@ var SessionObserver = class {
49811
49904
  // 异常静默吞,不阻塞 watcher 启动
49812
49905
  hydrateMetaTail(w2, maxLines = 200) {
49813
49906
  try {
49814
- const raw = import_node_fs26.default.readFileSync(w2.filePath, "utf8");
49907
+ const raw = import_node_fs27.default.readFileSync(w2.filePath, "utf8");
49815
49908
  if (!raw) return;
49816
49909
  const allLines = raw.split("\n").filter((l) => l.trim().length > 0);
49817
49910
  if (allLines.length === 0) return;
@@ -49835,7 +49928,7 @@ var SessionObserver = class {
49835
49928
  poll(w2) {
49836
49929
  let size = 0;
49837
49930
  try {
49838
- size = import_node_fs26.default.statSync(w2.filePath).size;
49931
+ size = import_node_fs27.default.statSync(w2.filePath).size;
49839
49932
  } catch {
49840
49933
  if (w2.sawFile) this.maybeRelocate(w2);
49841
49934
  return;
@@ -49846,11 +49939,11 @@ var SessionObserver = class {
49846
49939
  w2.buf = "";
49847
49940
  }
49848
49941
  if (size === w2.lastSize) return;
49849
- const fd = import_node_fs26.default.openSync(w2.filePath, "r");
49942
+ const fd = import_node_fs27.default.openSync(w2.filePath, "r");
49850
49943
  try {
49851
49944
  const len = size - w2.lastSize;
49852
49945
  const buf = Buffer.alloc(len);
49853
- import_node_fs26.default.readSync(fd, buf, 0, len, w2.lastSize);
49946
+ import_node_fs27.default.readSync(fd, buf, 0, len, w2.lastSize);
49854
49947
  w2.lastSize = size;
49855
49948
  w2.buf += buf.toString("utf8");
49856
49949
  let newlineIndex;
@@ -49873,7 +49966,7 @@ var SessionObserver = class {
49873
49966
  }
49874
49967
  }
49875
49968
  } finally {
49876
- import_node_fs26.default.closeSync(fd);
49969
+ import_node_fs27.default.closeSync(fd);
49877
49970
  }
49878
49971
  if (w2.pendingCwdDerive) this.maybeDeriveCwd(w2);
49879
49972
  }
@@ -49885,7 +49978,7 @@ var SessionObserver = class {
49885
49978
  const now = Date.now();
49886
49979
  if (now - w2.lastRelocateScanMs < 1e3) return;
49887
49980
  w2.lastRelocateScanMs = now;
49888
- const projectsRoot = import_node_path24.default.join(this.home, ".claude", "projects");
49981
+ const projectsRoot = import_node_path25.default.join(this.home, ".claude", "projects");
49889
49982
  const found = findTranscriptByToolSessionId(projectsRoot, w2.toolSessionId);
49890
49983
  if (!found || found === w2.filePath) return;
49891
49984
  try {
@@ -49894,7 +49987,7 @@ var SessionObserver = class {
49894
49987
  }
49895
49988
  w2.filePath = found;
49896
49989
  try {
49897
- w2.watcher = import_node_fs26.default.watch(import_node_path24.default.dirname(found), { persistent: false }, (_event, changedName) => {
49990
+ w2.watcher = import_node_fs27.default.watch(import_node_path25.default.dirname(found), { persistent: false }, (_event, changedName) => {
49898
49991
  if (!changedName || !w2.filePath.endsWith(changedName)) return;
49899
49992
  this.poll(w2);
49900
49993
  });
@@ -50486,6 +50579,8 @@ var EXPOSED_RPC_METHODS = [
50486
50579
  // B 端 dispatch MCP server 调 complete(带 outcome 触发 run 端 resolve)。详见
50487
50580
  // protocol/src/dispatch.ts。
50488
50581
  "personaDispatch.run",
50582
+ // 跨设备 exec(spec 2026-07-16):A 侧本地 cc 经 /api/rpc 调,带 peerDeviceId → daemon forward。
50583
+ "peerExec.run",
50489
50584
  "personaDispatch.complete",
50490
50585
  // clawd-shift v1 (spec 2026-06-24-clawd-shift):cc 子进程内的 shift MCP server 调
50491
50586
  // 这 5 个 method 管理 owner 的 shift(fire-and-forget 排班)。鉴权靠 body.personaId
@@ -50753,14 +50848,14 @@ async function authenticate(token, deps) {
50753
50848
  }
50754
50849
 
50755
50850
  // src/permission/capability-store.ts
50756
- var fs30 = __toESM(require("fs"), 1);
50757
- var path29 = __toESM(require("path"), 1);
50851
+ var fs31 = __toESM(require("fs"), 1);
50852
+ var path30 = __toESM(require("path"), 1);
50758
50853
  var CAPABILITIES_FILE_NAME = "capabilities.json";
50759
50854
  var FILE_VERSION = 1;
50760
50855
  var CapabilityStore = class {
50761
50856
  constructor(dataDir) {
50762
50857
  this.dataDir = dataDir;
50763
- fs30.mkdirSync(dataDir, { recursive: true });
50858
+ fs31.mkdirSync(dataDir, { recursive: true });
50764
50859
  this.cache = this.readFromDisk();
50765
50860
  }
50766
50861
  dataDir;
@@ -50784,13 +50879,13 @@ var CapabilityStore = class {
50784
50879
  this.flush();
50785
50880
  }
50786
50881
  filePath() {
50787
- return path29.join(this.dataDir, CAPABILITIES_FILE_NAME);
50882
+ return path30.join(this.dataDir, CAPABILITIES_FILE_NAME);
50788
50883
  }
50789
50884
  readFromDisk() {
50790
50885
  const file = this.filePath();
50791
50886
  let raw;
50792
50887
  try {
50793
- raw = fs30.readFileSync(file, "utf8");
50888
+ raw = fs31.readFileSync(file, "utf8");
50794
50889
  } catch (err) {
50795
50890
  if (err?.code === "ENOENT") return [];
50796
50891
  return [];
@@ -50818,10 +50913,10 @@ var CapabilityStore = class {
50818
50913
  }
50819
50914
  atomicWrite(file, content) {
50820
50915
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
50821
- fs30.writeFileSync(tmp, content, { mode: 384 });
50822
- fs30.renameSync(tmp, file);
50916
+ fs31.writeFileSync(tmp, content, { mode: 384 });
50917
+ fs31.renameSync(tmp, file);
50823
50918
  try {
50824
- fs30.chmodSync(file, 384);
50919
+ fs31.chmodSync(file, 384);
50825
50920
  } catch {
50826
50921
  }
50827
50922
  }
@@ -50914,14 +51009,14 @@ var CapabilityManager = class {
50914
51009
  };
50915
51010
 
50916
51011
  // src/permission/cleanup.ts
50917
- var fs31 = __toESM(require("fs"), 1);
51012
+ var fs32 = __toESM(require("fs"), 1);
50918
51013
  function cleanupGuestSessionsForCapability(cap, factory) {
50919
51014
  const removed = [];
50920
51015
  for (const g2 of cap.grants) {
50921
51016
  if (g2.resource.type !== "persona") continue;
50922
51017
  const dir = factory.vmGuestRoot(g2.resource.id, cap.id);
50923
51018
  try {
50924
- fs31.rmSync(dir, { recursive: true, force: true });
51019
+ fs32.rmSync(dir, { recursive: true, force: true });
50925
51020
  removed.push(dir);
50926
51021
  } catch {
50927
51022
  }
@@ -50930,13 +51025,13 @@ function cleanupGuestSessionsForCapability(cap, factory) {
50930
51025
  }
50931
51026
 
50932
51027
  // src/inbox/inbox-store.ts
50933
- var fs32 = __toESM(require("fs"), 1);
50934
- var path30 = __toESM(require("path"), 1);
51028
+ var fs33 = __toESM(require("fs"), 1);
51029
+ var path31 = __toESM(require("path"), 1);
50935
51030
  var INBOX_SUBDIR = "inbox";
50936
51031
  var InboxStore = class {
50937
51032
  constructor(dataDir) {
50938
51033
  this.dataDir = dataDir;
50939
- fs32.mkdirSync(this.dirPath(), { recursive: true });
51034
+ fs33.mkdirSync(this.dirPath(), { recursive: true });
50940
51035
  }
50941
51036
  dataDir;
50942
51037
  /**
@@ -50948,7 +51043,7 @@ var InboxStore = class {
50948
51043
  const file = this.filePath(peerDeviceId);
50949
51044
  let raw;
50950
51045
  try {
50951
- raw = fs32.readFileSync(file, "utf8");
51046
+ raw = fs33.readFileSync(file, "utf8");
50952
51047
  } catch (err) {
50953
51048
  if (err?.code === "ENOENT") return [];
50954
51049
  return [];
@@ -50964,7 +51059,7 @@ var InboxStore = class {
50964
51059
  const dir = this.dirPath();
50965
51060
  let entries;
50966
51061
  try {
50967
- entries = fs32.readdirSync(dir);
51062
+ entries = fs33.readdirSync(dir);
50968
51063
  } catch (err) {
50969
51064
  if (err?.code === "ENOENT") return [];
50970
51065
  return [];
@@ -50980,9 +51075,9 @@ var InboxStore = class {
50980
51075
  if (existing.some((m2) => m2.id === message.id)) return;
50981
51076
  const file = this.filePath(message.peerDeviceId);
50982
51077
  const line = JSON.stringify(message) + "\n";
50983
- fs32.appendFileSync(file, line, { mode: 384 });
51078
+ fs33.appendFileSync(file, line, { mode: 384 });
50984
51079
  try {
50985
- fs32.chmodSync(file, 384);
51080
+ fs33.chmodSync(file, 384);
50986
51081
  } catch {
50987
51082
  }
50988
51083
  }
@@ -51012,7 +51107,7 @@ var InboxStore = class {
51012
51107
  removeByPeerDeviceId(peerDeviceId) {
51013
51108
  const file = this.filePath(peerDeviceId);
51014
51109
  try {
51015
- fs32.unlinkSync(file);
51110
+ fs33.unlinkSync(file);
51016
51111
  } catch (err) {
51017
51112
  if (err?.code === "ENOENT") return;
51018
51113
  }
@@ -51021,18 +51116,18 @@ var InboxStore = class {
51021
51116
  const file = this.filePath(peerDeviceId);
51022
51117
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
51023
51118
  const content = messages.map((m2) => JSON.stringify(m2)).join("\n") + (messages.length > 0 ? "\n" : "");
51024
- fs32.writeFileSync(tmp, content, { mode: 384 });
51025
- fs32.renameSync(tmp, file);
51119
+ fs33.writeFileSync(tmp, content, { mode: 384 });
51120
+ fs33.renameSync(tmp, file);
51026
51121
  try {
51027
- fs32.chmodSync(file, 384);
51122
+ fs33.chmodSync(file, 384);
51028
51123
  } catch {
51029
51124
  }
51030
51125
  }
51031
51126
  dirPath() {
51032
- return path30.join(this.dataDir, INBOX_SUBDIR);
51127
+ return path31.join(this.dataDir, INBOX_SUBDIR);
51033
51128
  }
51034
51129
  filePath(peerDeviceId) {
51035
- return path30.join(this.dirPath(), `${peerDeviceId}.jsonl`);
51130
+ return path31.join(this.dirPath(), `${peerDeviceId}.jsonl`);
51036
51131
  }
51037
51132
  };
51038
51133
  function parseAllLines(raw) {
@@ -51120,8 +51215,8 @@ var InboxManager = class {
51120
51215
  };
51121
51216
 
51122
51217
  // src/state/contact-store.ts
51123
- var fs33 = __toESM(require("fs"), 1);
51124
- var path31 = __toESM(require("path"), 1);
51218
+ var fs34 = __toESM(require("fs"), 1);
51219
+ var path32 = __toESM(require("path"), 1);
51125
51220
  var FILE_NAME = "contacts.json";
51126
51221
  var ContactStore = class {
51127
51222
  constructor(dataDir) {
@@ -51131,10 +51226,10 @@ var ContactStore = class {
51131
51226
  contacts = /* @__PURE__ */ new Map();
51132
51227
  load() {
51133
51228
  this.contacts.clear();
51134
- const file = path31.join(this.dataDir, FILE_NAME);
51229
+ const file = path32.join(this.dataDir, FILE_NAME);
51135
51230
  let raw;
51136
51231
  try {
51137
- raw = fs33.readFileSync(file, "utf8");
51232
+ raw = fs34.readFileSync(file, "utf8");
51138
51233
  } catch (err) {
51139
51234
  if (err?.code !== "ENOENT") this.renameBak(file);
51140
51235
  return;
@@ -51189,35 +51284,35 @@ var ContactStore = class {
51189
51284
  return true;
51190
51285
  }
51191
51286
  /**
51192
- * 更新单条 contact 的 SSH 授权开关(对齐 setPin pattern)。T-29 去沙箱后只改 sshAllowed
51287
+ * 更新单条 contact 的 SSH 授权开关(对齐 setPin pattern)。T-29 去沙箱后只改 remoteAccessAllowed
51193
51288
  * exposedDirs 已废弃,本方法不再触碰它(保留 contact 原值不动)。
51194
51289
  * @returns 是否命中:deviceId 不存在返 false;命中即 flush.
51195
51290
  */
51196
- setSshAccess(deviceId, opts) {
51291
+ setRemoteAccess(deviceId, opts) {
51197
51292
  const existing = this.contacts.get(deviceId);
51198
51293
  if (!existing) return false;
51199
51294
  this.contacts.set(deviceId, {
51200
51295
  ...existing,
51201
- sshAllowed: opts.sshAllowed
51296
+ remoteAccessAllowed: opts.remoteAccessAllowed
51202
51297
  });
51203
51298
  this.flush();
51204
51299
  return true;
51205
51300
  }
51206
51301
  flush() {
51207
- const file = path31.join(this.dataDir, FILE_NAME);
51302
+ const file = path32.join(this.dataDir, FILE_NAME);
51208
51303
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
51209
51304
  const content = JSON.stringify(
51210
51305
  { contacts: Array.from(this.contacts.values()) },
51211
51306
  null,
51212
51307
  2
51213
51308
  );
51214
- fs33.mkdirSync(this.dataDir, { recursive: true });
51215
- fs33.writeFileSync(tmp, content, { mode: 384 });
51216
- fs33.renameSync(tmp, file);
51309
+ fs34.mkdirSync(this.dataDir, { recursive: true });
51310
+ fs34.writeFileSync(tmp, content, { mode: 384 });
51311
+ fs34.renameSync(tmp, file);
51217
51312
  }
51218
51313
  renameBak(file) {
51219
51314
  try {
51220
- fs33.renameSync(file, `${file}.bak`);
51315
+ fs34.renameSync(file, `${file}.bak`);
51221
51316
  } catch {
51222
51317
  }
51223
51318
  }
@@ -51362,7 +51457,7 @@ async function autoReverseContact(args) {
51362
51457
  grants,
51363
51458
  addedAt: now(),
51364
51459
  pinnedAt: existing?.pinnedAt ?? null,
51365
- sshAllowed: existing?.sshAllowed ?? false,
51460
+ remoteAccessAllowed: existing?.remoteAccessAllowed ?? false,
51366
51461
  // 建联交换:缺省不落 undefined 占位(JSON 持久化干净)。见 …#建联交换清单
51367
51462
  ...sshPublicKey ? { sshPublicKey } : {},
51368
51463
  ...osUser ? { osUser } : {},
@@ -51370,7 +51465,7 @@ async function autoReverseContact(args) {
51370
51465
  };
51371
51466
  args.store.upsert(base);
51372
51467
  args.broadcast({ type: "contact:added", contact: base });
51373
- if (base.sshAllowed && sshPublicKey && sshPublicKey !== existing?.sshPublicKey) {
51468
+ if (base.remoteAccessAllowed && sshPublicKey && sshPublicKey !== existing?.sshPublicKey) {
51374
51469
  args.rebuildAuthorizedKeys?.();
51375
51470
  }
51376
51471
  if (!args.exchangeToken) return;
@@ -51387,61 +51482,61 @@ async function autoReverseContact(args) {
51387
51482
  }
51388
51483
 
51389
51484
  // src/migrations/2026-05-20-flatten-sessions.ts
51390
- var fs34 = __toESM(require("fs"), 1);
51391
- var path32 = __toESM(require("path"), 1);
51485
+ var fs35 = __toESM(require("fs"), 1);
51486
+ var path33 = __toESM(require("path"), 1);
51392
51487
  var MIGRATION_FLAG_NAME = ".migration.v1.done";
51393
51488
  function migrateFlattenSessions(opts) {
51394
51489
  const dataDir = opts.dataDir;
51395
51490
  const now = opts.now ?? Date.now;
51396
- const sessionsDir = path32.join(dataDir, "sessions");
51397
- const flagPath = path32.join(sessionsDir, MIGRATION_FLAG_NAME);
51491
+ const sessionsDir = path33.join(dataDir, "sessions");
51492
+ const flagPath = path33.join(sessionsDir, MIGRATION_FLAG_NAME);
51398
51493
  if (existsSync3(flagPath)) {
51399
51494
  return { skipped: true, flagWritten: false, movedBare: 0, movedVmOwner: 0, archivedListener: 0 };
51400
51495
  }
51401
51496
  let movedBare = 0;
51402
51497
  let movedVmOwner = 0;
51403
51498
  let archivedListener = 0;
51404
- const defaultDir = path32.join(sessionsDir, "default");
51499
+ const defaultDir = path33.join(sessionsDir, "default");
51405
51500
  if (existsSync3(defaultDir)) {
51406
51501
  for (const entry of readdirSafe(defaultDir)) {
51407
51502
  if (!entry.endsWith(".json")) continue;
51408
- const src = path32.join(defaultDir, entry);
51409
- const dst = path32.join(sessionsDir, entry);
51410
- fs34.renameSync(src, dst);
51503
+ const src = path33.join(defaultDir, entry);
51504
+ const dst = path33.join(sessionsDir, entry);
51505
+ fs35.renameSync(src, dst);
51411
51506
  movedBare += 1;
51412
51507
  }
51413
51508
  rmdirIfEmpty(defaultDir);
51414
51509
  }
51415
51510
  for (const pid of readdirSafe(sessionsDir)) {
51416
- const personaDir = path32.join(sessionsDir, pid);
51511
+ const personaDir = path33.join(sessionsDir, pid);
51417
51512
  if (!isDir(personaDir)) continue;
51418
51513
  if (pid === "default") continue;
51419
- const ownerSrc = path32.join(personaDir, "owner");
51514
+ const ownerSrc = path33.join(personaDir, "owner");
51420
51515
  if (existsSync3(ownerSrc) && isDir(ownerSrc)) {
51421
- const ownerDst = path32.join(dataDir, "personas", pid, ".clawd", "sessions", "owner");
51422
- fs34.mkdirSync(ownerDst, { recursive: true });
51516
+ const ownerDst = path33.join(dataDir, "personas", pid, ".clawd", "sessions", "owner");
51517
+ fs35.mkdirSync(ownerDst, { recursive: true });
51423
51518
  for (const file of readdirSafe(ownerSrc)) {
51424
51519
  if (!file.endsWith(".json")) continue;
51425
- fs34.renameSync(path32.join(ownerSrc, file), path32.join(ownerDst, file));
51520
+ fs35.renameSync(path33.join(ownerSrc, file), path33.join(ownerDst, file));
51426
51521
  movedVmOwner += 1;
51427
51522
  }
51428
51523
  rmdirIfEmpty(ownerSrc);
51429
51524
  }
51430
- const listenerSrc = path32.join(personaDir, "listener");
51525
+ const listenerSrc = path33.join(personaDir, "listener");
51431
51526
  if (existsSync3(listenerSrc) && isDir(listenerSrc)) {
51432
- const archiveDst = path32.join(dataDir, ".legacy", `listener-${pid}`);
51433
- fs34.mkdirSync(archiveDst, { recursive: true });
51527
+ const archiveDst = path33.join(dataDir, ".legacy", `listener-${pid}`);
51528
+ fs35.mkdirSync(archiveDst, { recursive: true });
51434
51529
  for (const file of readdirSafe(listenerSrc)) {
51435
51530
  if (!file.endsWith(".json")) continue;
51436
- fs34.renameSync(path32.join(listenerSrc, file), path32.join(archiveDst, file));
51531
+ fs35.renameSync(path33.join(listenerSrc, file), path33.join(archiveDst, file));
51437
51532
  archivedListener += 1;
51438
51533
  }
51439
51534
  rmdirIfEmpty(listenerSrc);
51440
51535
  }
51441
51536
  rmdirIfEmpty(personaDir);
51442
51537
  }
51443
- fs34.mkdirSync(sessionsDir, { recursive: true });
51444
- fs34.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
51538
+ fs35.mkdirSync(sessionsDir, { recursive: true });
51539
+ fs35.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
51445
51540
  return {
51446
51541
  skipped: false,
51447
51542
  flagWritten: true,
@@ -51452,7 +51547,7 @@ function migrateFlattenSessions(opts) {
51452
51547
  }
51453
51548
  function existsSync3(p2) {
51454
51549
  try {
51455
- fs34.statSync(p2);
51550
+ fs35.statSync(p2);
51456
51551
  return true;
51457
51552
  } catch {
51458
51553
  return false;
@@ -51460,31 +51555,31 @@ function existsSync3(p2) {
51460
51555
  }
51461
51556
  function isDir(p2) {
51462
51557
  try {
51463
- return fs34.statSync(p2).isDirectory();
51558
+ return fs35.statSync(p2).isDirectory();
51464
51559
  } catch {
51465
51560
  return false;
51466
51561
  }
51467
51562
  }
51468
51563
  function readdirSafe(p2) {
51469
51564
  try {
51470
- return fs34.readdirSync(p2);
51565
+ return fs35.readdirSync(p2);
51471
51566
  } catch {
51472
51567
  return [];
51473
51568
  }
51474
51569
  }
51475
51570
  function rmdirIfEmpty(p2) {
51476
51571
  try {
51477
- fs34.rmdirSync(p2);
51572
+ fs35.rmdirSync(p2);
51478
51573
  } catch {
51479
51574
  }
51480
51575
  }
51481
51576
 
51482
51577
  // src/transport/http-router.ts
51483
- var import_node_fs28 = __toESM(require("fs"), 1);
51484
- var import_node_path28 = __toESM(require("path"), 1);
51578
+ var import_node_fs29 = __toESM(require("fs"), 1);
51579
+ var import_node_path29 = __toESM(require("path"), 1);
51485
51580
 
51486
51581
  // src/attachment/mime.ts
51487
- var import_node_path25 = __toESM(require("path"), 1);
51582
+ var import_node_path26 = __toESM(require("path"), 1);
51488
51583
  var TEXT_PLAIN = "text/plain; charset=utf-8";
51489
51584
  var EXT_TO_NATIVE_MIME = {
51490
51585
  // 图片
@@ -51591,7 +51686,7 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
51591
51686
  ".mk"
51592
51687
  ]);
51593
51688
  function lookupMime(filePathOrName) {
51594
- const ext = import_node_path25.default.extname(filePathOrName).toLowerCase();
51689
+ const ext = import_node_path26.default.extname(filePathOrName).toLowerCase();
51595
51690
  if (EXT_TO_NATIVE_MIME[ext]) return EXT_TO_NATIVE_MIME[ext];
51596
51691
  if (TEXT_EXTENSIONS.has(ext)) return TEXT_PLAIN;
51597
51692
  return "application/octet-stream";
@@ -51660,8 +51755,8 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
51660
51755
  }
51661
51756
 
51662
51757
  // src/attachment/upload.ts
51663
- var import_node_fs27 = __toESM(require("fs"), 1);
51664
- var import_node_path26 = __toESM(require("path"), 1);
51758
+ var import_node_fs28 = __toESM(require("fs"), 1);
51759
+ var import_node_path27 = __toESM(require("path"), 1);
51665
51760
  var import_node_crypto8 = __toESM(require("crypto"), 1);
51666
51761
  var import_promises2 = require("stream/promises");
51667
51762
  var UploadError = class extends Error {
@@ -51673,22 +51768,22 @@ var UploadError = class extends Error {
51673
51768
  code;
51674
51769
  };
51675
51770
  function assertValidFileName(fileName) {
51676
- if (fileName.length === 0 || fileName === "." || fileName === ".." || fileName.startsWith(".") || fileName.includes("/") || fileName.includes("\\") || fileName !== import_node_path26.default.basename(fileName)) {
51771
+ if (fileName.length === 0 || fileName === "." || fileName === ".." || fileName.startsWith(".") || fileName.includes("/") || fileName.includes("\\") || fileName !== import_node_path27.default.basename(fileName)) {
51677
51772
  throw new UploadError("INVALID_FILENAME", `fileName must be a plain basename, got: ${fileName}`);
51678
51773
  }
51679
51774
  }
51680
51775
  var HASH_PREFIX_LEN = 16;
51681
51776
  async function writeUploadedAttachment(args) {
51682
51777
  assertValidFileName(args.fileName);
51683
- const attachmentsRoot = import_node_path26.default.join(args.sessionDir, ".attachments");
51778
+ const attachmentsRoot = import_node_path27.default.join(args.sessionDir, ".attachments");
51684
51779
  try {
51685
- import_node_fs27.default.mkdirSync(attachmentsRoot, { recursive: true });
51780
+ import_node_fs28.default.mkdirSync(attachmentsRoot, { recursive: true });
51686
51781
  } catch (err) {
51687
51782
  throw new UploadError("STORAGE_ERROR", `mkdir failed: ${err.message}`);
51688
51783
  }
51689
51784
  const hasher = import_node_crypto8.default.createHash("sha256");
51690
51785
  let actualSize = 0;
51691
- const tmpPath = import_node_path26.default.join(
51786
+ const tmpPath = import_node_path27.default.join(
51692
51787
  attachmentsRoot,
51693
51788
  `.upload-${process.pid}-${Date.now()}-${import_node_crypto8.default.randomBytes(4).toString("hex")}`
51694
51789
  );
@@ -51703,18 +51798,18 @@ async function writeUploadedAttachment(args) {
51703
51798
  yield buf;
51704
51799
  }
51705
51800
  },
51706
- import_node_fs27.default.createWriteStream(tmpPath, { mode: 384 })
51801
+ import_node_fs28.default.createWriteStream(tmpPath, { mode: 384 })
51707
51802
  );
51708
51803
  } catch (err) {
51709
51804
  try {
51710
- import_node_fs27.default.unlinkSync(tmpPath);
51805
+ import_node_fs28.default.unlinkSync(tmpPath);
51711
51806
  } catch {
51712
51807
  }
51713
51808
  throw new UploadError("STORAGE_ERROR", `write failed: ${err.message}`);
51714
51809
  }
51715
51810
  if (actualSize !== args.contentLength) {
51716
51811
  try {
51717
- import_node_fs27.default.unlinkSync(tmpPath);
51812
+ import_node_fs28.default.unlinkSync(tmpPath);
51718
51813
  } catch {
51719
51814
  }
51720
51815
  throw new UploadError(
@@ -51723,35 +51818,35 @@ async function writeUploadedAttachment(args) {
51723
51818
  );
51724
51819
  }
51725
51820
  const attachmentId = hasher.digest("hex").slice(0, HASH_PREFIX_LEN);
51726
- const hashDir = import_node_path26.default.join(attachmentsRoot, attachmentId);
51821
+ const hashDir = import_node_path27.default.join(attachmentsRoot, attachmentId);
51727
51822
  let finalFileName;
51728
51823
  let hashDirExists = false;
51729
51824
  try {
51730
- hashDirExists = import_node_fs27.default.statSync(hashDir).isDirectory();
51825
+ hashDirExists = import_node_fs28.default.statSync(hashDir).isDirectory();
51731
51826
  } catch {
51732
51827
  }
51733
51828
  if (hashDirExists) {
51734
- const existing = import_node_fs27.default.readdirSync(hashDir).filter((n) => !n.startsWith("."));
51829
+ const existing = import_node_fs28.default.readdirSync(hashDir).filter((n) => !n.startsWith("."));
51735
51830
  finalFileName = existing[0] ?? args.fileName;
51736
51831
  try {
51737
- import_node_fs27.default.unlinkSync(tmpPath);
51832
+ import_node_fs28.default.unlinkSync(tmpPath);
51738
51833
  } catch {
51739
51834
  }
51740
51835
  } else {
51741
51836
  try {
51742
- import_node_fs27.default.mkdirSync(hashDir, { recursive: true });
51837
+ import_node_fs28.default.mkdirSync(hashDir, { recursive: true });
51743
51838
  finalFileName = args.fileName;
51744
- import_node_fs27.default.renameSync(tmpPath, import_node_path26.default.join(hashDir, finalFileName));
51839
+ import_node_fs28.default.renameSync(tmpPath, import_node_path27.default.join(hashDir, finalFileName));
51745
51840
  } catch (err) {
51746
51841
  try {
51747
- import_node_fs27.default.unlinkSync(tmpPath);
51842
+ import_node_fs28.default.unlinkSync(tmpPath);
51748
51843
  } catch {
51749
51844
  }
51750
51845
  throw new UploadError("STORAGE_ERROR", `rename failed: ${err.message}`);
51751
51846
  }
51752
51847
  }
51753
- const absPath = import_node_path26.default.join(hashDir, finalFileName);
51754
- const relPath = import_node_path26.default.relative(args.sessionDir, absPath);
51848
+ const absPath = import_node_path27.default.join(hashDir, finalFileName);
51849
+ const relPath = import_node_path27.default.relative(args.sessionDir, absPath);
51755
51850
  args.groupFileStore.upsert(args.scope, args.sessionId, {
51756
51851
  relPath: absPath,
51757
51852
  // 存绝对路径,与现有 agent 入清单的形态一致(attachment.ts:144 双形态兼容)
@@ -51765,7 +51860,7 @@ async function writeUploadedAttachment(args) {
51765
51860
 
51766
51861
  // src/extension/import.ts
51767
51862
  var import_promises3 = __toESM(require("fs/promises"), 1);
51768
- var import_node_path27 = __toESM(require("path"), 1);
51863
+ var import_node_path28 = __toESM(require("path"), 1);
51769
51864
  var import_node_os12 = __toESM(require("os"), 1);
51770
51865
  var import_jszip = __toESM(require_lib3(), 1);
51771
51866
  var ImportError = class extends Error {
@@ -51783,7 +51878,7 @@ async function importZip(buf, root) {
51783
51878
  throw new ImportError("ZIP_INVALID", `failed to load zip: ${e.message}`);
51784
51879
  }
51785
51880
  for (const name of Object.keys(zip.files)) {
51786
- if (name.includes("..") || name.startsWith("/") || import_node_path27.default.isAbsolute(name)) {
51881
+ if (name.includes("..") || name.startsWith("/") || import_node_path28.default.isAbsolute(name)) {
51787
51882
  throw new ImportError("ZIP_INVALID", `unsafe zip entry path: ${name}`);
51788
51883
  }
51789
51884
  }
@@ -51816,7 +51911,7 @@ async function importZip(buf, root) {
51816
51911
  );
51817
51912
  }
51818
51913
  }
51819
- const destDir = import_node_path27.default.join(root, manifest.id);
51914
+ const destDir = import_node_path28.default.join(root, manifest.id);
51820
51915
  let destExists = false;
51821
51916
  try {
51822
51917
  await import_promises3.default.access(destDir);
@@ -51826,15 +51921,15 @@ async function importZip(buf, root) {
51826
51921
  if (destExists) {
51827
51922
  throw new ImportError("ALREADY_EXISTS", `extension ${manifest.id} already installed`);
51828
51923
  }
51829
- const stage = await import_promises3.default.mkdtemp(import_node_path27.default.join(import_node_os12.default.tmpdir(), `clawd-ext-stage-${manifest.id}-`));
51924
+ const stage = await import_promises3.default.mkdtemp(import_node_path28.default.join(import_node_os12.default.tmpdir(), `clawd-ext-stage-${manifest.id}-`));
51830
51925
  try {
51831
51926
  for (const [name, entry] of Object.entries(zip.files)) {
51832
- const dest = import_node_path27.default.join(stage, name);
51927
+ const dest = import_node_path28.default.join(stage, name);
51833
51928
  if (entry.dir) {
51834
51929
  await import_promises3.default.mkdir(dest, { recursive: true });
51835
51930
  continue;
51836
51931
  }
51837
- await import_promises3.default.mkdir(import_node_path27.default.dirname(dest), { recursive: true });
51932
+ await import_promises3.default.mkdir(import_node_path28.default.dirname(dest), { recursive: true });
51838
51933
  const content = await entry.async("nodebuffer");
51839
51934
  await import_promises3.default.writeFile(dest, content);
51840
51935
  }
@@ -51865,7 +51960,7 @@ var SHARE_UI_ASSET_MIME = {
51865
51960
  ".wasm": "application/wasm"
51866
51961
  };
51867
51962
  function shareUiAssetMime(filePath) {
51868
- const ext = import_node_path28.default.extname(filePath).toLowerCase();
51963
+ const ext = import_node_path29.default.extname(filePath).toLowerCase();
51869
51964
  return SHARE_UI_ASSET_MIME[ext] ?? lookupMime(filePath);
51870
51965
  }
51871
51966
  var DISPATCH_HEARTBEAT_MS = 25e3;
@@ -51952,7 +52047,7 @@ function isValidUploadFileName(fileName) {
51952
52047
  if (fileName === "." || fileName === "..") return false;
51953
52048
  if (fileName.startsWith(".")) return false;
51954
52049
  if (fileName.includes("/") || fileName.includes("\\")) return false;
51955
- return fileName === import_node_path28.default.basename(fileName);
52050
+ return fileName === import_node_path29.default.basename(fileName);
51956
52051
  }
51957
52052
  function createHttpRouter(deps) {
51958
52053
  return async (req, res) => {
@@ -52024,7 +52119,7 @@ function createHttpRouter(deps) {
52024
52119
  }
52025
52120
  let buf;
52026
52121
  try {
52027
- buf = await import_node_fs28.default.promises.readFile(abs);
52122
+ buf = await import_node_fs29.default.promises.readFile(abs);
52028
52123
  } catch {
52029
52124
  sendJson(res, 404, { code: "NOT_FOUND", message: "asset not found" });
52030
52125
  return true;
@@ -52248,7 +52343,7 @@ function createHttpRouter(deps) {
52248
52343
  sendHtml(res, statusByCode[r.code], loader.renderErrorHtml(r.code, msgByCode[r.code]));
52249
52344
  return true;
52250
52345
  }
52251
- sendHtml(res, 200, loader.renderViewerHtml(import_node_path28.default.basename(r.absPath)));
52346
+ sendHtml(res, 200, loader.renderViewerHtml(import_node_path29.default.basename(r.absPath)));
52252
52347
  return true;
52253
52348
  }
52254
52349
  const ctx = deps.authResolver.resolveFromHeader(
@@ -52369,7 +52464,7 @@ function createHttpRouter(deps) {
52369
52464
  return true;
52370
52465
  }
52371
52466
  let absPath;
52372
- if (import_node_path28.default.isAbsolute(pathParam)) {
52467
+ if (import_node_path29.default.isAbsolute(pathParam)) {
52373
52468
  absPath = pathParam;
52374
52469
  } else if (deps.sessionStore) {
52375
52470
  const file = deps.sessionStore.read(sid);
@@ -52377,7 +52472,7 @@ function createHttpRouter(deps) {
52377
52472
  sendJson(res, 404, { code: "NOT_FOUND", message: `session ${sid} not found` });
52378
52473
  return true;
52379
52474
  }
52380
- absPath = import_node_path28.default.join(file.cwd, pathParam);
52475
+ absPath = import_node_path29.default.join(file.cwd, pathParam);
52381
52476
  } else {
52382
52477
  sendJson(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "sessionStore not wired" }));
52383
52478
  return true;
@@ -52464,7 +52559,7 @@ function withCtx(ctx, body) {
52464
52559
  function streamFile(res, absPath, logger) {
52465
52560
  let stat;
52466
52561
  try {
52467
- stat = import_node_fs28.default.statSync(absPath);
52562
+ stat = import_node_fs29.default.statSync(absPath);
52468
52563
  } catch (err) {
52469
52564
  const code = err?.code;
52470
52565
  if (code === "ENOENT") {
@@ -52479,7 +52574,7 @@ function streamFile(res, absPath, logger) {
52479
52574
  return;
52480
52575
  }
52481
52576
  const mime = lookupMime(absPath);
52482
- const basename = import_node_path28.default.basename(absPath);
52577
+ const basename = import_node_path29.default.basename(absPath);
52483
52578
  res.writeHead(200, {
52484
52579
  "Content-Type": mime,
52485
52580
  "Content-Length": String(stat.size),
@@ -52487,7 +52582,7 @@ function streamFile(res, absPath, logger) {
52487
52582
  // 防止浏览器把任意 mime 当 html 渲染
52488
52583
  "X-Content-Type-Options": "nosniff"
52489
52584
  });
52490
- const stream = import_node_fs28.default.createReadStream(absPath);
52585
+ const stream = import_node_fs29.default.createReadStream(absPath);
52491
52586
  stream.on("error", (err) => {
52492
52587
  logger?.warn("streamFile read error", { absPath, err: err.message });
52493
52588
  res.destroy();
@@ -52496,8 +52591,8 @@ function streamFile(res, absPath, logger) {
52496
52591
  }
52497
52592
 
52498
52593
  // src/attachment/gc.ts
52499
- var import_node_fs29 = __toESM(require("fs"), 1);
52500
- var import_node_path29 = __toESM(require("path"), 1);
52594
+ var import_node_fs30 = __toESM(require("fs"), 1);
52595
+ var import_node_path30 = __toESM(require("path"), 1);
52501
52596
  var DEFAULT_TTL_MS = 30 * 24 * 3600 * 1e3;
52502
52597
  function runAttachmentGc(args) {
52503
52598
  const now = (args.now ?? Date.now)();
@@ -52506,38 +52601,38 @@ function runAttachmentGc(args) {
52506
52601
  for (const { scope, sessionId } of args.sessionScopes) {
52507
52602
  for (const entry of args.groupFileStore.list(scope, sessionId)) {
52508
52603
  if (entry.stale) continue;
52509
- if (import_node_path29.default.isAbsolute(entry.relPath)) liveAbs.add(entry.relPath);
52604
+ if (import_node_path30.default.isAbsolute(entry.relPath)) liveAbs.add(entry.relPath);
52510
52605
  }
52511
52606
  }
52512
52607
  for (const { scope, sessionId } of args.sessionScopes) {
52513
- const sessionDir = args.getSessionCwd?.(sessionId) ?? import_node_path29.default.join(
52608
+ const sessionDir = args.getSessionCwd?.(sessionId) ?? import_node_path30.default.join(
52514
52609
  args.dataDir,
52515
52610
  "sessions",
52516
52611
  ...scopeSubPath(scope).map(safeFileName),
52517
52612
  safeFileName(sessionId)
52518
52613
  );
52519
- const attRoot = import_node_path29.default.join(sessionDir, ".attachments");
52614
+ const attRoot = import_node_path30.default.join(sessionDir, ".attachments");
52520
52615
  let hashDirs;
52521
52616
  try {
52522
- hashDirs = import_node_fs29.default.readdirSync(attRoot);
52617
+ hashDirs = import_node_fs30.default.readdirSync(attRoot);
52523
52618
  } catch (err) {
52524
52619
  if (err.code === "ENOENT") continue;
52525
52620
  args.logger?.warn("attachment gc: readdir failed", { attRoot, err: err.message });
52526
52621
  continue;
52527
52622
  }
52528
52623
  for (const hashDir of hashDirs) {
52529
- const hashDirAbs = import_node_path29.default.join(attRoot, hashDir);
52624
+ const hashDirAbs = import_node_path30.default.join(attRoot, hashDir);
52530
52625
  let files;
52531
52626
  try {
52532
- files = import_node_fs29.default.readdirSync(hashDirAbs);
52627
+ files = import_node_fs30.default.readdirSync(hashDirAbs);
52533
52628
  } catch {
52534
52629
  continue;
52535
52630
  }
52536
52631
  for (const name of files) {
52537
- const file = import_node_path29.default.join(hashDirAbs, name);
52632
+ const file = import_node_path30.default.join(hashDirAbs, name);
52538
52633
  let stat;
52539
52634
  try {
52540
- stat = import_node_fs29.default.statSync(file);
52635
+ stat = import_node_fs30.default.statSync(file);
52541
52636
  } catch {
52542
52637
  continue;
52543
52638
  }
@@ -52546,26 +52641,26 @@ function runAttachmentGc(args) {
52546
52641
  if (age < ttlMs) continue;
52547
52642
  if (liveAbs.has(file)) continue;
52548
52643
  try {
52549
- import_node_fs29.default.unlinkSync(file);
52644
+ import_node_fs30.default.unlinkSync(file);
52550
52645
  } catch (err) {
52551
52646
  args.logger?.warn("attachment gc: unlink failed", { file, err: err.message });
52552
52647
  }
52553
52648
  }
52554
52649
  try {
52555
- if (import_node_fs29.default.readdirSync(hashDirAbs).length === 0) import_node_fs29.default.rmdirSync(hashDirAbs);
52650
+ if (import_node_fs30.default.readdirSync(hashDirAbs).length === 0) import_node_fs30.default.rmdirSync(hashDirAbs);
52556
52651
  } catch {
52557
52652
  }
52558
52653
  }
52559
52654
  try {
52560
- if (import_node_fs29.default.readdirSync(attRoot).length === 0) import_node_fs29.default.rmdirSync(attRoot);
52655
+ if (import_node_fs30.default.readdirSync(attRoot).length === 0) import_node_fs30.default.rmdirSync(attRoot);
52561
52656
  } catch {
52562
52657
  }
52563
52658
  }
52564
52659
  }
52565
52660
 
52566
52661
  // src/attachment/group.ts
52567
- var import_node_fs30 = __toESM(require("fs"), 1);
52568
- var import_node_path30 = __toESM(require("path"), 1);
52662
+ var import_node_fs31 = __toESM(require("fs"), 1);
52663
+ var import_node_path31 = __toESM(require("path"), 1);
52569
52664
  var import_node_crypto9 = __toESM(require("crypto"), 1);
52570
52665
  init_protocol();
52571
52666
  var GroupFileStore = class {
@@ -52577,11 +52672,11 @@ var GroupFileStore = class {
52577
52672
  this.logger = opts.logger;
52578
52673
  }
52579
52674
  rootForScope(scope) {
52580
- return import_node_path30.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
52675
+ return import_node_path31.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
52581
52676
  }
52582
52677
  /** 与 SessionStore.filePath 平级,扩展名 .group-files.json */
52583
52678
  filePath(scope, sessionId) {
52584
- return import_node_path30.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
52679
+ return import_node_path31.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
52585
52680
  }
52586
52681
  cacheKey(scope, sessionId) {
52587
52682
  return scope.kind === "default" ? `default::${sessionId}` : `persona:${scope.personaId}:${scope.mode}::${sessionId}`;
@@ -52590,7 +52685,7 @@ var GroupFileStore = class {
52590
52685
  readFile(scope, sessionId) {
52591
52686
  const file = this.filePath(scope, sessionId);
52592
52687
  try {
52593
- const raw = import_node_fs30.default.readFileSync(file, "utf8");
52688
+ const raw = import_node_fs31.default.readFileSync(file, "utf8");
52594
52689
  const parsed = JSON.parse(raw);
52595
52690
  if (!Array.isArray(parsed)) {
52596
52691
  this.logger?.warn("GroupFileStore.readFile: not an array; resetting session entries", {
@@ -52616,10 +52711,10 @@ var GroupFileStore = class {
52616
52711
  }
52617
52712
  writeFile(scope, sessionId, entries) {
52618
52713
  const file = this.filePath(scope, sessionId);
52619
- import_node_fs30.default.mkdirSync(import_node_path30.default.dirname(file), { recursive: true });
52714
+ import_node_fs31.default.mkdirSync(import_node_path31.default.dirname(file), { recursive: true });
52620
52715
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
52621
- import_node_fs30.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
52622
- import_node_fs30.default.renameSync(tmp, file);
52716
+ import_node_fs31.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
52717
+ import_node_fs31.default.renameSync(tmp, file);
52623
52718
  }
52624
52719
  /** 拉一份当前 session 的清单。读盘 → cache;之后调用复用 cache */
52625
52720
  list(scope, sessionId) {
@@ -52705,10 +52800,10 @@ var GroupFileStore = class {
52705
52800
  };
52706
52801
 
52707
52802
  // src/discovery/state-file.ts
52708
- var import_node_fs31 = __toESM(require("fs"), 1);
52709
- var import_node_path31 = __toESM(require("path"), 1);
52803
+ var import_node_fs32 = __toESM(require("fs"), 1);
52804
+ var import_node_path32 = __toESM(require("path"), 1);
52710
52805
  function defaultStateFilePath(dataDir) {
52711
- return import_node_path31.default.join(dataDir, "state.json");
52806
+ return import_node_path32.default.join(dataDir, "state.json");
52712
52807
  }
52713
52808
  function isPidAlive(pid) {
52714
52809
  if (!Number.isFinite(pid) || pid <= 0) return false;
@@ -52730,7 +52825,7 @@ var StateFileManager = class {
52730
52825
  }
52731
52826
  read() {
52732
52827
  try {
52733
- const raw = import_node_fs31.default.readFileSync(this.file, "utf8");
52828
+ const raw = import_node_fs32.default.readFileSync(this.file, "utf8");
52734
52829
  const parsed = JSON.parse(raw);
52735
52830
  return parsed;
52736
52831
  } catch {
@@ -52744,20 +52839,20 @@ var StateFileManager = class {
52744
52839
  return { status: "stale", existing };
52745
52840
  }
52746
52841
  write(state) {
52747
- import_node_fs31.default.mkdirSync(import_node_path31.default.dirname(this.file), { recursive: true });
52842
+ import_node_fs32.default.mkdirSync(import_node_path32.default.dirname(this.file), { recursive: true });
52748
52843
  const tmp = `${this.file}.tmp.${process.pid}.${Date.now()}`;
52749
- import_node_fs31.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
52750
- import_node_fs31.default.renameSync(tmp, this.file);
52844
+ import_node_fs32.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
52845
+ import_node_fs32.default.renameSync(tmp, this.file);
52751
52846
  if (process.platform !== "win32") {
52752
52847
  try {
52753
- import_node_fs31.default.chmodSync(this.file, 384);
52848
+ import_node_fs32.default.chmodSync(this.file, 384);
52754
52849
  } catch {
52755
52850
  }
52756
52851
  }
52757
52852
  }
52758
52853
  delete() {
52759
52854
  try {
52760
- import_node_fs31.default.unlinkSync(this.file);
52855
+ import_node_fs32.default.unlinkSync(this.file);
52761
52856
  } catch {
52762
52857
  }
52763
52858
  }
@@ -52771,14 +52866,14 @@ function readSpawnedByDesktopFromEnv(env = process.env) {
52771
52866
  }
52772
52867
 
52773
52868
  // src/tunnel/tunnel-manager.ts
52774
- var import_node_fs35 = __toESM(require("fs"), 1);
52775
- var import_node_path35 = __toESM(require("path"), 1);
52869
+ var import_node_fs36 = __toESM(require("fs"), 1);
52870
+ var import_node_path36 = __toESM(require("path"), 1);
52776
52871
  var import_node_crypto10 = __toESM(require("crypto"), 1);
52777
52872
  var import_node_child_process9 = require("child_process");
52778
52873
 
52779
52874
  // src/tunnel/tunnel-store.ts
52780
- var import_node_fs32 = __toESM(require("fs"), 1);
52781
- var import_node_path32 = __toESM(require("path"), 1);
52875
+ var import_node_fs33 = __toESM(require("fs"), 1);
52876
+ var import_node_path33 = __toESM(require("path"), 1);
52782
52877
  var TunnelStore = class {
52783
52878
  constructor(filePath) {
52784
52879
  this.filePath = filePath;
@@ -52786,7 +52881,7 @@ var TunnelStore = class {
52786
52881
  filePath;
52787
52882
  async get() {
52788
52883
  try {
52789
- const raw = await import_node_fs32.default.promises.readFile(this.filePath, "utf8");
52884
+ const raw = await import_node_fs33.default.promises.readFile(this.filePath, "utf8");
52790
52885
  const obj = JSON.parse(raw);
52791
52886
  if (!isPersistedTunnel(obj)) return null;
52792
52887
  return obj;
@@ -52797,22 +52892,22 @@ var TunnelStore = class {
52797
52892
  }
52798
52893
  }
52799
52894
  async set(v2) {
52800
- const dir = import_node_path32.default.dirname(this.filePath);
52801
- await import_node_fs32.default.promises.mkdir(dir, { recursive: true });
52895
+ const dir = import_node_path33.default.dirname(this.filePath);
52896
+ await import_node_fs33.default.promises.mkdir(dir, { recursive: true });
52802
52897
  const data = JSON.stringify(v2, null, 2);
52803
52898
  const tmp = `${this.filePath}.tmp.${process.pid}.${Date.now()}`;
52804
- await import_node_fs32.default.promises.writeFile(tmp, data, { mode: 384 });
52899
+ await import_node_fs33.default.promises.writeFile(tmp, data, { mode: 384 });
52805
52900
  if (process.platform !== "win32") {
52806
52901
  try {
52807
- await import_node_fs32.default.promises.chmod(tmp, 384);
52902
+ await import_node_fs33.default.promises.chmod(tmp, 384);
52808
52903
  } catch {
52809
52904
  }
52810
52905
  }
52811
- await import_node_fs32.default.promises.rename(tmp, this.filePath);
52906
+ await import_node_fs33.default.promises.rename(tmp, this.filePath);
52812
52907
  }
52813
52908
  async clear() {
52814
52909
  try {
52815
- await import_node_fs32.default.promises.unlink(this.filePath);
52910
+ await import_node_fs33.default.promises.unlink(this.filePath);
52816
52911
  } catch (err) {
52817
52912
  const code = err?.code;
52818
52913
  if (code !== "ENOENT") throw err;
@@ -52907,9 +53002,9 @@ function escape(v2) {
52907
53002
  }
52908
53003
 
52909
53004
  // src/tunnel/frpc-binary.ts
52910
- var import_node_fs33 = __toESM(require("fs"), 1);
53005
+ var import_node_fs34 = __toESM(require("fs"), 1);
52911
53006
  var import_node_os13 = __toESM(require("os"), 1);
52912
- var import_node_path33 = __toESM(require("path"), 1);
53007
+ var import_node_path34 = __toESM(require("path"), 1);
52913
53008
  var import_node_child_process7 = require("child_process");
52914
53009
  var import_node_stream3 = require("stream");
52915
53010
  var import_promises4 = require("stream/promises");
@@ -52941,20 +53036,20 @@ function frpcDownloadUrl(version2, p2) {
52941
53036
  }
52942
53037
  async function ensureFrpcBinary(opts) {
52943
53038
  if (opts.override) {
52944
- if (!import_node_fs33.default.existsSync(opts.override)) {
53039
+ if (!import_node_fs34.default.existsSync(opts.override)) {
52945
53040
  throw new Error(`frpc binary not found at override path: ${opts.override}`);
52946
53041
  }
52947
53042
  return opts.override;
52948
53043
  }
52949
53044
  const version2 = opts.version ?? FRPC_VERSION;
52950
53045
  const platform = opts.platform ?? detectPlatform();
52951
- const binDir = import_node_path33.default.join(opts.dataDir, "bin");
52952
- import_node_fs33.default.mkdirSync(binDir, { recursive: true });
53046
+ const binDir = import_node_path34.default.join(opts.dataDir, "bin");
53047
+ import_node_fs34.default.mkdirSync(binDir, { recursive: true });
52953
53048
  cleanupStaleArtifacts(binDir);
52954
- const stableBin = import_node_path33.default.join(binDir, "frpc");
52955
- if (import_node_fs33.default.existsSync(stableBin)) return stableBin;
53049
+ const stableBin = import_node_path34.default.join(binDir, "frpc");
53050
+ if (import_node_fs34.default.existsSync(stableBin)) return stableBin;
52956
53051
  const partialBin = `${stableBin}.partial`;
52957
- const tarballPath = import_node_path33.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
53052
+ const tarballPath = import_node_path34.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
52958
53053
  try {
52959
53054
  const url = frpcDownloadUrl(version2, platform);
52960
53055
  await downloadToFile(url, tarballPath, opts.fetchImpl);
@@ -52963,8 +53058,8 @@ async function ensureFrpcBinary(opts) {
52963
53058
  } else {
52964
53059
  await extractFrpcFromTarball(tarballPath, binDir, version2, platform, partialBin);
52965
53060
  }
52966
- import_node_fs33.default.chmodSync(partialBin, 493);
52967
- import_node_fs33.default.renameSync(partialBin, stableBin);
53061
+ import_node_fs34.default.chmodSync(partialBin, 493);
53062
+ import_node_fs34.default.renameSync(partialBin, stableBin);
52968
53063
  } finally {
52969
53064
  safeUnlink(tarballPath);
52970
53065
  safeUnlink(partialBin);
@@ -52974,15 +53069,15 @@ async function ensureFrpcBinary(opts) {
52974
53069
  function cleanupStaleArtifacts(binDir) {
52975
53070
  let entries;
52976
53071
  try {
52977
- entries = import_node_fs33.default.readdirSync(binDir);
53072
+ entries = import_node_fs34.default.readdirSync(binDir);
52978
53073
  } catch {
52979
53074
  return;
52980
53075
  }
52981
53076
  for (const name of entries) {
52982
53077
  if (name.endsWith(".partial") || name.startsWith("extract-")) {
52983
- const full = import_node_path33.default.join(binDir, name);
53078
+ const full = import_node_path34.default.join(binDir, name);
52984
53079
  try {
52985
- import_node_fs33.default.rmSync(full, { recursive: true, force: true });
53080
+ import_node_fs34.default.rmSync(full, { recursive: true, force: true });
52986
53081
  } catch {
52987
53082
  }
52988
53083
  }
@@ -52990,7 +53085,7 @@ function cleanupStaleArtifacts(binDir) {
52990
53085
  }
52991
53086
  function safeUnlink(p2) {
52992
53087
  try {
52993
- import_node_fs33.default.unlinkSync(p2);
53088
+ import_node_fs34.default.unlinkSync(p2);
52994
53089
  } catch {
52995
53090
  }
52996
53091
  }
@@ -53001,13 +53096,13 @@ async function downloadToFile(url, dest, fetchImpl) {
53001
53096
  if (!res.ok || !res.body) {
53002
53097
  throw new Error(`download failed: ${res.status} ${res.statusText}`);
53003
53098
  }
53004
- const out = import_node_fs33.default.createWriteStream(dest);
53099
+ const out = import_node_fs34.default.createWriteStream(dest);
53005
53100
  const nodeStream = import_node_stream3.Readable.fromWeb(res.body);
53006
53101
  await (0, import_promises4.pipeline)(nodeStream, out);
53007
53102
  }
53008
53103
  async function extractFrpcFromTarball(tarball, binDir, version2, platform, destBin) {
53009
- const work = import_node_path33.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
53010
- import_node_fs33.default.mkdirSync(work, { recursive: true });
53104
+ const work = import_node_path34.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
53105
+ import_node_fs34.default.mkdirSync(work, { recursive: true });
53011
53106
  try {
53012
53107
  await new Promise((resolve6, reject) => {
53013
53108
  const proc = (0, import_node_child_process7.spawn)("tar", ["xzf", tarball, "-C", work], { stdio: "pipe" });
@@ -53015,32 +53110,32 @@ async function extractFrpcFromTarball(tarball, binDir, version2, platform, destB
53015
53110
  proc.on("exit", (code) => code === 0 ? resolve6() : reject(new Error(`tar exited ${code}`)));
53016
53111
  });
53017
53112
  const dirName = `frp_${version2}_${platform.os}_${platform.arch}`;
53018
- const src = import_node_path33.default.join(work, dirName, "frpc");
53019
- if (!import_node_fs33.default.existsSync(src)) {
53113
+ const src = import_node_path34.default.join(work, dirName, "frpc");
53114
+ if (!import_node_fs34.default.existsSync(src)) {
53020
53115
  throw new Error(`frpc not found inside tarball at ${src}`);
53021
53116
  }
53022
- import_node_fs33.default.copyFileSync(src, destBin);
53117
+ import_node_fs34.default.copyFileSync(src, destBin);
53023
53118
  } finally {
53024
- import_node_fs33.default.rmSync(work, { recursive: true, force: true });
53119
+ import_node_fs34.default.rmSync(work, { recursive: true, force: true });
53025
53120
  }
53026
53121
  }
53027
53122
 
53028
53123
  // src/tunnel/frpc-process.ts
53029
- var import_node_fs34 = __toESM(require("fs"), 1);
53030
- var import_node_path34 = __toESM(require("path"), 1);
53124
+ var import_node_fs35 = __toESM(require("fs"), 1);
53125
+ var import_node_path35 = __toESM(require("path"), 1);
53031
53126
  var import_node_child_process8 = require("child_process");
53032
53127
  function frpcPidFilePath(dataDir) {
53033
- return import_node_path34.default.join(dataDir, "frpc.pid");
53128
+ return import_node_path35.default.join(dataDir, "frpc.pid");
53034
53129
  }
53035
53130
  function writeFrpcPid(dataDir, pid) {
53036
53131
  try {
53037
- import_node_fs34.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
53132
+ import_node_fs35.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
53038
53133
  } catch {
53039
53134
  }
53040
53135
  }
53041
53136
  function clearFrpcPid(dataDir) {
53042
53137
  try {
53043
- import_node_fs34.default.unlinkSync(frpcPidFilePath(dataDir));
53138
+ import_node_fs35.default.unlinkSync(frpcPidFilePath(dataDir));
53044
53139
  } catch {
53045
53140
  }
53046
53141
  }
@@ -53056,7 +53151,7 @@ function defaultIsPidAlive(pid) {
53056
53151
  }
53057
53152
  function defaultReadPidFile(file) {
53058
53153
  try {
53059
- return import_node_fs34.default.readFileSync(file, "utf8");
53154
+ return import_node_fs35.default.readFileSync(file, "utf8");
53060
53155
  } catch {
53061
53156
  return null;
53062
53157
  }
@@ -53072,7 +53167,7 @@ function defaultSleep(ms) {
53072
53167
  }
53073
53168
  async function killStaleFrpc(deps) {
53074
53169
  const pidFile = frpcPidFilePath(deps.dataDir);
53075
- const tomlPath = import_node_path34.default.join(deps.dataDir, "frpc.toml");
53170
+ const tomlPath = import_node_path35.default.join(deps.dataDir, "frpc.toml");
53076
53171
  const readPidFile = deps.readPidFileImpl ?? defaultReadPidFile;
53077
53172
  const isAlive = deps.isPidAliveImpl ?? defaultIsPidAlive;
53078
53173
  const killPid = deps.killPidImpl ?? defaultKillPid;
@@ -53096,7 +53191,7 @@ async function killStaleFrpc(deps) {
53096
53191
  }
53097
53192
  if (victims.size === 0) {
53098
53193
  try {
53099
- import_node_fs34.default.unlinkSync(pidFile);
53194
+ import_node_fs35.default.unlinkSync(pidFile);
53100
53195
  } catch {
53101
53196
  }
53102
53197
  return;
@@ -53107,7 +53202,7 @@ async function killStaleFrpc(deps) {
53107
53202
  }
53108
53203
  await sleep2(deps.reapWaitMs ?? 300);
53109
53204
  try {
53110
- import_node_fs34.default.unlinkSync(pidFile);
53205
+ import_node_fs35.default.unlinkSync(pidFile);
53111
53206
  } catch {
53112
53207
  }
53113
53208
  }
@@ -53144,7 +53239,7 @@ var DEFAULT_TUNNEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
53144
53239
  var TunnelManager = class {
53145
53240
  constructor(deps) {
53146
53241
  this.deps = deps;
53147
- this.store = deps.store ?? new TunnelStore(import_node_path35.default.join(deps.dataDir, "tunnel.json"));
53242
+ this.store = deps.store ?? new TunnelStore(import_node_path36.default.join(deps.dataDir, "tunnel.json"));
53148
53243
  this.ttlMs = deps.ttlMs ?? DEFAULT_TUNNEL_TTL_MS;
53149
53244
  this.startupTimeoutMs = deps.startupTimeoutMs ?? 15e3;
53150
53245
  }
@@ -53271,7 +53366,7 @@ var TunnelManager = class {
53271
53366
  dataDir: this.deps.dataDir,
53272
53367
  override: this.deps.frpcBinaryOverride ?? void 0
53273
53368
  });
53274
- const tomlPath = import_node_path35.default.join(this.deps.dataDir, "frpc.toml");
53369
+ const tomlPath = import_node_path36.default.join(this.deps.dataDir, "frpc.toml");
53275
53370
  const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto10.default.randomBytes(3).toString("hex")}`;
53276
53371
  const toml = buildFrpcToml({
53277
53372
  serverAddr: t.frpsHost,
@@ -53282,14 +53377,14 @@ var TunnelManager = class {
53282
53377
  localPort,
53283
53378
  logLevel: "info"
53284
53379
  });
53285
- await import_node_fs35.default.promises.writeFile(tomlPath, toml, { mode: 384 });
53380
+ await import_node_fs36.default.promises.writeFile(tomlPath, toml, { mode: 384 });
53286
53381
  const proc = (this.deps.spawnImpl ?? import_node_child_process9.spawn)(frpcBin, ["-c", tomlPath], {
53287
53382
  stdio: ["ignore", "pipe", "pipe"]
53288
53383
  });
53289
- const logDir = import_node_path35.default.join(this.deps.dataDir, "log");
53290
- import_node_fs35.default.mkdirSync(logDir, { recursive: true });
53291
- const logFilePath = import_node_path35.default.join(logDir, "frpc.log");
53292
- const logStream = import_node_fs35.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
53384
+ const logDir = import_node_path36.default.join(this.deps.dataDir, "log");
53385
+ import_node_fs36.default.mkdirSync(logDir, { recursive: true });
53386
+ const logFilePath = import_node_path36.default.join(logDir, "frpc.log");
53387
+ const logStream = import_node_fs36.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
53293
53388
  logStream.on("error", () => {
53294
53389
  });
53295
53390
  const tee = (chunk) => {
@@ -53371,8 +53466,8 @@ async function waitForFrpcReady(proc, timeoutMs) {
53371
53466
  }
53372
53467
 
53373
53468
  // src/sshd/sshd-manager.ts
53374
- var import_node_fs39 = __toESM(require("fs"), 1);
53375
- var import_node_path39 = __toESM(require("path"), 1);
53469
+ var import_node_fs40 = __toESM(require("fs"), 1);
53470
+ var import_node_path40 = __toESM(require("path"), 1);
53376
53471
  var import_node_child_process11 = require("child_process");
53377
53472
 
53378
53473
  // src/sshd/sshd-config.ts
@@ -53397,23 +53492,23 @@ function buildSshdConfig(input) {
53397
53492
  }
53398
53493
 
53399
53494
  // src/sshd/sshd-process.ts
53400
- var import_node_fs36 = __toESM(require("fs"), 1);
53401
- var import_node_path36 = __toESM(require("path"), 1);
53495
+ var import_node_fs37 = __toESM(require("fs"), 1);
53496
+ var import_node_path37 = __toESM(require("path"), 1);
53402
53497
  var import_node_child_process10 = require("child_process");
53403
53498
  function sshdPidFilePath(dataDir) {
53404
- return import_node_path36.default.join(dataDir, "sshd", "sshd.pid");
53499
+ return import_node_path37.default.join(dataDir, "sshd", "sshd.pid");
53405
53500
  }
53406
53501
  function writeSshdPid(dataDir, pid) {
53407
53502
  try {
53408
53503
  const p2 = sshdPidFilePath(dataDir);
53409
- import_node_fs36.default.mkdirSync(import_node_path36.default.dirname(p2), { recursive: true, mode: 448 });
53410
- import_node_fs36.default.writeFileSync(p2, String(pid), { mode: 384 });
53504
+ import_node_fs37.default.mkdirSync(import_node_path37.default.dirname(p2), { recursive: true, mode: 448 });
53505
+ import_node_fs37.default.writeFileSync(p2, String(pid), { mode: 384 });
53411
53506
  } catch {
53412
53507
  }
53413
53508
  }
53414
53509
  function clearSshdPid(dataDir) {
53415
53510
  try {
53416
- import_node_fs36.default.unlinkSync(sshdPidFilePath(dataDir));
53511
+ import_node_fs37.default.unlinkSync(sshdPidFilePath(dataDir));
53417
53512
  } catch {
53418
53513
  }
53419
53514
  }
@@ -53429,7 +53524,7 @@ function defaultIsPidAlive2(pid) {
53429
53524
  }
53430
53525
  function defaultReadPidFile2(file) {
53431
53526
  try {
53432
- return import_node_fs36.default.readFileSync(file, "utf8");
53527
+ return import_node_fs37.default.readFileSync(file, "utf8");
53433
53528
  } catch {
53434
53529
  return null;
53435
53530
  }
@@ -53445,7 +53540,7 @@ function defaultSleep2(ms) {
53445
53540
  }
53446
53541
  async function killStaleSshd(deps) {
53447
53542
  const pidFile = sshdPidFilePath(deps.dataDir);
53448
- const configPath = import_node_path36.default.join(deps.dataDir, "sshd", "sshd_config");
53543
+ const configPath = import_node_path37.default.join(deps.dataDir, "sshd", "sshd_config");
53449
53544
  const readPidFile = deps.readPidFileImpl ?? defaultReadPidFile2;
53450
53545
  const isAlive = deps.isPidAliveImpl ?? defaultIsPidAlive2;
53451
53546
  const killPid = deps.killPidImpl ?? defaultKillPid2;
@@ -53469,7 +53564,7 @@ async function killStaleSshd(deps) {
53469
53564
  }
53470
53565
  if (victims.size === 0) {
53471
53566
  try {
53472
- import_node_fs36.default.unlinkSync(pidFile);
53567
+ import_node_fs37.default.unlinkSync(pidFile);
53473
53568
  } catch {
53474
53569
  }
53475
53570
  return;
@@ -53480,7 +53575,7 @@ async function killStaleSshd(deps) {
53480
53575
  }
53481
53576
  await sleep2(deps.reapWaitMs ?? 300);
53482
53577
  try {
53483
- import_node_fs36.default.unlinkSync(pidFile);
53578
+ import_node_fs37.default.unlinkSync(pidFile);
53484
53579
  } catch {
53485
53580
  }
53486
53581
  }
@@ -53513,8 +53608,8 @@ async function defaultScanSshdPidsByCmdline(configPath, logger) {
53513
53608
  }
53514
53609
 
53515
53610
  // src/sshd/jail-script.ts
53516
- var import_node_fs37 = __toESM(require("fs"), 1);
53517
- var import_node_path37 = __toESM(require("path"), 1);
53611
+ var import_node_fs38 = __toESM(require("fs"), 1);
53612
+ var import_node_path38 = __toESM(require("path"), 1);
53518
53613
  var CLAWD_SSH_JAIL_SCRIPT = String.raw`#!/usr/bin/env bash
53519
53614
  # clawd-ssh-jail — SSH reverse access sandbox wrapper (managed by clawd; do not edit)
53520
53615
  #
@@ -53523,7 +53618,7 @@ var CLAWD_SSH_JAIL_SCRIPT = String.raw`#!/usr/bin/env bash
53523
53618
  # 真实请求(interactive shell 时为空)。
53524
53619
  #
53525
53620
  # 职责:
53526
- # 1. 读 ~/.clawd/contacts.json 校验 contact.sshAllowed(不通过即拒)
53621
+ # 1. 读 ~/.clawd/contacts.json 校验 contact.remoteAccessAllowed(不通过即拒)
53527
53622
  # 2. 直接 exec shell(去沙箱后无目录级限制)
53528
53623
 
53529
53624
  set -euo pipefail
@@ -53551,13 +53646,13 @@ log_line INFO jail.entered "clawd-ssh-jail 起来 device=${"${DEVICE_ID}"} cmd=$
53551
53646
 
53552
53647
  CONTACTS="${"${HOME}"}/.clawd/contacts.json"
53553
53648
  if [ ! -f "$CONTACTS" ]; then
53554
- log_line ERROR jail.entered "contacts.json 不存在,无法校验 sshAllowed"
53649
+ log_line ERROR jail.entered "contacts.json 不存在,无法校验 remoteAccessAllowed"
53555
53650
  echo "clawd-ssh-jail: contacts.json missing" >&2
53556
53651
  exit 1
53557
53652
  fi
53558
53653
 
53559
- # 校验该 contact 的 sshAllowed(mac/linux 都自带 python3)。去沙箱后不再读目录白名单:
53560
- # sshAllowed=false → DENIED exit 2;deviceId 未命中 → NOT_FOUND exit 3;命中且允许 → exit 0。
53654
+ # 校验该 contact 的 remoteAccessAllowed(mac/linux 都自带 python3)。去沙箱后不再读目录白名单:
53655
+ # remoteAccessAllowed=false → DENIED exit 2;deviceId 未命中 → NOT_FOUND exit 3;命中且允许 → exit 0。
53561
53656
  # set -e 下 python 非 0 退出即终止脚本(不进 exec),只有 exit 0 才继续 exec shell。
53562
53657
  python3 -c "
53563
53658
  import json, sys
@@ -53565,13 +53660,13 @@ with open('$CONTACTS') as f:
53565
53660
  data = json.load(f)
53566
53661
  for c in data.get('contacts', []):
53567
53662
  if c.get('deviceId') == '$DEVICE_ID':
53568
- if not c.get('sshAllowed'):
53663
+ if not c.get('remoteAccessAllowed'):
53569
53664
  print('DENIED', file=sys.stderr); sys.exit(2)
53570
53665
  sys.exit(0)
53571
53666
  print('NOT_FOUND', file=sys.stderr); sys.exit(3)
53572
53667
  "
53573
53668
 
53574
- log_line INFO jail.entered "sshAllowed 校验通过,放行 exec shell"
53669
+ log_line INFO jail.entered "remoteAccessAllowed 校验通过,放行 exec shell"
53575
53670
 
53576
53671
  CMD="${"${SSH_ORIGINAL_COMMAND:-}"}"
53577
53672
  if [ -z "$CMD" ]; then
@@ -53582,39 +53677,68 @@ fi
53582
53677
 
53583
53678
  # 去掉 OS 沙箱层(原本 macOS Seatbelt 策略 / Linux 命名空间隔离):SSH 进来即该用户
53584
53679
  # 权限内的 shell,直接 exec。沙箱只限制文件、拦不住网络/进程,隔离价值有限,且相关 API
53585
- # 在 macOS 已 deprecated。授权即完全信任——sshAllowed 授权开关 + authorized_keys 即时
53680
+ # 在 macOS 已 deprecated。授权即完全信任——remoteAccessAllowed 授权开关 + authorized_keys 即时
53586
53681
  # 踢行是唯一访问闸。要加回沙箱:从 git 历史(T-29 之前)恢复本段的 OS 分支即可。
53587
53682
  exec "${"${SHELL_CMD[@]}"}"
53588
53683
  `;
53589
53684
  function ensureJailScript(dataDir) {
53590
- const binDir = import_node_path37.default.join(dataDir, "bin");
53591
- import_node_fs37.default.mkdirSync(binDir, { recursive: true, mode: 493 });
53592
- const target = import_node_path37.default.join(binDir, "clawd-ssh-jail");
53593
- import_node_fs37.default.writeFileSync(target, CLAWD_SSH_JAIL_SCRIPT, { mode: 493 });
53685
+ const binDir = import_node_path38.default.join(dataDir, "bin");
53686
+ import_node_fs38.default.mkdirSync(binDir, { recursive: true, mode: 493 });
53687
+ const target = import_node_path38.default.join(binDir, "clawd-ssh-jail");
53688
+ import_node_fs38.default.writeFileSync(target, CLAWD_SSH_JAIL_SCRIPT, { mode: 493 });
53594
53689
  return target;
53595
53690
  }
53596
53691
 
53597
53692
  // src/sshd/clawd-shim.ts
53598
- var import_node_fs38 = __toESM(require("fs"), 1);
53599
- var import_node_path38 = __toESM(require("path"), 1);
53693
+ var import_node_fs39 = __toESM(require("fs"), 1);
53694
+ var import_node_path39 = __toESM(require("path"), 1);
53695
+ var DEFAULT_FALLBACK_APPS = ["/Applications/Clawd.app", "$HOME/Applications/Clawd.app"];
53600
53696
  function shellQuote(s) {
53601
53697
  return `'${s.replace(/'/g, "'\\''")}'`;
53602
53698
  }
53603
- function buildClawdShim(execPath, cliPath) {
53699
+ function fallbackWord(app) {
53700
+ if (app.startsWith("$HOME/")) return `"$HOME"/${shellQuote(app.slice("$HOME/".length))}`;
53701
+ return shellQuote(app);
53702
+ }
53703
+ function buildClawdShim(execPath, cliPath, fallbackApps = DEFAULT_FALLBACK_APPS) {
53704
+ const candidates = fallbackApps.map(fallbackWord).join(" ");
53604
53705
  return `#!/usr/bin/env bash
53605
53706
  # clawd shim (managed by clawd; do not edit)
53606
53707
  #
53607
- # \u786C\u7F16\u7801\u672C daemon \u542F\u52A8\u65F6\u7528\u7684 execPath + cliPath\uFF1Bdaemon \u91CD\u542F\u4F1A\u8986\u5199\u672C\u6587\u4EF6\uFF08\u5E42\u7B49 seed\uFF09\u3002
53608
- # guest CC \u7528 SSH ProxyCommand \u8D70 ~/.clawd/bin/clawd ssh-relay \u65F6\u4F1A\u547D\u4E2D\u672C shim\uFF0C\u4FDD\u8BC1
53609
- # relay \u4E8C\u8FDB\u5236\u8DDF\u5F53\u524D\u8DD1\u7740\u7684 daemon \u540C\u6E90\u540C\u7248\u672C\u3002
53610
- exec ${shellQuote(execPath)} ${shellQuote(cliPath)} "$@"
53708
+ # primary = seed \u672C\u6587\u4EF6\u7684 daemon \u7684 execPath + cliPath\uFF1Bdaemon \u91CD\u542F\u4F1A\u8986\u5199\u672C\u6587\u4EF6\uFF08\u5E42\u7B49 seed\uFF09\u3002
53709
+ # primary \u5931\u6548\uFF08npx \u7F13\u5B58 GC / OTA bundle GC / app \u79FB\u52A8\uFF09\u65F6\u56DE\u9000 installer \u81EA\u5E26 CLI\uFF1B
53710
+ # \u5168\u706D\u7ED9\u53EF\u8BFB\u9519\u8BEF\u3002guest CC \u7528 SSH ProxyCommand \u8D70 ~/.clawd/bin/clawd ssh-relay \u65F6\u547D\u4E2D\u672C shim\u3002
53711
+ #
53712
+ # ELECTRON_RUN_AS_NODE \u663E\u5F0F\u5BFC\u51FA\uFF1Aprimary/fallback \u90FD\u53EF\u80FD\u662F Electron \u4E8C\u8FDB\u5236\uFF08plain node \u5FFD\u7565
53713
+ # \u8BE5\u53D8\u91CF\uFF0C\u65E0\u5BB3\uFF09\u2014\u2014\u4E0D\u518D\u4F9D\u8D56 desktop \u73AF\u5883\u53D8\u91CF\u6070\u597D\u6CC4\u6F0F\u5230 CC \u5B50\u8FDB\u7A0B\u3002
53714
+ export ELECTRON_RUN_AS_NODE=1
53715
+ EXEC=${shellQuote(execPath)}
53716
+ CLI=${shellQuote(cliPath)}
53717
+ if [ ! -x "$EXEC" ] || [ ! -f "$CLI" ]; then
53718
+ for APP in ${candidates}; do
53719
+ FCLI="$APP/Contents/Resources/daemon/dist/cli.cjs"
53720
+ [ -f "$FCLI" ] || continue
53721
+ for FEXE in "$APP"/Contents/MacOS/*; do
53722
+ if [ -f "$FEXE" ] && [ -x "$FEXE" ]; then
53723
+ EXEC="$FEXE"
53724
+ CLI="$FCLI"
53725
+ break 2
53726
+ fi
53727
+ done
53728
+ done
53729
+ fi
53730
+ if [ ! -x "$EXEC" ] || [ ! -f "$CLI" ]; then
53731
+ echo "clawd shim: daemon CLI not found ($CLI); \u91CD\u542F clawd daemon \u53EF\u91CD\u65B0\u751F\u6210\u672C\u6587\u4EF6" >&2
53732
+ exit 127
53733
+ fi
53734
+ exec "$EXEC" "$CLI" "$@"
53611
53735
  `;
53612
53736
  }
53613
53737
  function ensureClawdShim(dataDir, execPath, cliPath) {
53614
- const binDir = import_node_path38.default.join(dataDir, "bin");
53615
- import_node_fs38.default.mkdirSync(binDir, { recursive: true, mode: 493 });
53616
- const target = import_node_path38.default.join(binDir, "clawd");
53617
- import_node_fs38.default.writeFileSync(target, buildClawdShim(execPath, cliPath), { mode: 493 });
53738
+ const binDir = import_node_path39.default.join(dataDir, "bin");
53739
+ import_node_fs39.default.mkdirSync(binDir, { recursive: true, mode: 493 });
53740
+ const target = import_node_path39.default.join(binDir, "clawd");
53741
+ import_node_fs39.default.writeFileSync(target, buildClawdShim(execPath, cliPath), { mode: 493 });
53618
53742
  return target;
53619
53743
  }
53620
53744
 
@@ -53622,7 +53746,7 @@ function ensureClawdShim(dataDir, execPath, cliPath) {
53622
53746
  var SshdManager = class {
53623
53747
  constructor(deps) {
53624
53748
  this.deps = deps;
53625
- this.sshdDir = import_node_path39.default.join(deps.dataDir, "sshd");
53749
+ this.sshdDir = import_node_path40.default.join(deps.dataDir, "sshd");
53626
53750
  this.startupTimeoutMs = deps.startupTimeoutMs ?? 15e3;
53627
53751
  }
53628
53752
  deps;
@@ -53643,34 +53767,34 @@ var SshdManager = class {
53643
53767
  ownPid: process.pid,
53644
53768
  logger
53645
53769
  });
53646
- import_node_fs39.default.mkdirSync(this.sshdDir, { recursive: true, mode: 448 });
53647
- import_node_fs39.default.mkdirSync(import_node_path39.default.join(this.sshdDir, "authorized_keys.d"), { recursive: true, mode: 448 });
53770
+ import_node_fs40.default.mkdirSync(this.sshdDir, { recursive: true, mode: 448 });
53771
+ import_node_fs40.default.mkdirSync(import_node_path40.default.join(this.sshdDir, "authorized_keys.d"), { recursive: true, mode: 448 });
53648
53772
  ensureJailScript(this.deps.dataDir);
53649
53773
  ensureClawdShim(this.deps.dataDir, process.execPath, process.argv[1] ?? "");
53650
- const hostKeyPath = import_node_path39.default.join(this.sshdDir, "host_key");
53651
- if (!import_node_fs39.default.existsSync(hostKeyPath)) {
53774
+ const hostKeyPath = import_node_path40.default.join(this.sshdDir, "host_key");
53775
+ if (!import_node_fs40.default.existsSync(hostKeyPath)) {
53652
53776
  await this.generateHostKey(hostKeyPath);
53653
53777
  }
53654
- const akFile = import_node_path39.default.join(this.sshdDir, "authorized_keys.d", "clawd-contacts");
53655
- if (!import_node_fs39.default.existsSync(akFile)) {
53656
- import_node_fs39.default.writeFileSync(akFile, "", { mode: 384 });
53778
+ const akFile = import_node_path40.default.join(this.sshdDir, "authorized_keys.d", "clawd-contacts");
53779
+ if (!import_node_fs40.default.existsSync(akFile)) {
53780
+ import_node_fs40.default.writeFileSync(akFile, "", { mode: 384 });
53657
53781
  }
53658
- const configPath = import_node_path39.default.join(this.sshdDir, "sshd_config");
53782
+ const configPath = import_node_path40.default.join(this.sshdDir, "sshd_config");
53659
53783
  const config = buildSshdConfig({
53660
53784
  listenAddress: "127.0.0.1",
53661
53785
  port: this.deps.port,
53662
53786
  hostKeyPath,
53663
53787
  authorizedKeysFile: akFile,
53664
- pidFilePath: import_node_path39.default.join(this.sshdDir, "sshd.pid")
53788
+ pidFilePath: import_node_path40.default.join(this.sshdDir, "sshd.pid")
53665
53789
  });
53666
- import_node_fs39.default.writeFileSync(configPath, config, { mode: 384 });
53790
+ import_node_fs40.default.writeFileSync(configPath, config, { mode: 384 });
53667
53791
  const sshdBin = this.deps.sshdBin ?? "/usr/sbin/sshd";
53668
53792
  const proc = (this.deps.spawnImpl ?? import_node_child_process11.spawn)(sshdBin, ["-D", "-e", "-f", configPath], {
53669
53793
  stdio: ["ignore", "pipe", "pipe"]
53670
53794
  });
53671
- const sshdLogDir = import_node_path39.default.join(this.deps.dataDir, "log");
53672
- import_node_fs39.default.mkdirSync(sshdLogDir, { recursive: true });
53673
- const logStream = import_node_fs39.default.createWriteStream(import_node_path39.default.join(sshdLogDir, "sshd.log"), {
53795
+ const sshdLogDir = import_node_path40.default.join(this.deps.dataDir, "log");
53796
+ import_node_fs40.default.mkdirSync(sshdLogDir, { recursive: true });
53797
+ const logStream = import_node_fs40.default.createWriteStream(import_node_path40.default.join(sshdLogDir, "sshd.log"), {
53674
53798
  flags: "a",
53675
53799
  mode: 384
53676
53800
  });
@@ -53767,7 +53891,7 @@ ${tail}` : ready.error;
53767
53891
  p2.on("error", reject);
53768
53892
  });
53769
53893
  try {
53770
- import_node_fs39.default.chmodSync(hostKeyPath, 384);
53894
+ import_node_fs40.default.chmodSync(hostKeyPath, 384);
53771
53895
  } catch {
53772
53896
  }
53773
53897
  }
@@ -53839,20 +53963,20 @@ async function waitForSshdReady(proc, timeoutMs) {
53839
53963
  }
53840
53964
 
53841
53965
  // src/sshd/authorized-keys.ts
53842
- var import_node_fs40 = __toESM(require("fs"), 1);
53843
- var import_node_path40 = __toESM(require("path"), 1);
53966
+ var import_node_fs41 = __toESM(require("fs"), 1);
53967
+ var import_node_path41 = __toESM(require("path"), 1);
53844
53968
  var JAIL_BIN_PATH_ENV = "CLAWD_JAIL_BIN_PATH";
53845
53969
  var AUTHORIZED_KEYS_FILE = "clawd-contacts";
53846
53970
  function jailBinPath() {
53847
- return process.env[JAIL_BIN_PATH_ENV] ?? import_node_path40.default.join(process.env.HOME ?? "", ".clawd", "bin", "clawd-ssh-jail");
53971
+ return process.env[JAIL_BIN_PATH_ENV] ?? import_node_path41.default.join(process.env.HOME ?? "", ".clawd", "bin", "clawd-ssh-jail");
53848
53972
  }
53849
53973
  function rebuildAuthorizedKeys(store, sshdDir) {
53850
- const akDir = import_node_path40.default.join(sshdDir, "authorized_keys.d");
53851
- const target = import_node_path40.default.join(akDir, AUTHORIZED_KEYS_FILE);
53852
- import_node_fs40.default.mkdirSync(akDir, { recursive: true, mode: 448 });
53974
+ const akDir = import_node_path41.default.join(sshdDir, "authorized_keys.d");
53975
+ const target = import_node_path41.default.join(akDir, AUTHORIZED_KEYS_FILE);
53976
+ import_node_fs41.default.mkdirSync(akDir, { recursive: true, mode: 448 });
53853
53977
  const lines = ["# managed by clawd; do not edit", ""];
53854
53978
  for (const c of store.list()) {
53855
- if (!c.sshAllowed) continue;
53979
+ if (!c.remoteAccessAllowed) continue;
53856
53980
  const safe = /^[A-Za-z0-9_.-]+$/.test(c.deviceId);
53857
53981
  if (!safe) continue;
53858
53982
  const pubkey = c.sshPublicKey?.trim();
@@ -53863,25 +53987,25 @@ function rebuildAuthorizedKeys(store, sshdDir) {
53863
53987
  }
53864
53988
  const body = lines.join("\n") + "\n";
53865
53989
  const tmp = `${target}.tmp-${process.pid}-${Date.now()}`;
53866
- import_node_fs40.default.writeFileSync(tmp, body, { mode: 384 });
53867
- import_node_fs40.default.renameSync(tmp, target);
53990
+ import_node_fs41.default.writeFileSync(tmp, body, { mode: 384 });
53991
+ import_node_fs41.default.renameSync(tmp, target);
53868
53992
  }
53869
53993
 
53870
53994
  // src/sshd/ssh-identity.ts
53871
- var import_node_fs41 = __toESM(require("fs"), 1);
53872
- var import_node_path41 = __toESM(require("path"), 1);
53995
+ var import_node_fs42 = __toESM(require("fs"), 1);
53996
+ var import_node_path42 = __toESM(require("path"), 1);
53873
53997
  var import_node_child_process12 = require("child_process");
53874
53998
  var IDENTITY_DIR = "ssh-identity";
53875
53999
  var KEY_FILE = "id_ed25519";
53876
54000
  function identityPaths(dataDir) {
53877
- const privPath = import_node_path41.default.join(dataDir, IDENTITY_DIR, KEY_FILE);
54001
+ const privPath = import_node_path42.default.join(dataDir, IDENTITY_DIR, KEY_FILE);
53878
54002
  return { privPath, pubPath: `${privPath}.pub` };
53879
54003
  }
53880
54004
  async function ensureSshIdentity(dataDir, opts = {}) {
53881
54005
  const { privPath, pubPath } = identityPaths(dataDir);
53882
- import_node_fs41.default.mkdirSync(import_node_path41.default.dirname(privPath), { recursive: true, mode: 448 });
53883
- if (import_node_fs41.default.existsSync(privPath) && import_node_fs41.default.existsSync(pubPath)) {
53884
- return { privateKeyPath: privPath, publicKeyLine: import_node_fs41.default.readFileSync(pubPath, "utf8").trim() };
54006
+ import_node_fs42.default.mkdirSync(import_node_path42.default.dirname(privPath), { recursive: true, mode: 448 });
54007
+ if (import_node_fs42.default.existsSync(privPath) && import_node_fs42.default.existsSync(pubPath)) {
54008
+ return { privateKeyPath: privPath, publicKeyLine: import_node_fs42.default.readFileSync(pubPath, "utf8").trim() };
53885
54009
  }
53886
54010
  const bin = opts.keygenBin ?? "/usr/bin/ssh-keygen";
53887
54011
  await new Promise((resolve6, reject) => {
@@ -53894,18 +54018,18 @@ async function ensureSshIdentity(dataDir, opts = {}) {
53894
54018
  p2.on("error", reject);
53895
54019
  });
53896
54020
  try {
53897
- import_node_fs41.default.chmodSync(privPath, 384);
54021
+ import_node_fs42.default.chmodSync(privPath, 384);
53898
54022
  } catch {
53899
54023
  }
53900
54024
  try {
53901
- import_node_fs41.default.chmodSync(pubPath, 420);
54025
+ import_node_fs42.default.chmodSync(pubPath, 420);
53902
54026
  } catch {
53903
54027
  }
53904
- return { privateKeyPath: privPath, publicKeyLine: import_node_fs41.default.readFileSync(pubPath, "utf8").trim() };
54028
+ return { privateKeyPath: privPath, publicKeyLine: import_node_fs42.default.readFileSync(pubPath, "utf8").trim() };
53905
54029
  }
53906
54030
  function readSshIdentityPublicKey(dataDir) {
53907
54031
  try {
53908
- return import_node_fs41.default.readFileSync(identityPaths(dataDir).pubPath, "utf8").trim();
54032
+ return import_node_fs42.default.readFileSync(identityPaths(dataDir).pubPath, "utf8").trim();
53909
54033
  } catch {
53910
54034
  return null;
53911
54035
  }
@@ -53915,10 +54039,10 @@ function readSshIdentityPublicKey(dataDir) {
53915
54039
  var import_node_net2 = __toESM(require("net"), 1);
53916
54040
 
53917
54041
  // src/sshd/contact-ssh-log.ts
53918
- var import_node_fs42 = __toESM(require("fs"), 1);
53919
- var import_node_path42 = __toESM(require("path"), 1);
54042
+ var import_node_fs43 = __toESM(require("fs"), 1);
54043
+ var import_node_path43 = __toESM(require("path"), 1);
53920
54044
  function createContactSshLog(dataDir) {
53921
- const file = import_node_path42.default.join(dataDir, "log", "contact-ssh.log");
54045
+ const file = import_node_path43.default.join(dataDir, "log", "contact-ssh.log");
53922
54046
  function append(level, tag, message, meta) {
53923
54047
  const time = (/* @__PURE__ */ new Date()).toISOString();
53924
54048
  let line = `[${time}] [${level}] [${tag}] ${message}`;
@@ -53931,8 +54055,8 @@ function createContactSshLog(dataDir) {
53931
54055
  }
53932
54056
  line += "\n";
53933
54057
  try {
53934
- import_node_fs42.default.mkdirSync(import_node_path42.default.dirname(file), { recursive: true });
53935
- import_node_fs42.default.appendFileSync(file, line, { mode: 384 });
54058
+ import_node_fs43.default.mkdirSync(import_node_path43.default.dirname(file), { recursive: true });
54059
+ import_node_fs43.default.appendFileSync(file, line, { mode: 384 });
53936
54060
  } catch {
53937
54061
  }
53938
54062
  }
@@ -54240,27 +54364,27 @@ function respond404(socket) {
54240
54364
 
54241
54365
  // src/tunnel/device-key.ts
54242
54366
  var import_node_os14 = __toESM(require("os"), 1);
54243
- var import_node_path43 = __toESM(require("path"), 1);
54367
+ var import_node_path44 = __toESM(require("path"), 1);
54244
54368
  var import_node_crypto11 = __toESM(require("crypto"), 1);
54245
54369
  var DERIVE_SALT = "clawd-tunnel-device-v1";
54246
54370
  function deriveStableDeviceKey(opts = {}) {
54247
54371
  const hostname = opts.hostname ?? import_node_os14.default.hostname();
54248
54372
  const uid = opts.uid ?? (typeof import_node_os14.default.userInfo === "function" ? import_node_os14.default.userInfo().uid : 0);
54249
54373
  const home = opts.home ?? import_node_os14.default.homedir();
54250
- const defaultDataDir = import_node_path43.default.resolve(import_node_path43.default.join(home, ".clawd"));
54251
- const normalizedDataDir = opts.dataDir ? import_node_path43.default.resolve(opts.dataDir) : null;
54374
+ const defaultDataDir = import_node_path44.default.resolve(import_node_path44.default.join(home, ".clawd"));
54375
+ const normalizedDataDir = opts.dataDir ? import_node_path44.default.resolve(opts.dataDir) : null;
54252
54376
  const isDefaultDir = normalizedDataDir == null || normalizedDataDir === defaultDataDir;
54253
54377
  const input = isDefaultDir ? `${hostname}::${uid}` : `${hostname}::${uid}::${normalizedDataDir}`;
54254
54378
  return import_node_crypto11.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
54255
54379
  }
54256
54380
 
54257
54381
  // src/auth-store.ts
54258
- var import_node_fs43 = __toESM(require("fs"), 1);
54259
- var import_node_path44 = __toESM(require("path"), 1);
54382
+ var import_node_fs44 = __toESM(require("fs"), 1);
54383
+ var import_node_path45 = __toESM(require("path"), 1);
54260
54384
  var import_node_crypto12 = __toESM(require("crypto"), 1);
54261
54385
  var AUTH_FILE_NAME = "auth.json";
54262
54386
  function authFilePath(dataDir) {
54263
- return import_node_path44.default.join(dataDir, AUTH_FILE_NAME);
54387
+ return import_node_path45.default.join(dataDir, AUTH_FILE_NAME);
54264
54388
  }
54265
54389
  function loadOrCreateAuthFile(opts) {
54266
54390
  const file = authFilePath(opts.dataDir);
@@ -54296,7 +54420,7 @@ function defaultGenerateOwnerPrincipalId() {
54296
54420
  }
54297
54421
  function readAuthFile(file) {
54298
54422
  try {
54299
- const raw = import_node_fs43.default.readFileSync(file, "utf8");
54423
+ const raw = import_node_fs44.default.readFileSync(file, "utf8");
54300
54424
  const parsed = JSON.parse(raw);
54301
54425
  if (typeof parsed?.token !== "string" || parsed.token.length === 0) {
54302
54426
  return null;
@@ -54316,25 +54440,25 @@ function readAuthFile(file) {
54316
54440
  }
54317
54441
  }
54318
54442
  function writeAuthFile(file, content) {
54319
- import_node_fs43.default.mkdirSync(import_node_path44.default.dirname(file), { recursive: true });
54320
- import_node_fs43.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
54443
+ import_node_fs44.default.mkdirSync(import_node_path45.default.dirname(file), { recursive: true });
54444
+ import_node_fs44.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
54321
54445
  try {
54322
- import_node_fs43.default.chmodSync(file, 384);
54446
+ import_node_fs44.default.chmodSync(file, 384);
54323
54447
  } catch {
54324
54448
  }
54325
54449
  }
54326
54450
 
54327
54451
  // src/owner-profile.ts
54328
- var import_node_fs44 = __toESM(require("fs"), 1);
54452
+ var import_node_fs45 = __toESM(require("fs"), 1);
54329
54453
  var import_node_os15 = __toESM(require("os"), 1);
54330
- var import_node_path45 = __toESM(require("path"), 1);
54454
+ var import_node_path46 = __toESM(require("path"), 1);
54331
54455
  var PROFILE_FILENAME = "profile.json";
54332
54456
  function loadOwnerDisplayName(dataDir) {
54333
54457
  const fallback = import_node_os15.default.userInfo().username;
54334
- const profilePath = import_node_path45.default.join(dataDir, PROFILE_FILENAME);
54458
+ const profilePath = import_node_path46.default.join(dataDir, PROFILE_FILENAME);
54335
54459
  let raw;
54336
54460
  try {
54337
- raw = import_node_fs44.default.readFileSync(profilePath, "utf8");
54461
+ raw = import_node_fs45.default.readFileSync(profilePath, "utf8");
54338
54462
  } catch {
54339
54463
  return fallback;
54340
54464
  }
@@ -54357,18 +54481,18 @@ function loadOwnerDisplayName(dataDir) {
54357
54481
  }
54358
54482
 
54359
54483
  // src/feishu-auth/owner-identity-store.ts
54360
- var import_node_fs45 = __toESM(require("fs"), 1);
54361
- var import_node_path46 = __toESM(require("path"), 1);
54484
+ var import_node_fs46 = __toESM(require("fs"), 1);
54485
+ var import_node_path47 = __toESM(require("path"), 1);
54362
54486
  var OWNER_IDENTITY_FILE_NAME = "owner-identity.json";
54363
54487
  var OwnerIdentityStore = class {
54364
54488
  file;
54365
54489
  constructor(dataDir) {
54366
- this.file = import_node_path46.default.join(dataDir, OWNER_IDENTITY_FILE_NAME);
54490
+ this.file = import_node_path47.default.join(dataDir, OWNER_IDENTITY_FILE_NAME);
54367
54491
  }
54368
54492
  read() {
54369
54493
  let raw;
54370
54494
  try {
54371
- raw = import_node_fs45.default.readFileSync(this.file, "utf8");
54495
+ raw = import_node_fs46.default.readFileSync(this.file, "utf8");
54372
54496
  } catch {
54373
54497
  return null;
54374
54498
  }
@@ -54396,16 +54520,16 @@ var OwnerIdentityStore = class {
54396
54520
  };
54397
54521
  }
54398
54522
  write(record) {
54399
- import_node_fs45.default.mkdirSync(import_node_path46.default.dirname(this.file), { recursive: true });
54400
- import_node_fs45.default.writeFileSync(this.file, JSON.stringify(record, null, 2), { mode: 384 });
54523
+ import_node_fs46.default.mkdirSync(import_node_path47.default.dirname(this.file), { recursive: true });
54524
+ import_node_fs46.default.writeFileSync(this.file, JSON.stringify(record, null, 2), { mode: 384 });
54401
54525
  try {
54402
- import_node_fs45.default.chmodSync(this.file, 384);
54526
+ import_node_fs46.default.chmodSync(this.file, 384);
54403
54527
  } catch {
54404
54528
  }
54405
54529
  }
54406
54530
  clear() {
54407
54531
  try {
54408
- import_node_fs45.default.unlinkSync(this.file);
54532
+ import_node_fs46.default.unlinkSync(this.file);
54409
54533
  } catch (err) {
54410
54534
  const code = err?.code;
54411
54535
  if (code !== "ENOENT") throw err;
@@ -54542,9 +54666,9 @@ var CentralClientError = class extends Error {
54542
54666
  code;
54543
54667
  cause;
54544
54668
  };
54545
- async function centralRequest(opts, path76, init) {
54669
+ async function centralRequest(opts, path77, init) {
54546
54670
  const f = opts.fetchImpl ?? globalThis.fetch;
54547
- const url = `${opts.api.replace(/\/+$/, "")}${path76}`;
54671
+ const url = `${opts.api.replace(/\/+$/, "")}${path77}`;
54548
54672
  const ctrl = new AbortController();
54549
54673
  const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 15e3);
54550
54674
  let res;
@@ -54738,8 +54862,8 @@ function verifyConnectToken(args) {
54738
54862
  }
54739
54863
 
54740
54864
  // src/feishu-auth/server-key.ts
54741
- var fs55 = __toESM(require("fs"), 1);
54742
- var path55 = __toESM(require("path"), 1);
54865
+ var fs56 = __toESM(require("fs"), 1);
54866
+ var path56 = __toESM(require("path"), 1);
54743
54867
  var FILE_NAME2 = "server-signing-key.json";
54744
54868
  var ServerKeyStore = class {
54745
54869
  constructor(dataDir) {
@@ -54747,12 +54871,12 @@ var ServerKeyStore = class {
54747
54871
  }
54748
54872
  dataDir;
54749
54873
  filePath() {
54750
- return path55.join(this.dataDir, FILE_NAME2);
54874
+ return path56.join(this.dataDir, FILE_NAME2);
54751
54875
  }
54752
54876
  /** 读缓存的公钥;无缓存 / 损坏 → null(调用方决定是否触发拉取) */
54753
54877
  read() {
54754
54878
  try {
54755
- const raw = fs55.readFileSync(this.filePath(), "utf8");
54879
+ const raw = fs56.readFileSync(this.filePath(), "utf8");
54756
54880
  const parsed = JSON.parse(raw);
54757
54881
  if (typeof parsed.publicKeyPem === "string" && parsed.publicKeyPem.includes("PUBLIC KEY")) {
54758
54882
  return parsed.publicKeyPem;
@@ -54767,12 +54891,12 @@ var ServerKeyStore = class {
54767
54891
  publicKeyPem,
54768
54892
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
54769
54893
  };
54770
- fs55.mkdirSync(this.dataDir, { recursive: true });
54771
- fs55.writeFileSync(this.filePath(), JSON.stringify(content, null, 2), { mode: 384 });
54894
+ fs56.mkdirSync(this.dataDir, { recursive: true });
54895
+ fs56.writeFileSync(this.filePath(), JSON.stringify(content, null, 2), { mode: 384 });
54772
54896
  }
54773
54897
  clear() {
54774
54898
  try {
54775
- fs55.unlinkSync(this.filePath());
54899
+ fs56.unlinkSync(this.filePath());
54776
54900
  } catch {
54777
54901
  }
54778
54902
  }
@@ -54785,12 +54909,12 @@ init_protocol();
54785
54909
  init_protocol();
54786
54910
 
54787
54911
  // src/session/fork.ts
54788
- var import_node_fs46 = __toESM(require("fs"), 1);
54912
+ var import_node_fs47 = __toESM(require("fs"), 1);
54789
54913
  var import_node_os16 = __toESM(require("os"), 1);
54790
- var import_node_path47 = __toESM(require("path"), 1);
54914
+ var import_node_path48 = __toESM(require("path"), 1);
54791
54915
  init_claude_history();
54792
54916
  function readJsonlEntries(file) {
54793
- const raw = import_node_fs46.default.readFileSync(file, "utf8");
54917
+ const raw = import_node_fs47.default.readFileSync(file, "utf8");
54794
54918
  const out = [];
54795
54919
  for (const line of raw.split("\n")) {
54796
54920
  const t = line.trim();
@@ -54803,19 +54927,19 @@ function readJsonlEntries(file) {
54803
54927
  return out;
54804
54928
  }
54805
54929
  function forkSession(input) {
54806
- const baseDir = input.baseDir ?? import_node_path47.default.join(import_node_os16.default.homedir(), ".claude");
54807
- let projectDir = import_node_path47.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
54808
- let sourceFile = import_node_path47.default.join(projectDir, `${input.toolSessionId}.jsonl`);
54809
- if (!import_node_fs46.default.existsSync(sourceFile)) {
54930
+ const baseDir = input.baseDir ?? import_node_path48.default.join(import_node_os16.default.homedir(), ".claude");
54931
+ let projectDir = import_node_path48.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
54932
+ let sourceFile = import_node_path48.default.join(projectDir, `${input.toolSessionId}.jsonl`);
54933
+ if (!import_node_fs47.default.existsSync(sourceFile)) {
54810
54934
  const found = findTranscriptByToolSessionId(
54811
- import_node_path47.default.join(baseDir, "projects"),
54935
+ import_node_path48.default.join(baseDir, "projects"),
54812
54936
  input.toolSessionId
54813
54937
  );
54814
54938
  if (!found) {
54815
54939
  throw new Error(`fork: source transcript not found: ${sourceFile}`);
54816
54940
  }
54817
54941
  sourceFile = found;
54818
- projectDir = import_node_path47.default.dirname(found);
54942
+ projectDir = import_node_path48.default.dirname(found);
54819
54943
  }
54820
54944
  const entries = readJsonlEntries(sourceFile);
54821
54945
  const mainEntries = entries.filter((e) => e.isSidechain !== true);
@@ -54844,9 +54968,9 @@ function forkSession(input) {
54844
54968
  }
54845
54969
  forkedLines.push(JSON.stringify(forked));
54846
54970
  }
54847
- const forkedFilePath = import_node_path47.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
54848
- import_node_fs46.default.mkdirSync(projectDir, { recursive: true });
54849
- import_node_fs46.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
54971
+ const forkedFilePath = import_node_path48.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
54972
+ import_node_fs47.default.mkdirSync(projectDir, { recursive: true });
54973
+ import_node_fs47.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
54850
54974
  return { forkedToolSessionId, forkedFilePath };
54851
54975
  }
54852
54976
 
@@ -55217,7 +55341,7 @@ function buildPermissionHandlers(deps) {
55217
55341
  }
55218
55342
 
55219
55343
  // src/handlers/history.ts
55220
- var path58 = __toESM(require("path"), 1);
55344
+ var path59 = __toESM(require("path"), 1);
55221
55345
  init_protocol();
55222
55346
 
55223
55347
  // src/session/recent-dirs.ts
@@ -55235,7 +55359,7 @@ function listRecentDirs(store, limit = 50) {
55235
55359
  }
55236
55360
 
55237
55361
  // src/permission/persona-paths.ts
55238
- var path57 = __toESM(require("path"), 1);
55362
+ var path58 = __toESM(require("path"), 1);
55239
55363
  function getAllowedPersonaIds(grants, action) {
55240
55364
  const ids = /* @__PURE__ */ new Set();
55241
55365
  for (const g2 of grants) {
@@ -55248,42 +55372,42 @@ function getAllowedPersonaIds(grants, action) {
55248
55372
  return ids;
55249
55373
  }
55250
55374
  function isGuestPathAllowed(grants, absPath, personaRoot, action = "read", userWorkDir) {
55251
- const target = path57.resolve(absPath);
55375
+ const target = path58.resolve(absPath);
55252
55376
  if (userWorkDir) {
55253
- const u = path57.resolve(userWorkDir);
55254
- const usep = u.endsWith(path57.sep) ? "" : path57.sep;
55377
+ const u = path58.resolve(userWorkDir);
55378
+ const usep = u.endsWith(path58.sep) ? "" : path58.sep;
55255
55379
  if (target === u || target.startsWith(u + usep)) return true;
55256
55380
  }
55257
- const root = path57.resolve(personaRoot);
55258
- const sep3 = root.endsWith(path57.sep) ? "" : path57.sep;
55381
+ const root = path58.resolve(personaRoot);
55382
+ const sep3 = root.endsWith(path58.sep) ? "" : path58.sep;
55259
55383
  if (!target.startsWith(root + sep3)) return false;
55260
- const rel = path57.relative(root, target);
55384
+ const rel = path58.relative(root, target);
55261
55385
  if (!rel || rel.startsWith("..")) return false;
55262
- const personaId = rel.split(path57.sep)[0];
55386
+ const personaId = rel.split(path58.sep)[0];
55263
55387
  if (!personaId) return false;
55264
55388
  const allowed = getAllowedPersonaIds(grants, action);
55265
55389
  if (allowed === "*") return true;
55266
55390
  return allowed.has(personaId);
55267
55391
  }
55268
55392
  function personaIdFromPath(absPath, personaRoot) {
55269
- const root = path57.resolve(personaRoot);
55270
- const target = path57.resolve(absPath);
55271
- const sep3 = root.endsWith(path57.sep) ? "" : path57.sep;
55393
+ const root = path58.resolve(personaRoot);
55394
+ const target = path58.resolve(absPath);
55395
+ const sep3 = root.endsWith(path58.sep) ? "" : path58.sep;
55272
55396
  if (!target.startsWith(root + sep3)) return null;
55273
- const rel = path57.relative(root, target);
55397
+ const rel = path58.relative(root, target);
55274
55398
  if (!rel || rel.startsWith("..")) return null;
55275
- const id = rel.split(path57.sep)[0];
55399
+ const id = rel.split(path58.sep)[0];
55276
55400
  return id || null;
55277
55401
  }
55278
55402
  function isPathWithin(dir, absPath) {
55279
- const d = path57.resolve(dir);
55280
- const t = path57.resolve(absPath);
55281
- const sep3 = d.endsWith(path57.sep) ? "" : path57.sep;
55403
+ const d = path58.resolve(dir);
55404
+ const t = path58.resolve(absPath);
55405
+ const sep3 = d.endsWith(path58.sep) ? "" : path58.sep;
55282
55406
  return t === d || t.startsWith(d + sep3);
55283
55407
  }
55284
55408
  function isPathInGuestBoundary(personaRoot, personaId, userWorkDir, absPath) {
55285
55409
  if (userWorkDir && isPathWithin(userWorkDir, absPath)) return true;
55286
- return personaIdFromPath(path57.resolve(absPath), personaRoot) === personaId;
55410
+ return personaIdFromPath(path58.resolve(absPath), personaRoot) === personaId;
55287
55411
  }
55288
55412
 
55289
55413
  // src/handlers/history.ts
@@ -55309,7 +55433,7 @@ function buildHistoryHandlers(deps) {
55309
55433
  if (!pid) return false;
55310
55434
  return isGuestPathAllowed(
55311
55435
  ctx.grants,
55312
- path58.join(personaRoot, pid),
55436
+ path59.join(personaRoot, pid),
55313
55437
  personaRoot,
55314
55438
  "read",
55315
55439
  userWorkDir
@@ -55321,7 +55445,7 @@ function buildHistoryHandlers(deps) {
55321
55445
  };
55322
55446
  const list = async (frame, _client, ctx) => {
55323
55447
  const args = HistoryListArgs.parse(frame);
55324
- assertGuestPath(ctx, path58.resolve(args.projectPath), personaRoot, "history:list");
55448
+ assertGuestPath(ctx, path59.resolve(args.projectPath), personaRoot, "history:list");
55325
55449
  const sessions = await history.listSessions(args);
55326
55450
  return { response: { type: "history:list", sessions } };
55327
55451
  };
@@ -55353,13 +55477,13 @@ function buildHistoryHandlers(deps) {
55353
55477
  };
55354
55478
  const subagents = async (frame, _client, ctx) => {
55355
55479
  const args = HistorySubagentsArgs.parse(frame);
55356
- assertGuestPath(ctx, path58.resolve(args.cwd), personaRoot, "history:subagents", usersRoot);
55480
+ assertGuestPath(ctx, path59.resolve(args.cwd), personaRoot, "history:subagents", usersRoot);
55357
55481
  const subs = await history.listSubagents(args);
55358
55482
  return { response: { type: "history:subagents", subagents: subs } };
55359
55483
  };
55360
55484
  const subagentRead = async (frame, _client, ctx) => {
55361
55485
  const args = HistorySubagentReadArgs.parse(frame);
55362
- assertGuestPath(ctx, path58.resolve(args.cwd), personaRoot, "history:subagent-read", usersRoot);
55486
+ assertGuestPath(ctx, path59.resolve(args.cwd), personaRoot, "history:subagent-read", usersRoot);
55363
55487
  const res = await history.readSubagent(args);
55364
55488
  return { response: { type: "history:subagent-read", ...res } };
55365
55489
  };
@@ -55368,7 +55492,7 @@ function buildHistoryHandlers(deps) {
55368
55492
  if (ctx?.principal.kind === "guest" && personaRoot) {
55369
55493
  const userWorkDir = usersRoot ? deriveUserWorkDir(ctx.principal.id, usersRoot) : void 0;
55370
55494
  const filtered = dirs.filter(
55371
- (d) => isGuestPathAllowed(ctx.grants, path58.resolve(d.cwd), personaRoot, "read", userWorkDir)
55495
+ (d) => isGuestPathAllowed(ctx.grants, path59.resolve(d.cwd), personaRoot, "read", userWorkDir)
55372
55496
  );
55373
55497
  return { response: { type: "history:recentDirs", dirs: filtered } };
55374
55498
  }
@@ -55385,7 +55509,7 @@ function buildHistoryHandlers(deps) {
55385
55509
  }
55386
55510
 
55387
55511
  // src/handlers/workspace.ts
55388
- var path59 = __toESM(require("path"), 1);
55512
+ var path60 = __toESM(require("path"), 1);
55389
55513
  var os16 = __toESM(require("os"), 1);
55390
55514
  init_protocol();
55391
55515
  init_protocol();
@@ -55427,22 +55551,22 @@ function buildWorkspaceHandlers(deps) {
55427
55551
  const args = WorkspaceListArgs.parse(frame);
55428
55552
  const isGuest = ctx?.principal.kind === "guest";
55429
55553
  const fallbackCwd = isGuest && personaRoot ? personaRoot : os16.homedir();
55430
- const resolvedCwd = path59.resolve(args.cwd ?? fallbackCwd);
55431
- const target = args.path ? path59.resolve(resolvedCwd, args.path) : resolvedCwd;
55554
+ const resolvedCwd = path60.resolve(args.cwd ?? fallbackCwd);
55555
+ const target = args.path ? path60.resolve(resolvedCwd, args.path) : resolvedCwd;
55432
55556
  assertGuestPath2(ctx, target, personaRoot, "workspace:list", usersRoot);
55433
55557
  const res = workspace.list({ ...args, cwd: resolvedCwd });
55434
55558
  return { response: { type: "workspace:list", ...res } };
55435
55559
  };
55436
55560
  const read = async (frame, _client, ctx) => {
55437
55561
  const args = WorkspaceReadArgs.parse(frame);
55438
- const target = path59.isAbsolute(args.path) ? path59.resolve(args.path) : path59.resolve(args.cwd, args.path);
55562
+ const target = path60.isAbsolute(args.path) ? path60.resolve(args.path) : path60.resolve(args.cwd, args.path);
55439
55563
  assertGuestPath2(ctx, target, personaRoot, "workspace:read", usersRoot);
55440
55564
  const res = workspace.read(args);
55441
55565
  return { response: { type: "workspace:read", ...res } };
55442
55566
  };
55443
55567
  const skillsList = async (frame, _client, ctx) => {
55444
55568
  const args = SkillsListArgs.parse(frame);
55445
- const cwdAbs = path59.resolve(args.cwd);
55569
+ const cwdAbs = path60.resolve(args.cwd);
55446
55570
  assertGuestPath2(ctx, cwdAbs, personaRoot, "skills:list", usersRoot);
55447
55571
  const list2 = await getSkillsForTool(args.tool ?? "claude", cwdAbs);
55448
55572
  if (ctx?.principal.kind === "guest" && personaRoot) {
@@ -55454,7 +55578,7 @@ function buildWorkspaceHandlers(deps) {
55454
55578
  };
55455
55579
  const agentsList = async (frame, _client, ctx) => {
55456
55580
  const args = AgentsListArgs.parse(frame);
55457
- const cwdAbs = path59.resolve(args.cwd);
55581
+ const cwdAbs = path60.resolve(args.cwd);
55458
55582
  assertGuestPath2(ctx, cwdAbs, personaRoot, "agents:list", usersRoot);
55459
55583
  if (args.tool === "codex") {
55460
55584
  return { response: { type: "agents:list", agents: [] } };
@@ -55476,20 +55600,20 @@ function buildWorkspaceHandlers(deps) {
55476
55600
  }
55477
55601
 
55478
55602
  // src/handlers/git.ts
55479
- var path61 = __toESM(require("path"), 1);
55603
+ var path62 = __toESM(require("path"), 1);
55480
55604
  init_protocol();
55481
55605
  init_protocol();
55482
55606
 
55483
55607
  // src/workspace/git.ts
55484
55608
  var import_node_child_process13 = require("child_process");
55485
- var import_node_fs47 = __toESM(require("fs"), 1);
55486
- var import_node_path48 = __toESM(require("path"), 1);
55609
+ var import_node_fs48 = __toESM(require("fs"), 1);
55610
+ var import_node_path49 = __toESM(require("path"), 1);
55487
55611
  var import_node_util = require("util");
55488
55612
  var pexec = (0, import_node_util.promisify)(import_node_child_process13.execFile);
55489
55613
  function normalizePath(p2) {
55490
- const resolved = import_node_path48.default.resolve(p2);
55614
+ const resolved = import_node_path49.default.resolve(p2);
55491
55615
  try {
55492
- return import_node_fs47.default.realpathSync(resolved);
55616
+ return import_node_fs48.default.realpathSync(resolved);
55493
55617
  } catch {
55494
55618
  return resolved;
55495
55619
  }
@@ -55563,7 +55687,7 @@ async function listGitBranches(cwd) {
55563
55687
  function assertGuestCwd(ctx, cwd, personaRoot, method, usersRoot) {
55564
55688
  if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
55565
55689
  const userWorkDir = usersRoot ? deriveUserWorkDir(ctx.principal.id, usersRoot) : void 0;
55566
- if (!isGuestPathAllowed(ctx.grants, path61.resolve(cwd), personaRoot, "read", userWorkDir)) {
55690
+ if (!isGuestPathAllowed(ctx.grants, path62.resolve(cwd), personaRoot, "read", userWorkDir)) {
55567
55691
  throw new ClawdError(
55568
55692
  ERROR_CODES.UNAUTHORIZED,
55569
55693
  `guest ${ctx.principal.id} cannot ${method} cwd ${cwd}`
@@ -55915,21 +56039,21 @@ function ensureOwner2(ctx) {
55915
56039
  if (!ctx || ctx.principal.kind !== "owner") {
55916
56040
  throw new ClawdError(
55917
56041
  ERROR_CODES.UNAUTHORIZED,
55918
- "UNAUTHORIZED: contact:setSshAccess requires owner ctx"
56042
+ "UNAUTHORIZED: contact:setRemoteAccess requires owner ctx"
55919
56043
  );
55920
56044
  }
55921
56045
  }
55922
56046
  function buildContactSshHandlers(deps) {
55923
56047
  const sshLog = deps.sshLog ?? nullContactSshLog;
55924
- const setSshAccess = async (frame, _client, ctx) => {
56048
+ const setRemoteAccess = async (frame, _client, ctx) => {
55925
56049
  ensureOwner2(ctx);
55926
56050
  const { type: _t, requestId: _r, ...rest } = frame;
55927
- const args = ContactSetSshAccessArgsSchema.parse(rest);
55928
- const hit = deps.store.setSshAccess(args.deviceId, {
55929
- sshAllowed: args.sshAllowed
56051
+ const args = ContactSetRemoteAccessArgsSchema.parse(rest);
56052
+ const hit = deps.store.setRemoteAccess(args.deviceId, {
56053
+ remoteAccessAllowed: args.remoteAccessAllowed
55930
56054
  });
55931
56055
  if (!hit) {
55932
- sshLog.warn("authz.setSshAccess", "owner \u5C1D\u8BD5\u6539 SSH \u6388\u6743\u4F46 contact \u4E0D\u5728 store", {
56056
+ sshLog.warn("authz.setRemoteAccess", "owner \u5C1D\u8BD5\u6539 SSH \u6388\u6743\u4F46 contact \u4E0D\u5728 store", {
55933
56057
  peerDeviceId: args.deviceId
55934
56058
  });
55935
56059
  throw new ClawdError(
@@ -55938,25 +56062,25 @@ function buildContactSshHandlers(deps) {
55938
56062
  );
55939
56063
  }
55940
56064
  rebuildAuthorizedKeys(deps.store, deps.sshdDir);
55941
- sshLog.info("authz.setSshAccess", "owner \u6539\u4E86 SSH \u6388\u6743 \u2192 authorized_keys \u5DF2\u91CD\u5EFA", {
56065
+ sshLog.info("authz.setRemoteAccess", "owner \u6539\u4E86 SSH \u6388\u6743 \u2192 authorized_keys \u5DF2\u91CD\u5EFA", {
55942
56066
  peerDeviceId: args.deviceId,
55943
- sshAllowed: args.sshAllowed
56067
+ remoteAccessAllowed: args.remoteAccessAllowed
55944
56068
  });
55945
56069
  deps.broadcast({
55946
- type: "contact:ssh-access-updated",
56070
+ type: "contact:remote-access-updated",
55947
56071
  deviceId: args.deviceId,
55948
- sshAllowed: args.sshAllowed
56072
+ remoteAccessAllowed: args.remoteAccessAllowed
55949
56073
  });
55950
56074
  return {
55951
56075
  response: {
55952
- type: "contact:setSshAccess:ok",
56076
+ type: "contact:setRemoteAccess:ok",
55953
56077
  deviceId: args.deviceId,
55954
- sshAllowed: args.sshAllowed
56078
+ remoteAccessAllowed: args.remoteAccessAllowed
55955
56079
  }
55956
56080
  };
55957
56081
  };
55958
56082
  return {
55959
- "contact:setSshAccess": setSshAccess
56083
+ "contact:setRemoteAccess": setRemoteAccess
55960
56084
  };
55961
56085
  }
55962
56086
 
@@ -56124,7 +56248,7 @@ function buildDeviceHandlers(deps) {
56124
56248
  grants: wh.grants,
56125
56249
  addedAt: now(),
56126
56250
  pinnedAt: existing?.pinnedAt ?? null,
56127
- sshAllowed: existing?.sshAllowed ?? false,
56251
+ remoteAccessAllowed: existing?.remoteAccessAllowed ?? false,
56128
56252
  // 建联交换:对方 whoami:ok 下发的 pubkey / osUser 落库;对方老版本不带 →
56129
56253
  // 保留既有值 / 不落 undefined 占位。见 doc/glossary/peer-mesh.md#建联交换清单
56130
56254
  ...sshPublicKey ? { sshPublicKey } : {},
@@ -56133,7 +56257,7 @@ function buildDeviceHandlers(deps) {
56133
56257
  };
56134
56258
  deps.store.upsert(contact);
56135
56259
  deps.broadcast({ type: "contact:added", contact });
56136
- if (contact.sshAllowed && sshPublicKey && sshPublicKey !== existing?.sshPublicKey) {
56260
+ if (contact.remoteAccessAllowed && sshPublicKey && sshPublicKey !== existing?.sshPublicKey) {
56137
56261
  deps.rebuildAuthorizedKeys?.();
56138
56262
  }
56139
56263
  return {
@@ -56288,7 +56412,7 @@ function buildPersonaHandlers(deps) {
56288
56412
  }
56289
56413
 
56290
56414
  // src/handlers/attachment.ts
56291
- var import_node_path49 = __toESM(require("path"), 1);
56415
+ var import_node_path50 = __toESM(require("path"), 1);
56292
56416
  init_protocol();
56293
56417
  init_protocol();
56294
56418
  var DEFAULT_TTL_SECONDS = 24 * 3600;
@@ -56368,12 +56492,12 @@ function buildAttachmentHandlers(deps) {
56368
56492
  `session ${args.sessionId} scope unresolved`
56369
56493
  );
56370
56494
  }
56371
- const cwdAbs = import_node_path49.default.resolve(sessionFile.cwd);
56372
- const candidateAbs = import_node_path49.default.isAbsolute(args.relPath) ? import_node_path49.default.resolve(args.relPath) : import_node_path49.default.resolve(cwdAbs, args.relPath);
56495
+ const cwdAbs = import_node_path50.default.resolve(sessionFile.cwd);
56496
+ const candidateAbs = import_node_path50.default.isAbsolute(args.relPath) ? import_node_path50.default.resolve(args.relPath) : import_node_path50.default.resolve(cwdAbs, args.relPath);
56373
56497
  guardAttachmentPath(ctx, args.sessionId, candidateAbs, "attachment.signUrl", "group-acl");
56374
56498
  const entries = deps.groupFileStore.list(scope, args.sessionId);
56375
56499
  const entry = entries.find((e) => {
56376
- const storedAbs = import_node_path49.default.isAbsolute(e.relPath) ? import_node_path49.default.resolve(e.relPath) : import_node_path49.default.resolve(cwdAbs, e.relPath);
56500
+ const storedAbs = import_node_path50.default.isAbsolute(e.relPath) ? import_node_path50.default.resolve(e.relPath) : import_node_path50.default.resolve(cwdAbs, e.relPath);
56377
56501
  return storedAbs === candidateAbs && !e.stale;
56378
56502
  });
56379
56503
  if (!entry) {
@@ -56398,7 +56522,7 @@ function buildAttachmentHandlers(deps) {
56398
56522
  if (!ctx || ctx.principal.kind !== "guest" || !deps.personaRoot || !deps.sessionStore) return;
56399
56523
  const f = deps.sessionStore.read(sessionId);
56400
56524
  if (!f) return;
56401
- assertGuestAttachmentPath(ctx, import_node_path49.default.resolve(f.cwd), deps.personaRoot, method, deps.usersRoot);
56525
+ assertGuestAttachmentPath(ctx, import_node_path50.default.resolve(f.cwd), deps.personaRoot, method, deps.usersRoot);
56402
56526
  }
56403
56527
  const groupAdd = async (frame, _client, ctx) => {
56404
56528
  if (!deps.groupFileStore || !deps.getSessionScope) {
@@ -56413,8 +56537,8 @@ function buildAttachmentHandlers(deps) {
56413
56537
  if (!scope) {
56414
56538
  throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, `session ${args.sessionId} not found`);
56415
56539
  }
56416
- const cwdAbs = import_node_path49.default.resolve(deps.sessionStore?.read(args.sessionId)?.cwd ?? ".");
56417
- const candidateAbs = import_node_path49.default.isAbsolute(args.relPath) ? import_node_path49.default.resolve(args.relPath) : import_node_path49.default.resolve(cwdAbs, args.relPath);
56540
+ const cwdAbs = import_node_path50.default.resolve(deps.sessionStore?.read(args.sessionId)?.cwd ?? ".");
56541
+ const candidateAbs = import_node_path50.default.isAbsolute(args.relPath) ? import_node_path50.default.resolve(args.relPath) : import_node_path50.default.resolve(cwdAbs, args.relPath);
56418
56542
  guardAttachmentPath(ctx, args.sessionId, candidateAbs, "attachment.groupAdd", "cwd-subtree");
56419
56543
  const from = ctx?.principal.kind === "owner" ? "owner" : "agent";
56420
56544
  const size = 0;
@@ -56473,19 +56597,19 @@ function buildAttachmentHandlers(deps) {
56473
56597
 
56474
56598
  // src/handlers/extension.ts
56475
56599
  var import_promises8 = __toESM(require("fs/promises"), 1);
56476
- var import_node_path54 = __toESM(require("path"), 1);
56600
+ var import_node_path55 = __toESM(require("path"), 1);
56477
56601
  init_protocol();
56478
56602
 
56479
56603
  // src/extension/bundle-zip.ts
56480
56604
  var import_promises5 = __toESM(require("fs/promises"), 1);
56481
- var import_node_path50 = __toESM(require("path"), 1);
56605
+ var import_node_path51 = __toESM(require("path"), 1);
56482
56606
  var import_node_crypto14 = __toESM(require("crypto"), 1);
56483
56607
  var import_jszip2 = __toESM(require_lib3(), 1);
56484
56608
  async function bundleExtensionDir(dir) {
56485
56609
  const entries = await listFilesSorted(dir);
56486
56610
  const zip = new import_jszip2.default();
56487
56611
  for (const rel of entries) {
56488
- const abs = import_node_path50.default.join(dir, rel);
56612
+ const abs = import_node_path51.default.join(dir, rel);
56489
56613
  const content = await import_promises5.default.readFile(abs);
56490
56614
  zip.file(rel, content, { date: FIXED_DATE });
56491
56615
  }
@@ -56506,7 +56630,7 @@ async function listFilesSorted(rootDir) {
56506
56630
  return out;
56507
56631
  }
56508
56632
  async function walk(absRoot, relPrefix, out) {
56509
- const dirAbs = import_node_path50.default.join(absRoot, relPrefix);
56633
+ const dirAbs = import_node_path51.default.join(absRoot, relPrefix);
56510
56634
  const entries = await import_promises5.default.readdir(dirAbs, { withFileTypes: true });
56511
56635
  for (const e of entries) {
56512
56636
  if (IGNORE_BASENAMES.has(e.name)) continue;
@@ -56560,25 +56684,25 @@ function computePublishCheck(args) {
56560
56684
 
56561
56685
  // src/extension/install-flow.ts
56562
56686
  var import_promises6 = __toESM(require("fs/promises"), 1);
56563
- var import_node_path52 = __toESM(require("path"), 1);
56687
+ var import_node_path53 = __toESM(require("path"), 1);
56564
56688
  var import_node_os19 = __toESM(require("os"), 1);
56565
56689
  var import_node_crypto15 = __toESM(require("crypto"), 1);
56566
56690
  var import_jszip3 = __toESM(require_lib3(), 1);
56567
56691
 
56568
56692
  // src/extension/paths.ts
56569
56693
  var import_node_os18 = __toESM(require("os"), 1);
56570
- var import_node_path51 = __toESM(require("path"), 1);
56694
+ var import_node_path52 = __toESM(require("path"), 1);
56571
56695
  function clawdHomeRoot(override) {
56572
- return override ?? process.env.CLAWD_HOME ?? import_node_path51.default.join(import_node_os18.default.homedir(), ".clawd");
56696
+ return override ?? process.env.CLAWD_HOME ?? import_node_path52.default.join(import_node_os18.default.homedir(), ".clawd");
56573
56697
  }
56574
56698
  function extensionsRoot(override) {
56575
- return import_node_path51.default.join(clawdHomeRoot(override), "extensions");
56699
+ return import_node_path52.default.join(clawdHomeRoot(override), "extensions");
56576
56700
  }
56577
56701
  function publishedChannelsFile(override) {
56578
- return import_node_path51.default.join(clawdHomeRoot(override), "extensions-published.json");
56702
+ return import_node_path52.default.join(clawdHomeRoot(override), "extensions-published.json");
56579
56703
  }
56580
56704
  function bundleCacheRoot(override) {
56581
- return import_node_path51.default.join(clawdHomeRoot(override), "extension-bundles");
56705
+ return import_node_path52.default.join(clawdHomeRoot(override), "extension-bundles");
56582
56706
  }
56583
56707
 
56584
56708
  // src/extension/install-flow.ts
@@ -56605,7 +56729,7 @@ async function installFromChannel(args, deps) {
56605
56729
  throw new InstallError("ZIP_INVALID", `failed to load zip: ${e.message}`);
56606
56730
  }
56607
56731
  for (const name of Object.keys(zip.files)) {
56608
- if (name.includes("..") || name.startsWith("/") || import_node_path52.default.isAbsolute(name)) {
56732
+ if (name.includes("..") || name.startsWith("/") || import_node_path53.default.isAbsolute(name)) {
56609
56733
  throw new InstallError("ZIP_INVALID", `unsafe zip entry: ${name}`);
56610
56734
  }
56611
56735
  }
@@ -56637,7 +56761,7 @@ async function installFromChannel(args, deps) {
56637
56761
  );
56638
56762
  }
56639
56763
  const localExtId = namespacedExtId(ownerSlug, channelRef.ownerPrincipalId);
56640
- const destDir = import_node_path52.default.join(deps.extensionsRoot, localExtId);
56764
+ const destDir = import_node_path53.default.join(deps.extensionsRoot, localExtId);
56641
56765
  let destExists = false;
56642
56766
  try {
56643
56767
  await import_promises6.default.access(destDir);
@@ -56651,16 +56775,16 @@ async function installFromChannel(args, deps) {
56651
56775
  );
56652
56776
  }
56653
56777
  const stage = await import_promises6.default.mkdtemp(
56654
- import_node_path52.default.join(import_node_os19.default.tmpdir(), `clawd-ext-install-${localExtId}-`)
56778
+ import_node_path53.default.join(import_node_os19.default.tmpdir(), `clawd-ext-install-${localExtId}-`)
56655
56779
  );
56656
56780
  try {
56657
56781
  for (const [name, entry] of Object.entries(zip.files)) {
56658
- const dest = import_node_path52.default.join(stage, name);
56782
+ const dest = import_node_path53.default.join(stage, name);
56659
56783
  if (entry.dir) {
56660
56784
  await import_promises6.default.mkdir(dest, { recursive: true });
56661
56785
  continue;
56662
56786
  }
56663
- await import_promises6.default.mkdir(import_node_path52.default.dirname(dest), { recursive: true });
56787
+ await import_promises6.default.mkdir(import_node_path53.default.dirname(dest), { recursive: true });
56664
56788
  if (name === "manifest.json") {
56665
56789
  const rewritten = { ...parsed.data, id: localExtId };
56666
56790
  await import_promises6.default.writeFile(dest, JSON.stringify(rewritten, null, 2));
@@ -56681,7 +56805,7 @@ async function installFromChannel(args, deps) {
56681
56805
 
56682
56806
  // src/extension/update-flow.ts
56683
56807
  var import_promises7 = __toESM(require("fs/promises"), 1);
56684
- var import_node_path53 = __toESM(require("path"), 1);
56808
+ var import_node_path54 = __toESM(require("path"), 1);
56685
56809
  var import_node_os20 = __toESM(require("os"), 1);
56686
56810
  var import_node_crypto16 = __toESM(require("crypto"), 1);
56687
56811
  var import_jszip4 = __toESM(require_lib3(), 1);
@@ -56698,11 +56822,11 @@ async function updateFromChannel(args, deps) {
56698
56822
  channelRef.extId,
56699
56823
  channelRef.ownerPrincipalId
56700
56824
  );
56701
- const liveDir = import_node_path53.default.join(deps.extensionsRoot, localExtId);
56825
+ const liveDir = import_node_path54.default.join(deps.extensionsRoot, localExtId);
56702
56826
  const prevDir = `${liveDir}.prev`;
56703
56827
  let existingVersion;
56704
56828
  try {
56705
- const raw = await import_promises7.default.readFile(import_node_path53.default.join(liveDir, "manifest.json"), "utf8");
56829
+ const raw = await import_promises7.default.readFile(import_node_path54.default.join(liveDir, "manifest.json"), "utf8");
56706
56830
  const parsed2 = ExtensionManifestSchema.safeParse(JSON.parse(raw));
56707
56831
  if (!parsed2.success) {
56708
56832
  throw new UpdateError(
@@ -56735,7 +56859,7 @@ async function updateFromChannel(args, deps) {
56735
56859
  throw new UpdateError("ZIP_INVALID", `failed to load zip: ${e.message}`);
56736
56860
  }
56737
56861
  for (const name of Object.keys(zip.files)) {
56738
- if (name.includes("..") || name.startsWith("/") || import_node_path53.default.isAbsolute(name)) {
56862
+ if (name.includes("..") || name.startsWith("/") || import_node_path54.default.isAbsolute(name)) {
56739
56863
  throw new UpdateError("ZIP_INVALID", `unsafe zip entry: ${name}`);
56740
56864
  }
56741
56865
  }
@@ -56770,16 +56894,16 @@ async function updateFromChannel(args, deps) {
56770
56894
  await import_promises7.default.rm(prevDir, { recursive: true, force: true });
56771
56895
  await import_promises7.default.rename(liveDir, prevDir);
56772
56896
  const stage = await import_promises7.default.mkdtemp(
56773
- import_node_path53.default.join(import_node_os20.default.tmpdir(), `clawd-ext-update-${localExtId}-`)
56897
+ import_node_path54.default.join(import_node_os20.default.tmpdir(), `clawd-ext-update-${localExtId}-`)
56774
56898
  );
56775
56899
  try {
56776
56900
  for (const [name, entry] of Object.entries(zip.files)) {
56777
- const dest = import_node_path53.default.join(stage, name);
56901
+ const dest = import_node_path54.default.join(stage, name);
56778
56902
  if (entry.dir) {
56779
56903
  await import_promises7.default.mkdir(dest, { recursive: true });
56780
56904
  continue;
56781
56905
  }
56782
- await import_promises7.default.mkdir(import_node_path53.default.dirname(dest), { recursive: true });
56906
+ await import_promises7.default.mkdir(import_node_path54.default.dirname(dest), { recursive: true });
56783
56907
  if (name === "manifest.json") {
56784
56908
  const rewritten = { ...parsed.data, id: localExtId };
56785
56909
  await import_promises7.default.writeFile(dest, JSON.stringify(rewritten, null, 2));
@@ -56872,7 +56996,7 @@ async function rewriteManifestVersion(root, extId, newVersion, previousPublished
56872
56996
  );
56873
56997
  }
56874
56998
  }
56875
- const manifestPath = import_node_path54.default.join(root, extId, "manifest.json");
56999
+ const manifestPath = import_node_path55.default.join(root, extId, "manifest.json");
56876
57000
  const manifest = await readManifest(root, extId);
56877
57001
  const next = { ...manifest, version: newVersion };
56878
57002
  const tmp = `${manifestPath}.tmp`;
@@ -56880,7 +57004,7 @@ async function rewriteManifestVersion(root, extId, newVersion, previousPublished
56880
57004
  await import_promises8.default.rename(tmp, manifestPath);
56881
57005
  }
56882
57006
  async function readManifest(root, extId) {
56883
- const file = import_node_path54.default.join(root, extId, "manifest.json");
57007
+ const file = import_node_path55.default.join(root, extId, "manifest.json");
56884
57008
  let raw;
56885
57009
  try {
56886
57010
  raw = await import_promises8.default.readFile(file, "utf8");
@@ -56971,7 +57095,7 @@ function buildExtensionHandlers(deps) {
56971
57095
  };
56972
57096
  async function buildSnapshotMeta(extId) {
56973
57097
  const manifest = await readManifest(deps.root, extId);
56974
- const { sha256, buffer } = await bundleExtensionDir(import_node_path54.default.join(deps.root, extId));
57098
+ const { sha256, buffer } = await bundleExtensionDir(import_node_path55.default.join(deps.root, extId));
56975
57099
  return { manifest, contentHash: sha256, buffer };
56976
57100
  }
56977
57101
  const publish = async (frame, _client, ctx) => {
@@ -57152,9 +57276,9 @@ function buildExtensionHandlers(deps) {
57152
57276
  }
57153
57277
 
57154
57278
  // src/app-builder/project-store.ts
57155
- var import_node_fs48 = require("fs");
57279
+ var import_node_fs49 = require("fs");
57156
57280
  var import_node_child_process14 = require("child_process");
57157
- var import_node_path55 = require("path");
57281
+ var import_node_path56 = require("path");
57158
57282
  init_protocol();
57159
57283
  var PROJECTS_DIR = "projects";
57160
57284
  var META_FILE = ".clawd-project.json";
@@ -57168,19 +57292,19 @@ var ProjectStore = class {
57168
57292
  root;
57169
57293
  /** projects/<name>/.clawd-project.json 路径 */
57170
57294
  metaPath(name) {
57171
- return (0, import_node_path55.join)(this.projectsRoot(), name, META_FILE);
57295
+ return (0, import_node_path56.join)(this.projectsRoot(), name, META_FILE);
57172
57296
  }
57173
57297
  /** projects/<name>/ 目录路径(cwd 用) */
57174
57298
  projectDir(name) {
57175
- return (0, import_node_path55.join)(this.projectsRoot(), name);
57299
+ return (0, import_node_path56.join)(this.projectsRoot(), name);
57176
57300
  }
57177
57301
  projectsRoot() {
57178
- return (0, import_node_path55.join)(this.root, PROJECTS_DIR);
57302
+ return (0, import_node_path56.join)(this.root, PROJECTS_DIR);
57179
57303
  }
57180
57304
  async list() {
57181
57305
  let entries;
57182
57306
  try {
57183
- entries = await import_node_fs48.promises.readdir(this.projectsRoot());
57307
+ entries = await import_node_fs49.promises.readdir(this.projectsRoot());
57184
57308
  } catch (err) {
57185
57309
  if (err.code === "ENOENT") return [];
57186
57310
  throw err;
@@ -57188,7 +57312,7 @@ var ProjectStore = class {
57188
57312
  const out = [];
57189
57313
  for (const name of entries) {
57190
57314
  try {
57191
- const raw = await import_node_fs48.promises.readFile(this.metaPath(name), "utf8");
57315
+ const raw = await import_node_fs49.promises.readFile(this.metaPath(name), "utf8");
57192
57316
  const json = JSON.parse(raw);
57193
57317
  let migrated = false;
57194
57318
  if (typeof json.devCommand !== "string" || json.devCommand.length === 0) {
@@ -57199,7 +57323,7 @@ var ProjectStore = class {
57199
57323
  if (parsed.success) {
57200
57324
  out.push(parsed.data);
57201
57325
  if (migrated) {
57202
- void import_node_fs48.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
57326
+ void import_node_fs49.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
57203
57327
  });
57204
57328
  }
57205
57329
  }
@@ -57243,8 +57367,8 @@ var ProjectStore = class {
57243
57367
  throw new Error(`invalid name "${name}": ${validated.error.message}`);
57244
57368
  }
57245
57369
  const dir = this.projectDir(name);
57246
- await import_node_fs48.promises.mkdir(dir, { recursive: true });
57247
- await import_node_fs48.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
57370
+ await import_node_fs49.promises.mkdir(dir, { recursive: true });
57371
+ await import_node_fs49.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
57248
57372
  return meta;
57249
57373
  }
57250
57374
  /**
@@ -57287,7 +57411,7 @@ var ProjectStore = class {
57287
57411
  }
57288
57412
  async delete(name) {
57289
57413
  const dir = this.projectDir(name);
57290
- await import_node_fs48.promises.rm(dir, { recursive: true, force: true });
57414
+ await import_node_fs49.promises.rm(dir, { recursive: true, force: true });
57291
57415
  }
57292
57416
  async updatePort(name, newPort) {
57293
57417
  if (newPort < PROJECT_PORT_MIN || newPort > PROJECT_PORT_MAX) {
@@ -57303,7 +57427,7 @@ var ProjectStore = class {
57303
57427
  throw new Error(`port ${newPort} already used / \u5DF2\u88AB project "${conflict.name}" \u5360\u7528`);
57304
57428
  }
57305
57429
  const updated = { ...target, port: newPort };
57306
- await import_node_fs48.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
57430
+ await import_node_fs49.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
57307
57431
  return updated;
57308
57432
  }
57309
57433
  /**
@@ -57320,7 +57444,7 @@ var ProjectStore = class {
57320
57444
  if (!validated.success) {
57321
57445
  throw new Error(`invalid prodUrl "${url}": ${validated.error.message}`);
57322
57446
  }
57323
- await import_node_fs48.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
57447
+ await import_node_fs49.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
57324
57448
  return validated.data;
57325
57449
  }
57326
57450
  /**
@@ -57341,7 +57465,7 @@ var ProjectStore = class {
57341
57465
  if (!validated.success) {
57342
57466
  throw new Error(`invalid publishJob: ${validated.error.message}`);
57343
57467
  }
57344
- await import_node_fs48.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
57468
+ await import_node_fs49.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
57345
57469
  return validated.data;
57346
57470
  }
57347
57471
  /** 清掉 .clawd-project.json.publishJob 字段。其他字段保持原样。 */
@@ -57356,7 +57480,7 @@ var ProjectStore = class {
57356
57480
  if (!validated.success) {
57357
57481
  throw new Error(`failed to clear publishJob: ${validated.error.message}`);
57358
57482
  }
57359
- await import_node_fs48.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
57483
+ await import_node_fs49.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
57360
57484
  return validated.data;
57361
57485
  }
57362
57486
  };
@@ -57477,8 +57601,8 @@ var PublishJobRegistry = class {
57477
57601
 
57478
57602
  // src/app-builder/publish-job-runner.ts
57479
57603
  var import_node_child_process16 = require("child_process");
57480
- var import_node_fs49 = require("fs");
57481
- var import_node_path56 = require("path");
57604
+ var import_node_fs50 = require("fs");
57605
+ var import_node_path57 = require("path");
57482
57606
 
57483
57607
  // src/app-builder/publish-stage-parser.ts
57484
57608
  var STAGE_RE = /^\s*::stage::(build|deploy|verify)\s*$/;
@@ -57505,19 +57629,19 @@ function tailStderrLines(buf, n) {
57505
57629
  // src/app-builder/publish-job-runner.ts
57506
57630
  async function startPublishJob(deps, args) {
57507
57631
  const { registry: registry2, projectDir } = deps;
57508
- const spawn15 = deps.spawnImpl ?? import_node_child_process16.spawn;
57632
+ const spawn16 = deps.spawnImpl ?? import_node_child_process16.spawn;
57509
57633
  if (registry2.has(args.name)) {
57510
57634
  return { jobId: registry2.get(args.name).jobId, status: "already-publishing" };
57511
57635
  }
57512
57636
  const projDir = projectDir(args.name);
57513
- const logPath = (0, import_node_path56.join)(projDir, ".publish.log");
57637
+ const logPath = (0, import_node_path57.join)(projDir, ".publish.log");
57514
57638
  let logStream = null;
57515
57639
  try {
57516
- logStream = (0, import_node_fs49.createWriteStream)(logPath, { flags: "w" });
57640
+ logStream = (0, import_node_fs50.createWriteStream)(logPath, { flags: "w" });
57517
57641
  } catch {
57518
57642
  logStream = null;
57519
57643
  }
57520
- const child = spawn15("bash", [args.scriptPath, projDir, args.personaRoot ?? ""], {
57644
+ const child = spawn16("bash", [args.scriptPath, projDir, args.personaRoot ?? ""], {
57521
57645
  cwd: projDir,
57522
57646
  env: process.env,
57523
57647
  stdio: ["ignore", "pipe", "pipe"]
@@ -57770,8 +57894,8 @@ async function recoverInterruptedJobs(deps) {
57770
57894
 
57771
57895
  // src/handlers/app-builder.ts
57772
57896
  init_protocol();
57773
- var import_node_path57 = require("path");
57774
- var import_node_fs50 = require("fs");
57897
+ var import_node_path58 = require("path");
57898
+ var import_node_fs51 = require("fs");
57775
57899
  var APP_BUILDER_PERSONAS = ["persona-app-builder", "persona-dataclaw-builder"];
57776
57900
  var DEV_SERVER_READY_TIMEOUT_MS = 3e4;
57777
57901
  async function recoverInterruptedPublishJobs(store, logger) {
@@ -57852,7 +57976,7 @@ function buildAppBuilderHandlers(deps) {
57852
57976
  async function listAllUsersProjects() {
57853
57977
  if (!deps.usersRoot || !deps.getStore) return [];
57854
57978
  const getStore = deps.getStore;
57855
- const userIds = await import_node_fs50.promises.readdir(deps.usersRoot).catch(() => []);
57979
+ const userIds = await import_node_fs51.promises.readdir(deps.usersRoot).catch(() => []);
57856
57980
  const perUser = await Promise.all(
57857
57981
  userIds.map((uid) => getStore(uid).list().catch(() => []))
57858
57982
  );
@@ -57928,8 +58052,8 @@ function buildAppBuilderHandlers(deps) {
57928
58052
  const project = await userStore.create(f.name, reservedPorts);
57929
58053
  try {
57930
58054
  const personaRoot = deps.resolvePersonaRoot ? deps.resolvePersonaRoot(session.ownerPersonaId ?? "") : deps.personaRoot;
57931
- const templateSrcDir = (0, import_node_path57.join)(personaRoot, "extension-kit", "examples", DEFAULT_TEMPLATE);
57932
- const scaffoldScript = (0, import_node_path57.join)(deps.deployKitRoot, "scripts", "new-extension.sh");
58055
+ const templateSrcDir = (0, import_node_path58.join)(personaRoot, "extension-kit", "examples", DEFAULT_TEMPLATE);
58056
+ const scaffoldScript = (0, import_node_path58.join)(deps.deployKitRoot, "scripts", "new-extension.sh");
57933
58057
  const scaffoldResult = await userStore.scaffold(project.name, templateSrcDir, scaffoldScript);
57934
58058
  deps.logger?.info("app-builder.scaffold.done", {
57935
58059
  name: project.name,
@@ -58150,7 +58274,7 @@ function buildAppBuilderHandlers(deps) {
58150
58274
  await userStore.clearPublishJob(args.name);
58151
58275
  }
58152
58276
  const personaRoot = deps.resolvePersonaRoot ? deps.resolvePersonaRoot(boundSession.ownerPersonaId ?? "") : deps.personaRoot;
58153
- const scriptPath = (0, import_node_path57.join)(deps.deployKitRoot, "scripts", "publish.sh");
58277
+ const scriptPath = (0, import_node_path58.join)(deps.deployKitRoot, "scripts", "publish.sh");
58154
58278
  deps.logger?.info("app-builder.publish.start", {
58155
58279
  name: args.name,
58156
58280
  sessionId: boundSession.sessionId,
@@ -58326,7 +58450,7 @@ function buildVisitorHandlers(deps) {
58326
58450
 
58327
58451
  // src/extension/registry.ts
58328
58452
  var import_promises9 = __toESM(require("fs/promises"), 1);
58329
- var import_node_path58 = __toESM(require("path"), 1);
58453
+ var import_node_path59 = __toESM(require("path"), 1);
58330
58454
  async function loadAll(root) {
58331
58455
  let entries;
58332
58456
  try {
@@ -58339,13 +58463,13 @@ async function loadAll(root) {
58339
58463
  for (const ent of entries) {
58340
58464
  if (!ent.isDirectory()) continue;
58341
58465
  if (ent.name.startsWith(".")) continue;
58342
- records.push(await loadOne(import_node_path58.default.join(root, ent.name), ent.name));
58466
+ records.push(await loadOne(import_node_path59.default.join(root, ent.name), ent.name));
58343
58467
  }
58344
58468
  records.sort((a, b2) => a.extId < b2.extId ? -1 : a.extId > b2.extId ? 1 : 0);
58345
58469
  return records;
58346
58470
  }
58347
58471
  async function loadOne(dir, dirName) {
58348
- const manifestPath = import_node_path58.default.join(dir, "manifest.json");
58472
+ const manifestPath = import_node_path59.default.join(dir, "manifest.json");
58349
58473
  let raw;
58350
58474
  try {
58351
58475
  raw = await import_promises9.default.readFile(manifestPath, "utf8");
@@ -58390,7 +58514,7 @@ async function loadOne(dir, dirName) {
58390
58514
 
58391
58515
  // src/extension/uninstall.ts
58392
58516
  var import_promises10 = __toESM(require("fs/promises"), 1);
58393
- var import_node_path59 = __toESM(require("path"), 1);
58517
+ var import_node_path60 = __toESM(require("path"), 1);
58394
58518
  var UninstallError = class extends Error {
58395
58519
  constructor(code, message) {
58396
58520
  super(message);
@@ -58399,7 +58523,7 @@ var UninstallError = class extends Error {
58399
58523
  code;
58400
58524
  };
58401
58525
  async function uninstall(deps) {
58402
- const dir = import_node_path59.default.join(deps.root, deps.extId);
58526
+ const dir = import_node_path60.default.join(deps.root, deps.extId);
58403
58527
  try {
58404
58528
  await import_promises10.default.access(dir);
58405
58529
  } catch {
@@ -58411,6 +58535,131 @@ async function uninstall(deps) {
58411
58535
 
58412
58536
  // src/handlers/index.ts
58413
58537
  var import_node_crypto18 = require("crypto");
58538
+
58539
+ // src/handlers/peer-exec.ts
58540
+ init_protocol();
58541
+
58542
+ // src/peer-ops/run-command.ts
58543
+ var import_node_child_process17 = require("child_process");
58544
+ var import_node_os21 = __toESM(require("os"), 1);
58545
+ var DEFAULT_TIMEOUT_MS5 = 3e4;
58546
+ var MAX_TIMEOUT_MS = 3e5;
58547
+ var MAX_OUTPUT_BYTES = 1048576;
58548
+ function runCommand(command, opts = {}) {
58549
+ const timeoutMs = Math.min(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS5, MAX_TIMEOUT_MS);
58550
+ return new Promise((resolve6) => {
58551
+ const child = (0, import_node_child_process17.spawn)("bash", ["-c", command], { cwd: import_node_os21.default.homedir() });
58552
+ const out = [];
58553
+ const err = [];
58554
+ let outBytes = 0;
58555
+ let errBytes = 0;
58556
+ let outTrunc = false;
58557
+ let errTrunc = false;
58558
+ let timedOut = false;
58559
+ const capture = (chunk, buf, which) => {
58560
+ const used = which === "out" ? outBytes : errBytes;
58561
+ const room = MAX_OUTPUT_BYTES - used;
58562
+ if (room <= 0) {
58563
+ if (which === "out") outTrunc = true;
58564
+ else errTrunc = true;
58565
+ return;
58566
+ }
58567
+ if (chunk.length > room) {
58568
+ buf.push(chunk.subarray(0, room));
58569
+ if (which === "out") {
58570
+ outBytes += room;
58571
+ outTrunc = true;
58572
+ } else {
58573
+ errBytes += room;
58574
+ errTrunc = true;
58575
+ }
58576
+ } else {
58577
+ buf.push(chunk);
58578
+ if (which === "out") outBytes += chunk.length;
58579
+ else errBytes += chunk.length;
58580
+ }
58581
+ };
58582
+ child.stdout.on("data", (c) => capture(c, out, "out"));
58583
+ child.stderr.on("data", (c) => capture(c, err, "err"));
58584
+ const timer = setTimeout(() => {
58585
+ timedOut = true;
58586
+ child.kill("SIGTERM");
58587
+ setTimeout(() => child.kill("SIGKILL"), 2e3).unref();
58588
+ }, timeoutMs);
58589
+ child.on("close", (code) => {
58590
+ clearTimeout(timer);
58591
+ resolve6({
58592
+ stdout: Buffer.concat(out).toString("utf8"),
58593
+ stderr: Buffer.concat(err).toString("utf8"),
58594
+ exitCode: timedOut ? null : code,
58595
+ timedOut,
58596
+ stdoutTruncated: outTrunc,
58597
+ stderrTruncated: errTrunc
58598
+ });
58599
+ });
58600
+ child.on("error", () => {
58601
+ clearTimeout(timer);
58602
+ resolve6({
58603
+ stdout: Buffer.concat(out).toString("utf8"),
58604
+ stderr: Buffer.concat(err).toString("utf8"),
58605
+ exitCode: null,
58606
+ timedOut,
58607
+ stdoutTruncated: outTrunc,
58608
+ stderrTruncated: errTrunc
58609
+ });
58610
+ });
58611
+ });
58612
+ }
58613
+
58614
+ // src/handlers/peer-exec.ts
58615
+ function buildPeerExecHandlers(deps) {
58616
+ const run = async (frame, _client, ctx) => {
58617
+ const { type: _t, requestId: _r, sessionId: _s, ...rest } = frame;
58618
+ const args = PeerExecRunArgsSchema.parse(rest);
58619
+ if (args.peerDeviceId) {
58620
+ if (ctx?.principal.kind === "guest") {
58621
+ throw new ClawdError(
58622
+ ERROR_CODES.UNAUTHORIZED,
58623
+ "UNAUTHORIZED: cross-device exec cannot be initiated by a guest"
58624
+ );
58625
+ }
58626
+ const contact2 = deps.store.get(args.peerDeviceId);
58627
+ if (!contact2) {
58628
+ throw new ClawdError(
58629
+ ERROR_CODES.CONTACT_NOT_FOUND,
58630
+ `CONTACT_NOT_FOUND: ${args.peerDeviceId} not in store`
58631
+ );
58632
+ }
58633
+ const outcome = await deps.forwardToPeer({
58634
+ contact: { remoteUrl: contact2.remoteUrl, connectToken: contact2.connectToken },
58635
+ command: args.command,
58636
+ ...args.timeoutMs ? { timeoutMs: args.timeoutMs } : {}
58637
+ });
58638
+ return { response: outcome };
58639
+ }
58640
+ const callerId = ensureGuest(ctx);
58641
+ const contact = deps.store.get(callerId);
58642
+ if (!contact?.remoteAccessAllowed) {
58643
+ throw new ClawdError(
58644
+ ERROR_CODES.UNAUTHORIZED,
58645
+ `UNAUTHORIZED: ${callerId} not authorized for remote access`
58646
+ );
58647
+ }
58648
+ const result = await runCommand(args.command, args.timeoutMs ? { timeoutMs: args.timeoutMs } : {});
58649
+ return {
58650
+ response: { type: "peerExec:run:ok", ...result }
58651
+ };
58652
+ };
58653
+ return { "peerExec:run": run };
58654
+ }
58655
+ function ensureGuest(ctx) {
58656
+ if (!ctx || ctx.principal.kind !== "guest") {
58657
+ throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "UNAUTHORIZED: peerExec:run requires guest ctx");
58658
+ }
58659
+ return ctx.principal.id;
58660
+ }
58661
+
58662
+ // src/handlers/index.ts
58414
58663
  function buildMethodHandlers(deps) {
58415
58664
  return {
58416
58665
  ...buildSessionHandlers({
@@ -58460,6 +58709,12 @@ function buildMethodHandlers(deps) {
58460
58709
  sshdDir: deps.sshdDir,
58461
58710
  sshLog: deps.contactSshLog
58462
58711
  }),
58712
+ // 跨设备 exec(spec 2026-07-16):A 侧带 peerDeviceId → forwardPeerExecToPeer;
58713
+ // B 侧无 peerDeviceId → 本地 runCommand(handler 判 remoteAccessAllowed)。
58714
+ ...buildPeerExecHandlers({
58715
+ store: deps.contactStore,
58716
+ forwardToPeer: (a) => forwardPeerExecToPeer(a)
58717
+ }),
58463
58718
  whoami: buildWhoamiHandler({
58464
58719
  ownerDisplayName: deps.ownerDisplayName,
58465
58720
  ownerPrincipalId: deps.ownerPrincipalId,
@@ -58511,7 +58766,7 @@ function buildMethodHandlers(deps) {
58511
58766
  }
58512
58767
 
58513
58768
  // src/app-builder/dev-server-supervisor.ts
58514
- var import_node_child_process17 = require("child_process");
58769
+ var import_node_child_process18 = require("child_process");
58515
58770
  var import_node_events2 = require("events");
58516
58771
  var DEFAULT_READY_PATTERN = /Local:\s+https?:\/\/|Nest application successfully started|server listening on/i;
58517
58772
  var DevServerSupervisor = class extends import_node_events2.EventEmitter {
@@ -58548,7 +58803,7 @@ var DevServerSupervisor = class extends import_node_events2.EventEmitter {
58548
58803
  tunnelHost: args.tunnelHost,
58549
58804
  devCommand: cmd
58550
58805
  });
58551
- const child = (0, import_node_child_process17.spawn)("sh", ["-c", cmd], {
58806
+ const child = (0, import_node_child_process18.spawn)("sh", ["-c", cmd], {
58552
58807
  cwd: args.cwd,
58553
58808
  env,
58554
58809
  stdio: "pipe",
@@ -58800,9 +59055,9 @@ var METHOD_GRANT_MAP = {
58800
59055
  "contact:list": ADMIN_ANY,
58801
59056
  "contact:pin": ADMIN_ANY,
58802
59057
  "contact:remove": ADMIN_ANY,
58803
- // contact:setSshAccess (owner UI 配 SSH 授权):ADMIN_ANY
59058
+ // contact:setRemoteAccess (owner UI 配 SSH 授权):ADMIN_ANY
58804
59059
  // (对齐 inbox:postMessage 的"能连上=有 auth,业务在 handler 校"模式)
58805
- "contact:setSshAccess": ADMIN_ANY,
59060
+ "contact:setRemoteAccess": ADMIN_ANY,
58806
59061
  // ---- visitor:* (访客名单,owner-only) ----
58807
59062
  // owner 看完整访客名单(含没开会话的);guest 不可调(handler 内再 assertOwner 兜底)。
58808
59063
  "visitor:list": ADMIN_ANY,
@@ -58881,6 +59136,9 @@ var METHOD_GRANT_MAP = {
58881
59136
  // run handler 内做 persona.public + assertGrant(send) 双重校验(guest grants 是 wildcard,
58882
59137
  // 单 assertGrant 不够)。本地 cc 仍走 /api/rpc(EXPOSED_RPC_METHODS),不经此表。
58883
59138
  "personaDispatch:run": CAPABILITY_SCOPED,
59139
+ // 跨设备 exec(spec 2026-07-16):public——"能连上=有 auth",业务在 handler 校
59140
+ // (ensureGuest + remoteAccessAllowed),跟 inbox:postMessage 同模式。
59141
+ "peerExec:run": { kind: "public" },
58884
59142
  // 2026-05-26 修 clawd people TUI 渲染 mix 串扰回归(参考 f1133ff0 原 owner 路径
58885
59143
  // snapshot/buffer/flush 修复):之前三件套 ADMIN_ANY 把 guest 拦在 dispatcher 401,
58886
59144
  // 导致 XtermPanel 重挂载拉 snapshot 在 guest 路径不生效,老的字节流断裂串扰回来。
@@ -58987,8 +59245,8 @@ async function dispatchRpc(method, frame, client, ctx, deps) {
58987
59245
  }
58988
59246
 
58989
59247
  // src/extension/runtime.ts
58990
- var import_node_child_process18 = require("child_process");
58991
- var import_node_path60 = __toESM(require("path"), 1);
59248
+ var import_node_child_process19 = require("child_process");
59249
+ var import_node_path61 = __toESM(require("path"), 1);
58992
59250
  var import_promises11 = require("timers/promises");
58993
59251
 
58994
59252
  // src/extension/port-allocator.ts
@@ -59089,13 +59347,13 @@ var Runtime = class {
59089
59347
  /\$CLAWOS_EXT_PORT/g,
59090
59348
  String(port)
59091
59349
  );
59092
- const dir = import_node_path60.default.join(this.root, extId);
59350
+ const dir = import_node_path61.default.join(this.root, extId);
59093
59351
  const env = {
59094
59352
  ...process.env,
59095
59353
  CLAWOS_EXT_PORT: String(port),
59096
59354
  CLAWOS_EXT_ID: extId
59097
59355
  };
59098
- const child = (0, import_node_child_process18.spawn)("sh", ["-c", cmd], {
59356
+ const child = (0, import_node_child_process19.spawn)("sh", ["-c", cmd], {
59099
59357
  cwd: dir,
59100
59358
  env,
59101
59359
  stdio: ["ignore", "pipe", "pipe"],
@@ -59201,7 +59459,7 @@ ${handle.stderrTail}`
59201
59459
 
59202
59460
  // src/extension/published-channels.ts
59203
59461
  var import_promises12 = __toESM(require("fs/promises"), 1);
59204
- var import_node_path61 = __toESM(require("path"), 1);
59462
+ var import_node_path62 = __toESM(require("path"), 1);
59205
59463
  init_zod();
59206
59464
  var PublishedChannelsError = class extends Error {
59207
59465
  constructor(code, message) {
@@ -59300,7 +59558,7 @@ var PublishedChannelStore = class {
59300
59558
  )
59301
59559
  };
59302
59560
  const tmp = `${this.filePath}.tmp`;
59303
- await import_promises12.default.mkdir(import_node_path61.default.dirname(this.filePath), { recursive: true });
59561
+ await import_promises12.default.mkdir(import_node_path62.default.dirname(this.filePath), { recursive: true });
59304
59562
  await import_promises12.default.writeFile(tmp, JSON.stringify(data, null, 2), { mode: 384 });
59305
59563
  await import_promises12.default.rename(tmp, this.filePath);
59306
59564
  }
@@ -59308,7 +59566,7 @@ var PublishedChannelStore = class {
59308
59566
 
59309
59567
  // src/extension/bundle-cache.ts
59310
59568
  var import_promises13 = __toESM(require("fs/promises"), 1);
59311
- var import_node_path62 = __toESM(require("path"), 1);
59569
+ var import_node_path63 = __toESM(require("path"), 1);
59312
59570
  var BundleCache = class {
59313
59571
  constructor(rootDir) {
59314
59572
  this.rootDir = rootDir;
@@ -59317,14 +59575,14 @@ var BundleCache = class {
59317
59575
  /** Atomic write: stage tmp → rename. Caller passes the hex sha256. */
59318
59576
  async write(snapshotHash, buffer) {
59319
59577
  await import_promises13.default.mkdir(this.rootDir, { recursive: true });
59320
- const file = import_node_path62.default.join(this.rootDir, `${snapshotHash}.zip`);
59578
+ const file = import_node_path63.default.join(this.rootDir, `${snapshotHash}.zip`);
59321
59579
  const tmp = `${file}.tmp`;
59322
59580
  await import_promises13.default.writeFile(tmp, buffer, { mode: 384 });
59323
59581
  await import_promises13.default.rename(tmp, file);
59324
59582
  }
59325
59583
  /** Returns the bundle bytes, or null when the file doesn't exist. */
59326
59584
  async read(snapshotHash) {
59327
- const file = import_node_path62.default.join(this.rootDir, `${snapshotHash}.zip`);
59585
+ const file = import_node_path63.default.join(this.rootDir, `${snapshotHash}.zip`);
59328
59586
  try {
59329
59587
  return await import_promises13.default.readFile(file);
59330
59588
  } catch (e) {
@@ -59334,7 +59592,7 @@ var BundleCache = class {
59334
59592
  }
59335
59593
  /** Idempotent — missing file is not an error. */
59336
59594
  async delete(snapshotHash) {
59337
- const file = import_node_path62.default.join(this.rootDir, `${snapshotHash}.zip`);
59595
+ const file = import_node_path63.default.join(this.rootDir, `${snapshotHash}.zip`);
59338
59596
  await import_promises13.default.rm(file, { force: true });
59339
59597
  }
59340
59598
  };
@@ -59343,7 +59601,7 @@ var BundleCache = class {
59343
59601
  var import_meta6 = {};
59344
59602
  function currentOsUser() {
59345
59603
  try {
59346
- return import_node_os21.default.userInfo().username;
59604
+ return import_node_os22.default.userInfo().username;
59347
59605
  } catch {
59348
59606
  return "";
59349
59607
  }
@@ -59362,26 +59620,26 @@ async function startDaemon(config) {
59362
59620
  },
59363
59621
  source: "daemon",
59364
59622
  sampling: logShippingCfg.sampling,
59365
- homeDir: import_node_os21.default.homedir()
59623
+ homeDir: import_node_os22.default.homedir()
59366
59624
  });
59367
- const logDir = import_node_path63.default.join(config.dataDir, "log");
59368
- import_node_fs51.default.mkdirSync(logDir, { recursive: true });
59625
+ const logDir = import_node_path64.default.join(config.dataDir, "log");
59626
+ import_node_fs52.default.mkdirSync(logDir, { recursive: true });
59369
59627
  const logger = createLogger({
59370
59628
  level: config.logLevel,
59371
- file: import_node_path63.default.join(logDir, "clawd.log"),
59629
+ file: import_node_path64.default.join(logDir, "clawd.log"),
59372
59630
  logClient
59373
59631
  });
59374
59632
  logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
59375
59633
  const screenIdleProbeLogger = createFileOnlyLogger({
59376
- file: import_node_path63.default.join(logDir, "screen-idle-probe.log"),
59634
+ file: import_node_path64.default.join(logDir, "screen-idle-probe.log"),
59377
59635
  level: "debug"
59378
59636
  });
59379
59637
  logger.info("screen-idle probe logger enabled", {
59380
- file: import_node_path63.default.join(logDir, "screen-idle-probe.log")
59638
+ file: import_node_path64.default.join(logDir, "screen-idle-probe.log")
59381
59639
  });
59382
59640
  const CAP_TARGETS = [
59383
- import_node_path63.default.join(logDir, "clawd.log"),
59384
- import_node_path63.default.join(logDir, "screen-idle-probe.log")
59641
+ import_node_path64.default.join(logDir, "clawd.log"),
59642
+ import_node_path64.default.join(logDir, "screen-idle-probe.log")
59385
59643
  ];
59386
59644
  const LOG_CAP_MAX_BYTES = 10 * 1024 * 1024;
59387
59645
  const LOG_CAP_KEEP_BYTES = 5 * 1024 * 1024;
@@ -59492,7 +59750,7 @@ async function startDaemon(config) {
59492
59750
  // 立即重建 authorized_keys(重新握手 = 迁移路径)。见 …#建联交换清单
59493
59751
  sshPublicKey: selfSshPublicKey,
59494
59752
  osUser: selfOsUser,
59495
- rebuildAuthorizedKeys: () => rebuildAuthorizedKeys(contactStore, import_node_path63.default.join(config.dataDir, "sshd")),
59753
+ rebuildAuthorizedKeys: () => rebuildAuthorizedKeys(contactStore, import_node_path64.default.join(config.dataDir, "sshd")),
59496
59754
  // 决策 #16 换票补全:用 owner 当前 jwt 换 aud=对方设备 的票(本机主动连对方用)。
59497
59755
  // 现读 ownerIdentityStore(热切换后用新身份);失败 → null → 保持空票。
59498
59756
  exchangeToken: async (targetDeviceId) => {
@@ -59540,8 +59798,8 @@ async function startDaemon(config) {
59540
59798
  const agents = new AgentsScanner();
59541
59799
  const history = new ClaudeHistoryReader();
59542
59800
  let transport = null;
59543
- const personaStore = new PersonaStore(import_node_path63.default.join(config.dataDir, "personas"));
59544
- const usersRoot = import_node_path63.default.join(config.dataDir, "users");
59801
+ const personaStore = new PersonaStore(import_node_path64.default.join(config.dataDir, "personas"));
59802
+ const usersRoot = import_node_path64.default.join(config.dataDir, "users");
59545
59803
  const defaultsRoot = findDefaultsRoot(logger);
59546
59804
  if (defaultsRoot) {
59547
59805
  seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
@@ -59561,17 +59819,17 @@ async function startDaemon(config) {
59561
59819
  migrateCodexSandbox({ store: personaStore, logger });
59562
59820
  const groupFileStore = new GroupFileStore({ dataDir: config.dataDir, logger });
59563
59821
  const personaDispatchManager = new PersonaDispatchManager({ genId: () => v4_default() });
59564
- const here = typeof __dirname === "string" ? __dirname : import_node_path63.default.dirname((0, import_node_url4.fileURLToPath)(import_meta6.url));
59822
+ const here = typeof __dirname === "string" ? __dirname : import_node_path64.default.dirname((0, import_node_url4.fileURLToPath)(import_meta6.url));
59565
59823
  const mcpConfigs = [];
59566
59824
  const dispatchServerCandidates = [
59567
- import_node_path63.default.join(here, "dispatch", "mcp-server.cjs"),
59825
+ import_node_path64.default.join(here, "dispatch", "mcp-server.cjs"),
59568
59826
  // 生产 dist/index → dist/dispatch/mcp-server.cjs
59569
- import_node_path63.default.join(here, "..", "dist", "dispatch", "mcp-server.cjs")
59827
+ import_node_path64.default.join(here, "..", "dist", "dispatch", "mcp-server.cjs")
59570
59828
  // dev tsx src/index → ../dist/dispatch/mcp-server.cjs
59571
59829
  ];
59572
- const dispatchServerScriptPath = dispatchServerCandidates.find((p2) => import_node_fs51.default.existsSync(p2));
59830
+ const dispatchServerScriptPath = dispatchServerCandidates.find((p2) => import_node_fs52.default.existsSync(p2));
59573
59831
  if (dispatchServerScriptPath) {
59574
- const dispatchLogPath = import_node_path63.default.join(logDir, "dispatch-mcp-server.log");
59832
+ const dispatchLogPath = import_node_path64.default.join(logDir, "dispatch-mcp-server.log");
59575
59833
  const dispatchCfgPath = writeDispatchMcpConfig({
59576
59834
  dataDir: config.dataDir,
59577
59835
  serverScriptPath: dispatchServerScriptPath,
@@ -59589,12 +59847,12 @@ async function startDaemon(config) {
59589
59847
  });
59590
59848
  }
59591
59849
  const ticketServerCandidates = [
59592
- import_node_path63.default.join(here, "ticket", "mcp-server.cjs"),
59593
- import_node_path63.default.join(here, "..", "dist", "ticket", "mcp-server.cjs")
59850
+ import_node_path64.default.join(here, "ticket", "mcp-server.cjs"),
59851
+ import_node_path64.default.join(here, "..", "dist", "ticket", "mcp-server.cjs")
59594
59852
  ];
59595
- const ticketServerScriptPath = ticketServerCandidates.find((p2) => import_node_fs51.default.existsSync(p2));
59853
+ const ticketServerScriptPath = ticketServerCandidates.find((p2) => import_node_fs52.default.existsSync(p2));
59596
59854
  if (ticketServerScriptPath) {
59597
- const ticketLogPath = import_node_path63.default.join(logDir, "ticket-mcp-server.log");
59855
+ const ticketLogPath = import_node_path64.default.join(logDir, "ticket-mcp-server.log");
59598
59856
  const ticketCfgPath = writeTicketMcpConfig({
59599
59857
  dataDir: config.dataDir,
59600
59858
  serverScriptPath: ticketServerScriptPath,
@@ -59614,12 +59872,12 @@ async function startDaemon(config) {
59614
59872
  });
59615
59873
  }
59616
59874
  const shiftServerCandidates = [
59617
- import_node_path63.default.join(here, "shift", "mcp-server.cjs"),
59618
- import_node_path63.default.join(here, "..", "dist", "shift", "mcp-server.cjs")
59875
+ import_node_path64.default.join(here, "shift", "mcp-server.cjs"),
59876
+ import_node_path64.default.join(here, "..", "dist", "shift", "mcp-server.cjs")
59619
59877
  ];
59620
- const shiftServerScriptPath = shiftServerCandidates.find((p2) => import_node_fs51.default.existsSync(p2));
59878
+ const shiftServerScriptPath = shiftServerCandidates.find((p2) => import_node_fs52.default.existsSync(p2));
59621
59879
  if (shiftServerScriptPath) {
59622
- const shiftLogPath = import_node_path63.default.join(logDir, "shift-mcp-server.log");
59880
+ const shiftLogPath = import_node_path64.default.join(logDir, "shift-mcp-server.log");
59623
59881
  const shiftCfgPath = await writeShiftMcpConfig({
59624
59882
  dataDir: config.dataDir,
59625
59883
  serverScriptPath: shiftServerScriptPath,
@@ -59638,12 +59896,12 @@ async function startDaemon(config) {
59638
59896
  );
59639
59897
  }
59640
59898
  const inboxServerCandidates = [
59641
- import_node_path63.default.join(here, "inbox", "mcp-server.cjs"),
59642
- import_node_path63.default.join(here, "..", "dist", "inbox", "mcp-server.cjs")
59899
+ import_node_path64.default.join(here, "inbox", "mcp-server.cjs"),
59900
+ import_node_path64.default.join(here, "..", "dist", "inbox", "mcp-server.cjs")
59643
59901
  ];
59644
- const inboxServerScriptPath = inboxServerCandidates.find((p2) => import_node_fs51.default.existsSync(p2));
59902
+ const inboxServerScriptPath = inboxServerCandidates.find((p2) => import_node_fs52.default.existsSync(p2));
59645
59903
  if (inboxServerScriptPath) {
59646
- const inboxLogPath = import_node_path63.default.join(logDir, "inbox-mcp-server.log");
59904
+ const inboxLogPath = import_node_path64.default.join(logDir, "inbox-mcp-server.log");
59647
59905
  const inboxCfgPath = await writeInboxMcpConfig({
59648
59906
  dataDir: config.dataDir,
59649
59907
  serverScriptPath: inboxServerScriptPath,
@@ -59661,8 +59919,29 @@ async function startDaemon(config) {
59661
59919
  { tried: inboxServerCandidates }
59662
59920
  );
59663
59921
  }
59922
+ const peerOpsServerCandidates = [
59923
+ import_node_path64.default.join(here, "peer-ops", "mcp-server.cjs"),
59924
+ import_node_path64.default.join(here, "..", "dist", "peer-ops", "mcp-server.cjs")
59925
+ ];
59926
+ const peerOpsServerScriptPath = peerOpsServerCandidates.find((p2) => import_node_fs52.default.existsSync(p2));
59927
+ if (peerOpsServerScriptPath) {
59928
+ const peerOpsCfgPath = writePeerOpsMcpConfig({
59929
+ dataDir: config.dataDir,
59930
+ serverScriptPath: peerOpsServerScriptPath
59931
+ });
59932
+ mcpConfigs.push({ id: "peer-ops", path: peerOpsCfgPath });
59933
+ logger.info("peer-ops.mcp.json written", {
59934
+ path: peerOpsCfgPath,
59935
+ server: peerOpsServerScriptPath
59936
+ });
59937
+ } else {
59938
+ logger.warn(
59939
+ "peer-ops-mcp-server.cjs not found; cross-device exec MCP tool disabled (dev tsx \u6A21\u5F0F\u4E0B\u9700\u5148\u8DD1\u8FC7 build)",
59940
+ { tried: peerOpsServerCandidates }
59941
+ );
59942
+ }
59664
59943
  const shiftStore = createShiftStore({
59665
- filePath: import_node_path63.default.join(config.dataDir, "shift.json"),
59944
+ filePath: import_node_path64.default.join(config.dataDir, "shift.json"),
59666
59945
  ownerIdProvider: () => ownerPrincipalId,
59667
59946
  now: () => Date.now()
59668
59947
  });
@@ -59684,7 +59963,7 @@ async function startDaemon(config) {
59684
59963
  getAdapter,
59685
59964
  historyReader: history,
59686
59965
  dataDir: config.dataDir,
59687
- personaRoot: import_node_path63.default.join(config.dataDir, "personas"),
59966
+ personaRoot: import_node_path64.default.join(config.dataDir, "personas"),
59688
59967
  usersRoot,
59689
59968
  personaStore,
59690
59969
  ownerDisplayName,
@@ -59722,10 +60001,10 @@ async function startDaemon(config) {
59722
60001
  // 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
59723
60002
  attachmentGroup: {
59724
60003
  onFileEdit: (input) => {
59725
- const absPath = import_node_path63.default.isAbsolute(input.relPath) ? input.relPath : import_node_path63.default.join(input.cwd, input.relPath);
60004
+ const absPath = import_node_path64.default.isAbsolute(input.relPath) ? input.relPath : import_node_path64.default.join(input.cwd, input.relPath);
59726
60005
  let size = 0;
59727
60006
  try {
59728
- size = import_node_fs51.default.statSync(absPath).size;
60007
+ size = import_node_fs52.default.statSync(absPath).size;
59729
60008
  } catch (err) {
59730
60009
  logger.warn("attachment.onFileEdit stat failed", {
59731
60010
  sessionId: input.sessionId,
@@ -59962,11 +60241,11 @@ async function startDaemon(config) {
59962
60241
  // 'persona/<pid>/owner',default 走 'default'。
59963
60242
  getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
59964
60243
  // guest path guard:candidate 必须在 personaRoot 子树或调用者自己的 user-dir 下
59965
- personaRoot: import_node_path63.default.join(config.dataDir, "personas"),
60244
+ personaRoot: import_node_path64.default.join(config.dataDir, "personas"),
59966
60245
  usersRoot
59967
60246
  },
59968
60247
  // workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
59969
- personaRoot: import_node_path63.default.join(config.dataDir, "personas"),
60248
+ personaRoot: import_node_path64.default.join(config.dataDir, "personas"),
59970
60249
  // v2 多人 persona 隔离:handler 派生 guest user-dir 放行
59971
60250
  usersRoot,
59972
60251
  // capability:list / delete handler 依赖
@@ -59995,7 +60274,7 @@ async function startDaemon(config) {
59995
60274
  contactStore,
59996
60275
  // <dataDir>/sshd 绝对路径 —— contact-ssh handlers 用它拼 authorized_keys / keys/ 子路径
59997
60276
  // Task 10 会加 SshdManager 起 sshd;handlers wire 提前挂 sshdDir 让 typecheck 过
59998
- sshdDir: import_node_path63.default.join(config.dataDir, "sshd"),
60277
+ sshdDir: import_node_path64.default.join(config.dataDir, "sshd"),
59999
60278
  contactSshLog: sshLog,
60000
60279
  // inbox:sendDm 用:sessionId → session 出身(复用 attachment 同款 findOwnedSessionScope)
60001
60280
  getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
@@ -60069,7 +60348,7 @@ async function startDaemon(config) {
60069
60348
  selfSshPublicKey: () => readSshIdentityPublicKey(config.dataDir),
60070
60349
  // 建联交换:出站自报本机 OS 账号名。见 doc/glossary/peer-mesh.md#建联交换清单
60071
60350
  selfOsUser: currentOsUser,
60072
- rebuildAuthorizedKeys: () => rebuildAuthorizedKeys(contactStore, import_node_path63.default.join(config.dataDir, "sshd"))
60351
+ rebuildAuthorizedKeys: () => rebuildAuthorizedKeys(contactStore, import_node_path64.default.join(config.dataDir, "sshd"))
60073
60352
  },
60074
60353
  // app-builder
60075
60354
  appBuilderStore,
@@ -60092,11 +60371,11 @@ async function startDaemon(config) {
60092
60371
  // 发布上线脚手架化 (spec 2026-06-03 §5.2):
60093
60372
  // appBuilderPersonaRoot 用于拼 publish.sh 绝对路径(persona-app-builder 安装在
60094
60373
  // dataDir/personas/persona-app-builder 之下,extension-kit/scripts/publish.sh 是相对路径)。
60095
- appBuilderPersonaRoot: import_node_path63.default.join(config.dataDir, "personas", "persona-app-builder"),
60374
+ appBuilderPersonaRoot: import_node_path64.default.join(config.dataDir, "personas", "persona-app-builder"),
60096
60375
  // 共享 deploy-kit 根:scaffold/publish 脚本骨架 + 阿里云凭证单一真源。
60097
- deployKitRoot: import_node_path63.default.join(config.dataDir, "deploy-kit"),
60376
+ deployKitRoot: import_node_path64.default.join(config.dataDir, "deploy-kit"),
60098
60377
  // scaffold/publish 按当前 session 的 persona 解析其安装根,让每个 persona 用自己的模板/注入配置。
60099
- resolvePersonaRoot: (personaId) => import_node_path63.default.join(config.dataDir, "personas", personaId),
60378
+ resolvePersonaRoot: (personaId) => import_node_path64.default.join(config.dataDir, "personas", personaId),
60100
60379
  // 发布上线脚手架化 (spec 2026-06-03 §5.2.2):
60101
60380
  // 复用 SessionManagerDeps.broadcastFrame 同款 dispatch 逻辑 —— runner 调 manager.send
60102
60381
  // 取回 broadcast 帧后逐帧 push 到 transport,跟 manager 自身的 deps 一致。
@@ -60139,8 +60418,8 @@ async function startDaemon(config) {
60139
60418
  }
60140
60419
  let sourceJsonlPath = "(no transcript yet \u2014 operate from the task description alone)";
60141
60420
  if (sourceFile && sourceFile.toolSessionId) {
60142
- sourceJsonlPath = import_node_path63.default.join(
60143
- import_node_os21.default.homedir(),
60421
+ sourceJsonlPath = import_node_path64.default.join(
60422
+ import_node_os22.default.homedir(),
60144
60423
  ".claude",
60145
60424
  "projects",
60146
60425
  cwdToHashDir(sourceFile.cwd),
@@ -60499,8 +60778,8 @@ async function startDaemon(config) {
60499
60778
  const lines = [
60500
60779
  `Tunnel: ${r.url}`,
60501
60780
  ...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
60502
- `Frpc config: ${import_node_path63.default.join(config.dataDir, "frpc.toml")}`,
60503
- `Frpc log: ${import_node_path63.default.join(logDir, "frpc.log")}`
60781
+ `Frpc config: ${import_node_path64.default.join(config.dataDir, "frpc.toml")}`,
60782
+ `Frpc log: ${import_node_path64.default.join(logDir, "frpc.log")}`
60504
60783
  ];
60505
60784
  const width = Math.max(...lines.map((l) => l.length));
60506
60785
  const bar = "\u2550".repeat(width + 4);
@@ -60513,8 +60792,8 @@ ${bar}
60513
60792
 
60514
60793
  `);
60515
60794
  try {
60516
- const connectPath = import_node_path63.default.join(config.dataDir, "connect.txt");
60517
- import_node_fs51.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
60795
+ const connectPath = import_node_path64.default.join(config.dataDir, "connect.txt");
60796
+ import_node_fs52.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
60518
60797
  } catch {
60519
60798
  }
60520
60799
  } catch (err) {
@@ -60550,7 +60829,7 @@ ${bar}
60550
60829
  });
60551
60830
  try {
60552
60831
  await sshdMgr.start();
60553
- rebuildAuthorizedKeys(contactStore, import_node_path63.default.join(config.dataDir, "sshd"));
60832
+ rebuildAuthorizedKeys(contactStore, import_node_path64.default.join(config.dataDir, "sshd"));
60554
60833
  logger.info("sshd: contact-ssh sandbox ready", { port: config.sshdPort });
60555
60834
  } catch (err) {
60556
60835
  logger.warn("sshd start failed; contact SSH grant will not work until fixed", {
@@ -60609,9 +60888,9 @@ ${bar}
60609
60888
  };
60610
60889
  }
60611
60890
  function migrateDropPersonsDir(dataDir) {
60612
- const dir = import_node_path63.default.join(dataDir, "persons");
60891
+ const dir = import_node_path64.default.join(dataDir, "persons");
60613
60892
  try {
60614
- import_node_fs51.default.rmSync(dir, { recursive: true, force: true });
60893
+ import_node_fs52.default.rmSync(dir, { recursive: true, force: true });
60615
60894
  } catch {
60616
60895
  }
60617
60896
  }