@clawos-dev/clawd 0.2.199-beta.400.ba99f40 → 0.2.199

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -158,11 +158,6 @@ var init_methods = __esm({
158
158
  "contact:list",
159
159
  "contact:pin",
160
160
  "contact:remove",
161
- // ---- contact:setSshAccess / contact:sshKey:issue (SSH 反向访问,PR: contact-ssh-sandbox) ----
162
- // owner UI 授权 contact 通过 daemon 专用 sshd 反向 SSH 进本机;guest daemon 用 sshKey:issue 拉 privkey.
163
- // 详见 protocol/src/contact-ssh.ts.
164
- "contact:setSshAccess",
165
- "contact:sshKey:issue",
166
161
  // ---- visitor:* (web 访客 · persona web 分享,spec 2026-06-24-persona-web-share-design) ----
167
162
  // owner-only:列出登录访问过本机 public persona 的 web 访客(零安装、飞书登录、daemon 自签
168
163
  // visitor token)。存储 ~/.clawd/visitors.json(VisitorStore),登录(exchange)即 upsert;
@@ -742,8 +737,8 @@ var init_parseUtil = __esm({
742
737
  init_errors2();
743
738
  init_en();
744
739
  makeIssue = (params) => {
745
- const { data, path: path73, errorMaps, issueData } = params;
746
- const fullPath = [...path73, ...issueData.path || []];
740
+ const { data, path: path68, errorMaps, issueData } = params;
741
+ const fullPath = [...path68, ...issueData.path || []];
747
742
  const fullIssue = {
748
743
  ...issueData,
749
744
  path: fullPath
@@ -1054,11 +1049,11 @@ var init_types = __esm({
1054
1049
  init_parseUtil();
1055
1050
  init_util();
1056
1051
  ParseInputLazyPath = class {
1057
- constructor(parent, value, path73, key) {
1052
+ constructor(parent, value, path68, key) {
1058
1053
  this._cachedPath = [];
1059
1054
  this.parent = parent;
1060
1055
  this.data = value;
1061
- this._path = path73;
1056
+ this._path = path68;
1062
1057
  this._key = key;
1063
1058
  }
1064
1059
  get path() {
@@ -5683,19 +5678,7 @@ var init_contact = __esm({
5683
5678
  * 老 contacts.json 缺此字段 → zod default 补 null(无破坏性升级;不 orphan)。
5684
5679
  * 对齐 SessionFile.pinnedAt 语义(daemon 侧持久化,contact:pin RPC 更新 → contact:pinned push)。
5685
5680
  */
5686
- pinnedAt: external_exports.number().int().nullable().default(null),
5687
- /**
5688
- * SSH 反向访问授权(PR: contact-ssh-sandbox)。owner 视角:允许该 contact 通过 daemon
5689
- * 专用 sshd 反向 SSH 进本机。default=false 保证老 contacts.json 兼容(zod default 补齐)。
5690
- * 授权粒度 per-people (per-contact):借用别人 persona 本来就带信任对方本人的前提;
5691
- * 极端场景(区别对方多个 persona)留 per-persona override 演进空间。
5692
- */
5693
- sshAllowed: external_exports.boolean().default(false),
5694
- /**
5695
- * 授权访问的目录白名单(绝对路径)。空数组 + sshAllowed=true 意味 SSH 通道能拨到但无处可读,
5696
- * jail 会拒绝 shell 启动。由 clawd-ssh-jail 运行时读,生成 sbpl / bwrap policy 白名单。
5697
- */
5698
- exposedDirs: external_exports.array(external_exports.string()).default([])
5681
+ pinnedAt: external_exports.number().int().nullable().default(null)
5699
5682
  }).strict();
5700
5683
  ContactWireSchema = ContactSchema;
5701
5684
  ContactRemoveArgsSchema = external_exports.object({
@@ -5734,40 +5717,6 @@ var init_contact = __esm({
5734
5717
  }
5735
5718
  });
5736
5719
 
5737
- // ../protocol/src/contact-ssh.ts
5738
- var ContactSetSshAccessArgsSchema, ContactSetSshAccessOkSchema, ContactSshKeyIssueArgsSchema, ContactSshKeyIssueOkSchema, ContactSshAccessUpdatedFrameSchema;
5739
- var init_contact_ssh = __esm({
5740
- "../protocol/src/contact-ssh.ts"() {
5741
- "use strict";
5742
- init_zod();
5743
- ContactSetSshAccessArgsSchema = external_exports.object({
5744
- deviceId: external_exports.string().min(1),
5745
- sshAllowed: external_exports.boolean(),
5746
- exposedDirs: external_exports.array(external_exports.string())
5747
- }).strict();
5748
- ContactSetSshAccessOkSchema = external_exports.object({
5749
- type: external_exports.literal("contact:setSshAccess:ok"),
5750
- deviceId: external_exports.string().min(1),
5751
- sshAllowed: external_exports.boolean(),
5752
- exposedDirs: external_exports.array(external_exports.string())
5753
- }).strict();
5754
- ContactSshKeyIssueArgsSchema = external_exports.object({
5755
- deviceId: external_exports.string().min(1)
5756
- }).strict();
5757
- ContactSshKeyIssueOkSchema = external_exports.object({
5758
- type: external_exports.literal("contact:sshKey:issue:ok"),
5759
- privateKeyPem: external_exports.string().min(1),
5760
- publicKeyLine: external_exports.string().min(1)
5761
- }).strict();
5762
- ContactSshAccessUpdatedFrameSchema = external_exports.object({
5763
- type: external_exports.literal("contact:ssh-access-updated"),
5764
- deviceId: external_exports.string().min(1),
5765
- sshAllowed: external_exports.boolean(),
5766
- exposedDirs: external_exports.array(external_exports.string())
5767
- }).strict();
5768
- }
5769
- });
5770
-
5771
5720
  // ../protocol/src/extension.ts
5772
5721
  function isAllowedHostedUrl(raw) {
5773
5722
  let u;
@@ -6169,7 +6118,6 @@ var init_runtime = __esm({
6169
6118
  init_capability();
6170
6119
  init_inbox();
6171
6120
  init_contact();
6172
- init_contact_ssh();
6173
6121
  init_extension();
6174
6122
  init_feishu_auth();
6175
6123
  init_dispatch();
@@ -6449,8 +6397,8 @@ var require_req = __commonJS({
6449
6397
  if (req.originalUrl) {
6450
6398
  _req.url = req.originalUrl;
6451
6399
  } else {
6452
- const path73 = req.path;
6453
- _req.url = typeof path73 === "string" ? path73 : req.url ? req.url.path || req.url : void 0;
6400
+ const path68 = req.path;
6401
+ _req.url = typeof path68 === "string" ? path68 : req.url ? req.url.path || req.url : void 0;
6454
6402
  }
6455
6403
  if (req.query) {
6456
6404
  _req.query = req.query;
@@ -6615,14 +6563,14 @@ var require_redact = __commonJS({
6615
6563
  }
6616
6564
  return obj;
6617
6565
  }
6618
- function parsePath(path73) {
6566
+ function parsePath(path68) {
6619
6567
  const parts = [];
6620
6568
  let current = "";
6621
6569
  let inBrackets = false;
6622
6570
  let inQuotes = false;
6623
6571
  let quoteChar = "";
6624
- for (let i = 0; i < path73.length; i++) {
6625
- const char = path73[i];
6572
+ for (let i = 0; i < path68.length; i++) {
6573
+ const char = path68[i];
6626
6574
  if (!inBrackets && char === ".") {
6627
6575
  if (current) {
6628
6576
  parts.push(current);
@@ -6753,10 +6701,10 @@ var require_redact = __commonJS({
6753
6701
  return current;
6754
6702
  }
6755
6703
  function redactPaths(obj, paths, censor, remove = false) {
6756
- for (const path73 of paths) {
6757
- const parts = parsePath(path73);
6704
+ for (const path68 of paths) {
6705
+ const parts = parsePath(path68);
6758
6706
  if (parts.includes("*")) {
6759
- redactWildcardPath(obj, parts, censor, path73, remove);
6707
+ redactWildcardPath(obj, parts, censor, path68, remove);
6760
6708
  } else {
6761
6709
  if (remove) {
6762
6710
  removeKey(obj, parts);
@@ -6841,8 +6789,8 @@ var require_redact = __commonJS({
6841
6789
  }
6842
6790
  } else {
6843
6791
  if (afterWildcard.includes("*")) {
6844
- const wrappedCensor = typeof censor === "function" ? (value, path73) => {
6845
- const fullPath = [...pathArray.slice(0, pathLength), ...path73];
6792
+ const wrappedCensor = typeof censor === "function" ? (value, path68) => {
6793
+ const fullPath = [...pathArray.slice(0, pathLength), ...path68];
6846
6794
  return censor(value, fullPath);
6847
6795
  } : censor;
6848
6796
  redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
@@ -6877,8 +6825,8 @@ var require_redact = __commonJS({
6877
6825
  return null;
6878
6826
  }
6879
6827
  const pathStructure = /* @__PURE__ */ new Map();
6880
- for (const path73 of pathsToClone) {
6881
- const parts = parsePath(path73);
6828
+ for (const path68 of pathsToClone) {
6829
+ const parts = parsePath(path68);
6882
6830
  let current = pathStructure;
6883
6831
  for (let i = 0; i < parts.length; i++) {
6884
6832
  const part = parts[i];
@@ -6930,24 +6878,24 @@ var require_redact = __commonJS({
6930
6878
  }
6931
6879
  return cloneSelectively(obj, pathStructure);
6932
6880
  }
6933
- function validatePath(path73) {
6934
- if (typeof path73 !== "string") {
6881
+ function validatePath(path68) {
6882
+ if (typeof path68 !== "string") {
6935
6883
  throw new Error("Paths must be (non-empty) strings");
6936
6884
  }
6937
- if (path73 === "") {
6885
+ if (path68 === "") {
6938
6886
  throw new Error("Invalid redaction path ()");
6939
6887
  }
6940
- if (path73.includes("..")) {
6941
- throw new Error(`Invalid redaction path (${path73})`);
6888
+ if (path68.includes("..")) {
6889
+ throw new Error(`Invalid redaction path (${path68})`);
6942
6890
  }
6943
- if (path73.includes(",")) {
6944
- throw new Error(`Invalid redaction path (${path73})`);
6891
+ if (path68.includes(",")) {
6892
+ throw new Error(`Invalid redaction path (${path68})`);
6945
6893
  }
6946
6894
  let bracketCount = 0;
6947
6895
  let inQuotes = false;
6948
6896
  let quoteChar = "";
6949
- for (let i = 0; i < path73.length; i++) {
6950
- const char = path73[i];
6897
+ for (let i = 0; i < path68.length; i++) {
6898
+ const char = path68[i];
6951
6899
  if ((char === '"' || char === "'") && bracketCount > 0) {
6952
6900
  if (!inQuotes) {
6953
6901
  inQuotes = true;
@@ -6961,20 +6909,20 @@ var require_redact = __commonJS({
6961
6909
  } else if (char === "]" && !inQuotes) {
6962
6910
  bracketCount--;
6963
6911
  if (bracketCount < 0) {
6964
- throw new Error(`Invalid redaction path (${path73})`);
6912
+ throw new Error(`Invalid redaction path (${path68})`);
6965
6913
  }
6966
6914
  }
6967
6915
  }
6968
6916
  if (bracketCount !== 0) {
6969
- throw new Error(`Invalid redaction path (${path73})`);
6917
+ throw new Error(`Invalid redaction path (${path68})`);
6970
6918
  }
6971
6919
  }
6972
6920
  function validatePaths(paths) {
6973
6921
  if (!Array.isArray(paths)) {
6974
6922
  throw new TypeError("paths must be an array");
6975
6923
  }
6976
- for (const path73 of paths) {
6977
- validatePath(path73);
6924
+ for (const path68 of paths) {
6925
+ validatePath(path68);
6978
6926
  }
6979
6927
  }
6980
6928
  function slowRedact(options = {}) {
@@ -7142,8 +7090,8 @@ var require_redaction = __commonJS({
7142
7090
  if (shape[k2] === null) {
7143
7091
  o[k2] = (value) => topCensor(value, [k2]);
7144
7092
  } else {
7145
- const wrappedCensor = typeof censor === "function" ? (value, path73) => {
7146
- return censor(value, [k2, ...path73]);
7093
+ const wrappedCensor = typeof censor === "function" ? (value, path68) => {
7094
+ return censor(value, [k2, ...path68]);
7147
7095
  } : censor;
7148
7096
  o[k2] = Redact({
7149
7097
  paths: shape[k2],
@@ -7361,10 +7309,10 @@ var require_atomic_sleep = __commonJS({
7361
7309
  var require_sonic_boom = __commonJS({
7362
7310
  "../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
7363
7311
  "use strict";
7364
- var fs66 = require("fs");
7312
+ var fs61 = require("fs");
7365
7313
  var EventEmitter3 = require("events");
7366
7314
  var inherits = require("util").inherits;
7367
- var path73 = require("path");
7315
+ var path68 = require("path");
7368
7316
  var sleep2 = require_atomic_sleep();
7369
7317
  var assert = require("assert");
7370
7318
  var BUSY_WRITE_TIMEOUT = 100;
@@ -7418,20 +7366,20 @@ var require_sonic_boom = __commonJS({
7418
7366
  const mode = sonic.mode;
7419
7367
  if (sonic.sync) {
7420
7368
  try {
7421
- if (sonic.mkdir) fs66.mkdirSync(path73.dirname(file), { recursive: true });
7422
- const fd = fs66.openSync(file, flags, mode);
7369
+ if (sonic.mkdir) fs61.mkdirSync(path68.dirname(file), { recursive: true });
7370
+ const fd = fs61.openSync(file, flags, mode);
7423
7371
  fileOpened(null, fd);
7424
7372
  } catch (err) {
7425
7373
  fileOpened(err);
7426
7374
  throw err;
7427
7375
  }
7428
7376
  } else if (sonic.mkdir) {
7429
- fs66.mkdir(path73.dirname(file), { recursive: true }, (err) => {
7377
+ fs61.mkdir(path68.dirname(file), { recursive: true }, (err) => {
7430
7378
  if (err) return fileOpened(err);
7431
- fs66.open(file, flags, mode, fileOpened);
7379
+ fs61.open(file, flags, mode, fileOpened);
7432
7380
  });
7433
7381
  } else {
7434
- fs66.open(file, flags, mode, fileOpened);
7382
+ fs61.open(file, flags, mode, fileOpened);
7435
7383
  }
7436
7384
  }
7437
7385
  function SonicBoom(opts) {
@@ -7472,8 +7420,8 @@ var require_sonic_boom = __commonJS({
7472
7420
  this.flush = flushBuffer;
7473
7421
  this.flushSync = flushBufferSync;
7474
7422
  this._actualWrite = actualWriteBuffer;
7475
- fsWriteSync = () => fs66.writeSync(this.fd, this._writingBuf);
7476
- fsWrite = () => fs66.write(this.fd, this._writingBuf, this.release);
7423
+ fsWriteSync = () => fs61.writeSync(this.fd, this._writingBuf);
7424
+ fsWrite = () => fs61.write(this.fd, this._writingBuf, this.release);
7477
7425
  } else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
7478
7426
  this._writingBuf = "";
7479
7427
  this.write = write;
@@ -7482,15 +7430,15 @@ var require_sonic_boom = __commonJS({
7482
7430
  this._actualWrite = actualWrite;
7483
7431
  fsWriteSync = () => {
7484
7432
  if (Buffer.isBuffer(this._writingBuf)) {
7485
- return fs66.writeSync(this.fd, this._writingBuf);
7433
+ return fs61.writeSync(this.fd, this._writingBuf);
7486
7434
  }
7487
- return fs66.writeSync(this.fd, this._writingBuf, "utf8");
7435
+ return fs61.writeSync(this.fd, this._writingBuf, "utf8");
7488
7436
  };
7489
7437
  fsWrite = () => {
7490
7438
  if (Buffer.isBuffer(this._writingBuf)) {
7491
- return fs66.write(this.fd, this._writingBuf, this.release);
7439
+ return fs61.write(this.fd, this._writingBuf, this.release);
7492
7440
  }
7493
- return fs66.write(this.fd, this._writingBuf, "utf8", this.release);
7441
+ return fs61.write(this.fd, this._writingBuf, "utf8", this.release);
7494
7442
  };
7495
7443
  } else {
7496
7444
  throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
@@ -7547,7 +7495,7 @@ var require_sonic_boom = __commonJS({
7547
7495
  }
7548
7496
  }
7549
7497
  if (this._fsync) {
7550
- fs66.fsyncSync(this.fd);
7498
+ fs61.fsyncSync(this.fd);
7551
7499
  }
7552
7500
  const len = this._len;
7553
7501
  if (this._reopening) {
@@ -7661,7 +7609,7 @@ var require_sonic_boom = __commonJS({
7661
7609
  const onDrain = () => {
7662
7610
  if (!this._fsync) {
7663
7611
  try {
7664
- fs66.fsync(this.fd, (err) => {
7612
+ fs61.fsync(this.fd, (err) => {
7665
7613
  this._flushPending = false;
7666
7614
  cb(err);
7667
7615
  });
@@ -7763,7 +7711,7 @@ var require_sonic_boom = __commonJS({
7763
7711
  const fd = this.fd;
7764
7712
  this.once("ready", () => {
7765
7713
  if (fd !== this.fd) {
7766
- fs66.close(fd, (err) => {
7714
+ fs61.close(fd, (err) => {
7767
7715
  if (err) {
7768
7716
  return this.emit("error", err);
7769
7717
  }
@@ -7812,7 +7760,7 @@ var require_sonic_boom = __commonJS({
7812
7760
  buf = this._bufs[0];
7813
7761
  }
7814
7762
  try {
7815
- const n = Buffer.isBuffer(buf) ? fs66.writeSync(this.fd, buf) : fs66.writeSync(this.fd, buf, "utf8");
7763
+ const n = Buffer.isBuffer(buf) ? fs61.writeSync(this.fd, buf) : fs61.writeSync(this.fd, buf, "utf8");
7816
7764
  const releasedBufObj = releaseWritingBuf(buf, this._len, n);
7817
7765
  buf = releasedBufObj.writingBuf;
7818
7766
  this._len = releasedBufObj.len;
@@ -7828,7 +7776,7 @@ var require_sonic_boom = __commonJS({
7828
7776
  }
7829
7777
  }
7830
7778
  try {
7831
- fs66.fsyncSync(this.fd);
7779
+ fs61.fsyncSync(this.fd);
7832
7780
  } catch {
7833
7781
  }
7834
7782
  }
@@ -7849,7 +7797,7 @@ var require_sonic_boom = __commonJS({
7849
7797
  buf = mergeBuf(this._bufs[0], this._lens[0]);
7850
7798
  }
7851
7799
  try {
7852
- const n = fs66.writeSync(this.fd, buf);
7800
+ const n = fs61.writeSync(this.fd, buf);
7853
7801
  buf = buf.subarray(n);
7854
7802
  this._len = Math.max(this._len - n, 0);
7855
7803
  if (buf.length <= 0) {
@@ -7877,13 +7825,13 @@ var require_sonic_boom = __commonJS({
7877
7825
  this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
7878
7826
  if (this.sync) {
7879
7827
  try {
7880
- const written = Buffer.isBuffer(this._writingBuf) ? fs66.writeSync(this.fd, this._writingBuf) : fs66.writeSync(this.fd, this._writingBuf, "utf8");
7828
+ const written = Buffer.isBuffer(this._writingBuf) ? fs61.writeSync(this.fd, this._writingBuf) : fs61.writeSync(this.fd, this._writingBuf, "utf8");
7881
7829
  release(null, written);
7882
7830
  } catch (err) {
7883
7831
  release(err);
7884
7832
  }
7885
7833
  } else {
7886
- fs66.write(this.fd, this._writingBuf, release);
7834
+ fs61.write(this.fd, this._writingBuf, release);
7887
7835
  }
7888
7836
  }
7889
7837
  function actualWriteBuffer() {
@@ -7892,7 +7840,7 @@ var require_sonic_boom = __commonJS({
7892
7840
  this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
7893
7841
  if (this.sync) {
7894
7842
  try {
7895
- const written = fs66.writeSync(this.fd, this._writingBuf);
7843
+ const written = fs61.writeSync(this.fd, this._writingBuf);
7896
7844
  release(null, written);
7897
7845
  } catch (err) {
7898
7846
  release(err);
@@ -7901,7 +7849,7 @@ var require_sonic_boom = __commonJS({
7901
7849
  if (kCopyBuffer) {
7902
7850
  this._writingBuf = Buffer.from(this._writingBuf);
7903
7851
  }
7904
- fs66.write(this.fd, this._writingBuf, release);
7852
+ fs61.write(this.fd, this._writingBuf, release);
7905
7853
  }
7906
7854
  }
7907
7855
  function actualClose(sonic) {
@@ -7917,12 +7865,12 @@ var require_sonic_boom = __commonJS({
7917
7865
  sonic._lens = [];
7918
7866
  assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
7919
7867
  try {
7920
- fs66.fsync(sonic.fd, closeWrapped);
7868
+ fs61.fsync(sonic.fd, closeWrapped);
7921
7869
  } catch {
7922
7870
  }
7923
7871
  function closeWrapped() {
7924
7872
  if (sonic.fd !== 1 && sonic.fd !== 2) {
7925
- fs66.close(sonic.fd, done);
7873
+ fs61.close(sonic.fd, done);
7926
7874
  } else {
7927
7875
  done();
7928
7876
  }
@@ -11057,11 +11005,11 @@ var init_lib = __esm({
11057
11005
  }
11058
11006
  }
11059
11007
  },
11060
- addToPath: function addToPath(path73, added, removed, oldPosInc, options) {
11061
- var last = path73.lastComponent;
11008
+ addToPath: function addToPath(path68, added, removed, oldPosInc, options) {
11009
+ var last = path68.lastComponent;
11062
11010
  if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
11063
11011
  return {
11064
- oldPos: path73.oldPos + oldPosInc,
11012
+ oldPos: path68.oldPos + oldPosInc,
11065
11013
  lastComponent: {
11066
11014
  count: last.count + 1,
11067
11015
  added,
@@ -11071,7 +11019,7 @@ var init_lib = __esm({
11071
11019
  };
11072
11020
  } else {
11073
11021
  return {
11074
- oldPos: path73.oldPos + oldPosInc,
11022
+ oldPos: path68.oldPos + oldPosInc,
11075
11023
  lastComponent: {
11076
11024
  count: 1,
11077
11025
  added,
@@ -11537,10 +11485,10 @@ function attachmentToHistoryMessage(o, ts) {
11537
11485
  const memories = raw.map((m2) => {
11538
11486
  if (!m2 || typeof m2 !== "object") return null;
11539
11487
  const rec3 = m2;
11540
- const path73 = typeof rec3.path === "string" ? rec3.path : null;
11488
+ const path68 = typeof rec3.path === "string" ? rec3.path : null;
11541
11489
  const content = typeof rec3.content === "string" ? rec3.content : null;
11542
- if (!path73 || content == null) return null;
11543
- const entry = { path: path73, content };
11490
+ if (!path68 || content == null) return null;
11491
+ const entry = { path: path68, content };
11544
11492
  if (typeof rec3.mtimeMs === "number") entry.mtimeMs = rec3.mtimeMs;
11545
11493
  return entry;
11546
11494
  }).filter((m2) => m2 !== null);
@@ -12352,10 +12300,10 @@ function parseAttachment(obj) {
12352
12300
  const memories = raw.map((m2) => {
12353
12301
  if (!m2 || typeof m2 !== "object") return null;
12354
12302
  const rec3 = m2;
12355
- const path73 = typeof rec3.path === "string" ? rec3.path : null;
12303
+ const path68 = typeof rec3.path === "string" ? rec3.path : null;
12356
12304
  const content = typeof rec3.content === "string" ? rec3.content : null;
12357
- if (!path73 || content == null) return null;
12358
- const out = { path: path73, content };
12305
+ if (!path68 || content == null) return null;
12306
+ const out = { path: path68, content };
12359
12307
  if (typeof rec3.mtimeMs === "number") out.mtimeMs = rec3.mtimeMs;
12360
12308
  return out;
12361
12309
  }).filter((m2) => m2 !== null);
@@ -29030,7 +28978,7 @@ var require_websocket = __commonJS({
29030
28978
  var http3 = require("http");
29031
28979
  var net3 = require("net");
29032
28980
  var tls = require("tls");
29033
- var { randomBytes, createHash: createHash2 } = require("crypto");
28981
+ var { randomBytes, createHash: createHash3 } = require("crypto");
29034
28982
  var { Duplex, Readable: Readable3 } = require("stream");
29035
28983
  var { URL: URL2 } = require("url");
29036
28984
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -29690,7 +29638,7 @@ var require_websocket = __commonJS({
29690
29638
  abortHandshake(websocket, socket, "Invalid Upgrade header");
29691
29639
  return;
29692
29640
  }
29693
- const digest = createHash2("sha1").update(key + GUID).digest("base64");
29641
+ const digest = createHash3("sha1").update(key + GUID).digest("base64");
29694
29642
  if (res.headers["sec-websocket-accept"] !== digest) {
29695
29643
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
29696
29644
  return;
@@ -30057,7 +30005,7 @@ var require_websocket_server = __commonJS({
30057
30005
  var EventEmitter3 = require("events");
30058
30006
  var http3 = require("http");
30059
30007
  var { Duplex } = require("stream");
30060
- var { createHash: createHash2 } = require("crypto");
30008
+ var { createHash: createHash3 } = require("crypto");
30061
30009
  var extension2 = require_extension();
30062
30010
  var PerMessageDeflate2 = require_permessage_deflate();
30063
30011
  var subprotocol2 = require_subprotocol();
@@ -30358,7 +30306,7 @@ var require_websocket_server = __commonJS({
30358
30306
  );
30359
30307
  }
30360
30308
  if (this._state > RUNNING) return abortHandshake(socket, 503);
30361
- const digest = createHash2("sha1").update(key + GUID).digest("base64");
30309
+ const digest = createHash3("sha1").update(key + GUID).digest("base64");
30362
30310
  const headers = [
30363
30311
  "HTTP/1.1 101 Switching Protocols",
30364
30312
  "Upgrade: websocket",
@@ -33320,8 +33268,8 @@ var require_utils = __commonJS({
33320
33268
  var result = transform[inputType][outputType](input);
33321
33269
  return result;
33322
33270
  };
33323
- exports2.resolve = function(path73) {
33324
- var parts = path73.split("/");
33271
+ exports2.resolve = function(path68) {
33272
+ var parts = path68.split("/");
33325
33273
  var result = [];
33326
33274
  for (var index = 0; index < parts.length; index++) {
33327
33275
  var part = parts[index];
@@ -39174,18 +39122,18 @@ var require_object = __commonJS({
39174
39122
  var object = new ZipObject(name, zipObjectContent, o);
39175
39123
  this.files[name] = object;
39176
39124
  };
39177
- var parentFolder = function(path73) {
39178
- if (path73.slice(-1) === "/") {
39179
- path73 = path73.substring(0, path73.length - 1);
39125
+ var parentFolder = function(path68) {
39126
+ if (path68.slice(-1) === "/") {
39127
+ path68 = path68.substring(0, path68.length - 1);
39180
39128
  }
39181
- var lastSlash = path73.lastIndexOf("/");
39182
- return lastSlash > 0 ? path73.substring(0, lastSlash) : "";
39129
+ var lastSlash = path68.lastIndexOf("/");
39130
+ return lastSlash > 0 ? path68.substring(0, lastSlash) : "";
39183
39131
  };
39184
- var forceTrailingSlash = function(path73) {
39185
- if (path73.slice(-1) !== "/") {
39186
- path73 += "/";
39132
+ var forceTrailingSlash = function(path68) {
39133
+ if (path68.slice(-1) !== "/") {
39134
+ path68 += "/";
39187
39135
  }
39188
- return path73;
39136
+ return path68;
39189
39137
  };
39190
39138
  var folderAdd = function(name, createFolders) {
39191
39139
  createFolders = typeof createFolders !== "undefined" ? createFolders : defaults.createFolders;
@@ -40187,7 +40135,7 @@ var require_lib3 = __commonJS({
40187
40135
  // src/run-case/recorder.ts
40188
40136
  function startRunCaseRecorder(opts) {
40189
40137
  const now = opts.now ?? Date.now;
40190
- const dir = import_node_path61.default.dirname(opts.recordPath);
40138
+ const dir = import_node_path56.default.dirname(opts.recordPath);
40191
40139
  let stream = null;
40192
40140
  let closing = false;
40193
40141
  let closedSettled = false;
@@ -40201,8 +40149,8 @@ function startRunCaseRecorder(opts) {
40201
40149
  });
40202
40150
  const ensureStream = () => {
40203
40151
  if (stream) return stream;
40204
- import_node_fs48.default.mkdirSync(dir, { recursive: true });
40205
- stream = import_node_fs48.default.createWriteStream(opts.recordPath, { flags: "a" });
40152
+ import_node_fs43.default.mkdirSync(dir, { recursive: true });
40153
+ stream = import_node_fs43.default.createWriteStream(opts.recordPath, { flags: "a" });
40206
40154
  stream.on("close", () => closedResolve());
40207
40155
  return stream;
40208
40156
  };
@@ -40227,12 +40175,12 @@ function startRunCaseRecorder(opts) {
40227
40175
  };
40228
40176
  return { tap, close, closed };
40229
40177
  }
40230
- var import_node_fs48, import_node_path61;
40178
+ var import_node_fs43, import_node_path56;
40231
40179
  var init_recorder = __esm({
40232
40180
  "src/run-case/recorder.ts"() {
40233
40181
  "use strict";
40234
- import_node_fs48 = __toESM(require("fs"), 1);
40235
- import_node_path61 = __toESM(require("path"), 1);
40182
+ import_node_fs43 = __toESM(require("fs"), 1);
40183
+ import_node_path56 = __toESM(require("path"), 1);
40236
40184
  }
40237
40185
  });
40238
40186
 
@@ -40275,7 +40223,7 @@ var init_wire = __esm({
40275
40223
  // src/run-case/controller.ts
40276
40224
  async function runController(opts) {
40277
40225
  const now = opts.now ?? Date.now;
40278
- const cwd = opts.cwd ?? (0, import_node_fs49.mkdtempSync)(import_node_path62.default.join(import_node_os22.default.tmpdir(), "clawd-runcase-"));
40226
+ const cwd = opts.cwd ?? (0, import_node_fs44.mkdtempSync)(import_node_path57.default.join(import_node_os22.default.tmpdir(), "clawd-runcase-"));
40279
40227
  const ownsCwd = opts.cwd === void 0;
40280
40228
  const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
40281
40229
  const spawnCtx = { cwd };
@@ -40436,19 +40384,19 @@ async function runController(opts) {
40436
40384
  if (sigintHandler) process.off("SIGINT", sigintHandler);
40437
40385
  if (ownsCwd) {
40438
40386
  try {
40439
- (0, import_node_fs49.rmSync)(cwd, { recursive: true, force: true });
40387
+ (0, import_node_fs44.rmSync)(cwd, { recursive: true, force: true });
40440
40388
  } catch {
40441
40389
  }
40442
40390
  }
40443
40391
  return exitCode ?? 0;
40444
40392
  }
40445
- var import_node_fs49, import_node_os22, import_node_path62;
40393
+ var import_node_fs44, import_node_os22, import_node_path57;
40446
40394
  var init_controller = __esm({
40447
40395
  "src/run-case/controller.ts"() {
40448
40396
  "use strict";
40449
- import_node_fs49 = require("fs");
40397
+ import_node_fs44 = require("fs");
40450
40398
  import_node_os22 = __toESM(require("os"), 1);
40451
- import_node_path62 = __toESM(require("path"), 1);
40399
+ import_node_path57 = __toESM(require("path"), 1);
40452
40400
  init_claude();
40453
40401
  init_stdout_splitter();
40454
40402
  init_permission_stdio();
@@ -40551,7 +40499,6 @@ var import_node_path = __toESM(require("path"), 1);
40551
40499
  init_protocol();
40552
40500
  var DEFAULT_PORT = 18790;
40553
40501
  var DEFAULT_HOST = "127.0.0.1";
40554
- var DEFAULT_SSHD_PORT = 22422;
40555
40502
  var DEFAULT_CLAWOS_API = "https://api.clawos.chat";
40556
40503
  var DEFAULT_LOG_ENDPOINT = "https://clawd-prod.cn-hangzhou.log.aliyuncs.com/logstores/app-logs/track";
40557
40504
  function resolveLogShipping(raw, cliNoShipping) {
@@ -40619,14 +40566,6 @@ function parseArgs(argv) {
40619
40566
  case "--no-log-shipping":
40620
40567
  out.noLogShipping = true;
40621
40568
  break;
40622
- case "--sshd-port": {
40623
- const n = Number.parseInt(next() ?? "", 10);
40624
- if (!Number.isFinite(n) || n <= 0 || n > 65535) {
40625
- throw new Error(`invalid --sshd-port value: ${argv[i]}`);
40626
- }
40627
- out.sshdPort = n;
40628
- break;
40629
- }
40630
40569
  default:
40631
40570
  if (a.startsWith("--")) throw new Error(`unknown flag: ${a}`);
40632
40571
  break;
@@ -40676,7 +40615,6 @@ function resolveConfig(opts) {
40676
40615
  fileCfg.logShipping,
40677
40616
  args.noLogShipping ?? false
40678
40617
  );
40679
- const sshdPort = args.sshdPort ?? (typeof fileCfg.sshdPort === "number" ? fileCfg.sshdPort : DEFAULT_SSHD_PORT);
40680
40618
  return {
40681
40619
  port,
40682
40620
  host,
@@ -40690,8 +40628,7 @@ function resolveConfig(opts) {
40690
40628
  frpcBinary,
40691
40629
  mode,
40692
40630
  previewPorts,
40693
- logShipping,
40694
- sshdPort
40631
+ logShipping
40695
40632
  };
40696
40633
  }
40697
40634
  var HELP_TEXT = `clawd [options]
@@ -40703,7 +40640,6 @@ var HELP_TEXT = `clawd [options]
40703
40640
  --clawos-api <url> tunnel register \u63A5\u53E3\u7684 base url\uFF08\u9ED8\u8BA4 https://api.clawos.chat\uFF09
40704
40641
  --auth-token <s> \u6307\u5B9A daemon auth token\uFF1B\u7F3A\u7701\u65F6 tunnel \u6A21\u5F0F\u4ECE ~/.clawd/auth.json \u590D\u7528\uFF0C\u6CA1\u6709\u5C31\u751F\u6210
40705
40642
  --no-log-shipping \u7981\u7528 SLS \u65E5\u5FD7\u4E0A\u884C\uFF08\u8986\u76D6 config.json logShipping.mode=off\uFF09
40706
- --sshd-port <n> Contact SSH \u53CD\u5411\u8BBF\u95EE loopback \u7AEF\u53E3\uFF0C\u9ED8\u8BA4 22422
40707
40643
  --help / -h \u663E\u793A\u5E2E\u52A9
40708
40644
  --version / -v \u663E\u793A\u7248\u672C
40709
40645
 
@@ -40722,8 +40658,8 @@ Env (advanced):
40722
40658
  `;
40723
40659
 
40724
40660
  // src/index.ts
40725
- var import_node_path60 = __toESM(require("path"), 1);
40726
- var import_node_fs47 = __toESM(require("fs"), 1);
40661
+ var import_node_path55 = __toESM(require("path"), 1);
40662
+ var import_node_fs42 = __toESM(require("fs"), 1);
40727
40663
  var import_node_os21 = __toESM(require("os"), 1);
40728
40664
 
40729
40665
  // ../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/stringify.js
@@ -40843,6 +40779,18 @@ function createLogger(opts = {}) {
40843
40779
  );
40844
40780
  return wrap(base);
40845
40781
  }
40782
+ function createFileOnlyLogger(opts) {
40783
+ const level = opts.level ?? "debug";
40784
+ try {
40785
+ import_node_fs2.default.mkdirSync(import_node_path2.default.dirname(opts.file), { recursive: true });
40786
+ } catch {
40787
+ }
40788
+ const base = (0, import_pino.default)(
40789
+ { level, base: {} },
40790
+ import_pino.default.destination({ dest: opts.file, mkdir: true, sync: true })
40791
+ );
40792
+ return wrap(base);
40793
+ }
40846
40794
  function pinoLevelToString(n) {
40847
40795
  if (typeof n !== "number") return null;
40848
40796
  if (n >= 50) return "error";
@@ -43008,9 +42956,18 @@ var SessionManager = class {
43008
42956
  // 由 observer 监听 jsonl user 行后调 recordRealUserUuid 建立映射;rewind 系列 RPC 在
43009
42957
  // 入参 / 出参做转译,保证 UI 看到的 uuid 始终是 events 流里的 synth uuid
43010
42958
  realUuidBySynth = /* @__PURE__ */ new Map();
43011
- // observeScreenIdle 复合条件闸用:sessionIdobserver 上次喂出业务事件的时刻(deps.now())。
43012
- // observerIdleWaitMs 据此判 observer 是否也静止 idleMs(屏幕静止 AND observer 静止才补 turn_end)。
43013
- lastObserverEventAt = /* @__PURE__ */ new Map();
42959
+ // observer 收到 `turn_duration` 信号但屏幕还没稳定 5s 挂进 pending,等 `notifyScreenIdle`
42960
+ // (屏幕 armed=true 触发点)flush 成真正的 turn_end reducer。
42961
+ //
42962
+ // 语义澄清:`turn_duration` 是 CC 报的原始信号("本轮 API 调用完了"),**不代表 turn 真的结束**
42963
+ //(背景 agent 可能还在跑)。屏幕 5s 稳定 + 无 popup 才是"确认信号"。
42964
+ // 两者 AND 后 daemon 才产生真正的 `turn_end` 事件送给 reducer。
42965
+ //
42966
+ // pending 不设截止时间:屏幕稳定的时机由 CC UI 决定,可能几秒也可能几分钟。观察者以
42967
+ // "屏幕真的稳定"作为触发点,signal-driven 而非 timer-driven。
42968
+ //
42969
+ // 清理点:newSession / stop / session-delete / stopAll。
42970
+ pendingTurnDurationSignals = /* @__PURE__ */ new Set();
43014
42971
  // SessionStore 按 scope 派生(root = <dataDir>/sessions/<scopeSubPath>/)。
43015
42972
  // default scope 直接复用 deps.store;persona scope(owner / listener)第一次访问时按需创建并缓存。
43016
42973
  // 取代旧的 storesByAgent —— agentId 概念由 SessionScope 取代,路径即身份,
@@ -43350,6 +43307,14 @@ var SessionManager = class {
43350
43307
  routeFromRunner(frame, target) {
43351
43308
  const compressed = compressFrameForWire(frame);
43352
43309
  if (!compressed) return;
43310
+ if (compressed.type === "session:status") {
43311
+ const s = compressed;
43312
+ this.deps.screenIdleProbeLogger?.info("session:status wire emit", {
43313
+ sessionId: s.sessionId,
43314
+ status: s.status,
43315
+ target
43316
+ });
43317
+ }
43353
43318
  if (compressed.type === "session:event" || compressed.type === "session:status") {
43354
43319
  const sid = compressed.sessionId;
43355
43320
  if (sid) {
@@ -43623,7 +43588,7 @@ var SessionManager = class {
43623
43588
  this.runners.delete(args.sessionId);
43624
43589
  this.realUuidBySynth.delete(args.sessionId);
43625
43590
  this.lastUiSizeBySessionId.delete(args.sessionId);
43626
- this.lastObserverEventAt.delete(args.sessionId);
43591
+ this.clearPendingTurnEnd(args.sessionId);
43627
43592
  return { response: { sessionId: args.sessionId }, broadcast };
43628
43593
  }
43629
43594
  this.deleteOwned(args.sessionId);
@@ -43653,6 +43618,7 @@ var SessionManager = class {
43653
43618
  async stop(args) {
43654
43619
  const runner = this.runners.get(args.sessionId);
43655
43620
  if (!runner) return { response: { ok: true }, broadcast: [] };
43621
+ this.clearPendingTurnEnd(args.sessionId);
43656
43622
  const { broadcast } = this.withCollector(() => {
43657
43623
  runner.input({ kind: "command", command: { kind: "stop" } });
43658
43624
  });
@@ -43786,6 +43752,7 @@ var SessionManager = class {
43786
43752
  newSession(args) {
43787
43753
  const existingFile = this.getFile(args.sessionId);
43788
43754
  const nextToolSessionId = this.deps.mode === "tui" && (existingFile.tool ?? "claude") === "claude" ? v4_default() : void 0;
43755
+ this.clearPendingTurnEnd(args.sessionId);
43789
43756
  const runner = this.runners.get(args.sessionId);
43790
43757
  if (runner) {
43791
43758
  const { value, broadcast } = this.withCollector(() => {
@@ -43886,6 +43853,7 @@ var SessionManager = class {
43886
43853
  for (const r of this.runners.values()) {
43887
43854
  r.input({ kind: "command", command: { kind: "stop" } });
43888
43855
  }
43856
+ this.pendingTurnDurationSignals.clear();
43889
43857
  }
43890
43858
  // 给 observer 用:拿已存在的 runner
43891
43859
  getActive(sessionId) {
@@ -44102,7 +44070,7 @@ var SessionManager = class {
44102
44070
  this.runners.delete(args.sessionId);
44103
44071
  this.realUuidBySynth.delete(args.sessionId);
44104
44072
  this.lastUiSizeBySessionId.delete(args.sessionId);
44105
- this.lastObserverEventAt.delete(args.sessionId);
44073
+ this.clearPendingTurnEnd(args.sessionId);
44106
44074
  return { response: { sessionId: args.sessionId }, broadcast };
44107
44075
  }
44108
44076
  this.storeFor(args.scope).delete(args.sessionId);
@@ -44348,23 +44316,93 @@ var SessionManager = class {
44348
44316
  return;
44349
44317
  }
44350
44318
  }
44351
- this.lastObserverEventAt.set(sessionId, (this.deps.now ?? Date.now)());
44352
44319
  let feedEvents = outEvents;
44353
44320
  if (outEvents.some((e) => e.kind === "turn_end")) {
44354
- const ev = this.peekTurnEvidence(runner);
44355
- if (!ev.turnHasContent) {
44321
+ const runnerState = runner.getState();
44322
+ const toolSessionId = runnerState.file.toolSessionId;
44323
+ const adapter = this.deps.getAdapter(runnerState.file.tool ?? "claude");
44324
+ const gateOpen = !adapter.canAcceptTurnEnd || !toolSessionId ? true : adapter.canAcceptTurnEnd(toolSessionId);
44325
+ this.deps.screenIdleProbeLogger?.info("turn_duration signal received", {
44326
+ sessionId,
44327
+ toolSessionId,
44328
+ batchKinds: outEvents.map((e) => e.kind),
44329
+ gateOpen,
44330
+ hasCanAcceptGate: !!adapter.canAcceptTurnEnd
44331
+ });
44332
+ if (gateOpen) {
44333
+ this.deps.screenIdleProbeLogger?.info(
44334
+ "turn_duration \u2192 turn_end confirmed (gate open) \u2192 fed to reducer",
44335
+ { sessionId, toolSessionId }
44336
+ );
44337
+ } else {
44356
44338
  feedEvents = outEvents.filter((e) => e.kind !== "turn_end");
44357
- this.deps.logger?.info("[TE-PROBE] drop spurious observer turn_end", {
44358
- sessionId,
44359
- src: "observer",
44360
- ...ev,
44361
- batchKinds: outEvents.map((e) => e.kind)
44362
- });
44339
+ if (this.pendingTurnDurationSignals.has(sessionId)) {
44340
+ this.deps.screenIdleProbeLogger?.info(
44341
+ "turn_duration dedup: pending already set (repeated observer signal)",
44342
+ { sessionId, toolSessionId }
44343
+ );
44344
+ } else {
44345
+ this.pendingTurnDurationSignals.add(sessionId);
44346
+ this.deps.screenIdleProbeLogger?.info(
44347
+ "turn_duration pending: gate closed \u2192 waiting for screen-idle signal",
44348
+ { sessionId, toolSessionId }
44349
+ );
44350
+ }
44363
44351
  }
44364
44352
  }
44365
44353
  if (feedEvents.length === 0) return;
44366
44354
  runner.feedObserverEvents(feedEvents);
44367
44355
  }
44356
+ /**
44357
+ * `ClaudeTuiAdapter.observeScreenIdle` fire triggered → armed=true 时调用(一次性触发点)。
44358
+ *
44359
+ * 语义:屏幕真的稳定了 5s。查有没有 pending 的 turn_duration 信号:
44360
+ * - 有 → 之前收到的 turn_duration 被屏幕稳定**确认**了,flush 成 turn_end 进 reducer
44361
+ * - 无 → 屏幕稳定但从没收到 turn_duration → noop(不生成 turn_end;补偿路径的错误做法)
44362
+ *
44363
+ * pending 集合是**必要前提**:turn_end 只能来自"observer 收 turn_duration + 屏幕后续稳定"
44364
+ * 双源确认,任何一个缺少都不能 emit。这跟 PR #962 拆掉的 dispatchTurnIdle "屏幕静止就补
44365
+ * turn_end" 语义不同。
44366
+ *
44367
+ * 仅 TUI 模式;SDK / codex 没有屏幕信号也就不会触发本方法。
44368
+ */
44369
+ notifyScreenIdle(toolSessionId) {
44370
+ if (this.deps.mode !== "tui") return;
44371
+ const sid = this.sessionIdByToolSid(toolSessionId);
44372
+ if (!sid) {
44373
+ this.deps.screenIdleProbeLogger?.warn("notifyScreenIdle: no session for toolSessionId", {
44374
+ toolSessionId
44375
+ });
44376
+ return;
44377
+ }
44378
+ if (!this.pendingTurnDurationSignals.has(sid)) {
44379
+ this.deps.screenIdleProbeLogger?.info(
44380
+ "notifyScreenIdle: no pending turn_duration \u2192 noop",
44381
+ { sessionId: sid, toolSessionId }
44382
+ );
44383
+ return;
44384
+ }
44385
+ const runner = this.runners.get(sid);
44386
+ if (!runner) {
44387
+ this.pendingTurnDurationSignals.delete(sid);
44388
+ this.deps.screenIdleProbeLogger?.warn(
44389
+ "notifyScreenIdle: pending but no runner \u2192 cleared without inject",
44390
+ { sessionId: sid, toolSessionId }
44391
+ );
44392
+ return;
44393
+ }
44394
+ this.pendingTurnDurationSignals.delete(sid);
44395
+ this.deps.screenIdleProbeLogger?.info(
44396
+ "notifyScreenIdle: pending turn_duration + screen idle confirmed \u2192 inject turn_end",
44397
+ { sessionId: sid, toolSessionId }
44398
+ );
44399
+ runner.input({ kind: "inject-events", events: [{ kind: "turn_end" }] });
44400
+ }
44401
+ clearPendingTurnEnd(sessionId) {
44402
+ if (this.pendingTurnDurationSignals.delete(sessionId)) {
44403
+ this.deps.screenIdleProbeLogger?.info("pending turn_duration cleared", { sessionId });
44404
+ }
44405
+ }
44368
44406
  // AskUserQuestion 表单回写(plan: clawd-ask-user-question):UI 答完所有 question 后调用。
44369
44407
  // - session 不存在 / 无 runner → noop 幂等返回 ok(first-decider-wins)
44370
44408
  // - reducer noop(toolUseId 不存在或已答过)也保持幂等返回,handler 不抛
@@ -44623,70 +44661,6 @@ var SessionManager = class {
44623
44661
  if (!runner) return;
44624
44662
  runner.input({ kind: "ready-detected" });
44625
44663
  }
44626
- /**
44627
- * ClaudeTuiAdapter onTurnIdle callback:屏幕内容静止时**复发**本轮已出现过的权威 turn_end。
44628
- * 本意:turn_duration 写盘早于尾段正文 → observer 把尾随 text 推进 buffer 盖掉 lastEventKind →
44629
- * spinner 不熄;屏幕静止时再补一条 turn_end 排到尾随 text 之后。
44630
- *
44631
- * Fix A(修 bug1 "UI 还在变却显示结束" / bug2 "发消息后无 spinner"):补偿**只复发不 originate**——
44632
- * 仅当本轮已出现过 turn_end(turnEndSeenThisTurn,即真有过尾随 text 覆盖场景)才补。turnEndSeenThisTurn
44633
- * ===false 说明本轮 CC 从未结束过(仍在工作 / 刚发消息没产出 / 漏检弹框等用户),屏幕静止 ≠ turn 结束,
44634
- * 凭空 inject turn_end 会误灭 spinner——故跳过,让真正的 turn_duration 来时再正常收。
44635
- * 仅 tui 模式;runner 缺失 noop。turn_end 无 uuid 不参与 dedup。
44636
- */
44637
- dispatchTurnIdle(toolSessionId) {
44638
- if (this.deps.mode !== "tui") return;
44639
- const sid = this.sessionIdByToolSid(toolSessionId);
44640
- const runner = sid ? this.runners.get(sid) : void 0;
44641
- if (!runner) return;
44642
- const ev = this.peekTurnEvidence(runner);
44643
- const willInject = ev.turnEndSeenThisTurn;
44644
- this.deps.logger?.info("[TE-PROBE] screen-idle compensation", {
44645
- sessionId: sid,
44646
- src: "screen-idle",
44647
- willInject,
44648
- ...ev
44649
- });
44650
- if (!willInject) return;
44651
- runner.input({ kind: "inject-events", events: [{ kind: "turn_end" }] });
44652
- }
44653
- /**
44654
- * 读 runner 当前 turn 态快照,供两个 turn_end 注入源(屏幕静止补偿 / observer turn_duration)
44655
- * 判断误发 + 打 [TE-PROBE] 日志。
44656
- * turnEndSeenThisTurn:从 buffer 末尾回扫到最近 user_text 期间是否已出现过 turn_end
44657
- * (已出现=本轮真结束过、合法尾随;未出现=本轮还没结束过)→ Fix A 复发闸。
44658
- * turnHasContent:末条是否 assistant 产出(非 user_text/turn_end/空)→ Fix B 空 turn 守卫闸。
44659
- */
44660
- peekTurnEvidence(runner) {
44661
- const st = runner.getState();
44662
- const buf = st.buffer;
44663
- const lastEventKindBefore = buf.length > 0 ? buf[buf.length - 1].event.kind : null;
44664
- let turnEndSeenThisTurn = false;
44665
- for (let i = buf.length - 1; i >= 0; i--) {
44666
- const k2 = buf[i].event.kind;
44667
- if (k2 === "user_text") break;
44668
- if (k2 === "turn_end") {
44669
- turnEndSeenThisTurn = true;
44670
- break;
44671
- }
44672
- }
44673
- const turnHasContent = lastEventKindBefore !== null && lastEventKindBefore !== "user_text" && lastEventKindBefore !== "turn_end";
44674
- return { turnOpenBefore: st.turnOpen, lastEventKindBefore, turnEndSeenThisTurn, turnHasContent };
44675
- }
44676
- /**
44677
- * observer 还需静止多久(ms)才满 idleMs,0 = 已满。observeScreenIdle 复合条件闸:屏幕静止后
44678
- * 精确等这段剩余再补 turn_end —— turn_duration 写盘早于尾段正文,observer 把尾随 text poll 落盘
44679
- * 期间屏幕可能已静止,仅看屏幕会早 fire(补的 turn_end 盖不到尾随 text 之后)。
44680
- * 找不到 runner / 从无事件 → 0(不阻塞 fire)。idleMs 由装配处传 SCREEN_IDLE_MS。
44681
- */
44682
- observerIdleWaitMs(toolSessionId, idleMs) {
44683
- const sid = this.sessionIdByToolSid(toolSessionId);
44684
- if (!sid) return 0;
44685
- const last = this.lastObserverEventAt.get(sid);
44686
- if (last === void 0) return 0;
44687
- const elapsed = (this.deps.now ?? Date.now)() - last;
44688
- return Math.max(0, idleMs - elapsed);
44689
- }
44690
44664
  /** toolSessionId → sessionId 反查(遍历 runners);session 数典型 < 10,O(n) 可接受 */
44691
44665
  sessionIdByToolSid(toolSessionId) {
44692
44666
  for (const [sid, runner] of this.runners) {
@@ -46196,8 +46170,8 @@ function turnStartInput(text) {
46196
46170
  const items = [];
46197
46171
  let leftover = text;
46198
46172
  for (const m2 of text.matchAll(SKILL_RE)) {
46199
- const [marker, name, path73] = m2;
46200
- items.push({ type: "skill", name, path: path73 });
46173
+ const [marker, name, path68] = m2;
46174
+ items.push({ type: "skill", name, path: path68 });
46201
46175
  leftover = leftover.replace(marker, "");
46202
46176
  }
46203
46177
  for (const m2 of text.matchAll(ATTACHMENT_RE2)) {
@@ -46429,6 +46403,7 @@ var CodexAdapter = class {
46429
46403
  };
46430
46404
 
46431
46405
  // src/tools/claude-tui.ts
46406
+ var import_node_crypto5 = require("crypto");
46432
46407
  var import_node_fs16 = __toESM(require("fs"), 1);
46433
46408
  var import_node_os7 = __toESM(require("os"), 1);
46434
46409
  var import_node_path14 = __toESM(require("path"), 1);
@@ -47239,22 +47214,56 @@ function observeScreenIdle(surface, opts) {
47239
47214
  timer = null;
47240
47215
  if (disposed) return;
47241
47216
  if (opts.getPopupVisible()) {
47217
+ opts.probeLogger?.info("screen-idle fire suppressed: popup visible", {
47218
+ label: opts.probeLabel
47219
+ });
47242
47220
  timer = setTimeout(fire, opts.idleMs);
47243
47221
  return;
47244
47222
  }
47245
47223
  const obsWait = opts.getObserverWaitMs?.() ?? 0;
47246
47224
  if (obsWait > 0) {
47225
+ opts.probeLogger?.info("screen-idle fire suppressed: observer not idle", {
47226
+ label: opts.probeLabel,
47227
+ obsWait
47228
+ });
47247
47229
  timer = setTimeout(fire, Math.max(obsWait, REWAIT_MIN_MS));
47248
47230
  return;
47249
47231
  }
47250
- if (armed) return;
47232
+ if (armed) {
47233
+ opts.probeLogger?.debug("screen-idle fire noop: already armed", {
47234
+ label: opts.probeLabel
47235
+ });
47236
+ return;
47237
+ }
47251
47238
  armed = true;
47239
+ opts.probeLogger?.info("screen-idle fire triggered \u2192 armed=true, calling onIdle", {
47240
+ label: opts.probeLabel
47241
+ });
47252
47242
  opts.onIdle();
47253
47243
  };
47254
47244
  const unsub = surface.onTick((lines) => {
47255
47245
  if (disposed) return;
47256
47246
  const snap = snapOf(lines);
47257
47247
  if (snap === lastSnap) return;
47248
+ if (opts.probeLogger) {
47249
+ const prev = lastSnap;
47250
+ const meta = {
47251
+ label: opts.probeLabel,
47252
+ prevHash: prev === null ? null : shortHash(prev),
47253
+ nextHash: shortHash(snap),
47254
+ prevLen: prev?.length ?? 0,
47255
+ nextLen: snap.length
47256
+ };
47257
+ if (prev !== null) {
47258
+ const diff2 = firstLineDiff(prev, snap);
47259
+ if (diff2) {
47260
+ meta.diffRow = diff2.row;
47261
+ meta.prevRow = diff2.prev;
47262
+ meta.nextRow = diff2.next;
47263
+ }
47264
+ }
47265
+ opts.probeLogger.info("screen-idle tick snap changed", meta);
47266
+ }
47258
47267
  lastSnap = snap;
47259
47268
  armed = false;
47260
47269
  clear();
@@ -47265,9 +47274,38 @@ function observeScreenIdle(surface, opts) {
47265
47274
  disposed = true;
47266
47275
  unsub();
47267
47276
  clear();
47277
+ },
47278
+ isIdle() {
47279
+ const popupVisible = opts.getPopupVisible();
47280
+ const idle = armed && !popupVisible;
47281
+ if (opts.probeLogger) {
47282
+ opts.probeLogger.info("screen-idle isIdle check", {
47283
+ label: opts.probeLabel,
47284
+ idle,
47285
+ armed,
47286
+ popupVisible
47287
+ });
47288
+ }
47289
+ return idle;
47268
47290
  }
47269
47291
  };
47270
47292
  }
47293
+ function shortHash(s) {
47294
+ return (0, import_node_crypto5.createHash)("sha1").update(s).digest("hex").slice(0, 8);
47295
+ }
47296
+ function firstLineDiff(prev, next) {
47297
+ const p2 = prev.split("\n");
47298
+ const n = next.split("\n");
47299
+ const rows = Math.max(p2.length, n.length);
47300
+ for (let i = 0; i < rows; i++) {
47301
+ const pl = p2[i] ?? "";
47302
+ const nl = n[i] ?? "";
47303
+ if (pl !== nl) {
47304
+ return { row: i, prev: pl.slice(0, 60), next: nl.slice(0, 60) };
47305
+ }
47306
+ }
47307
+ return null;
47308
+ }
47271
47309
  var BYPASS_SETTLE_MS = 300;
47272
47310
  var SCREEN_IDLE_MS = 5e3;
47273
47311
  function createBootGate(pty, logger) {
@@ -47342,11 +47380,42 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
47342
47380
  // 用于 spawn / PtyChildProcess 链路打日志
47343
47381
  tuiLogger;
47344
47382
  tuiOpts;
47383
+ /**
47384
+ * per-toolSessionId 的 tui 观察者句柄,仅用于 turn_end gate 查询(`canAcceptTurnEnd`)。
47385
+ * onIdle / onPopupTransition 等回调仍走原有闭包(不复用这份 map),本 map 只承担
47386
+ * "manager 需要跨模块查屏幕/弹框状态"这单一职责。
47387
+ */
47388
+ tuiStates = /* @__PURE__ */ new Map();
47345
47389
  constructor(opts = {}) {
47346
47390
  super(opts);
47347
47391
  this.tuiLogger = opts.logger;
47348
47392
  this.tuiOpts = opts;
47349
47393
  }
47394
+ /**
47395
+ * TUI adapter 的 turn_end 权威判定:屏幕已 idle 且非弹框态才放行。
47396
+ *
47397
+ * `feedObserverEvents` 收到 observer 回灌 `turn_end` 时调用。屏幕仍在变(如后台 agent 在跑)
47398
+ * 时 drop 掉 turn_end,避免 `system/turn_duration` JSONL 帧误触发 running-idle 状态转换。
47399
+ *
47400
+ * 未跟踪的 toolSessionId(spawn 前 / spawn 失败 / 已 dispose)视为 pass —— gate 只 drop
47401
+ * "有证据判定为伪信号"的场景,不做 unknown → block。
47402
+ */
47403
+ canAcceptTurnEnd(toolSessionId) {
47404
+ const state = this.tuiStates.get(toolSessionId);
47405
+ if (!state) {
47406
+ this.tuiOpts.screenIdleProbeLogger?.info(
47407
+ "canAcceptTurnEnd: no tuiState \u2192 pass (\u672A\u8DDF\u8E2A)",
47408
+ { toolSessionId }
47409
+ );
47410
+ return true;
47411
+ }
47412
+ const result = state.screenIdle.isIdle();
47413
+ this.tuiOpts.screenIdleProbeLogger?.info("canAcceptTurnEnd", {
47414
+ toolSessionId,
47415
+ result
47416
+ });
47417
+ return result;
47418
+ }
47350
47419
  spawn(ctx) {
47351
47420
  const args = buildTuiSpawnArgs(ctx, jsonlExistsForCtx(ctx));
47352
47421
  const cmd = process.env.CLAUDE_BIN ?? "claude";
@@ -47404,18 +47473,26 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
47404
47473
  const screenIdleObserver = observeScreenIdle(surface, {
47405
47474
  idleMs: SCREEN_IDLE_MS,
47406
47475
  onIdle: () => {
47407
- if (!ctx.toolSessionId || !this.tuiOpts.onTurnIdle) return;
47408
- this.tuiLogger?.debug("screen-idle \u2192 turn_end", { toolSessionId: ctx.toolSessionId });
47409
- this.tuiOpts.onTurnIdle(ctx.toolSessionId);
47476
+ if (!ctx.toolSessionId || !this.tuiOpts.onScreenIdle) return;
47477
+ this.tuiLogger?.debug("screen-idle \u2192 notifyScreenIdle", { toolSessionId: ctx.toolSessionId });
47478
+ this.tuiOpts.onScreenIdle(ctx.toolSessionId);
47410
47479
  },
47411
47480
  getPopupVisible: () => popupObserver.visibleKind !== null,
47412
- // observer 还需静止多久才满 SCREEN_IDLE_MS(复合条件 AND):屏幕静止后精确等这段剩余再补
47413
- // turn_end,确保它排在尾段 text + turn_duration 全部 poll 落盘之后 = buffer 末条。
47414
- getObserverWaitMs: () => ctx.toolSessionId ? this.tuiOpts.getObserverWaitMs?.(ctx.toolSessionId, SCREEN_IDLE_MS) ?? 0 : 0
47481
+ // 取证 probe(可选,装配处传独立 file-only logger,跟主 daemon.log 解耦)
47482
+ ...this.tuiOpts.screenIdleProbeLogger ? {
47483
+ probeLogger: this.tuiOpts.screenIdleProbeLogger,
47484
+ probeLabel: ctx.toolSessionId ?? "<no-tsid>"
47485
+ } : {}
47415
47486
  });
47416
47487
  if (ctx.toolSessionId && this.tuiOpts.onSurfaceRegister) {
47417
47488
  this.tuiOpts.onSurfaceRegister(ctx.toolSessionId, surface);
47418
47489
  }
47490
+ if (ctx.toolSessionId) {
47491
+ this.tuiStates.set(ctx.toolSessionId, {
47492
+ screenIdle: screenIdleObserver,
47493
+ popup: popupObserver
47494
+ });
47495
+ }
47419
47496
  let chunkSeq = 0;
47420
47497
  if (ctx.toolSessionId && this.tuiOpts.onPtyReplayRegister) {
47421
47498
  this.tuiOpts.onPtyReplayRegister(ctx.toolSessionId, async () => {
@@ -47461,6 +47538,9 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
47461
47538
  readyObserver.dispose();
47462
47539
  popupObserver.dispose();
47463
47540
  screenIdleObserver.dispose();
47541
+ if (ctx.toolSessionId) {
47542
+ this.tuiStates.delete(ctx.toolSessionId);
47543
+ }
47464
47544
  if (ctx.toolSessionId && this.tuiOpts.onSurfaceUnregister) {
47465
47545
  this.tuiOpts.onSurfaceUnregister(ctx.toolSessionId);
47466
47546
  }
@@ -47743,7 +47823,7 @@ async function writeInboxMcpConfig(args) {
47743
47823
  // src/shift/store.ts
47744
47824
  var import_promises = __toESM(require("fs/promises"), 1);
47745
47825
  var import_node_path19 = __toESM(require("path"), 1);
47746
- var import_node_crypto5 = require("crypto");
47826
+ var import_node_crypto6 = require("crypto");
47747
47827
 
47748
47828
  // src/shift/constants.ts
47749
47829
  var MAX_RUNS_PER_SHIFT = 30;
@@ -47839,7 +47919,7 @@ function createShiftStore(deps) {
47839
47919
  const nextRunAtMs = computeNextRunAtMs(input.schedule, now) ?? void 0;
47840
47920
  const shift = {
47841
47921
  ...input,
47842
- id: (0, import_node_crypto5.randomUUID)(),
47922
+ id: (0, import_node_crypto6.randomUUID)(),
47843
47923
  createdAtMs: now,
47844
47924
  updatedAtMs: now,
47845
47925
  state: { nextRunAtMs },
@@ -48615,13 +48695,13 @@ function mapSkillsListResponse(res) {
48615
48695
  const r = s ?? {};
48616
48696
  const name = str3(r.name);
48617
48697
  if (!name) continue;
48618
- const path73 = str3(r.path);
48698
+ const path68 = str3(r.path);
48619
48699
  const description = str3(r.description);
48620
48700
  const isPlugin = name.includes(":");
48621
48701
  out.push({
48622
48702
  name,
48623
48703
  source: isPlugin ? "plugin" : "project",
48624
- ...path73 ? { path: path73 } : {},
48704
+ ...path68 ? { path: path68 } : {},
48625
48705
  ...description ? { description } : {},
48626
48706
  ...isPlugin ? { plugin: name.split(":")[0] } : {}
48627
48707
  });
@@ -50415,23 +50495,6 @@ var ContactStore = class {
50415
50495
  this.flush();
50416
50496
  return true;
50417
50497
  }
50418
- /**
50419
- * 更新单条 contact 的 SSH 授权(PR: contact-ssh-sandbox)。对齐 setPin pattern:
50420
- * store 只做原始 mutation,不做业务校验(如"sshAllowed=false 时清空 exposedDirs")——
50421
- * 那是 handler / UI 的责任。数组语义是完全替换(不 append)。
50422
- * @returns 是否命中:deviceId 不存在返 false;命中即 flush.
50423
- */
50424
- setSshAccess(deviceId, opts) {
50425
- const existing = this.contacts.get(deviceId);
50426
- if (!existing) return false;
50427
- this.contacts.set(deviceId, {
50428
- ...existing,
50429
- sshAllowed: opts.sshAllowed,
50430
- exposedDirs: opts.exposedDirs
50431
- });
50432
- this.flush();
50433
- return true;
50434
- }
50435
50498
  flush() {
50436
50499
  const file = path30.join(this.dataDir, FILE_NAME);
50437
50500
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
@@ -50579,9 +50642,7 @@ async function autoReverseContact(args) {
50579
50642
  connectToken: "",
50580
50643
  grants,
50581
50644
  addedAt: now(),
50582
- pinnedAt: null,
50583
- sshAllowed: false,
50584
- exposedDirs: []
50645
+ pinnedAt: null
50585
50646
  };
50586
50647
  args.store.upsert(base);
50587
50648
  args.broadcast({ type: "contact:added", contact: base });
@@ -50810,7 +50871,7 @@ function lookupMime(filePathOrName) {
50810
50871
  }
50811
50872
 
50812
50873
  // src/attachment/sign-url.ts
50813
- var import_node_crypto6 = __toESM(require("crypto"), 1);
50874
+ var import_node_crypto7 = __toESM(require("crypto"), 1);
50814
50875
  var HMAC_ALGO = "sha256";
50815
50876
  function base64urlEncode(buf) {
50816
50877
  const b2 = typeof buf === "string" ? Buffer.from(buf, "utf8") : buf;
@@ -50827,7 +50888,7 @@ function decodeAbsPathFromUrl(encoded) {
50827
50888
  }
50828
50889
  function computeSig(secret, absPath, e) {
50829
50890
  const msg = e === null ? absPath : `${absPath}|${e}`;
50830
- return import_node_crypto6.default.createHmac(HMAC_ALGO, secret).update(msg).digest();
50891
+ return import_node_crypto7.default.createHmac(HMAC_ALGO, secret).update(msg).digest();
50831
50892
  }
50832
50893
  function signUrlParts(secret, absPath, ttlSeconds, now = Date.now) {
50833
50894
  const e = ttlSeconds === null ? null : Math.floor(now() / 1e3) + ttlSeconds;
@@ -50862,7 +50923,7 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
50862
50923
  if (provided.length !== expected.length) {
50863
50924
  return { ok: false, code: "BAD_SIG" };
50864
50925
  }
50865
- if (!import_node_crypto6.default.timingSafeEqual(provided, expected)) {
50926
+ if (!import_node_crypto7.default.timingSafeEqual(provided, expected)) {
50866
50927
  return { ok: false, code: "BAD_SIG" };
50867
50928
  }
50868
50929
  if (e !== null && now() / 1e3 > e) {
@@ -50874,7 +50935,7 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
50874
50935
  // src/attachment/upload.ts
50875
50936
  var import_node_fs25 = __toESM(require("fs"), 1);
50876
50937
  var import_node_path25 = __toESM(require("path"), 1);
50877
- var import_node_crypto7 = __toESM(require("crypto"), 1);
50938
+ var import_node_crypto8 = __toESM(require("crypto"), 1);
50878
50939
  var import_promises2 = require("stream/promises");
50879
50940
  var UploadError = class extends Error {
50880
50941
  constructor(code, message) {
@@ -50898,11 +50959,11 @@ async function writeUploadedAttachment(args) {
50898
50959
  } catch (err) {
50899
50960
  throw new UploadError("STORAGE_ERROR", `mkdir failed: ${err.message}`);
50900
50961
  }
50901
- const hasher = import_node_crypto7.default.createHash("sha256");
50962
+ const hasher = import_node_crypto8.default.createHash("sha256");
50902
50963
  let actualSize = 0;
50903
50964
  const tmpPath = import_node_path25.default.join(
50904
50965
  attachmentsRoot,
50905
- `.upload-${process.pid}-${Date.now()}-${import_node_crypto7.default.randomBytes(4).toString("hex")}`
50966
+ `.upload-${process.pid}-${Date.now()}-${import_node_crypto8.default.randomBytes(4).toString("hex")}`
50906
50967
  );
50907
50968
  try {
50908
50969
  await (0, import_promises2.pipeline)(
@@ -51778,7 +51839,7 @@ function runAttachmentGc(args) {
51778
51839
  // src/attachment/group.ts
51779
51840
  var import_node_fs28 = __toESM(require("fs"), 1);
51780
51841
  var import_node_path29 = __toESM(require("path"), 1);
51781
- var import_node_crypto8 = __toESM(require("crypto"), 1);
51842
+ var import_node_crypto9 = __toESM(require("crypto"), 1);
51782
51843
  init_protocol();
51783
51844
  var GroupFileStore = class {
51784
51845
  dataDir;
@@ -51867,7 +51928,7 @@ var GroupFileStore = class {
51867
51928
  entries[idx] = next;
51868
51929
  } else {
51869
51930
  next = {
51870
- id: `gf-${import_node_crypto8.default.randomBytes(6).toString("base64url")}`,
51931
+ id: `gf-${import_node_crypto9.default.randomBytes(6).toString("base64url")}`,
51871
51932
  relPath: input.relPath,
51872
51933
  from: input.from,
51873
51934
  label: input.label,
@@ -51986,7 +52047,7 @@ function readDaemonSourceFromEnv(env = process.env) {
51986
52047
  // src/tunnel/tunnel-manager.ts
51987
52048
  var import_node_fs33 = __toESM(require("fs"), 1);
51988
52049
  var import_node_path34 = __toESM(require("path"), 1);
51989
- var import_node_crypto9 = __toESM(require("crypto"), 1);
52050
+ var import_node_crypto10 = __toESM(require("crypto"), 1);
51990
52051
  var import_node_child_process9 = require("child_process");
51991
52052
 
51992
52053
  // src/tunnel/tunnel-store.ts
@@ -52485,7 +52546,7 @@ var TunnelManager = class {
52485
52546
  override: this.deps.frpcBinaryOverride ?? void 0
52486
52547
  });
52487
52548
  const tomlPath = import_node_path34.default.join(this.deps.dataDir, "frpc.toml");
52488
- const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto9.default.randomBytes(3).toString("hex")}`;
52549
+ const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto10.default.randomBytes(3).toString("hex")}`;
52489
52550
  const toml = buildFrpcToml({
52490
52551
  serverAddr: t.frpsHost,
52491
52552
  serverPort: t.frpsPort,
@@ -52581,510 +52642,29 @@ async function waitForFrpcReady(proc, timeoutMs) {
52581
52642
  });
52582
52643
  }
52583
52644
 
52584
- // src/sshd/sshd-manager.ts
52585
- var import_node_fs36 = __toESM(require("fs"), 1);
52586
- var import_node_path37 = __toESM(require("path"), 1);
52587
- var import_node_child_process11 = require("child_process");
52588
-
52589
- // src/sshd/sshd-config.ts
52590
- function buildSshdConfig(input) {
52591
- const lines = [
52592
- `ListenAddress ${input.listenAddress}`,
52593
- `Port ${input.port}`,
52594
- `HostKey ${input.hostKeyPath}`,
52595
- `PidFile ${input.pidFilePath}`,
52596
- `AuthorizedKeysFile ${input.authorizedKeysFile}`,
52597
- `PubkeyAuthentication yes`,
52598
- `PasswordAuthentication no`,
52599
- `ChallengeResponseAuthentication no`,
52600
- `KbdInteractiveAuthentication no`,
52601
- `PermitRootLogin no`,
52602
- `StrictModes no`,
52603
- `UsePAM no`,
52604
- `LogLevel INFO`,
52605
- `Subsystem sftp internal-sftp`
52606
- ];
52607
- return lines.join("\n") + "\n";
52608
- }
52609
-
52610
- // src/sshd/sshd-process.ts
52611
- var import_node_fs34 = __toESM(require("fs"), 1);
52612
- var import_node_path35 = __toESM(require("path"), 1);
52613
- var import_node_child_process10 = require("child_process");
52614
- function sshdPidFilePath(dataDir) {
52615
- return import_node_path35.default.join(dataDir, "sshd", "sshd.pid");
52616
- }
52617
- function writeSshdPid(dataDir, pid) {
52618
- try {
52619
- const p2 = sshdPidFilePath(dataDir);
52620
- import_node_fs34.default.mkdirSync(import_node_path35.default.dirname(p2), { recursive: true, mode: 448 });
52621
- import_node_fs34.default.writeFileSync(p2, String(pid), { mode: 384 });
52622
- } catch {
52623
- }
52624
- }
52625
- function clearSshdPid(dataDir) {
52626
- try {
52627
- import_node_fs34.default.unlinkSync(sshdPidFilePath(dataDir));
52628
- } catch {
52629
- }
52630
- }
52631
- function defaultIsPidAlive2(pid) {
52632
- if (!Number.isFinite(pid) || pid <= 0) return false;
52633
- try {
52634
- process.kill(pid, 0);
52635
- return true;
52636
- } catch (err) {
52637
- const code = err.code;
52638
- return code === "EPERM";
52639
- }
52640
- }
52641
- function defaultReadPidFile2(file) {
52642
- try {
52643
- return import_node_fs34.default.readFileSync(file, "utf8");
52644
- } catch {
52645
- return null;
52646
- }
52647
- }
52648
- function defaultKillPid2(pid, signal) {
52649
- try {
52650
- process.kill(pid, signal);
52651
- } catch {
52652
- }
52653
- }
52654
- function defaultSleep2(ms) {
52655
- return new Promise((r) => setTimeout(r, ms));
52656
- }
52657
- async function killStaleSshd(deps) {
52658
- const pidFile = sshdPidFilePath(deps.dataDir);
52659
- const configPath = import_node_path35.default.join(deps.dataDir, "sshd", "sshd_config");
52660
- const readPidFile = deps.readPidFileImpl ?? defaultReadPidFile2;
52661
- const isAlive = deps.isPidAliveImpl ?? defaultIsPidAlive2;
52662
- const killPid = deps.killPidImpl ?? defaultKillPid2;
52663
- const scanPids = deps.scanSshdPidsImpl ?? ((cp) => defaultScanSshdPidsByCmdline(cp, deps.logger));
52664
- const sleep2 = deps.sleepImpl ?? defaultSleep2;
52665
- const victims = /* @__PURE__ */ new Set();
52666
- const raw = readPidFile(pidFile);
52667
- if (raw) {
52668
- const pid = parseInt(raw.trim(), 10);
52669
- if (Number.isFinite(pid) && pid > 0 && pid !== deps.ownPid && isAlive(pid)) {
52670
- victims.add(pid);
52671
- }
52672
- }
52673
- try {
52674
- const scanned = await scanPids(configPath);
52675
- for (const pid of scanned) {
52676
- if (pid > 0 && pid !== deps.ownPid && isAlive(pid)) victims.add(pid);
52677
- }
52678
- } catch (e) {
52679
- deps.logger?.warn("sshd: stale-sshd cmdline scan failed", { err: e.message });
52680
- }
52681
- if (victims.size === 0) {
52682
- try {
52683
- import_node_fs34.default.unlinkSync(pidFile);
52684
- } catch {
52685
- }
52686
- return;
52687
- }
52688
- for (const pid of victims) {
52689
- deps.logger?.warn("sshd: killing stale sshd before respawn", { pid });
52690
- killPid(pid, "SIGKILL");
52691
- }
52692
- await sleep2(deps.reapWaitMs ?? 300);
52693
- try {
52694
- import_node_fs34.default.unlinkSync(pidFile);
52695
- } catch {
52696
- }
52697
- }
52698
- async function defaultScanSshdPidsByCmdline(configPath, logger) {
52699
- if (process.platform === "win32") return [];
52700
- return new Promise((resolve6) => {
52701
- const ps = (0, import_node_child_process10.spawn)("ps", ["-axo", "pid=,command="], { stdio: ["ignore", "pipe", "ignore"] });
52702
- let buf = "";
52703
- ps.stdout.on("data", (c) => {
52704
- buf += c.toString();
52705
- });
52706
- ps.on("exit", () => {
52707
- const pids = [];
52708
- for (const line of buf.split("\n")) {
52709
- const m2 = /^\s*(\d+)\s+(.*)$/.exec(line);
52710
- if (!m2) continue;
52711
- const cmd = m2[2];
52712
- if (!/\bsshd\b/.test(cmd)) continue;
52713
- if (!cmd.includes(configPath)) continue;
52714
- const pid = parseInt(m2[1], 10);
52715
- if (Number.isFinite(pid) && pid > 0) pids.push(pid);
52716
- }
52717
- resolve6(pids);
52718
- });
52719
- ps.on("error", (e) => {
52720
- logger?.warn("sshd: ps scan failed", { err: e.message });
52721
- resolve6([]);
52722
- });
52723
- });
52724
- }
52725
-
52726
- // src/sshd/jail-script.ts
52727
- var import_node_fs35 = __toESM(require("fs"), 1);
52728
- var import_node_path36 = __toESM(require("path"), 1);
52729
- var CLAWD_SSH_JAIL_SCRIPT = String.raw`#!/usr/bin/env bash
52730
- # clawd-ssh-jail — SSH reverse access sandbox wrapper (managed by clawd; do not edit)
52731
- #
52732
- # 由 sshd authorized_keys 的 command= 强制入口调用。
52733
- # 用法: sshd 会以 \`clawd-ssh-jail <deviceId>\` 起本脚本;$SSH_ORIGINAL_COMMAND = client
52734
- # 真实请求(interactive shell 时为空)。
52735
- #
52736
- # 职责:
52737
- # 1. 读 ~/.clawd/contacts.json 找 contact.exposedDirs
52738
- # 2. macOS 用 sandbox-exec + sbpl; Linux 用 bwrap
52739
- # 3. exec 沙箱 shell
52740
-
52741
- set -euo pipefail
52742
-
52743
- DEVICE_ID="\${1:-}"
52744
- if [ -z "$DEVICE_ID" ]; then
52745
- echo "clawd-ssh-jail: missing deviceId" >&2
52746
- exit 1
52747
- fi
52748
-
52749
- CONTACTS="\${HOME}/.clawd/contacts.json"
52750
- if [ ! -f "$CONTACTS" ]; then
52751
- echo "clawd-ssh-jail: contacts.json missing" >&2
52752
- exit 1
52753
- fi
52754
-
52755
- # 读 contact 的 exposedDirs (mac/linux 都自带 python3)
52756
- EXPOSED_JSON=$(python3 -c "
52757
- import json, sys
52758
- with open('$CONTACTS') as f:
52759
- data = json.load(f)
52760
- for c in data.get('contacts', []):
52761
- if c.get('deviceId') == '$DEVICE_ID':
52762
- if not c.get('sshAllowed'):
52763
- print('DENIED', file=sys.stderr); sys.exit(2)
52764
- for d in c.get('exposedDirs', []):
52765
- print(d)
52766
- sys.exit(0)
52767
- sys.exit(3)
52768
- ")
52769
-
52770
- if [ -z "$EXPOSED_JSON" ]; then
52771
- echo "clawd-ssh-jail: contact not found or no exposed dirs" >&2
52772
- exit 1
52773
- fi
52774
-
52775
- # 校验路径安全(bash 侧二次防御)
52776
- while IFS= read -r line; do
52777
- case "$line" in
52778
- /*) : ;;
52779
- *) echo "clawd-ssh-jail: bad path: $line" >&2; exit 1 ;;
52780
- esac
52781
- case "$line" in
52782
- *[\"\'\`\$\;\|\&\(\)\{\}\[\]\<\>\*\?]*)
52783
- echo "clawd-ssh-jail: unsafe path: $line" >&2; exit 1 ;;
52784
- esac
52785
- done <<< "$EXPOSED_JSON"
52786
-
52787
- CMD="\${SSH_ORIGINAL_COMMAND:-}"
52788
- if [ -z "$CMD" ]; then
52789
- SHELL_CMD=(bash --login)
52790
- else
52791
- SHELL_CMD=(bash -c "$CMD")
52792
- fi
52793
-
52794
- case "$(uname -s)" in
52795
- Darwin)
52796
- POLICY="(version 1)
52797
- (deny default)
52798
- (allow process*)
52799
- (allow signal (target self))
52800
- (allow sysctl-read)
52801
- (allow mach-lookup)
52802
- (allow file-read-metadata)
52803
- (allow network*)
52804
- (allow file-read* (subpath \"/usr\"))
52805
- (allow file-read* (subpath \"/bin\"))
52806
- (allow file-read* (subpath \"/sbin\"))
52807
- (allow file-read* (subpath \"/System\"))
52808
- (allow file-read* (subpath \"/Library\"))
52809
- (allow file-read* (subpath \"/etc\"))
52810
- (allow file-read* (subpath \"/private/etc\"))"
52811
- while IFS= read -r d; do
52812
- POLICY+="
52813
- (allow file-read* file-write* (subpath \"$d\"))"
52814
- done <<< "$EXPOSED_JSON"
52815
- exec sandbox-exec -p "$POLICY" "\${SHELL_CMD[@]}"
52816
- ;;
52817
- Linux)
52818
- BWRAP_ARGS=(
52819
- --unshare-user --unshare-ipc --unshare-pid --unshare-uts
52820
- --die-with-parent
52821
- --proc /proc --dev /dev --tmpfs /tmp
52822
- --ro-bind /usr /usr --ro-bind /bin /bin --ro-bind /sbin /sbin
52823
- --ro-bind /lib /lib --ro-bind /etc /etc
52824
- )
52825
- if [ -d /lib64 ]; then BWRAP_ARGS+=(--ro-bind /lib64 /lib64); fi
52826
- while IFS= read -r d; do
52827
- BWRAP_ARGS+=(--bind "$d" "$d")
52828
- done <<< "$EXPOSED_JSON"
52829
- exec bwrap "\${BWRAP_ARGS[@]}" "\${SHELL_CMD[@]}"
52830
- ;;
52831
- *)
52832
- echo "clawd-ssh-jail: unsupported OS $(uname -s)" >&2
52833
- exit 1
52834
- ;;
52835
- esac
52836
- `;
52837
- function ensureJailScript(dataDir) {
52838
- const binDir = import_node_path36.default.join(dataDir, "bin");
52839
- import_node_fs35.default.mkdirSync(binDir, { recursive: true, mode: 493 });
52840
- const target = import_node_path36.default.join(binDir, "clawd-ssh-jail");
52841
- import_node_fs35.default.writeFileSync(target, CLAWD_SSH_JAIL_SCRIPT, { mode: 493 });
52842
- return target;
52843
- }
52844
-
52845
- // src/sshd/sshd-manager.ts
52846
- var SshdManager = class {
52847
- constructor(deps) {
52848
- this.deps = deps;
52849
- this.sshdDir = import_node_path37.default.join(deps.dataDir, "sshd");
52850
- this.startupTimeoutMs = deps.startupTimeoutMs ?? 15e3;
52851
- }
52852
- deps;
52853
- proc = null;
52854
- sshdDir;
52855
- stopping = false;
52856
- exitHookInstalled = false;
52857
- startupTimeoutMs;
52858
- get port() {
52859
- return this.deps.port;
52860
- }
52861
- async start() {
52862
- const { logger } = this.deps;
52863
- await (this.deps.killStaleImpl ?? killStaleSshd)({
52864
- dataDir: this.deps.dataDir,
52865
- ownPid: process.pid,
52866
- logger
52867
- });
52868
- import_node_fs36.default.mkdirSync(this.sshdDir, { recursive: true, mode: 448 });
52869
- import_node_fs36.default.mkdirSync(import_node_path37.default.join(this.sshdDir, "authorized_keys.d"), { recursive: true, mode: 448 });
52870
- ensureJailScript(this.deps.dataDir);
52871
- const hostKeyPath = import_node_path37.default.join(this.sshdDir, "host_key");
52872
- if (!import_node_fs36.default.existsSync(hostKeyPath)) {
52873
- await this.generateHostKey(hostKeyPath);
52874
- }
52875
- const akFile = import_node_path37.default.join(this.sshdDir, "authorized_keys.d", "clawd-contacts");
52876
- if (!import_node_fs36.default.existsSync(akFile)) {
52877
- import_node_fs36.default.writeFileSync(akFile, "", { mode: 384 });
52878
- }
52879
- const configPath = import_node_path37.default.join(this.sshdDir, "sshd_config");
52880
- const config = buildSshdConfig({
52881
- listenAddress: "127.0.0.1",
52882
- port: this.deps.port,
52883
- hostKeyPath,
52884
- authorizedKeysFile: akFile,
52885
- pidFilePath: import_node_path37.default.join(this.sshdDir, "sshd.pid")
52886
- });
52887
- import_node_fs36.default.writeFileSync(configPath, config, { mode: 384 });
52888
- const sshdBin = this.deps.sshdBin ?? "/usr/sbin/sshd";
52889
- const proc = (this.deps.spawnImpl ?? import_node_child_process11.spawn)(sshdBin, ["-D", "-e", "-f", configPath], {
52890
- stdio: ["ignore", "pipe", "pipe"]
52891
- });
52892
- const logStream = import_node_fs36.default.createWriteStream(import_node_path37.default.join(this.sshdDir, "sshd.log"), {
52893
- flags: "a",
52894
- mode: 384
52895
- });
52896
- logStream.on("error", () => {
52897
- });
52898
- const tee = (c) => {
52899
- logStream.write(String(c));
52900
- };
52901
- proc.stdout?.on("data", tee);
52902
- proc.stderr?.on("data", tee);
52903
- proc.once("exit", () => logStream.end());
52904
- const ready = await waitForSshdReady(proc, this.startupTimeoutMs);
52905
- if (!ready.ok) {
52906
- try {
52907
- proc.kill("SIGTERM");
52908
- } catch {
52909
- }
52910
- const tail = ready.output.slice(-500);
52911
- const msg = tail ? `${ready.error}
52912
- ${tail}` : ready.error;
52913
- throw new Error(msg);
52914
- }
52915
- if (typeof proc.pid === "number") writeSshdPid(this.deps.dataDir, proc.pid);
52916
- this.proc = proc;
52917
- this.installProcessExitHandlersIfNeeded();
52918
- this.attachExitListener(proc);
52919
- logger?.info("sshd: up", { port: this.deps.port, pid: proc.pid ?? null });
52920
- return { port: this.deps.port };
52921
- }
52922
- async stop() {
52923
- this.stopping = true;
52924
- const proc = this.proc;
52925
- this.proc = null;
52926
- if (!proc) {
52927
- clearSshdPid(this.deps.dataDir);
52928
- return;
52929
- }
52930
- proc.kill("SIGTERM");
52931
- await new Promise((resolve6) => {
52932
- const t = setTimeout(() => {
52933
- try {
52934
- proc.kill("SIGKILL");
52935
- } catch {
52936
- }
52937
- resolve6();
52938
- }, 5e3);
52939
- proc.once("exit", () => {
52940
- clearTimeout(t);
52941
- resolve6();
52942
- });
52943
- });
52944
- clearSshdPid(this.deps.dataDir);
52945
- }
52946
- killSync() {
52947
- const proc = this.proc;
52948
- this.proc = null;
52949
- clearSshdPid(this.deps.dataDir);
52950
- if (!proc) return;
52951
- try {
52952
- proc.kill("SIGTERM");
52953
- } catch {
52954
- }
52955
- }
52956
- attachExitListener(proc) {
52957
- proc.on("exit", (code) => {
52958
- this.deps.logger?.warn("sshd exited", { code });
52959
- if (this.stopping) return;
52960
- this.proc = null;
52961
- this.deps.onSshdExit?.({ code });
52962
- });
52963
- }
52964
- installProcessExitHandlersIfNeeded() {
52965
- if (this.exitHookInstalled) return;
52966
- if (this.deps.installProcessExitHandlers !== true) return;
52967
- this.exitHookInstalled = true;
52968
- const sync = () => this.killSync();
52969
- process.once("exit", sync);
52970
- process.once("SIGHUP", sync);
52971
- process.once("uncaughtException", sync);
52972
- }
52973
- async generateHostKey(hostKeyPath) {
52974
- const keygenBin = this.deps.keygenBin ?? "/usr/bin/ssh-keygen";
52975
- await new Promise((resolve6, reject) => {
52976
- const p2 = (this.deps.spawnImpl ?? import_node_child_process11.spawn)(
52977
- keygenBin,
52978
- ["-t", "ed25519", "-f", hostKeyPath, "-N", "", "-q"],
52979
- { stdio: "ignore" }
52980
- );
52981
- p2.on("exit", (code) => code === 0 ? resolve6() : reject(new Error(`ssh-keygen exit ${code}`)));
52982
- p2.on("error", reject);
52983
- });
52984
- try {
52985
- import_node_fs36.default.chmodSync(hostKeyPath, 384);
52986
- } catch {
52987
- }
52988
- }
52989
- };
52990
- async function waitForSshdReady(proc, timeoutMs) {
52991
- return new Promise((resolve6) => {
52992
- let settled = false;
52993
- let buf = "";
52994
- const finish = (r) => {
52995
- if (settled) return;
52996
- settled = true;
52997
- cleanup();
52998
- resolve6(r);
52999
- };
53000
- const onData = (chunk) => {
53001
- buf += String(chunk);
53002
- if (/Server listening on/i.test(buf)) finish({ ok: true });
53003
- if (/fatal:/i.test(buf) || /error: Bind to port/i.test(buf)) {
53004
- finish({ ok: false, error: "sshd startup failed", output: buf });
53005
- }
53006
- };
53007
- const onExit = (code) => finish({ ok: false, error: `sshd exited before ready (code=${code})`, output: buf });
53008
- const onErr = (err) => finish({ ok: false, error: `sshd spawn error: ${err.message}`, output: buf });
53009
- const cleanup = () => {
53010
- proc.stdout?.off("data", onData);
53011
- proc.stderr?.off("data", onData);
53012
- proc.off("exit", onExit);
53013
- proc.off("error", onErr);
53014
- clearTimeout(timer);
53015
- };
53016
- proc.stdout?.on("data", onData);
53017
- proc.stderr?.on("data", onData);
53018
- proc.on("exit", onExit);
53019
- proc.on("error", onErr);
53020
- const timer = setTimeout(
53021
- () => finish({ ok: false, error: `sshd startup timeout after ${timeoutMs}ms`, output: buf }),
53022
- timeoutMs
53023
- );
53024
- });
53025
- }
53026
-
53027
- // src/sshd/authorized-keys.ts
53028
- var import_node_fs37 = __toESM(require("fs"), 1);
53029
- var import_node_path38 = __toESM(require("path"), 1);
53030
- var JAIL_BIN_PATH_ENV = "CLAWD_JAIL_BIN_PATH";
53031
- var AUTHORIZED_KEYS_FILE = "clawd-contacts";
53032
- function jailBinPath() {
53033
- return process.env[JAIL_BIN_PATH_ENV] ?? import_node_path38.default.join(process.env.HOME ?? "", ".clawd", "bin", "clawd-ssh-jail");
53034
- }
53035
- function rebuildAuthorizedKeys(store, sshdDir) {
53036
- const akDir = import_node_path38.default.join(sshdDir, "authorized_keys.d");
53037
- const target = import_node_path38.default.join(akDir, AUTHORIZED_KEYS_FILE);
53038
- import_node_fs37.default.mkdirSync(akDir, { recursive: true, mode: 448 });
53039
- const lines = ["# managed by clawd; do not edit", ""];
53040
- for (const c of store.list()) {
53041
- if (!c.sshAllowed) continue;
53042
- const safe = /^[A-Za-z0-9_.-]+$/.test(c.deviceId);
53043
- if (!safe) continue;
53044
- const pubkey = readIssuedPubkey(sshdDir, c.deviceId);
53045
- if (!pubkey) continue;
53046
- const bin = jailBinPath();
53047
- lines.push(`command="${bin} ${c.deviceId}",restrict ${pubkey.trim()}`);
53048
- lines.push(`# contact:${c.deviceId}`);
53049
- }
53050
- const body = lines.join("\n") + "\n";
53051
- const tmp = `${target}.tmp-${process.pid}-${Date.now()}`;
53052
- import_node_fs37.default.writeFileSync(tmp, body, { mode: 384 });
53053
- import_node_fs37.default.renameSync(tmp, target);
53054
- }
53055
- function readIssuedPubkey(sshdDir, deviceId) {
53056
- const safeId = deviceId.replace(/[\/\\]/g, "_");
53057
- const p2 = import_node_path38.default.join(sshdDir, "keys", `${safeId}.ed25519.pub`);
53058
- try {
53059
- return import_node_fs37.default.readFileSync(p2, "utf8");
53060
- } catch {
53061
- return null;
53062
- }
53063
- }
53064
-
53065
52645
  // src/tunnel/device-key.ts
53066
52646
  var import_node_os14 = __toESM(require("os"), 1);
53067
- var import_node_path39 = __toESM(require("path"), 1);
53068
- var import_node_crypto10 = __toESM(require("crypto"), 1);
52647
+ var import_node_path35 = __toESM(require("path"), 1);
52648
+ var import_node_crypto11 = __toESM(require("crypto"), 1);
53069
52649
  var DERIVE_SALT = "clawd-tunnel-device-v1";
53070
52650
  function deriveStableDeviceKey(opts = {}) {
53071
52651
  const hostname = opts.hostname ?? import_node_os14.default.hostname();
53072
52652
  const uid = opts.uid ?? (typeof import_node_os14.default.userInfo === "function" ? import_node_os14.default.userInfo().uid : 0);
53073
52653
  const home = opts.home ?? import_node_os14.default.homedir();
53074
- const defaultDataDir = import_node_path39.default.resolve(import_node_path39.default.join(home, ".clawd"));
53075
- const normalizedDataDir = opts.dataDir ? import_node_path39.default.resolve(opts.dataDir) : null;
52654
+ const defaultDataDir = import_node_path35.default.resolve(import_node_path35.default.join(home, ".clawd"));
52655
+ const normalizedDataDir = opts.dataDir ? import_node_path35.default.resolve(opts.dataDir) : null;
53076
52656
  const isDefaultDir = normalizedDataDir == null || normalizedDataDir === defaultDataDir;
53077
52657
  const input = isDefaultDir ? `${hostname}::${uid}` : `${hostname}::${uid}::${normalizedDataDir}`;
53078
- return import_node_crypto10.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
52658
+ return import_node_crypto11.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
53079
52659
  }
53080
52660
 
53081
52661
  // src/auth-store.ts
53082
- var import_node_fs38 = __toESM(require("fs"), 1);
53083
- var import_node_path40 = __toESM(require("path"), 1);
53084
- var import_node_crypto11 = __toESM(require("crypto"), 1);
52662
+ var import_node_fs34 = __toESM(require("fs"), 1);
52663
+ var import_node_path36 = __toESM(require("path"), 1);
52664
+ var import_node_crypto12 = __toESM(require("crypto"), 1);
53085
52665
  var AUTH_FILE_NAME = "auth.json";
53086
52666
  function authFilePath(dataDir) {
53087
- return import_node_path40.default.join(dataDir, AUTH_FILE_NAME);
52667
+ return import_node_path36.default.join(dataDir, AUTH_FILE_NAME);
53088
52668
  }
53089
52669
  function loadOrCreateAuthFile(opts) {
53090
52670
  const file = authFilePath(opts.dataDir);
@@ -53113,14 +52693,14 @@ function loadOrCreateAuthFile(opts) {
53113
52693
  return next;
53114
52694
  }
53115
52695
  function defaultGenerateToken() {
53116
- return import_node_crypto11.default.randomBytes(32).toString("base64url");
52696
+ return import_node_crypto12.default.randomBytes(32).toString("base64url");
53117
52697
  }
53118
52698
  function defaultGenerateOwnerPrincipalId() {
53119
- return `owner-${import_node_crypto11.default.randomUUID()}`;
52699
+ return `owner-${import_node_crypto12.default.randomUUID()}`;
53120
52700
  }
53121
52701
  function readAuthFile(file) {
53122
52702
  try {
53123
- const raw = import_node_fs38.default.readFileSync(file, "utf8");
52703
+ const raw = import_node_fs34.default.readFileSync(file, "utf8");
53124
52704
  const parsed = JSON.parse(raw);
53125
52705
  if (typeof parsed?.token !== "string" || parsed.token.length === 0) {
53126
52706
  return null;
@@ -53140,25 +52720,25 @@ function readAuthFile(file) {
53140
52720
  }
53141
52721
  }
53142
52722
  function writeAuthFile(file, content) {
53143
- import_node_fs38.default.mkdirSync(import_node_path40.default.dirname(file), { recursive: true });
53144
- import_node_fs38.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
52723
+ import_node_fs34.default.mkdirSync(import_node_path36.default.dirname(file), { recursive: true });
52724
+ import_node_fs34.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
53145
52725
  try {
53146
- import_node_fs38.default.chmodSync(file, 384);
52726
+ import_node_fs34.default.chmodSync(file, 384);
53147
52727
  } catch {
53148
52728
  }
53149
52729
  }
53150
52730
 
53151
52731
  // src/owner-profile.ts
53152
- var import_node_fs39 = __toESM(require("fs"), 1);
52732
+ var import_node_fs35 = __toESM(require("fs"), 1);
53153
52733
  var import_node_os15 = __toESM(require("os"), 1);
53154
- var import_node_path41 = __toESM(require("path"), 1);
52734
+ var import_node_path37 = __toESM(require("path"), 1);
53155
52735
  var PROFILE_FILENAME = "profile.json";
53156
52736
  function loadOwnerDisplayName(dataDir) {
53157
52737
  const fallback = import_node_os15.default.userInfo().username;
53158
- const profilePath = import_node_path41.default.join(dataDir, PROFILE_FILENAME);
52738
+ const profilePath = import_node_path37.default.join(dataDir, PROFILE_FILENAME);
53159
52739
  let raw;
53160
52740
  try {
53161
- raw = import_node_fs39.default.readFileSync(profilePath, "utf8");
52741
+ raw = import_node_fs35.default.readFileSync(profilePath, "utf8");
53162
52742
  } catch {
53163
52743
  return fallback;
53164
52744
  }
@@ -53181,18 +52761,18 @@ function loadOwnerDisplayName(dataDir) {
53181
52761
  }
53182
52762
 
53183
52763
  // src/feishu-auth/owner-identity-store.ts
53184
- var import_node_fs40 = __toESM(require("fs"), 1);
53185
- var import_node_path42 = __toESM(require("path"), 1);
52764
+ var import_node_fs36 = __toESM(require("fs"), 1);
52765
+ var import_node_path38 = __toESM(require("path"), 1);
53186
52766
  var OWNER_IDENTITY_FILE_NAME = "owner-identity.json";
53187
52767
  var OwnerIdentityStore = class {
53188
52768
  file;
53189
52769
  constructor(dataDir) {
53190
- this.file = import_node_path42.default.join(dataDir, OWNER_IDENTITY_FILE_NAME);
52770
+ this.file = import_node_path38.default.join(dataDir, OWNER_IDENTITY_FILE_NAME);
53191
52771
  }
53192
52772
  read() {
53193
52773
  let raw;
53194
52774
  try {
53195
- raw = import_node_fs40.default.readFileSync(this.file, "utf8");
52775
+ raw = import_node_fs36.default.readFileSync(this.file, "utf8");
53196
52776
  } catch {
53197
52777
  return null;
53198
52778
  }
@@ -53220,16 +52800,16 @@ var OwnerIdentityStore = class {
53220
52800
  };
53221
52801
  }
53222
52802
  write(record) {
53223
- import_node_fs40.default.mkdirSync(import_node_path42.default.dirname(this.file), { recursive: true });
53224
- import_node_fs40.default.writeFileSync(this.file, JSON.stringify(record, null, 2), { mode: 384 });
52803
+ import_node_fs36.default.mkdirSync(import_node_path38.default.dirname(this.file), { recursive: true });
52804
+ import_node_fs36.default.writeFileSync(this.file, JSON.stringify(record, null, 2), { mode: 384 });
53225
52805
  try {
53226
- import_node_fs40.default.chmodSync(this.file, 384);
52806
+ import_node_fs36.default.chmodSync(this.file, 384);
53227
52807
  } catch {
53228
52808
  }
53229
52809
  }
53230
52810
  clear() {
53231
52811
  try {
53232
- import_node_fs40.default.unlinkSync(this.file);
52812
+ import_node_fs36.default.unlinkSync(this.file);
53233
52813
  } catch (err) {
53234
52814
  const code = err?.code;
53235
52815
  if (code !== "ENOENT") throw err;
@@ -53238,7 +52818,7 @@ var OwnerIdentityStore = class {
53238
52818
  };
53239
52819
 
53240
52820
  // src/feishu-auth/login-flow.ts
53241
- var import_node_crypto12 = __toESM(require("crypto"), 1);
52821
+ var import_node_crypto13 = __toESM(require("crypto"), 1);
53242
52822
  var STATE_TTL_MS = 5 * 60 * 1e3;
53243
52823
  var LoginFlow = class {
53244
52824
  constructor(deps) {
@@ -53247,7 +52827,7 @@ var LoginFlow = class {
53247
52827
  deps;
53248
52828
  pendingStates = /* @__PURE__ */ new Map();
53249
52829
  start() {
53250
- const state = import_node_crypto12.default.randomBytes(16).toString("base64url");
52830
+ const state = import_node_crypto13.default.randomBytes(16).toString("base64url");
53251
52831
  const now = (this.deps.now ?? Date.now)();
53252
52832
  this.pendingStates.set(state, now);
53253
52833
  this.gcExpired(now);
@@ -53350,9 +52930,9 @@ var CentralClientError = class extends Error {
53350
52930
  code;
53351
52931
  cause;
53352
52932
  };
53353
- async function centralRequest(opts, path73, init) {
52933
+ async function centralRequest(opts, path68, init) {
53354
52934
  const f = opts.fetchImpl ?? globalThis.fetch;
53355
- const url = `${opts.api.replace(/\/+$/, "")}${path73}`;
52935
+ const url = `${opts.api.replace(/\/+$/, "")}${path68}`;
53356
52936
  const ctrl = new AbortController();
53357
52937
  const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 15e3);
53358
52938
  let res;
@@ -53494,8 +53074,8 @@ function verifyConnectToken(args) {
53494
53074
  }
53495
53075
 
53496
53076
  // src/feishu-auth/server-key.ts
53497
- var fs50 = __toESM(require("fs"), 1);
53498
- var path51 = __toESM(require("path"), 1);
53077
+ var fs46 = __toESM(require("fs"), 1);
53078
+ var path47 = __toESM(require("path"), 1);
53499
53079
  var FILE_NAME2 = "server-signing-key.json";
53500
53080
  var ServerKeyStore = class {
53501
53081
  constructor(dataDir) {
@@ -53503,12 +53083,12 @@ var ServerKeyStore = class {
53503
53083
  }
53504
53084
  dataDir;
53505
53085
  filePath() {
53506
- return path51.join(this.dataDir, FILE_NAME2);
53086
+ return path47.join(this.dataDir, FILE_NAME2);
53507
53087
  }
53508
53088
  /** 读缓存的公钥;无缓存 / 损坏 → null(调用方决定是否触发拉取) */
53509
53089
  read() {
53510
53090
  try {
53511
- const raw = fs50.readFileSync(this.filePath(), "utf8");
53091
+ const raw = fs46.readFileSync(this.filePath(), "utf8");
53512
53092
  const parsed = JSON.parse(raw);
53513
53093
  if (typeof parsed.publicKeyPem === "string" && parsed.publicKeyPem.includes("PUBLIC KEY")) {
53514
53094
  return parsed.publicKeyPem;
@@ -53523,12 +53103,12 @@ var ServerKeyStore = class {
53523
53103
  publicKeyPem,
53524
53104
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
53525
53105
  };
53526
- fs50.mkdirSync(this.dataDir, { recursive: true });
53527
- fs50.writeFileSync(this.filePath(), JSON.stringify(content, null, 2), { mode: 384 });
53106
+ fs46.mkdirSync(this.dataDir, { recursive: true });
53107
+ fs46.writeFileSync(this.filePath(), JSON.stringify(content, null, 2), { mode: 384 });
53528
53108
  }
53529
53109
  clear() {
53530
53110
  try {
53531
- fs50.unlinkSync(this.filePath());
53111
+ fs46.unlinkSync(this.filePath());
53532
53112
  } catch {
53533
53113
  }
53534
53114
  }
@@ -53541,12 +53121,12 @@ init_protocol();
53541
53121
  init_protocol();
53542
53122
 
53543
53123
  // src/session/fork.ts
53544
- var import_node_fs41 = __toESM(require("fs"), 1);
53124
+ var import_node_fs37 = __toESM(require("fs"), 1);
53545
53125
  var import_node_os16 = __toESM(require("os"), 1);
53546
- var import_node_path43 = __toESM(require("path"), 1);
53126
+ var import_node_path39 = __toESM(require("path"), 1);
53547
53127
  init_claude_history();
53548
53128
  function readJsonlEntries(file) {
53549
- const raw = import_node_fs41.default.readFileSync(file, "utf8");
53129
+ const raw = import_node_fs37.default.readFileSync(file, "utf8");
53550
53130
  const out = [];
53551
53131
  for (const line of raw.split("\n")) {
53552
53132
  const t = line.trim();
@@ -53559,10 +53139,10 @@ function readJsonlEntries(file) {
53559
53139
  return out;
53560
53140
  }
53561
53141
  function forkSession(input) {
53562
- const baseDir = input.baseDir ?? import_node_path43.default.join(import_node_os16.default.homedir(), ".claude");
53563
- const projectDir = import_node_path43.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
53564
- const sourceFile = import_node_path43.default.join(projectDir, `${input.toolSessionId}.jsonl`);
53565
- if (!import_node_fs41.default.existsSync(sourceFile)) {
53142
+ const baseDir = input.baseDir ?? import_node_path39.default.join(import_node_os16.default.homedir(), ".claude");
53143
+ const projectDir = import_node_path39.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
53144
+ const sourceFile = import_node_path39.default.join(projectDir, `${input.toolSessionId}.jsonl`);
53145
+ if (!import_node_fs37.default.existsSync(sourceFile)) {
53566
53146
  throw new Error(`fork: source transcript not found: ${sourceFile}`);
53567
53147
  }
53568
53148
  const entries = readJsonlEntries(sourceFile);
@@ -53592,9 +53172,9 @@ function forkSession(input) {
53592
53172
  }
53593
53173
  forkedLines.push(JSON.stringify(forked));
53594
53174
  }
53595
- const forkedFilePath = import_node_path43.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
53596
- import_node_fs41.default.mkdirSync(projectDir, { recursive: true });
53597
- import_node_fs41.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
53175
+ const forkedFilePath = import_node_path39.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
53176
+ import_node_fs37.default.mkdirSync(projectDir, { recursive: true });
53177
+ import_node_fs37.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
53598
53178
  return { forkedToolSessionId, forkedFilePath };
53599
53179
  }
53600
53180
 
@@ -53946,7 +53526,7 @@ function buildPermissionHandlers(deps) {
53946
53526
  }
53947
53527
 
53948
53528
  // src/handlers/history.ts
53949
- var path54 = __toESM(require("path"), 1);
53529
+ var path50 = __toESM(require("path"), 1);
53950
53530
  init_protocol();
53951
53531
 
53952
53532
  // src/session/recent-dirs.ts
@@ -53964,7 +53544,7 @@ function listRecentDirs(store, limit = 50) {
53964
53544
  }
53965
53545
 
53966
53546
  // src/permission/persona-paths.ts
53967
- var path53 = __toESM(require("path"), 1);
53547
+ var path49 = __toESM(require("path"), 1);
53968
53548
  function getAllowedPersonaIds(grants, action) {
53969
53549
  const ids = /* @__PURE__ */ new Set();
53970
53550
  for (const g2 of grants) {
@@ -53977,42 +53557,42 @@ function getAllowedPersonaIds(grants, action) {
53977
53557
  return ids;
53978
53558
  }
53979
53559
  function isGuestPathAllowed(grants, absPath, personaRoot, action = "read", userWorkDir) {
53980
- const target = path53.resolve(absPath);
53560
+ const target = path49.resolve(absPath);
53981
53561
  if (userWorkDir) {
53982
- const u = path53.resolve(userWorkDir);
53983
- const usep = u.endsWith(path53.sep) ? "" : path53.sep;
53562
+ const u = path49.resolve(userWorkDir);
53563
+ const usep = u.endsWith(path49.sep) ? "" : path49.sep;
53984
53564
  if (target === u || target.startsWith(u + usep)) return true;
53985
53565
  }
53986
- const root = path53.resolve(personaRoot);
53987
- const sep3 = root.endsWith(path53.sep) ? "" : path53.sep;
53566
+ const root = path49.resolve(personaRoot);
53567
+ const sep3 = root.endsWith(path49.sep) ? "" : path49.sep;
53988
53568
  if (!target.startsWith(root + sep3)) return false;
53989
- const rel = path53.relative(root, target);
53569
+ const rel = path49.relative(root, target);
53990
53570
  if (!rel || rel.startsWith("..")) return false;
53991
- const personaId = rel.split(path53.sep)[0];
53571
+ const personaId = rel.split(path49.sep)[0];
53992
53572
  if (!personaId) return false;
53993
53573
  const allowed = getAllowedPersonaIds(grants, action);
53994
53574
  if (allowed === "*") return true;
53995
53575
  return allowed.has(personaId);
53996
53576
  }
53997
53577
  function personaIdFromPath(absPath, personaRoot) {
53998
- const root = path53.resolve(personaRoot);
53999
- const target = path53.resolve(absPath);
54000
- const sep3 = root.endsWith(path53.sep) ? "" : path53.sep;
53578
+ const root = path49.resolve(personaRoot);
53579
+ const target = path49.resolve(absPath);
53580
+ const sep3 = root.endsWith(path49.sep) ? "" : path49.sep;
54001
53581
  if (!target.startsWith(root + sep3)) return null;
54002
- const rel = path53.relative(root, target);
53582
+ const rel = path49.relative(root, target);
54003
53583
  if (!rel || rel.startsWith("..")) return null;
54004
- const id = rel.split(path53.sep)[0];
53584
+ const id = rel.split(path49.sep)[0];
54005
53585
  return id || null;
54006
53586
  }
54007
53587
  function isPathWithin(dir, absPath) {
54008
- const d = path53.resolve(dir);
54009
- const t = path53.resolve(absPath);
54010
- const sep3 = d.endsWith(path53.sep) ? "" : path53.sep;
53588
+ const d = path49.resolve(dir);
53589
+ const t = path49.resolve(absPath);
53590
+ const sep3 = d.endsWith(path49.sep) ? "" : path49.sep;
54011
53591
  return t === d || t.startsWith(d + sep3);
54012
53592
  }
54013
53593
  function isPathInGuestBoundary(personaRoot, personaId, userWorkDir, absPath) {
54014
53594
  if (userWorkDir && isPathWithin(userWorkDir, absPath)) return true;
54015
- return personaIdFromPath(path53.resolve(absPath), personaRoot) === personaId;
53595
+ return personaIdFromPath(path49.resolve(absPath), personaRoot) === personaId;
54016
53596
  }
54017
53597
 
54018
53598
  // src/handlers/history.ts
@@ -54038,7 +53618,7 @@ function buildHistoryHandlers(deps) {
54038
53618
  if (!pid) return false;
54039
53619
  return isGuestPathAllowed(
54040
53620
  ctx.grants,
54041
- path54.join(personaRoot, pid),
53621
+ path50.join(personaRoot, pid),
54042
53622
  personaRoot,
54043
53623
  "read",
54044
53624
  userWorkDir
@@ -54050,7 +53630,7 @@ function buildHistoryHandlers(deps) {
54050
53630
  };
54051
53631
  const list = async (frame, _client, ctx) => {
54052
53632
  const args = HistoryListArgs.parse(frame);
54053
- assertGuestPath(ctx, path54.resolve(args.projectPath), personaRoot, "history:list");
53633
+ assertGuestPath(ctx, path50.resolve(args.projectPath), personaRoot, "history:list");
54054
53634
  const sessions = await history.listSessions(args);
54055
53635
  return { response: { type: "history:list", sessions } };
54056
53636
  };
@@ -54082,13 +53662,13 @@ function buildHistoryHandlers(deps) {
54082
53662
  };
54083
53663
  const subagents = async (frame, _client, ctx) => {
54084
53664
  const args = HistorySubagentsArgs.parse(frame);
54085
- assertGuestPath(ctx, path54.resolve(args.cwd), personaRoot, "history:subagents", usersRoot);
53665
+ assertGuestPath(ctx, path50.resolve(args.cwd), personaRoot, "history:subagents", usersRoot);
54086
53666
  const subs = await history.listSubagents(args);
54087
53667
  return { response: { type: "history:subagents", subagents: subs } };
54088
53668
  };
54089
53669
  const subagentRead = async (frame, _client, ctx) => {
54090
53670
  const args = HistorySubagentReadArgs.parse(frame);
54091
- assertGuestPath(ctx, path54.resolve(args.cwd), personaRoot, "history:subagent-read", usersRoot);
53671
+ assertGuestPath(ctx, path50.resolve(args.cwd), personaRoot, "history:subagent-read", usersRoot);
54092
53672
  const res = await history.readSubagent(args);
54093
53673
  return { response: { type: "history:subagent-read", ...res } };
54094
53674
  };
@@ -54097,7 +53677,7 @@ function buildHistoryHandlers(deps) {
54097
53677
  if (ctx?.principal.kind === "guest" && personaRoot) {
54098
53678
  const userWorkDir = usersRoot ? deriveUserWorkDir(ctx.principal.id, usersRoot) : void 0;
54099
53679
  const filtered = dirs.filter(
54100
- (d) => isGuestPathAllowed(ctx.grants, path54.resolve(d.cwd), personaRoot, "read", userWorkDir)
53680
+ (d) => isGuestPathAllowed(ctx.grants, path50.resolve(d.cwd), personaRoot, "read", userWorkDir)
54101
53681
  );
54102
53682
  return { response: { type: "history:recentDirs", dirs: filtered } };
54103
53683
  }
@@ -54114,7 +53694,7 @@ function buildHistoryHandlers(deps) {
54114
53694
  }
54115
53695
 
54116
53696
  // src/handlers/workspace.ts
54117
- var path55 = __toESM(require("path"), 1);
53697
+ var path51 = __toESM(require("path"), 1);
54118
53698
  var os16 = __toESM(require("os"), 1);
54119
53699
  init_protocol();
54120
53700
  init_protocol();
@@ -54156,22 +53736,22 @@ function buildWorkspaceHandlers(deps) {
54156
53736
  const args = WorkspaceListArgs.parse(frame);
54157
53737
  const isGuest = ctx?.principal.kind === "guest";
54158
53738
  const fallbackCwd = isGuest && personaRoot ? personaRoot : os16.homedir();
54159
- const resolvedCwd = path55.resolve(args.cwd ?? fallbackCwd);
54160
- const target = args.path ? path55.resolve(resolvedCwd, args.path) : resolvedCwd;
53739
+ const resolvedCwd = path51.resolve(args.cwd ?? fallbackCwd);
53740
+ const target = args.path ? path51.resolve(resolvedCwd, args.path) : resolvedCwd;
54161
53741
  assertGuestPath2(ctx, target, personaRoot, "workspace:list", usersRoot);
54162
53742
  const res = workspace.list({ ...args, cwd: resolvedCwd });
54163
53743
  return { response: { type: "workspace:list", ...res } };
54164
53744
  };
54165
53745
  const read = async (frame, _client, ctx) => {
54166
53746
  const args = WorkspaceReadArgs.parse(frame);
54167
- const target = path55.isAbsolute(args.path) ? path55.resolve(args.path) : path55.resolve(args.cwd, args.path);
53747
+ const target = path51.isAbsolute(args.path) ? path51.resolve(args.path) : path51.resolve(args.cwd, args.path);
54168
53748
  assertGuestPath2(ctx, target, personaRoot, "workspace:read", usersRoot);
54169
53749
  const res = workspace.read(args);
54170
53750
  return { response: { type: "workspace:read", ...res } };
54171
53751
  };
54172
53752
  const skillsList = async (frame, _client, ctx) => {
54173
53753
  const args = SkillsListArgs.parse(frame);
54174
- const cwdAbs = path55.resolve(args.cwd);
53754
+ const cwdAbs = path51.resolve(args.cwd);
54175
53755
  assertGuestPath2(ctx, cwdAbs, personaRoot, "skills:list", usersRoot);
54176
53756
  const list2 = await getSkillsForTool(args.tool ?? "claude", cwdAbs);
54177
53757
  if (ctx?.principal.kind === "guest" && personaRoot) {
@@ -54183,7 +53763,7 @@ function buildWorkspaceHandlers(deps) {
54183
53763
  };
54184
53764
  const agentsList = async (frame, _client, ctx) => {
54185
53765
  const args = AgentsListArgs.parse(frame);
54186
- const cwdAbs = path55.resolve(args.cwd);
53766
+ const cwdAbs = path51.resolve(args.cwd);
54187
53767
  assertGuestPath2(ctx, cwdAbs, personaRoot, "agents:list", usersRoot);
54188
53768
  if (args.tool === "codex") {
54189
53769
  return { response: { type: "agents:list", agents: [] } };
@@ -54205,20 +53785,20 @@ function buildWorkspaceHandlers(deps) {
54205
53785
  }
54206
53786
 
54207
53787
  // src/handlers/git.ts
54208
- var path57 = __toESM(require("path"), 1);
53788
+ var path53 = __toESM(require("path"), 1);
54209
53789
  init_protocol();
54210
53790
  init_protocol();
54211
53791
 
54212
53792
  // src/workspace/git.ts
54213
- var import_node_child_process12 = require("child_process");
54214
- var import_node_fs42 = __toESM(require("fs"), 1);
54215
- var import_node_path44 = __toESM(require("path"), 1);
53793
+ var import_node_child_process10 = require("child_process");
53794
+ var import_node_fs38 = __toESM(require("fs"), 1);
53795
+ var import_node_path40 = __toESM(require("path"), 1);
54216
53796
  var import_node_util = require("util");
54217
- var pexec = (0, import_node_util.promisify)(import_node_child_process12.execFile);
53797
+ var pexec = (0, import_node_util.promisify)(import_node_child_process10.execFile);
54218
53798
  function normalizePath(p2) {
54219
- const resolved = import_node_path44.default.resolve(p2);
53799
+ const resolved = import_node_path40.default.resolve(p2);
54220
53800
  try {
54221
- return import_node_fs42.default.realpathSync(resolved);
53801
+ return import_node_fs38.default.realpathSync(resolved);
54222
53802
  } catch {
54223
53803
  return resolved;
54224
53804
  }
@@ -54292,7 +53872,7 @@ async function listGitBranches(cwd) {
54292
53872
  function assertGuestCwd(ctx, cwd, personaRoot, method, usersRoot) {
54293
53873
  if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
54294
53874
  const userWorkDir = usersRoot ? deriveUserWorkDir(ctx.principal.id, usersRoot) : void 0;
54295
- if (!isGuestPathAllowed(ctx.grants, path57.resolve(cwd), personaRoot, "read", userWorkDir)) {
53875
+ if (!isGuestPathAllowed(ctx.grants, path53.resolve(cwd), personaRoot, "read", userWorkDir)) {
54296
53876
  throw new ClawdError(
54297
53877
  ERROR_CODES.UNAUTHORIZED,
54298
53878
  `guest ${ctx.principal.id} cannot ${method} cwd ${cwd}`
@@ -54625,128 +54205,6 @@ function buildContactHandlers(deps) {
54625
54205
  };
54626
54206
  }
54627
54207
 
54628
- // src/handlers/contact-ssh.ts
54629
- init_protocol();
54630
-
54631
- // src/sshd/key-issue.ts
54632
- var import_node_fs43 = __toESM(require("fs"), 1);
54633
- var import_node_path45 = __toESM(require("path"), 1);
54634
- var import_node_child_process13 = require("child_process");
54635
- function safeDeviceId(deviceId) {
54636
- return deviceId.replace(/[\/\\]/g, "_");
54637
- }
54638
- async function issueContactSshKey(deviceId, sshdDir, opts = {}) {
54639
- const safeId = safeDeviceId(deviceId);
54640
- const keysDir = import_node_path45.default.join(sshdDir, "keys");
54641
- import_node_fs43.default.mkdirSync(keysDir, { recursive: true, mode: 448 });
54642
- const privPath = import_node_path45.default.join(keysDir, `${safeId}.ed25519`);
54643
- const pubPath = `${privPath}.pub`;
54644
- if (import_node_fs43.default.existsSync(privPath) && import_node_fs43.default.existsSync(pubPath)) {
54645
- return {
54646
- privateKeyPem: import_node_fs43.default.readFileSync(privPath, "utf8"),
54647
- publicKeyLine: import_node_fs43.default.readFileSync(pubPath, "utf8").trim()
54648
- };
54649
- }
54650
- const bin = opts.keygenBin ?? "/usr/bin/ssh-keygen";
54651
- await new Promise((resolve6, reject) => {
54652
- const p2 = (opts.spawnImpl ?? import_node_child_process13.spawn)(
54653
- bin,
54654
- ["-t", "ed25519", "-f", privPath, "-N", "", "-q", "-C", `clawd-contact-${safeId}`],
54655
- { stdio: "ignore" }
54656
- );
54657
- p2.on("exit", (code) => code === 0 ? resolve6() : reject(new Error(`ssh-keygen exit ${code}`)));
54658
- p2.on("error", reject);
54659
- });
54660
- try {
54661
- import_node_fs43.default.chmodSync(privPath, 384);
54662
- } catch {
54663
- }
54664
- try {
54665
- import_node_fs43.default.chmodSync(pubPath, 420);
54666
- } catch {
54667
- }
54668
- return {
54669
- privateKeyPem: import_node_fs43.default.readFileSync(privPath, "utf8"),
54670
- publicKeyLine: import_node_fs43.default.readFileSync(pubPath, "utf8").trim()
54671
- };
54672
- }
54673
-
54674
- // src/handlers/contact-ssh.ts
54675
- function ensureOwner2(ctx) {
54676
- if (!ctx || ctx.principal.kind !== "owner") {
54677
- throw new ClawdError(
54678
- ERROR_CODES.UNAUTHORIZED,
54679
- "UNAUTHORIZED: contact:setSshAccess requires owner ctx"
54680
- );
54681
- }
54682
- }
54683
- function ensureGuest(ctx) {
54684
- if (!ctx || ctx.principal.kind !== "guest") {
54685
- throw new ClawdError(
54686
- ERROR_CODES.UNAUTHORIZED,
54687
- "UNAUTHORIZED: contact:sshKey:issue requires guest ctx"
54688
- );
54689
- }
54690
- return ctx.principal.id;
54691
- }
54692
- function buildContactSshHandlers(deps) {
54693
- const setSshAccess = async (frame, _client, ctx) => {
54694
- ensureOwner2(ctx);
54695
- const { type: _t, requestId: _r, ...rest } = frame;
54696
- const args = ContactSetSshAccessArgsSchema.parse(rest);
54697
- const hit = deps.store.setSshAccess(args.deviceId, {
54698
- sshAllowed: args.sshAllowed,
54699
- exposedDirs: args.exposedDirs
54700
- });
54701
- if (!hit) {
54702
- throw new ClawdError(
54703
- ERROR_CODES.CONTACT_NOT_FOUND,
54704
- `CONTACT_NOT_FOUND: contact ${args.deviceId} not in store`
54705
- );
54706
- }
54707
- rebuildAuthorizedKeys(deps.store, deps.sshdDir);
54708
- deps.broadcast({
54709
- type: "contact:ssh-access-updated",
54710
- deviceId: args.deviceId,
54711
- sshAllowed: args.sshAllowed,
54712
- exposedDirs: args.exposedDirs
54713
- });
54714
- return {
54715
- response: {
54716
- type: "contact:setSshAccess:ok",
54717
- deviceId: args.deviceId,
54718
- sshAllowed: args.sshAllowed,
54719
- exposedDirs: args.exposedDirs
54720
- }
54721
- };
54722
- };
54723
- const sshKeyIssue = async (frame, _client, ctx) => {
54724
- const callerDeviceId = ensureGuest(ctx);
54725
- const { type: _t, requestId: _r, ...rest } = frame;
54726
- ContactSshKeyIssueArgsSchema.parse(rest);
54727
- const contact = deps.store.get(callerDeviceId);
54728
- if (!contact || !contact.sshAllowed) {
54729
- throw new ClawdError(
54730
- ERROR_CODES.UNAUTHORIZED,
54731
- `UNAUTHORIZED: contact ${callerDeviceId} not authorized for SSH`
54732
- );
54733
- }
54734
- const { privateKeyPem, publicKeyLine } = await issueContactSshKey(callerDeviceId, deps.sshdDir);
54735
- rebuildAuthorizedKeys(deps.store, deps.sshdDir);
54736
- return {
54737
- response: {
54738
- type: "contact:sshKey:issue:ok",
54739
- privateKeyPem,
54740
- publicKeyLine
54741
- }
54742
- };
54743
- };
54744
- return {
54745
- "contact:setSshAccess": setSshAccess,
54746
- "contact:sshKey:issue": sshKeyIssue
54747
- };
54748
- }
54749
-
54750
54208
  // src/handlers/whoami.ts
54751
54209
  init_protocol();
54752
54210
  function buildWhoamiHandler(deps) {
@@ -54842,7 +54300,7 @@ function buildFeishuAuthHandlers(deps) {
54842
54300
 
54843
54301
  // src/handlers/device.ts
54844
54302
  init_protocol();
54845
- function ensureOwner3(ctx) {
54303
+ function ensureOwner2(ctx) {
54846
54304
  if (!ctx || ctx.principal.kind !== "owner") {
54847
54305
  throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "UNAUTHORIZED: device:* requires owner ctx");
54848
54306
  }
@@ -54850,14 +54308,14 @@ function ensureOwner3(ctx) {
54850
54308
  function buildDeviceHandlers(deps) {
54851
54309
  const now = deps.now ?? Date.now;
54852
54310
  const list = async (_frame, _client, ctx) => {
54853
- ensureOwner3(ctx);
54311
+ ensureOwner2(ctx);
54854
54312
  const devices = await deps.listDevices();
54855
54313
  return {
54856
54314
  response: { type: "device:list:ok", devices }
54857
54315
  };
54858
54316
  };
54859
54317
  const connect = async (frame, _client, ctx) => {
54860
- ensureOwner3(ctx);
54318
+ ensureOwner2(ctx);
54861
54319
  const { type: _t, requestId: _r, ...rest } = frame;
54862
54320
  const args = DeviceConnectArgsSchema.parse(rest);
54863
54321
  const exchanged = await deps.exchange(args.deviceId);
@@ -54900,9 +54358,7 @@ function buildDeviceHandlers(deps) {
54900
54358
  connectToken: exchanged.token,
54901
54359
  grants: wh.grants,
54902
54360
  addedAt: now(),
54903
- pinnedAt: null,
54904
- sshAllowed: false,
54905
- exposedDirs: []
54361
+ pinnedAt: null
54906
54362
  };
54907
54363
  deps.store.upsert(contact);
54908
54364
  deps.broadcast({ type: "contact:added", contact });
@@ -55058,7 +54514,7 @@ function buildPersonaHandlers(deps) {
55058
54514
  }
55059
54515
 
55060
54516
  // src/handlers/attachment.ts
55061
- var import_node_path46 = __toESM(require("path"), 1);
54517
+ var import_node_path41 = __toESM(require("path"), 1);
55062
54518
  init_protocol();
55063
54519
  init_protocol();
55064
54520
  var DEFAULT_TTL_SECONDS = 24 * 3600;
@@ -55138,12 +54594,12 @@ function buildAttachmentHandlers(deps) {
55138
54594
  `session ${args.sessionId} scope unresolved`
55139
54595
  );
55140
54596
  }
55141
- const cwdAbs = import_node_path46.default.resolve(sessionFile.cwd);
55142
- const candidateAbs = import_node_path46.default.isAbsolute(args.relPath) ? import_node_path46.default.resolve(args.relPath) : import_node_path46.default.resolve(cwdAbs, args.relPath);
54597
+ const cwdAbs = import_node_path41.default.resolve(sessionFile.cwd);
54598
+ const candidateAbs = import_node_path41.default.isAbsolute(args.relPath) ? import_node_path41.default.resolve(args.relPath) : import_node_path41.default.resolve(cwdAbs, args.relPath);
55143
54599
  guardAttachmentPath(ctx, args.sessionId, candidateAbs, "attachment.signUrl", "group-acl");
55144
54600
  const entries = deps.groupFileStore.list(scope, args.sessionId);
55145
54601
  const entry = entries.find((e) => {
55146
- const storedAbs = import_node_path46.default.isAbsolute(e.relPath) ? import_node_path46.default.resolve(e.relPath) : import_node_path46.default.resolve(cwdAbs, e.relPath);
54602
+ const storedAbs = import_node_path41.default.isAbsolute(e.relPath) ? import_node_path41.default.resolve(e.relPath) : import_node_path41.default.resolve(cwdAbs, e.relPath);
55147
54603
  return storedAbs === candidateAbs && !e.stale;
55148
54604
  });
55149
54605
  if (!entry) {
@@ -55168,7 +54624,7 @@ function buildAttachmentHandlers(deps) {
55168
54624
  if (!ctx || ctx.principal.kind !== "guest" || !deps.personaRoot || !deps.sessionStore) return;
55169
54625
  const f = deps.sessionStore.read(sessionId);
55170
54626
  if (!f) return;
55171
- assertGuestAttachmentPath(ctx, import_node_path46.default.resolve(f.cwd), deps.personaRoot, method, deps.usersRoot);
54627
+ assertGuestAttachmentPath(ctx, import_node_path41.default.resolve(f.cwd), deps.personaRoot, method, deps.usersRoot);
55172
54628
  }
55173
54629
  const groupAdd = async (frame, _client, ctx) => {
55174
54630
  if (!deps.groupFileStore || !deps.getSessionScope) {
@@ -55183,8 +54639,8 @@ function buildAttachmentHandlers(deps) {
55183
54639
  if (!scope) {
55184
54640
  throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, `session ${args.sessionId} not found`);
55185
54641
  }
55186
- const cwdAbs = import_node_path46.default.resolve(deps.sessionStore?.read(args.sessionId)?.cwd ?? ".");
55187
- const candidateAbs = import_node_path46.default.isAbsolute(args.relPath) ? import_node_path46.default.resolve(args.relPath) : import_node_path46.default.resolve(cwdAbs, args.relPath);
54642
+ const cwdAbs = import_node_path41.default.resolve(deps.sessionStore?.read(args.sessionId)?.cwd ?? ".");
54643
+ const candidateAbs = import_node_path41.default.isAbsolute(args.relPath) ? import_node_path41.default.resolve(args.relPath) : import_node_path41.default.resolve(cwdAbs, args.relPath);
55188
54644
  guardAttachmentPath(ctx, args.sessionId, candidateAbs, "attachment.groupAdd", "cwd-subtree");
55189
54645
  const from = ctx?.principal.kind === "owner" ? "owner" : "agent";
55190
54646
  const size = 0;
@@ -55243,19 +54699,19 @@ function buildAttachmentHandlers(deps) {
55243
54699
 
55244
54700
  // src/handlers/extension.ts
55245
54701
  var import_promises8 = __toESM(require("fs/promises"), 1);
55246
- var import_node_path51 = __toESM(require("path"), 1);
54702
+ var import_node_path46 = __toESM(require("path"), 1);
55247
54703
  init_protocol();
55248
54704
 
55249
54705
  // src/extension/bundle-zip.ts
55250
54706
  var import_promises5 = __toESM(require("fs/promises"), 1);
55251
- var import_node_path47 = __toESM(require("path"), 1);
55252
- var import_node_crypto13 = __toESM(require("crypto"), 1);
54707
+ var import_node_path42 = __toESM(require("path"), 1);
54708
+ var import_node_crypto14 = __toESM(require("crypto"), 1);
55253
54709
  var import_jszip2 = __toESM(require_lib3(), 1);
55254
54710
  async function bundleExtensionDir(dir) {
55255
54711
  const entries = await listFilesSorted(dir);
55256
54712
  const zip = new import_jszip2.default();
55257
54713
  for (const rel of entries) {
55258
- const abs = import_node_path47.default.join(dir, rel);
54714
+ const abs = import_node_path42.default.join(dir, rel);
55259
54715
  const content = await import_promises5.default.readFile(abs);
55260
54716
  zip.file(rel, content, { date: FIXED_DATE });
55261
54717
  }
@@ -55264,7 +54720,7 @@ async function bundleExtensionDir(dir) {
55264
54720
  compression: "DEFLATE",
55265
54721
  compressionOptions: { level: 6 }
55266
54722
  });
55267
- const sha256 = import_node_crypto13.default.createHash("sha256").update(buffer).digest("hex");
54723
+ const sha256 = import_node_crypto14.default.createHash("sha256").update(buffer).digest("hex");
55268
54724
  return { buffer, sha256 };
55269
54725
  }
55270
54726
  var FIXED_DATE = /* @__PURE__ */ new Date("2020-01-01T00:00:00.000Z");
@@ -55276,7 +54732,7 @@ async function listFilesSorted(rootDir) {
55276
54732
  return out;
55277
54733
  }
55278
54734
  async function walk(absRoot, relPrefix, out) {
55279
- const dirAbs = import_node_path47.default.join(absRoot, relPrefix);
54735
+ const dirAbs = import_node_path42.default.join(absRoot, relPrefix);
55280
54736
  const entries = await import_promises5.default.readdir(dirAbs, { withFileTypes: true });
55281
54737
  for (const e of entries) {
55282
54738
  if (IGNORE_BASENAMES.has(e.name)) continue;
@@ -55330,25 +54786,25 @@ function computePublishCheck(args) {
55330
54786
 
55331
54787
  // src/extension/install-flow.ts
55332
54788
  var import_promises6 = __toESM(require("fs/promises"), 1);
55333
- var import_node_path49 = __toESM(require("path"), 1);
54789
+ var import_node_path44 = __toESM(require("path"), 1);
55334
54790
  var import_node_os19 = __toESM(require("os"), 1);
55335
- var import_node_crypto14 = __toESM(require("crypto"), 1);
54791
+ var import_node_crypto15 = __toESM(require("crypto"), 1);
55336
54792
  var import_jszip3 = __toESM(require_lib3(), 1);
55337
54793
 
55338
54794
  // src/extension/paths.ts
55339
54795
  var import_node_os18 = __toESM(require("os"), 1);
55340
- var import_node_path48 = __toESM(require("path"), 1);
54796
+ var import_node_path43 = __toESM(require("path"), 1);
55341
54797
  function clawdHomeRoot(override) {
55342
- return override ?? process.env.CLAWD_HOME ?? import_node_path48.default.join(import_node_os18.default.homedir(), ".clawd");
54798
+ return override ?? process.env.CLAWD_HOME ?? import_node_path43.default.join(import_node_os18.default.homedir(), ".clawd");
55343
54799
  }
55344
54800
  function extensionsRoot(override) {
55345
- return import_node_path48.default.join(clawdHomeRoot(override), "extensions");
54801
+ return import_node_path43.default.join(clawdHomeRoot(override), "extensions");
55346
54802
  }
55347
54803
  function publishedChannelsFile(override) {
55348
- return import_node_path48.default.join(clawdHomeRoot(override), "extensions-published.json");
54804
+ return import_node_path43.default.join(clawdHomeRoot(override), "extensions-published.json");
55349
54805
  }
55350
54806
  function bundleCacheRoot(override) {
55351
- return import_node_path48.default.join(clawdHomeRoot(override), "extension-bundles");
54807
+ return import_node_path43.default.join(clawdHomeRoot(override), "extension-bundles");
55352
54808
  }
55353
54809
 
55354
54810
  // src/extension/install-flow.ts
@@ -55361,7 +54817,7 @@ var InstallError = class extends Error {
55361
54817
  };
55362
54818
  async function installFromChannel(args, deps) {
55363
54819
  const { channelRef, snapshotHash, bundleZip } = args;
55364
- const computed = import_node_crypto14.default.createHash("sha256").update(bundleZip).digest("hex");
54820
+ const computed = import_node_crypto15.default.createHash("sha256").update(bundleZip).digest("hex");
55365
54821
  if (computed !== snapshotHash) {
55366
54822
  throw new InstallError(
55367
54823
  "HASH_MISMATCH",
@@ -55375,7 +54831,7 @@ async function installFromChannel(args, deps) {
55375
54831
  throw new InstallError("ZIP_INVALID", `failed to load zip: ${e.message}`);
55376
54832
  }
55377
54833
  for (const name of Object.keys(zip.files)) {
55378
- if (name.includes("..") || name.startsWith("/") || import_node_path49.default.isAbsolute(name)) {
54834
+ if (name.includes("..") || name.startsWith("/") || import_node_path44.default.isAbsolute(name)) {
55379
54835
  throw new InstallError("ZIP_INVALID", `unsafe zip entry: ${name}`);
55380
54836
  }
55381
54837
  }
@@ -55407,7 +54863,7 @@ async function installFromChannel(args, deps) {
55407
54863
  );
55408
54864
  }
55409
54865
  const localExtId = namespacedExtId(ownerSlug, channelRef.ownerPrincipalId);
55410
- const destDir = import_node_path49.default.join(deps.extensionsRoot, localExtId);
54866
+ const destDir = import_node_path44.default.join(deps.extensionsRoot, localExtId);
55411
54867
  let destExists = false;
55412
54868
  try {
55413
54869
  await import_promises6.default.access(destDir);
@@ -55421,16 +54877,16 @@ async function installFromChannel(args, deps) {
55421
54877
  );
55422
54878
  }
55423
54879
  const stage = await import_promises6.default.mkdtemp(
55424
- import_node_path49.default.join(import_node_os19.default.tmpdir(), `clawd-ext-install-${localExtId}-`)
54880
+ import_node_path44.default.join(import_node_os19.default.tmpdir(), `clawd-ext-install-${localExtId}-`)
55425
54881
  );
55426
54882
  try {
55427
54883
  for (const [name, entry] of Object.entries(zip.files)) {
55428
- const dest = import_node_path49.default.join(stage, name);
54884
+ const dest = import_node_path44.default.join(stage, name);
55429
54885
  if (entry.dir) {
55430
54886
  await import_promises6.default.mkdir(dest, { recursive: true });
55431
54887
  continue;
55432
54888
  }
55433
- await import_promises6.default.mkdir(import_node_path49.default.dirname(dest), { recursive: true });
54889
+ await import_promises6.default.mkdir(import_node_path44.default.dirname(dest), { recursive: true });
55434
54890
  if (name === "manifest.json") {
55435
54891
  const rewritten = { ...parsed.data, id: localExtId };
55436
54892
  await import_promises6.default.writeFile(dest, JSON.stringify(rewritten, null, 2));
@@ -55451,9 +54907,9 @@ async function installFromChannel(args, deps) {
55451
54907
 
55452
54908
  // src/extension/update-flow.ts
55453
54909
  var import_promises7 = __toESM(require("fs/promises"), 1);
55454
- var import_node_path50 = __toESM(require("path"), 1);
54910
+ var import_node_path45 = __toESM(require("path"), 1);
55455
54911
  var import_node_os20 = __toESM(require("os"), 1);
55456
- var import_node_crypto15 = __toESM(require("crypto"), 1);
54912
+ var import_node_crypto16 = __toESM(require("crypto"), 1);
55457
54913
  var import_jszip4 = __toESM(require_lib3(), 1);
55458
54914
  var UpdateError = class extends Error {
55459
54915
  constructor(code, message) {
@@ -55468,11 +54924,11 @@ async function updateFromChannel(args, deps) {
55468
54924
  channelRef.extId,
55469
54925
  channelRef.ownerPrincipalId
55470
54926
  );
55471
- const liveDir = import_node_path50.default.join(deps.extensionsRoot, localExtId);
54927
+ const liveDir = import_node_path45.default.join(deps.extensionsRoot, localExtId);
55472
54928
  const prevDir = `${liveDir}.prev`;
55473
54929
  let existingVersion;
55474
54930
  try {
55475
- const raw = await import_promises7.default.readFile(import_node_path50.default.join(liveDir, "manifest.json"), "utf8");
54931
+ const raw = await import_promises7.default.readFile(import_node_path45.default.join(liveDir, "manifest.json"), "utf8");
55476
54932
  const parsed2 = ExtensionManifestSchema.safeParse(JSON.parse(raw));
55477
54933
  if (!parsed2.success) {
55478
54934
  throw new UpdateError(
@@ -55491,7 +54947,7 @@ async function updateFromChannel(args, deps) {
55491
54947
  if (e instanceof UpdateError) throw e;
55492
54948
  throw e;
55493
54949
  }
55494
- const computed = import_node_crypto15.default.createHash("sha256").update(bundleZip).digest("hex");
54950
+ const computed = import_node_crypto16.default.createHash("sha256").update(bundleZip).digest("hex");
55495
54951
  if (computed !== snapshotHash) {
55496
54952
  throw new UpdateError(
55497
54953
  "HASH_MISMATCH",
@@ -55505,7 +54961,7 @@ async function updateFromChannel(args, deps) {
55505
54961
  throw new UpdateError("ZIP_INVALID", `failed to load zip: ${e.message}`);
55506
54962
  }
55507
54963
  for (const name of Object.keys(zip.files)) {
55508
- if (name.includes("..") || name.startsWith("/") || import_node_path50.default.isAbsolute(name)) {
54964
+ if (name.includes("..") || name.startsWith("/") || import_node_path45.default.isAbsolute(name)) {
55509
54965
  throw new UpdateError("ZIP_INVALID", `unsafe zip entry: ${name}`);
55510
54966
  }
55511
54967
  }
@@ -55540,16 +54996,16 @@ async function updateFromChannel(args, deps) {
55540
54996
  await import_promises7.default.rm(prevDir, { recursive: true, force: true });
55541
54997
  await import_promises7.default.rename(liveDir, prevDir);
55542
54998
  const stage = await import_promises7.default.mkdtemp(
55543
- import_node_path50.default.join(import_node_os20.default.tmpdir(), `clawd-ext-update-${localExtId}-`)
54999
+ import_node_path45.default.join(import_node_os20.default.tmpdir(), `clawd-ext-update-${localExtId}-`)
55544
55000
  );
55545
55001
  try {
55546
55002
  for (const [name, entry] of Object.entries(zip.files)) {
55547
- const dest = import_node_path50.default.join(stage, name);
55003
+ const dest = import_node_path45.default.join(stage, name);
55548
55004
  if (entry.dir) {
55549
55005
  await import_promises7.default.mkdir(dest, { recursive: true });
55550
55006
  continue;
55551
55007
  }
55552
- await import_promises7.default.mkdir(import_node_path50.default.dirname(dest), { recursive: true });
55008
+ await import_promises7.default.mkdir(import_node_path45.default.dirname(dest), { recursive: true });
55553
55009
  if (name === "manifest.json") {
55554
55010
  const rewritten = { ...parsed.data, id: localExtId };
55555
55011
  await import_promises7.default.writeFile(dest, JSON.stringify(rewritten, null, 2));
@@ -55642,7 +55098,7 @@ async function rewriteManifestVersion(root, extId, newVersion, previousPublished
55642
55098
  );
55643
55099
  }
55644
55100
  }
55645
- const manifestPath = import_node_path51.default.join(root, extId, "manifest.json");
55101
+ const manifestPath = import_node_path46.default.join(root, extId, "manifest.json");
55646
55102
  const manifest = await readManifest(root, extId);
55647
55103
  const next = { ...manifest, version: newVersion };
55648
55104
  const tmp = `${manifestPath}.tmp`;
@@ -55650,7 +55106,7 @@ async function rewriteManifestVersion(root, extId, newVersion, previousPublished
55650
55106
  await import_promises8.default.rename(tmp, manifestPath);
55651
55107
  }
55652
55108
  async function readManifest(root, extId) {
55653
- const file = import_node_path51.default.join(root, extId, "manifest.json");
55109
+ const file = import_node_path46.default.join(root, extId, "manifest.json");
55654
55110
  let raw;
55655
55111
  try {
55656
55112
  raw = await import_promises8.default.readFile(file, "utf8");
@@ -55741,7 +55197,7 @@ function buildExtensionHandlers(deps) {
55741
55197
  };
55742
55198
  async function buildSnapshotMeta(extId) {
55743
55199
  const manifest = await readManifest(deps.root, extId);
55744
- const { sha256, buffer } = await bundleExtensionDir(import_node_path51.default.join(deps.root, extId));
55200
+ const { sha256, buffer } = await bundleExtensionDir(import_node_path46.default.join(deps.root, extId));
55745
55201
  return { manifest, contentHash: sha256, buffer };
55746
55202
  }
55747
55203
  const publish = async (frame, _client, ctx) => {
@@ -55922,9 +55378,9 @@ function buildExtensionHandlers(deps) {
55922
55378
  }
55923
55379
 
55924
55380
  // src/app-builder/project-store.ts
55925
- var import_node_fs44 = require("fs");
55926
- var import_node_child_process14 = require("child_process");
55927
- var import_node_path52 = require("path");
55381
+ var import_node_fs39 = require("fs");
55382
+ var import_node_child_process11 = require("child_process");
55383
+ var import_node_path47 = require("path");
55928
55384
  init_protocol();
55929
55385
  var PROJECTS_DIR = "projects";
55930
55386
  var META_FILE = ".clawd-project.json";
@@ -55938,19 +55394,19 @@ var ProjectStore = class {
55938
55394
  root;
55939
55395
  /** projects/<name>/.clawd-project.json 路径 */
55940
55396
  metaPath(name) {
55941
- return (0, import_node_path52.join)(this.projectsRoot(), name, META_FILE);
55397
+ return (0, import_node_path47.join)(this.projectsRoot(), name, META_FILE);
55942
55398
  }
55943
55399
  /** projects/<name>/ 目录路径(cwd 用) */
55944
55400
  projectDir(name) {
55945
- return (0, import_node_path52.join)(this.projectsRoot(), name);
55401
+ return (0, import_node_path47.join)(this.projectsRoot(), name);
55946
55402
  }
55947
55403
  projectsRoot() {
55948
- return (0, import_node_path52.join)(this.root, PROJECTS_DIR);
55404
+ return (0, import_node_path47.join)(this.root, PROJECTS_DIR);
55949
55405
  }
55950
55406
  async list() {
55951
55407
  let entries;
55952
55408
  try {
55953
- entries = await import_node_fs44.promises.readdir(this.projectsRoot());
55409
+ entries = await import_node_fs39.promises.readdir(this.projectsRoot());
55954
55410
  } catch (err) {
55955
55411
  if (err.code === "ENOENT") return [];
55956
55412
  throw err;
@@ -55958,7 +55414,7 @@ var ProjectStore = class {
55958
55414
  const out = [];
55959
55415
  for (const name of entries) {
55960
55416
  try {
55961
- const raw = await import_node_fs44.promises.readFile(this.metaPath(name), "utf8");
55417
+ const raw = await import_node_fs39.promises.readFile(this.metaPath(name), "utf8");
55962
55418
  const json = JSON.parse(raw);
55963
55419
  let migrated = false;
55964
55420
  if (typeof json.devCommand !== "string" || json.devCommand.length === 0) {
@@ -55969,7 +55425,7 @@ var ProjectStore = class {
55969
55425
  if (parsed.success) {
55970
55426
  out.push(parsed.data);
55971
55427
  if (migrated) {
55972
- void import_node_fs44.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
55428
+ void import_node_fs39.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
55973
55429
  });
55974
55430
  }
55975
55431
  }
@@ -56013,8 +55469,8 @@ var ProjectStore = class {
56013
55469
  throw new Error(`invalid name "${name}": ${validated.error.message}`);
56014
55470
  }
56015
55471
  const dir = this.projectDir(name);
56016
- await import_node_fs44.promises.mkdir(dir, { recursive: true });
56017
- await import_node_fs44.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
55472
+ await import_node_fs39.promises.mkdir(dir, { recursive: true });
55473
+ await import_node_fs39.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
56018
55474
  return meta;
56019
55475
  }
56020
55476
  /**
@@ -56028,7 +55484,7 @@ var ProjectStore = class {
56028
55484
  async scaffold(name, templateSrcDir, scaffoldScriptPath) {
56029
55485
  const destDir = this.projectDir(name);
56030
55486
  return await new Promise((resolve6, reject) => {
56031
- const child = (0, import_node_child_process14.spawn)("bash", [scaffoldScriptPath, name, templateSrcDir, destDir], {
55487
+ const child = (0, import_node_child_process11.spawn)("bash", [scaffoldScriptPath, name, templateSrcDir, destDir], {
56032
55488
  env: { ...process.env, PATH: process.env.PATH ?? "" },
56033
55489
  stdio: ["ignore", "pipe", "pipe"]
56034
55490
  });
@@ -56057,7 +55513,7 @@ var ProjectStore = class {
56057
55513
  }
56058
55514
  async delete(name) {
56059
55515
  const dir = this.projectDir(name);
56060
- await import_node_fs44.promises.rm(dir, { recursive: true, force: true });
55516
+ await import_node_fs39.promises.rm(dir, { recursive: true, force: true });
56061
55517
  }
56062
55518
  async updatePort(name, newPort) {
56063
55519
  if (newPort < PROJECT_PORT_MIN || newPort > PROJECT_PORT_MAX) {
@@ -56073,7 +55529,7 @@ var ProjectStore = class {
56073
55529
  throw new Error(`port ${newPort} already used / \u5DF2\u88AB project "${conflict.name}" \u5360\u7528`);
56074
55530
  }
56075
55531
  const updated = { ...target, port: newPort };
56076
- await import_node_fs44.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
55532
+ await import_node_fs39.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
56077
55533
  return updated;
56078
55534
  }
56079
55535
  /**
@@ -56090,7 +55546,7 @@ var ProjectStore = class {
56090
55546
  if (!validated.success) {
56091
55547
  throw new Error(`invalid prodUrl "${url}": ${validated.error.message}`);
56092
55548
  }
56093
- await import_node_fs44.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
55549
+ await import_node_fs39.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
56094
55550
  return validated.data;
56095
55551
  }
56096
55552
  /**
@@ -56111,7 +55567,7 @@ var ProjectStore = class {
56111
55567
  if (!validated.success) {
56112
55568
  throw new Error(`invalid publishJob: ${validated.error.message}`);
56113
55569
  }
56114
- await import_node_fs44.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
55570
+ await import_node_fs39.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
56115
55571
  return validated.data;
56116
55572
  }
56117
55573
  /** 清掉 .clawd-project.json.publishJob 字段。其他字段保持原样。 */
@@ -56126,13 +55582,13 @@ var ProjectStore = class {
56126
55582
  if (!validated.success) {
56127
55583
  throw new Error(`failed to clear publishJob: ${validated.error.message}`);
56128
55584
  }
56129
- await import_node_fs44.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
55585
+ await import_node_fs39.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
56130
55586
  return validated.data;
56131
55587
  }
56132
55588
  };
56133
55589
 
56134
55590
  // src/app-builder/kill-port.ts
56135
- var import_node_child_process15 = require("child_process");
55591
+ var import_node_child_process12 = require("child_process");
56136
55592
  async function killPortOccupants(port, ownedPids, logger) {
56137
55593
  let pids;
56138
55594
  try {
@@ -56174,7 +55630,7 @@ async function killPortOccupants(port, ownedPids, logger) {
56174
55630
  }
56175
55631
  function listPidsOnPort(port) {
56176
55632
  return new Promise((resolve6, reject) => {
56177
- (0, import_node_child_process15.execFile)(
55633
+ (0, import_node_child_process12.execFile)(
56178
55634
  "lsof",
56179
55635
  ["-ti", `:${port}`],
56180
55636
  { timeout: 3e3 },
@@ -56196,7 +55652,7 @@ function listPidsOnPort(port) {
56196
55652
  }
56197
55653
 
56198
55654
  // src/app-builder/publish-registry.ts
56199
- var import_node_crypto16 = require("crypto");
55655
+ var import_node_crypto17 = require("crypto");
56200
55656
  var PublishJobRegistry = class {
56201
55657
  jobs = /* @__PURE__ */ new Map();
56202
55658
  has(name) {
@@ -56213,7 +55669,7 @@ var PublishJobRegistry = class {
56213
55669
  if (this.jobs.has(args.name)) {
56214
55670
  throw new Error(`already publishing: ${args.name}`);
56215
55671
  }
56216
- const jobId = args.jobId ?? `job-${(0, import_node_crypto16.randomUUID)()}`;
55672
+ const jobId = args.jobId ?? `job-${(0, import_node_crypto17.randomUUID)()}`;
56217
55673
  this.jobs.set(args.name, {
56218
55674
  jobId,
56219
55675
  name: args.name,
@@ -56246,9 +55702,9 @@ var PublishJobRegistry = class {
56246
55702
  };
56247
55703
 
56248
55704
  // src/app-builder/publish-job-runner.ts
56249
- var import_node_child_process16 = require("child_process");
56250
- var import_node_fs45 = require("fs");
56251
- var import_node_path53 = require("path");
55705
+ var import_node_child_process13 = require("child_process");
55706
+ var import_node_fs40 = require("fs");
55707
+ var import_node_path48 = require("path");
56252
55708
 
56253
55709
  // src/app-builder/publish-stage-parser.ts
56254
55710
  var STAGE_RE = /^\s*::stage::(build|deploy|verify)\s*$/;
@@ -56275,19 +55731,19 @@ function tailStderrLines(buf, n) {
56275
55731
  // src/app-builder/publish-job-runner.ts
56276
55732
  async function startPublishJob(deps, args) {
56277
55733
  const { registry: registry2, projectDir } = deps;
56278
- const spawn15 = deps.spawnImpl ?? import_node_child_process16.spawn;
55734
+ const spawn12 = deps.spawnImpl ?? import_node_child_process13.spawn;
56279
55735
  if (registry2.has(args.name)) {
56280
55736
  return { jobId: registry2.get(args.name).jobId, status: "already-publishing" };
56281
55737
  }
56282
55738
  const projDir = projectDir(args.name);
56283
- const logPath = (0, import_node_path53.join)(projDir, ".publish.log");
55739
+ const logPath = (0, import_node_path48.join)(projDir, ".publish.log");
56284
55740
  let logStream = null;
56285
55741
  try {
56286
- logStream = (0, import_node_fs45.createWriteStream)(logPath, { flags: "w" });
55742
+ logStream = (0, import_node_fs40.createWriteStream)(logPath, { flags: "w" });
56287
55743
  } catch {
56288
55744
  logStream = null;
56289
55745
  }
56290
- const child = spawn15("bash", [args.scriptPath, projDir, args.personaRoot ?? ""], {
55746
+ const child = spawn12("bash", [args.scriptPath, projDir, args.personaRoot ?? ""], {
56291
55747
  cwd: projDir,
56292
55748
  env: process.env,
56293
55749
  stdio: ["ignore", "pipe", "pipe"]
@@ -56540,8 +55996,8 @@ async function recoverInterruptedJobs(deps) {
56540
55996
 
56541
55997
  // src/handlers/app-builder.ts
56542
55998
  init_protocol();
56543
- var import_node_path54 = require("path");
56544
- var import_node_fs46 = require("fs");
55999
+ var import_node_path49 = require("path");
56000
+ var import_node_fs41 = require("fs");
56545
56001
  var APP_BUILDER_PERSONAS = ["persona-app-builder", "persona-dataclaw-builder"];
56546
56002
  var DEV_SERVER_READY_TIMEOUT_MS = 3e4;
56547
56003
  async function recoverInterruptedPublishJobs(store, logger) {
@@ -56622,7 +56078,7 @@ function buildAppBuilderHandlers(deps) {
56622
56078
  async function listAllUsersProjects() {
56623
56079
  if (!deps.usersRoot || !deps.getStore) return [];
56624
56080
  const getStore = deps.getStore;
56625
- const userIds = await import_node_fs46.promises.readdir(deps.usersRoot).catch(() => []);
56081
+ const userIds = await import_node_fs41.promises.readdir(deps.usersRoot).catch(() => []);
56626
56082
  const perUser = await Promise.all(
56627
56083
  userIds.map((uid) => getStore(uid).list().catch(() => []))
56628
56084
  );
@@ -56698,8 +56154,8 @@ function buildAppBuilderHandlers(deps) {
56698
56154
  const project = await userStore.create(f.name, reservedPorts);
56699
56155
  try {
56700
56156
  const personaRoot = deps.resolvePersonaRoot ? deps.resolvePersonaRoot(session.ownerPersonaId ?? "") : deps.personaRoot;
56701
- const templateSrcDir = (0, import_node_path54.join)(personaRoot, "extension-kit", "examples", DEFAULT_TEMPLATE);
56702
- const scaffoldScript = (0, import_node_path54.join)(deps.deployKitRoot, "scripts", "new-extension.sh");
56157
+ const templateSrcDir = (0, import_node_path49.join)(personaRoot, "extension-kit", "examples", DEFAULT_TEMPLATE);
56158
+ const scaffoldScript = (0, import_node_path49.join)(deps.deployKitRoot, "scripts", "new-extension.sh");
56703
56159
  const scaffoldResult = await userStore.scaffold(project.name, templateSrcDir, scaffoldScript);
56704
56160
  deps.logger?.info("app-builder.scaffold.done", {
56705
56161
  name: project.name,
@@ -56920,7 +56376,7 @@ function buildAppBuilderHandlers(deps) {
56920
56376
  await userStore.clearPublishJob(args.name);
56921
56377
  }
56922
56378
  const personaRoot = deps.resolvePersonaRoot ? deps.resolvePersonaRoot(boundSession.ownerPersonaId ?? "") : deps.personaRoot;
56923
- const scriptPath = (0, import_node_path54.join)(deps.deployKitRoot, "scripts", "publish.sh");
56379
+ const scriptPath = (0, import_node_path49.join)(deps.deployKitRoot, "scripts", "publish.sh");
56924
56380
  deps.logger?.info("app-builder.publish.start", {
56925
56381
  name: args.name,
56926
56382
  sessionId: boundSession.sessionId,
@@ -57064,7 +56520,7 @@ function buildShiftHandlers(deps) {
57064
56520
 
57065
56521
  // src/handlers/visitor.ts
57066
56522
  init_protocol();
57067
- function ensureOwner4(ctx) {
56523
+ function ensureOwner3(ctx) {
57068
56524
  if (!ctx || ctx.principal.kind !== "owner") {
57069
56525
  throw new ClawdError(
57070
56526
  ERROR_CODES.UNAUTHORIZED,
@@ -57074,7 +56530,7 @@ function ensureOwner4(ctx) {
57074
56530
  }
57075
56531
  function buildVisitorHandlers(deps) {
57076
56532
  const list = async (_frame, _client, ctx) => {
57077
- ensureOwner4(ctx);
56533
+ ensureOwner3(ctx);
57078
56534
  return {
57079
56535
  response: {
57080
56536
  type: "visitor:list",
@@ -57089,7 +56545,7 @@ function buildVisitorHandlers(deps) {
57089
56545
 
57090
56546
  // src/extension/registry.ts
57091
56547
  var import_promises9 = __toESM(require("fs/promises"), 1);
57092
- var import_node_path55 = __toESM(require("path"), 1);
56548
+ var import_node_path50 = __toESM(require("path"), 1);
57093
56549
  async function loadAll(root) {
57094
56550
  let entries;
57095
56551
  try {
@@ -57102,13 +56558,13 @@ async function loadAll(root) {
57102
56558
  for (const ent of entries) {
57103
56559
  if (!ent.isDirectory()) continue;
57104
56560
  if (ent.name.startsWith(".")) continue;
57105
- records.push(await loadOne(import_node_path55.default.join(root, ent.name), ent.name));
56561
+ records.push(await loadOne(import_node_path50.default.join(root, ent.name), ent.name));
57106
56562
  }
57107
56563
  records.sort((a, b2) => a.extId < b2.extId ? -1 : a.extId > b2.extId ? 1 : 0);
57108
56564
  return records;
57109
56565
  }
57110
56566
  async function loadOne(dir, dirName) {
57111
- const manifestPath = import_node_path55.default.join(dir, "manifest.json");
56567
+ const manifestPath = import_node_path50.default.join(dir, "manifest.json");
57112
56568
  let raw;
57113
56569
  try {
57114
56570
  raw = await import_promises9.default.readFile(manifestPath, "utf8");
@@ -57153,7 +56609,7 @@ async function loadOne(dir, dirName) {
57153
56609
 
57154
56610
  // src/extension/uninstall.ts
57155
56611
  var import_promises10 = __toESM(require("fs/promises"), 1);
57156
- var import_node_path56 = __toESM(require("path"), 1);
56612
+ var import_node_path51 = __toESM(require("path"), 1);
57157
56613
  var UninstallError = class extends Error {
57158
56614
  constructor(code, message) {
57159
56615
  super(message);
@@ -57162,7 +56618,7 @@ var UninstallError = class extends Error {
57162
56618
  code;
57163
56619
  };
57164
56620
  async function uninstall(deps) {
57165
- const dir = import_node_path56.default.join(deps.root, deps.extId);
56621
+ const dir = import_node_path51.default.join(deps.root, deps.extId);
57166
56622
  try {
57167
56623
  await import_promises10.default.access(dir);
57168
56624
  } catch {
@@ -57173,7 +56629,7 @@ async function uninstall(deps) {
57173
56629
  }
57174
56630
 
57175
56631
  // src/handlers/index.ts
57176
- var import_node_crypto17 = require("crypto");
56632
+ var import_node_crypto18 = require("crypto");
57177
56633
  function buildMethodHandlers(deps) {
57178
56634
  return {
57179
56635
  ...buildSessionHandlers({
@@ -57206,7 +56662,7 @@ function buildMethodHandlers(deps) {
57206
56662
  const c = deps.contactStore.get(deviceId);
57207
56663
  return c ? { deviceId: c.deviceId, remoteUrl: c.remoteUrl, connectToken: c.connectToken } : null;
57208
56664
  },
57209
- genId: () => (0, import_node_crypto17.randomUUID)(),
56665
+ genId: () => (0, import_node_crypto18.randomUUID)(),
57210
56666
  now: () => Date.now(),
57211
56667
  forwardInboxPostToPeer,
57212
56668
  logger: deps.logger
@@ -57217,11 +56673,6 @@ function buildMethodHandlers(deps) {
57217
56673
  broadcast: deps.broadcastToOwners,
57218
56674
  now: () => Date.now()
57219
56675
  }),
57220
- ...buildContactSshHandlers({
57221
- store: deps.contactStore,
57222
- broadcast: deps.broadcastToOwners,
57223
- sshdDir: deps.sshdDir
57224
- }),
57225
56676
  whoami: buildWhoamiHandler({
57226
56677
  ownerDisplayName: deps.ownerDisplayName,
57227
56678
  ownerPrincipalId: deps.ownerPrincipalId,
@@ -57268,7 +56719,7 @@ function buildMethodHandlers(deps) {
57268
56719
  }
57269
56720
 
57270
56721
  // src/app-builder/dev-server-supervisor.ts
57271
- var import_node_child_process17 = require("child_process");
56722
+ var import_node_child_process14 = require("child_process");
57272
56723
  var import_node_events2 = require("events");
57273
56724
  var DEFAULT_READY_PATTERN = /Local:\s+https?:\/\/|Nest application successfully started|server listening on/i;
57274
56725
  var DevServerSupervisor = class extends import_node_events2.EventEmitter {
@@ -57305,7 +56756,7 @@ var DevServerSupervisor = class extends import_node_events2.EventEmitter {
57305
56756
  tunnelHost: args.tunnelHost,
57306
56757
  devCommand: cmd
57307
56758
  });
57308
- const child = (0, import_node_child_process17.spawn)("sh", ["-c", cmd], {
56759
+ const child = (0, import_node_child_process14.spawn)("sh", ["-c", cmd], {
57309
56760
  cwd: args.cwd,
57310
56761
  env,
57311
56762
  stdio: "pipe",
@@ -57557,12 +57008,6 @@ var METHOD_GRANT_MAP = {
57557
57008
  "contact:list": ADMIN_ANY,
57558
57009
  "contact:pin": ADMIN_ANY,
57559
57010
  "contact:remove": ADMIN_ANY,
57560
- // contact:setSshAccess (owner UI 配 SSH 授权):ADMIN_ANY
57561
- // contact:sshKey:issue (guest daemon 拉自己的 privkey):public — handler 内校
57562
- // ctx.principal.kind==='guest' + store.get(callerId).sshAllowed
57563
- // (对齐 inbox:postMessage 的"能连上=有 auth,业务在 handler 校"模式)
57564
- "contact:setSshAccess": ADMIN_ANY,
57565
- "contact:sshKey:issue": { kind: "public" },
57566
57011
  // ---- visitor:* (访客名单,owner-only) ----
57567
57012
  // owner 看完整访客名单(含没开会话的);guest 不可调(handler 内再 assertOwner 兜底)。
57568
57013
  "visitor:list": ADMIN_ANY,
@@ -57743,8 +57188,8 @@ async function dispatchRpc(method, frame, client, ctx, deps) {
57743
57188
  }
57744
57189
 
57745
57190
  // src/extension/runtime.ts
57746
- var import_node_child_process18 = require("child_process");
57747
- var import_node_path57 = __toESM(require("path"), 1);
57191
+ var import_node_child_process15 = require("child_process");
57192
+ var import_node_path52 = __toESM(require("path"), 1);
57748
57193
  var import_promises11 = require("timers/promises");
57749
57194
 
57750
57195
  // src/extension/port-allocator.ts
@@ -57845,13 +57290,13 @@ var Runtime = class {
57845
57290
  /\$CLAWOS_EXT_PORT/g,
57846
57291
  String(port)
57847
57292
  );
57848
- const dir = import_node_path57.default.join(this.root, extId);
57293
+ const dir = import_node_path52.default.join(this.root, extId);
57849
57294
  const env = {
57850
57295
  ...process.env,
57851
57296
  CLAWOS_EXT_PORT: String(port),
57852
57297
  CLAWOS_EXT_ID: extId
57853
57298
  };
57854
- const child = (0, import_node_child_process18.spawn)("sh", ["-c", cmd], {
57299
+ const child = (0, import_node_child_process15.spawn)("sh", ["-c", cmd], {
57855
57300
  cwd: dir,
57856
57301
  env,
57857
57302
  stdio: ["ignore", "pipe", "pipe"],
@@ -57957,7 +57402,7 @@ ${handle.stderrTail}`
57957
57402
 
57958
57403
  // src/extension/published-channels.ts
57959
57404
  var import_promises12 = __toESM(require("fs/promises"), 1);
57960
- var import_node_path58 = __toESM(require("path"), 1);
57405
+ var import_node_path53 = __toESM(require("path"), 1);
57961
57406
  init_zod();
57962
57407
  var PublishedChannelsError = class extends Error {
57963
57408
  constructor(code, message) {
@@ -58056,7 +57501,7 @@ var PublishedChannelStore = class {
58056
57501
  )
58057
57502
  };
58058
57503
  const tmp = `${this.filePath}.tmp`;
58059
- await import_promises12.default.mkdir(import_node_path58.default.dirname(this.filePath), { recursive: true });
57504
+ await import_promises12.default.mkdir(import_node_path53.default.dirname(this.filePath), { recursive: true });
58060
57505
  await import_promises12.default.writeFile(tmp, JSON.stringify(data, null, 2), { mode: 384 });
58061
57506
  await import_promises12.default.rename(tmp, this.filePath);
58062
57507
  }
@@ -58064,7 +57509,7 @@ var PublishedChannelStore = class {
58064
57509
 
58065
57510
  // src/extension/bundle-cache.ts
58066
57511
  var import_promises13 = __toESM(require("fs/promises"), 1);
58067
- var import_node_path59 = __toESM(require("path"), 1);
57512
+ var import_node_path54 = __toESM(require("path"), 1);
58068
57513
  var BundleCache = class {
58069
57514
  constructor(rootDir) {
58070
57515
  this.rootDir = rootDir;
@@ -58073,14 +57518,14 @@ var BundleCache = class {
58073
57518
  /** Atomic write: stage tmp → rename. Caller passes the hex sha256. */
58074
57519
  async write(snapshotHash, buffer) {
58075
57520
  await import_promises13.default.mkdir(this.rootDir, { recursive: true });
58076
- const file = import_node_path59.default.join(this.rootDir, `${snapshotHash}.zip`);
57521
+ const file = import_node_path54.default.join(this.rootDir, `${snapshotHash}.zip`);
58077
57522
  const tmp = `${file}.tmp`;
58078
57523
  await import_promises13.default.writeFile(tmp, buffer, { mode: 384 });
58079
57524
  await import_promises13.default.rename(tmp, file);
58080
57525
  }
58081
57526
  /** Returns the bundle bytes, or null when the file doesn't exist. */
58082
57527
  async read(snapshotHash) {
58083
- const file = import_node_path59.default.join(this.rootDir, `${snapshotHash}.zip`);
57528
+ const file = import_node_path54.default.join(this.rootDir, `${snapshotHash}.zip`);
58084
57529
  try {
58085
57530
  return await import_promises13.default.readFile(file);
58086
57531
  } catch (e) {
@@ -58090,7 +57535,7 @@ var BundleCache = class {
58090
57535
  }
58091
57536
  /** Idempotent — missing file is not an error. */
58092
57537
  async delete(snapshotHash) {
58093
- const file = import_node_path59.default.join(this.rootDir, `${snapshotHash}.zip`);
57538
+ const file = import_node_path54.default.join(this.rootDir, `${snapshotHash}.zip`);
58094
57539
  await import_promises13.default.rm(file, { force: true });
58095
57540
  }
58096
57541
  };
@@ -58115,10 +57560,17 @@ async function startDaemon(config) {
58115
57560
  });
58116
57561
  const logger = createLogger({
58117
57562
  level: config.logLevel,
58118
- file: import_node_path60.default.join(config.dataDir, "clawd.log"),
57563
+ file: import_node_path55.default.join(config.dataDir, "clawd.log"),
58119
57564
  logClient
58120
57565
  });
58121
57566
  logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
57567
+ const screenIdleProbeLogger = createFileOnlyLogger({
57568
+ file: import_node_path55.default.join(config.dataDir, "screen-idle-probe.log"),
57569
+ level: "debug"
57570
+ });
57571
+ logger.info("screen-idle probe logger enabled", {
57572
+ file: import_node_path55.default.join(config.dataDir, "screen-idle-probe.log")
57573
+ });
58122
57574
  const stateMgr = new StateFileManager({ dataDir: config.dataDir });
58123
57575
  const pre = stateMgr.preflight();
58124
57576
  if (pre.status === "active") {
@@ -58255,8 +57707,8 @@ async function startDaemon(config) {
58255
57707
  const agents = new AgentsScanner();
58256
57708
  const history = new ClaudeHistoryReader();
58257
57709
  let transport = null;
58258
- const personaStore = new PersonaStore(import_node_path60.default.join(config.dataDir, "personas"));
58259
- const usersRoot = import_node_path60.default.join(config.dataDir, "users");
57710
+ const personaStore = new PersonaStore(import_node_path55.default.join(config.dataDir, "personas"));
57711
+ const usersRoot = import_node_path55.default.join(config.dataDir, "users");
58260
57712
  const defaultsRoot = findDefaultsRoot(logger);
58261
57713
  if (defaultsRoot) {
58262
57714
  seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
@@ -58276,17 +57728,17 @@ async function startDaemon(config) {
58276
57728
  migrateCodexSandbox({ store: personaStore, logger });
58277
57729
  const groupFileStore = new GroupFileStore({ dataDir: config.dataDir, logger });
58278
57730
  const personaDispatchManager = new PersonaDispatchManager({ genId: () => v4_default() });
58279
- const here = typeof __dirname === "string" ? __dirname : import_node_path60.default.dirname((0, import_node_url4.fileURLToPath)(import_meta6.url));
57731
+ const here = typeof __dirname === "string" ? __dirname : import_node_path55.default.dirname((0, import_node_url4.fileURLToPath)(import_meta6.url));
58280
57732
  const dispatchServerCandidates = [
58281
- import_node_path60.default.join(here, "dispatch", "mcp-server.cjs"),
57733
+ import_node_path55.default.join(here, "dispatch", "mcp-server.cjs"),
58282
57734
  // 生产 dist/index → dist/dispatch/mcp-server.cjs
58283
- import_node_path60.default.join(here, "..", "dist", "dispatch", "mcp-server.cjs")
57735
+ import_node_path55.default.join(here, "..", "dist", "dispatch", "mcp-server.cjs")
58284
57736
  // dev tsx src/index → ../dist/dispatch/mcp-server.cjs
58285
57737
  ];
58286
- const dispatchServerScriptPath = dispatchServerCandidates.find((p2) => import_node_fs47.default.existsSync(p2));
57738
+ const dispatchServerScriptPath = dispatchServerCandidates.find((p2) => import_node_fs42.default.existsSync(p2));
58287
57739
  let dispatchMcpConfigPath2;
58288
57740
  if (dispatchServerScriptPath) {
58289
- const dispatchLogPath = import_node_path60.default.join(config.dataDir, "dispatch-mcp-server.log");
57741
+ const dispatchLogPath = import_node_path55.default.join(config.dataDir, "dispatch-mcp-server.log");
58290
57742
  dispatchMcpConfigPath2 = writeDispatchMcpConfig({
58291
57743
  dataDir: config.dataDir,
58292
57744
  serverScriptPath: dispatchServerScriptPath,
@@ -58303,15 +57755,15 @@ async function startDaemon(config) {
58303
57755
  });
58304
57756
  }
58305
57757
  const ticketServerCandidates = [
58306
- import_node_path60.default.join(here, "ticket", "mcp-server.cjs"),
58307
- import_node_path60.default.join(here, "..", "dist", "ticket", "mcp-server.cjs")
57758
+ import_node_path55.default.join(here, "ticket", "mcp-server.cjs"),
57759
+ import_node_path55.default.join(here, "..", "dist", "ticket", "mcp-server.cjs")
58308
57760
  ];
58309
- const ticketServerScriptPath = ticketServerCandidates.find((p2) => import_node_fs47.default.existsSync(p2));
57761
+ const ticketServerScriptPath = ticketServerCandidates.find((p2) => import_node_fs42.default.existsSync(p2));
58310
57762
  const ticketOwnerUnionId = feishuIdentity?.identity.unionId ?? "";
58311
57763
  const ticketOwnerName = feishuIdentity?.identity.displayName ?? "";
58312
57764
  let ticketMcpConfigPath2;
58313
57765
  if (ticketServerScriptPath && ticketOwnerUnionId) {
58314
- const ticketLogPath = import_node_path60.default.join(config.dataDir, "ticket-mcp-server.log");
57766
+ const ticketLogPath = import_node_path55.default.join(config.dataDir, "ticket-mcp-server.log");
58315
57767
  ticketMcpConfigPath2 = writeTicketMcpConfig({
58316
57768
  dataDir: config.dataDir,
58317
57769
  serverScriptPath: ticketServerScriptPath,
@@ -58332,13 +57784,13 @@ async function startDaemon(config) {
58332
57784
  });
58333
57785
  }
58334
57786
  const shiftServerCandidates = [
58335
- import_node_path60.default.join(here, "shift", "mcp-server.cjs"),
58336
- import_node_path60.default.join(here, "..", "dist", "shift", "mcp-server.cjs")
57787
+ import_node_path55.default.join(here, "shift", "mcp-server.cjs"),
57788
+ import_node_path55.default.join(here, "..", "dist", "shift", "mcp-server.cjs")
58337
57789
  ];
58338
- const shiftServerScriptPath = shiftServerCandidates.find((p2) => import_node_fs47.default.existsSync(p2));
57790
+ const shiftServerScriptPath = shiftServerCandidates.find((p2) => import_node_fs42.default.existsSync(p2));
58339
57791
  let shiftMcpConfigPath2;
58340
57792
  if (shiftServerScriptPath) {
58341
- const shiftLogPath = import_node_path60.default.join(config.dataDir, "shift-mcp-server.log");
57793
+ const shiftLogPath = import_node_path55.default.join(config.dataDir, "shift-mcp-server.log");
58342
57794
  shiftMcpConfigPath2 = await writeShiftMcpConfig({
58343
57795
  dataDir: config.dataDir,
58344
57796
  serverScriptPath: shiftServerScriptPath,
@@ -58356,13 +57808,13 @@ async function startDaemon(config) {
58356
57808
  );
58357
57809
  }
58358
57810
  const inboxServerCandidates = [
58359
- import_node_path60.default.join(here, "inbox", "mcp-server.cjs"),
58360
- import_node_path60.default.join(here, "..", "dist", "inbox", "mcp-server.cjs")
57811
+ import_node_path55.default.join(here, "inbox", "mcp-server.cjs"),
57812
+ import_node_path55.default.join(here, "..", "dist", "inbox", "mcp-server.cjs")
58361
57813
  ];
58362
- const inboxServerScriptPath = inboxServerCandidates.find((p2) => import_node_fs47.default.existsSync(p2));
57814
+ const inboxServerScriptPath = inboxServerCandidates.find((p2) => import_node_fs42.default.existsSync(p2));
58363
57815
  let inboxMcpConfigPath2;
58364
57816
  if (inboxServerScriptPath) {
58365
- const inboxLogPath = import_node_path60.default.join(config.dataDir, "inbox-mcp-server.log");
57817
+ const inboxLogPath = import_node_path55.default.join(config.dataDir, "inbox-mcp-server.log");
58366
57818
  inboxMcpConfigPath2 = await writeInboxMcpConfig({
58367
57819
  dataDir: config.dataDir,
58368
57820
  serverScriptPath: inboxServerScriptPath,
@@ -58380,7 +57832,7 @@ async function startDaemon(config) {
58380
57832
  );
58381
57833
  }
58382
57834
  const shiftStore = createShiftStore({
58383
- filePath: import_node_path60.default.join(config.dataDir, "shift.json"),
57835
+ filePath: import_node_path55.default.join(config.dataDir, "shift.json"),
58384
57836
  ownerIdProvider: () => ownerPrincipalId,
58385
57837
  now: () => Date.now()
58386
57838
  });
@@ -58395,10 +57847,14 @@ async function startDaemon(config) {
58395
57847
  // 新布局派生 (sessions/* + personas/<pid>/.clawd/sessions/owner/*)
58396
57848
  storeFactory: sessionStoreFactory,
58397
57849
  logger,
57850
+ // 取证 probe(可选,CLAWD_SCREEN_IDLE_PROBE=1 时启用):manager turn_end 判定链
57851
+ // 的所有决策点打到独立文件,跟 adapter 的 observeScreenIdle probe 共用同一份 file logger,
57852
+ // 便于 grep sessionId 时 tui 层 + manager 层交叉时序都在同一文件里
57853
+ ...screenIdleProbeLogger ? { screenIdleProbeLogger } : {},
58398
57854
  getAdapter,
58399
57855
  historyReader: history,
58400
57856
  dataDir: config.dataDir,
58401
- personaRoot: import_node_path60.default.join(config.dataDir, "personas"),
57857
+ personaRoot: import_node_path55.default.join(config.dataDir, "personas"),
58402
57858
  usersRoot,
58403
57859
  personaStore,
58404
57860
  ownerDisplayName,
@@ -58441,10 +57897,10 @@ async function startDaemon(config) {
58441
57897
  // 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
58442
57898
  attachmentGroup: {
58443
57899
  onFileEdit: (input) => {
58444
- const absPath = import_node_path60.default.isAbsolute(input.relPath) ? input.relPath : import_node_path60.default.join(input.cwd, input.relPath);
57900
+ const absPath = import_node_path55.default.isAbsolute(input.relPath) ? input.relPath : import_node_path55.default.join(input.cwd, input.relPath);
58445
57901
  let size = 0;
58446
57902
  try {
58447
- size = import_node_fs47.default.statSync(absPath).size;
57903
+ size = import_node_fs42.default.statSync(absPath).size;
58448
57904
  } catch (err) {
58449
57905
  logger.warn("attachment.onFileEdit stat failed", {
58450
57906
  sessionId: input.sessionId,
@@ -58512,10 +57968,10 @@ async function startDaemon(config) {
58512
57968
  onSurfaceUnregister: (tsid) => manager.unregisterSurface(tsid),
58513
57969
  // ReadyGate v2:ReadyDetector emit ready 时投递 reducer 'ready-detected' input
58514
57970
  onReady: (tsid) => manager.dispatchReadyDetected(tsid),
58515
- // 屏幕静止补权威 turn_end(修 turn_duration 尾随 text 覆盖 lastEventKind)
58516
- onTurnIdle: (tsid) => manager.dispatchTurnIdle(tsid),
58517
- // 复合条件闸:observer 还需静止多久才满 idleMs(屏幕静止后精确等这段剩余再补 turn_end
58518
- getObserverWaitMs: (tsid, idleMs) => manager.observerIdleWaitMs(tsid, idleMs)
57971
+ // 屏幕真稳定 5s 的一次性信号 manager pending turn_duration flush turn_end
57972
+ onScreenIdle: (tsid) => manager.notifyScreenIdle(tsid),
57973
+ // 取证 probe(默认无条件启用;见 createFileOnlyLogger
57974
+ screenIdleProbeLogger
58519
57975
  }) : new ClaudeAdapter({ logger, historyReader: new ClaudeHistoryReader() });
58520
57976
  registerAdapter("claude", claudeAdapter);
58521
57977
  registerAdapter("codex", new CodexAdapter({ logger, historyReader: new CodexHistoryReader() }));
@@ -58642,11 +58098,11 @@ async function startDaemon(config) {
58642
58098
  // 'persona/<pid>/owner',default 走 'default'。
58643
58099
  getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
58644
58100
  // guest path guard:candidate 必须在 personaRoot 子树或调用者自己的 user-dir 下
58645
- personaRoot: import_node_path60.default.join(config.dataDir, "personas"),
58101
+ personaRoot: import_node_path55.default.join(config.dataDir, "personas"),
58646
58102
  usersRoot
58647
58103
  },
58648
58104
  // workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
58649
- personaRoot: import_node_path60.default.join(config.dataDir, "personas"),
58105
+ personaRoot: import_node_path55.default.join(config.dataDir, "personas"),
58650
58106
  // v2 多人 persona 隔离:handler 派生 guest user-dir 放行
58651
58107
  usersRoot,
58652
58108
  // capability:list / delete handler 依赖
@@ -58666,9 +58122,6 @@ async function startDaemon(config) {
58666
58122
  inboxStore,
58667
58123
  // 联系人列表 store(device:connect / 自动反向落同一 store)
58668
58124
  contactStore,
58669
- // <dataDir>/sshd 绝对路径 —— contact-ssh handlers 用它拼 authorized_keys / keys/ 子路径
58670
- // Task 10 会加 SshdManager 起 sshd;handlers wire 提前挂 sshdDir 让 typecheck 过
58671
- sshdDir: import_node_path60.default.join(config.dataDir, "sshd"),
58672
58125
  // inbox:sendDm 用:sessionId → session 出身(复用 attachment 同款 findOwnedSessionScope)
58673
58126
  getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
58674
58127
  // contact:removed broadcast;复用 capability:tokenIssued 同款通路
@@ -58758,11 +58211,11 @@ async function startDaemon(config) {
58758
58211
  // 发布上线脚手架化 (spec 2026-06-03 §5.2):
58759
58212
  // appBuilderPersonaRoot 用于拼 publish.sh 绝对路径(persona-app-builder 安装在
58760
58213
  // dataDir/personas/persona-app-builder 之下,extension-kit/scripts/publish.sh 是相对路径)。
58761
- appBuilderPersonaRoot: import_node_path60.default.join(config.dataDir, "personas", "persona-app-builder"),
58214
+ appBuilderPersonaRoot: import_node_path55.default.join(config.dataDir, "personas", "persona-app-builder"),
58762
58215
  // 共享 deploy-kit 根:scaffold/publish 脚本骨架 + 阿里云凭证单一真源。
58763
- deployKitRoot: import_node_path60.default.join(config.dataDir, "deploy-kit"),
58216
+ deployKitRoot: import_node_path55.default.join(config.dataDir, "deploy-kit"),
58764
58217
  // scaffold/publish 按当前 session 的 persona 解析其安装根,让每个 persona 用自己的模板/注入配置。
58765
- resolvePersonaRoot: (personaId) => import_node_path60.default.join(config.dataDir, "personas", personaId),
58218
+ resolvePersonaRoot: (personaId) => import_node_path55.default.join(config.dataDir, "personas", personaId),
58766
58219
  // 发布上线脚手架化 (spec 2026-06-03 §5.2.2):
58767
58220
  // 复用 SessionManagerDeps.broadcastFrame 同款 dispatch 逻辑 —— runner 调 manager.send
58768
58221
  // 取回 broadcast 帧后逐帧 push 到 transport,跟 manager 自身的 deps 一致。
@@ -58805,7 +58258,7 @@ async function startDaemon(config) {
58805
58258
  }
58806
58259
  let sourceJsonlPath = "(no transcript yet \u2014 operate from the task description alone)";
58807
58260
  if (sourceFile && sourceFile.toolSessionId) {
58808
- sourceJsonlPath = import_node_path60.default.join(
58261
+ sourceJsonlPath = import_node_path55.default.join(
58809
58262
  import_node_os21.default.homedir(),
58810
58263
  ".claude",
58811
58264
  "projects",
@@ -59105,8 +58558,8 @@ async function startDaemon(config) {
59105
58558
  const lines = [
59106
58559
  `Tunnel: ${r.url}`,
59107
58560
  ...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
59108
- `Frpc config: ${import_node_path60.default.join(config.dataDir, "frpc.toml")}`,
59109
- `Frpc log: ${import_node_path60.default.join(config.dataDir, "frpc.log")}`
58561
+ `Frpc config: ${import_node_path55.default.join(config.dataDir, "frpc.toml")}`,
58562
+ `Frpc log: ${import_node_path55.default.join(config.dataDir, "frpc.log")}`
59110
58563
  ];
59111
58564
  const width = Math.max(...lines.map((l) => l.length));
59112
58565
  const bar = "\u2550".repeat(width + 4);
@@ -59119,8 +58572,8 @@ ${bar}
59119
58572
 
59120
58573
  `);
59121
58574
  try {
59122
- const connectPath = import_node_path60.default.join(config.dataDir, "connect.txt");
59123
- import_node_fs47.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
58575
+ const connectPath = import_node_path55.default.join(config.dataDir, "connect.txt");
58576
+ import_node_fs42.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
59124
58577
  } catch {
59125
58578
  }
59126
58579
  } catch (err) {
@@ -59145,22 +58598,6 @@ ${bar}
59145
58598
  logger.warn("tunnel unavailable, degraded to local mode", { reason: tunnelError });
59146
58599
  }
59147
58600
  }
59148
- const sshdMgr = new SshdManager({
59149
- dataDir: config.dataDir,
59150
- port: config.sshdPort,
59151
- logger,
59152
- installProcessExitHandlers: true,
59153
- onSshdExit: (info) => logger.warn("sshd exited unexpectedly", info)
59154
- });
59155
- try {
59156
- await sshdMgr.start();
59157
- rebuildAuthorizedKeys(contactStore, import_node_path60.default.join(config.dataDir, "sshd"));
59158
- logger.info("sshd: contact-ssh sandbox ready", { port: config.sshdPort });
59159
- } catch (err) {
59160
- logger.warn("sshd start failed; contact SSH grant will not work until fixed", {
59161
- err: err.message
59162
- });
59163
- }
59164
58601
  void reportDevice();
59165
58602
  void fetchServerKey();
59166
58603
  const tickAttachmentGc = () => {
@@ -59195,11 +58632,6 @@ ${bar}
59195
58632
  if (tunnelMgr) {
59196
58633
  await tunnelMgr.stop();
59197
58634
  }
59198
- await sshdMgr.stop().catch((err) => {
59199
- logger.warn("shutdown.sshd-stop-failed", {
59200
- error: err instanceof Error ? err.message : String(err)
59201
- });
59202
- });
59203
58635
  await wss.stop();
59204
58636
  stateMgr.delete();
59205
58637
  if (logClient) await logClient.dispose();
@@ -59213,9 +58645,9 @@ ${bar}
59213
58645
  };
59214
58646
  }
59215
58647
  function migrateDropPersonsDir(dataDir) {
59216
- const dir = import_node_path60.default.join(dataDir, "persons");
58648
+ const dir = import_node_path55.default.join(dataDir, "persons");
59217
58649
  try {
59218
- import_node_fs47.default.rmSync(dir, { recursive: true, force: true });
58650
+ import_node_fs42.default.rmSync(dir, { recursive: true, force: true });
59219
58651
  } catch {
59220
58652
  }
59221
58653
  }