@clawos-dev/clawd 0.2.199-beta.399.3cb813c → 0.2.199-beta.400.ba99f40
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 +1143 -575
- package/dist/share-md-viewer-error.html +1 -1
- package/dist/share-md-viewer.html +1 -1
- package/dist/share-ui/assets/{guest-BtMmEdS6.js → guest-B1dInTij.js} +27 -27
- package/dist/share-ui/assets/guest-B5ChN9T3.css +32 -0
- package/dist/share-ui/guest.html +2 -2
- package/package.json +1 -1
- package/dist/share-ui/assets/guest-CCfI4zdC.css +0 -32
package/dist/cli.cjs
CHANGED
|
@@ -158,6 +158,11 @@ 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",
|
|
161
166
|
// ---- visitor:* (web 访客 · persona web 分享,spec 2026-06-24-persona-web-share-design) ----
|
|
162
167
|
// owner-only:列出登录访问过本机 public persona 的 web 访客(零安装、飞书登录、daemon 自签
|
|
163
168
|
// visitor token)。存储 ~/.clawd/visitors.json(VisitorStore),登录(exchange)即 upsert;
|
|
@@ -737,8 +742,8 @@ var init_parseUtil = __esm({
|
|
|
737
742
|
init_errors2();
|
|
738
743
|
init_en();
|
|
739
744
|
makeIssue = (params) => {
|
|
740
|
-
const { data, path:
|
|
741
|
-
const fullPath = [...
|
|
745
|
+
const { data, path: path73, errorMaps, issueData } = params;
|
|
746
|
+
const fullPath = [...path73, ...issueData.path || []];
|
|
742
747
|
const fullIssue = {
|
|
743
748
|
...issueData,
|
|
744
749
|
path: fullPath
|
|
@@ -1049,11 +1054,11 @@ var init_types = __esm({
|
|
|
1049
1054
|
init_parseUtil();
|
|
1050
1055
|
init_util();
|
|
1051
1056
|
ParseInputLazyPath = class {
|
|
1052
|
-
constructor(parent, value,
|
|
1057
|
+
constructor(parent, value, path73, key) {
|
|
1053
1058
|
this._cachedPath = [];
|
|
1054
1059
|
this.parent = parent;
|
|
1055
1060
|
this.data = value;
|
|
1056
|
-
this._path =
|
|
1061
|
+
this._path = path73;
|
|
1057
1062
|
this._key = key;
|
|
1058
1063
|
}
|
|
1059
1064
|
get path() {
|
|
@@ -5678,7 +5683,19 @@ var init_contact = __esm({
|
|
|
5678
5683
|
* 老 contacts.json 缺此字段 → zod default 补 null(无破坏性升级;不 orphan)。
|
|
5679
5684
|
* 对齐 SessionFile.pinnedAt 语义(daemon 侧持久化,contact:pin RPC 更新 → contact:pinned push)。
|
|
5680
5685
|
*/
|
|
5681
|
-
pinnedAt: external_exports.number().int().nullable().default(null)
|
|
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([])
|
|
5682
5699
|
}).strict();
|
|
5683
5700
|
ContactWireSchema = ContactSchema;
|
|
5684
5701
|
ContactRemoveArgsSchema = external_exports.object({
|
|
@@ -5717,6 +5734,40 @@ var init_contact = __esm({
|
|
|
5717
5734
|
}
|
|
5718
5735
|
});
|
|
5719
5736
|
|
|
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
|
+
|
|
5720
5771
|
// ../protocol/src/extension.ts
|
|
5721
5772
|
function isAllowedHostedUrl(raw) {
|
|
5722
5773
|
let u;
|
|
@@ -6118,6 +6169,7 @@ var init_runtime = __esm({
|
|
|
6118
6169
|
init_capability();
|
|
6119
6170
|
init_inbox();
|
|
6120
6171
|
init_contact();
|
|
6172
|
+
init_contact_ssh();
|
|
6121
6173
|
init_extension();
|
|
6122
6174
|
init_feishu_auth();
|
|
6123
6175
|
init_dispatch();
|
|
@@ -6397,8 +6449,8 @@ var require_req = __commonJS({
|
|
|
6397
6449
|
if (req.originalUrl) {
|
|
6398
6450
|
_req.url = req.originalUrl;
|
|
6399
6451
|
} else {
|
|
6400
|
-
const
|
|
6401
|
-
_req.url = typeof
|
|
6452
|
+
const path73 = req.path;
|
|
6453
|
+
_req.url = typeof path73 === "string" ? path73 : req.url ? req.url.path || req.url : void 0;
|
|
6402
6454
|
}
|
|
6403
6455
|
if (req.query) {
|
|
6404
6456
|
_req.query = req.query;
|
|
@@ -6563,14 +6615,14 @@ var require_redact = __commonJS({
|
|
|
6563
6615
|
}
|
|
6564
6616
|
return obj;
|
|
6565
6617
|
}
|
|
6566
|
-
function parsePath(
|
|
6618
|
+
function parsePath(path73) {
|
|
6567
6619
|
const parts = [];
|
|
6568
6620
|
let current = "";
|
|
6569
6621
|
let inBrackets = false;
|
|
6570
6622
|
let inQuotes = false;
|
|
6571
6623
|
let quoteChar = "";
|
|
6572
|
-
for (let i = 0; i <
|
|
6573
|
-
const char =
|
|
6624
|
+
for (let i = 0; i < path73.length; i++) {
|
|
6625
|
+
const char = path73[i];
|
|
6574
6626
|
if (!inBrackets && char === ".") {
|
|
6575
6627
|
if (current) {
|
|
6576
6628
|
parts.push(current);
|
|
@@ -6701,10 +6753,10 @@ var require_redact = __commonJS({
|
|
|
6701
6753
|
return current;
|
|
6702
6754
|
}
|
|
6703
6755
|
function redactPaths(obj, paths, censor, remove = false) {
|
|
6704
|
-
for (const
|
|
6705
|
-
const parts = parsePath(
|
|
6756
|
+
for (const path73 of paths) {
|
|
6757
|
+
const parts = parsePath(path73);
|
|
6706
6758
|
if (parts.includes("*")) {
|
|
6707
|
-
redactWildcardPath(obj, parts, censor,
|
|
6759
|
+
redactWildcardPath(obj, parts, censor, path73, remove);
|
|
6708
6760
|
} else {
|
|
6709
6761
|
if (remove) {
|
|
6710
6762
|
removeKey(obj, parts);
|
|
@@ -6789,8 +6841,8 @@ var require_redact = __commonJS({
|
|
|
6789
6841
|
}
|
|
6790
6842
|
} else {
|
|
6791
6843
|
if (afterWildcard.includes("*")) {
|
|
6792
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
6793
|
-
const fullPath = [...pathArray.slice(0, pathLength), ...
|
|
6844
|
+
const wrappedCensor = typeof censor === "function" ? (value, path73) => {
|
|
6845
|
+
const fullPath = [...pathArray.slice(0, pathLength), ...path73];
|
|
6794
6846
|
return censor(value, fullPath);
|
|
6795
6847
|
} : censor;
|
|
6796
6848
|
redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
|
|
@@ -6825,8 +6877,8 @@ var require_redact = __commonJS({
|
|
|
6825
6877
|
return null;
|
|
6826
6878
|
}
|
|
6827
6879
|
const pathStructure = /* @__PURE__ */ new Map();
|
|
6828
|
-
for (const
|
|
6829
|
-
const parts = parsePath(
|
|
6880
|
+
for (const path73 of pathsToClone) {
|
|
6881
|
+
const parts = parsePath(path73);
|
|
6830
6882
|
let current = pathStructure;
|
|
6831
6883
|
for (let i = 0; i < parts.length; i++) {
|
|
6832
6884
|
const part = parts[i];
|
|
@@ -6878,24 +6930,24 @@ var require_redact = __commonJS({
|
|
|
6878
6930
|
}
|
|
6879
6931
|
return cloneSelectively(obj, pathStructure);
|
|
6880
6932
|
}
|
|
6881
|
-
function validatePath(
|
|
6882
|
-
if (typeof
|
|
6933
|
+
function validatePath(path73) {
|
|
6934
|
+
if (typeof path73 !== "string") {
|
|
6883
6935
|
throw new Error("Paths must be (non-empty) strings");
|
|
6884
6936
|
}
|
|
6885
|
-
if (
|
|
6937
|
+
if (path73 === "") {
|
|
6886
6938
|
throw new Error("Invalid redaction path ()");
|
|
6887
6939
|
}
|
|
6888
|
-
if (
|
|
6889
|
-
throw new Error(`Invalid redaction path (${
|
|
6940
|
+
if (path73.includes("..")) {
|
|
6941
|
+
throw new Error(`Invalid redaction path (${path73})`);
|
|
6890
6942
|
}
|
|
6891
|
-
if (
|
|
6892
|
-
throw new Error(`Invalid redaction path (${
|
|
6943
|
+
if (path73.includes(",")) {
|
|
6944
|
+
throw new Error(`Invalid redaction path (${path73})`);
|
|
6893
6945
|
}
|
|
6894
6946
|
let bracketCount = 0;
|
|
6895
6947
|
let inQuotes = false;
|
|
6896
6948
|
let quoteChar = "";
|
|
6897
|
-
for (let i = 0; i <
|
|
6898
|
-
const char =
|
|
6949
|
+
for (let i = 0; i < path73.length; i++) {
|
|
6950
|
+
const char = path73[i];
|
|
6899
6951
|
if ((char === '"' || char === "'") && bracketCount > 0) {
|
|
6900
6952
|
if (!inQuotes) {
|
|
6901
6953
|
inQuotes = true;
|
|
@@ -6909,20 +6961,20 @@ var require_redact = __commonJS({
|
|
|
6909
6961
|
} else if (char === "]" && !inQuotes) {
|
|
6910
6962
|
bracketCount--;
|
|
6911
6963
|
if (bracketCount < 0) {
|
|
6912
|
-
throw new Error(`Invalid redaction path (${
|
|
6964
|
+
throw new Error(`Invalid redaction path (${path73})`);
|
|
6913
6965
|
}
|
|
6914
6966
|
}
|
|
6915
6967
|
}
|
|
6916
6968
|
if (bracketCount !== 0) {
|
|
6917
|
-
throw new Error(`Invalid redaction path (${
|
|
6969
|
+
throw new Error(`Invalid redaction path (${path73})`);
|
|
6918
6970
|
}
|
|
6919
6971
|
}
|
|
6920
6972
|
function validatePaths(paths) {
|
|
6921
6973
|
if (!Array.isArray(paths)) {
|
|
6922
6974
|
throw new TypeError("paths must be an array");
|
|
6923
6975
|
}
|
|
6924
|
-
for (const
|
|
6925
|
-
validatePath(
|
|
6976
|
+
for (const path73 of paths) {
|
|
6977
|
+
validatePath(path73);
|
|
6926
6978
|
}
|
|
6927
6979
|
}
|
|
6928
6980
|
function slowRedact(options = {}) {
|
|
@@ -7090,8 +7142,8 @@ var require_redaction = __commonJS({
|
|
|
7090
7142
|
if (shape[k2] === null) {
|
|
7091
7143
|
o[k2] = (value) => topCensor(value, [k2]);
|
|
7092
7144
|
} else {
|
|
7093
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
7094
|
-
return censor(value, [k2, ...
|
|
7145
|
+
const wrappedCensor = typeof censor === "function" ? (value, path73) => {
|
|
7146
|
+
return censor(value, [k2, ...path73]);
|
|
7095
7147
|
} : censor;
|
|
7096
7148
|
o[k2] = Redact({
|
|
7097
7149
|
paths: shape[k2],
|
|
@@ -7309,10 +7361,10 @@ var require_atomic_sleep = __commonJS({
|
|
|
7309
7361
|
var require_sonic_boom = __commonJS({
|
|
7310
7362
|
"../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
|
|
7311
7363
|
"use strict";
|
|
7312
|
-
var
|
|
7364
|
+
var fs66 = require("fs");
|
|
7313
7365
|
var EventEmitter3 = require("events");
|
|
7314
7366
|
var inherits = require("util").inherits;
|
|
7315
|
-
var
|
|
7367
|
+
var path73 = require("path");
|
|
7316
7368
|
var sleep2 = require_atomic_sleep();
|
|
7317
7369
|
var assert = require("assert");
|
|
7318
7370
|
var BUSY_WRITE_TIMEOUT = 100;
|
|
@@ -7366,20 +7418,20 @@ var require_sonic_boom = __commonJS({
|
|
|
7366
7418
|
const mode = sonic.mode;
|
|
7367
7419
|
if (sonic.sync) {
|
|
7368
7420
|
try {
|
|
7369
|
-
if (sonic.mkdir)
|
|
7370
|
-
const fd =
|
|
7421
|
+
if (sonic.mkdir) fs66.mkdirSync(path73.dirname(file), { recursive: true });
|
|
7422
|
+
const fd = fs66.openSync(file, flags, mode);
|
|
7371
7423
|
fileOpened(null, fd);
|
|
7372
7424
|
} catch (err) {
|
|
7373
7425
|
fileOpened(err);
|
|
7374
7426
|
throw err;
|
|
7375
7427
|
}
|
|
7376
7428
|
} else if (sonic.mkdir) {
|
|
7377
|
-
|
|
7429
|
+
fs66.mkdir(path73.dirname(file), { recursive: true }, (err) => {
|
|
7378
7430
|
if (err) return fileOpened(err);
|
|
7379
|
-
|
|
7431
|
+
fs66.open(file, flags, mode, fileOpened);
|
|
7380
7432
|
});
|
|
7381
7433
|
} else {
|
|
7382
|
-
|
|
7434
|
+
fs66.open(file, flags, mode, fileOpened);
|
|
7383
7435
|
}
|
|
7384
7436
|
}
|
|
7385
7437
|
function SonicBoom(opts) {
|
|
@@ -7420,8 +7472,8 @@ var require_sonic_boom = __commonJS({
|
|
|
7420
7472
|
this.flush = flushBuffer;
|
|
7421
7473
|
this.flushSync = flushBufferSync;
|
|
7422
7474
|
this._actualWrite = actualWriteBuffer;
|
|
7423
|
-
fsWriteSync = () =>
|
|
7424
|
-
fsWrite = () =>
|
|
7475
|
+
fsWriteSync = () => fs66.writeSync(this.fd, this._writingBuf);
|
|
7476
|
+
fsWrite = () => fs66.write(this.fd, this._writingBuf, this.release);
|
|
7425
7477
|
} else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
|
|
7426
7478
|
this._writingBuf = "";
|
|
7427
7479
|
this.write = write;
|
|
@@ -7430,15 +7482,15 @@ var require_sonic_boom = __commonJS({
|
|
|
7430
7482
|
this._actualWrite = actualWrite;
|
|
7431
7483
|
fsWriteSync = () => {
|
|
7432
7484
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
7433
|
-
return
|
|
7485
|
+
return fs66.writeSync(this.fd, this._writingBuf);
|
|
7434
7486
|
}
|
|
7435
|
-
return
|
|
7487
|
+
return fs66.writeSync(this.fd, this._writingBuf, "utf8");
|
|
7436
7488
|
};
|
|
7437
7489
|
fsWrite = () => {
|
|
7438
7490
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
7439
|
-
return
|
|
7491
|
+
return fs66.write(this.fd, this._writingBuf, this.release);
|
|
7440
7492
|
}
|
|
7441
|
-
return
|
|
7493
|
+
return fs66.write(this.fd, this._writingBuf, "utf8", this.release);
|
|
7442
7494
|
};
|
|
7443
7495
|
} else {
|
|
7444
7496
|
throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
|
|
@@ -7495,7 +7547,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7495
7547
|
}
|
|
7496
7548
|
}
|
|
7497
7549
|
if (this._fsync) {
|
|
7498
|
-
|
|
7550
|
+
fs66.fsyncSync(this.fd);
|
|
7499
7551
|
}
|
|
7500
7552
|
const len = this._len;
|
|
7501
7553
|
if (this._reopening) {
|
|
@@ -7609,7 +7661,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7609
7661
|
const onDrain = () => {
|
|
7610
7662
|
if (!this._fsync) {
|
|
7611
7663
|
try {
|
|
7612
|
-
|
|
7664
|
+
fs66.fsync(this.fd, (err) => {
|
|
7613
7665
|
this._flushPending = false;
|
|
7614
7666
|
cb(err);
|
|
7615
7667
|
});
|
|
@@ -7711,7 +7763,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7711
7763
|
const fd = this.fd;
|
|
7712
7764
|
this.once("ready", () => {
|
|
7713
7765
|
if (fd !== this.fd) {
|
|
7714
|
-
|
|
7766
|
+
fs66.close(fd, (err) => {
|
|
7715
7767
|
if (err) {
|
|
7716
7768
|
return this.emit("error", err);
|
|
7717
7769
|
}
|
|
@@ -7760,7 +7812,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7760
7812
|
buf = this._bufs[0];
|
|
7761
7813
|
}
|
|
7762
7814
|
try {
|
|
7763
|
-
const n = Buffer.isBuffer(buf) ?
|
|
7815
|
+
const n = Buffer.isBuffer(buf) ? fs66.writeSync(this.fd, buf) : fs66.writeSync(this.fd, buf, "utf8");
|
|
7764
7816
|
const releasedBufObj = releaseWritingBuf(buf, this._len, n);
|
|
7765
7817
|
buf = releasedBufObj.writingBuf;
|
|
7766
7818
|
this._len = releasedBufObj.len;
|
|
@@ -7776,7 +7828,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7776
7828
|
}
|
|
7777
7829
|
}
|
|
7778
7830
|
try {
|
|
7779
|
-
|
|
7831
|
+
fs66.fsyncSync(this.fd);
|
|
7780
7832
|
} catch {
|
|
7781
7833
|
}
|
|
7782
7834
|
}
|
|
@@ -7797,7 +7849,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7797
7849
|
buf = mergeBuf(this._bufs[0], this._lens[0]);
|
|
7798
7850
|
}
|
|
7799
7851
|
try {
|
|
7800
|
-
const n =
|
|
7852
|
+
const n = fs66.writeSync(this.fd, buf);
|
|
7801
7853
|
buf = buf.subarray(n);
|
|
7802
7854
|
this._len = Math.max(this._len - n, 0);
|
|
7803
7855
|
if (buf.length <= 0) {
|
|
@@ -7825,13 +7877,13 @@ var require_sonic_boom = __commonJS({
|
|
|
7825
7877
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
|
|
7826
7878
|
if (this.sync) {
|
|
7827
7879
|
try {
|
|
7828
|
-
const written = Buffer.isBuffer(this._writingBuf) ?
|
|
7880
|
+
const written = Buffer.isBuffer(this._writingBuf) ? fs66.writeSync(this.fd, this._writingBuf) : fs66.writeSync(this.fd, this._writingBuf, "utf8");
|
|
7829
7881
|
release(null, written);
|
|
7830
7882
|
} catch (err) {
|
|
7831
7883
|
release(err);
|
|
7832
7884
|
}
|
|
7833
7885
|
} else {
|
|
7834
|
-
|
|
7886
|
+
fs66.write(this.fd, this._writingBuf, release);
|
|
7835
7887
|
}
|
|
7836
7888
|
}
|
|
7837
7889
|
function actualWriteBuffer() {
|
|
@@ -7840,7 +7892,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7840
7892
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
|
|
7841
7893
|
if (this.sync) {
|
|
7842
7894
|
try {
|
|
7843
|
-
const written =
|
|
7895
|
+
const written = fs66.writeSync(this.fd, this._writingBuf);
|
|
7844
7896
|
release(null, written);
|
|
7845
7897
|
} catch (err) {
|
|
7846
7898
|
release(err);
|
|
@@ -7849,7 +7901,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7849
7901
|
if (kCopyBuffer) {
|
|
7850
7902
|
this._writingBuf = Buffer.from(this._writingBuf);
|
|
7851
7903
|
}
|
|
7852
|
-
|
|
7904
|
+
fs66.write(this.fd, this._writingBuf, release);
|
|
7853
7905
|
}
|
|
7854
7906
|
}
|
|
7855
7907
|
function actualClose(sonic) {
|
|
@@ -7865,12 +7917,12 @@ var require_sonic_boom = __commonJS({
|
|
|
7865
7917
|
sonic._lens = [];
|
|
7866
7918
|
assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
|
|
7867
7919
|
try {
|
|
7868
|
-
|
|
7920
|
+
fs66.fsync(sonic.fd, closeWrapped);
|
|
7869
7921
|
} catch {
|
|
7870
7922
|
}
|
|
7871
7923
|
function closeWrapped() {
|
|
7872
7924
|
if (sonic.fd !== 1 && sonic.fd !== 2) {
|
|
7873
|
-
|
|
7925
|
+
fs66.close(sonic.fd, done);
|
|
7874
7926
|
} else {
|
|
7875
7927
|
done();
|
|
7876
7928
|
}
|
|
@@ -11005,11 +11057,11 @@ var init_lib = __esm({
|
|
|
11005
11057
|
}
|
|
11006
11058
|
}
|
|
11007
11059
|
},
|
|
11008
|
-
addToPath: function addToPath(
|
|
11009
|
-
var last =
|
|
11060
|
+
addToPath: function addToPath(path73, added, removed, oldPosInc, options) {
|
|
11061
|
+
var last = path73.lastComponent;
|
|
11010
11062
|
if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
|
|
11011
11063
|
return {
|
|
11012
|
-
oldPos:
|
|
11064
|
+
oldPos: path73.oldPos + oldPosInc,
|
|
11013
11065
|
lastComponent: {
|
|
11014
11066
|
count: last.count + 1,
|
|
11015
11067
|
added,
|
|
@@ -11019,7 +11071,7 @@ var init_lib = __esm({
|
|
|
11019
11071
|
};
|
|
11020
11072
|
} else {
|
|
11021
11073
|
return {
|
|
11022
|
-
oldPos:
|
|
11074
|
+
oldPos: path73.oldPos + oldPosInc,
|
|
11023
11075
|
lastComponent: {
|
|
11024
11076
|
count: 1,
|
|
11025
11077
|
added,
|
|
@@ -11485,10 +11537,10 @@ function attachmentToHistoryMessage(o, ts) {
|
|
|
11485
11537
|
const memories = raw.map((m2) => {
|
|
11486
11538
|
if (!m2 || typeof m2 !== "object") return null;
|
|
11487
11539
|
const rec3 = m2;
|
|
11488
|
-
const
|
|
11540
|
+
const path73 = typeof rec3.path === "string" ? rec3.path : null;
|
|
11489
11541
|
const content = typeof rec3.content === "string" ? rec3.content : null;
|
|
11490
|
-
if (!
|
|
11491
|
-
const entry = { path:
|
|
11542
|
+
if (!path73 || content == null) return null;
|
|
11543
|
+
const entry = { path: path73, content };
|
|
11492
11544
|
if (typeof rec3.mtimeMs === "number") entry.mtimeMs = rec3.mtimeMs;
|
|
11493
11545
|
return entry;
|
|
11494
11546
|
}).filter((m2) => m2 !== null);
|
|
@@ -12300,10 +12352,10 @@ function parseAttachment(obj) {
|
|
|
12300
12352
|
const memories = raw.map((m2) => {
|
|
12301
12353
|
if (!m2 || typeof m2 !== "object") return null;
|
|
12302
12354
|
const rec3 = m2;
|
|
12303
|
-
const
|
|
12355
|
+
const path73 = typeof rec3.path === "string" ? rec3.path : null;
|
|
12304
12356
|
const content = typeof rec3.content === "string" ? rec3.content : null;
|
|
12305
|
-
if (!
|
|
12306
|
-
const out = { path:
|
|
12357
|
+
if (!path73 || content == null) return null;
|
|
12358
|
+
const out = { path: path73, content };
|
|
12307
12359
|
if (typeof rec3.mtimeMs === "number") out.mtimeMs = rec3.mtimeMs;
|
|
12308
12360
|
return out;
|
|
12309
12361
|
}).filter((m2) => m2 !== null);
|
|
@@ -28978,7 +29030,7 @@ var require_websocket = __commonJS({
|
|
|
28978
29030
|
var http3 = require("http");
|
|
28979
29031
|
var net3 = require("net");
|
|
28980
29032
|
var tls = require("tls");
|
|
28981
|
-
var { randomBytes, createHash:
|
|
29033
|
+
var { randomBytes, createHash: createHash2 } = require("crypto");
|
|
28982
29034
|
var { Duplex, Readable: Readable3 } = require("stream");
|
|
28983
29035
|
var { URL: URL2 } = require("url");
|
|
28984
29036
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -29638,7 +29690,7 @@ var require_websocket = __commonJS({
|
|
|
29638
29690
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
29639
29691
|
return;
|
|
29640
29692
|
}
|
|
29641
|
-
const digest =
|
|
29693
|
+
const digest = createHash2("sha1").update(key + GUID).digest("base64");
|
|
29642
29694
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
29643
29695
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
29644
29696
|
return;
|
|
@@ -30005,7 +30057,7 @@ var require_websocket_server = __commonJS({
|
|
|
30005
30057
|
var EventEmitter3 = require("events");
|
|
30006
30058
|
var http3 = require("http");
|
|
30007
30059
|
var { Duplex } = require("stream");
|
|
30008
|
-
var { createHash:
|
|
30060
|
+
var { createHash: createHash2 } = require("crypto");
|
|
30009
30061
|
var extension2 = require_extension();
|
|
30010
30062
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
30011
30063
|
var subprotocol2 = require_subprotocol();
|
|
@@ -30306,7 +30358,7 @@ var require_websocket_server = __commonJS({
|
|
|
30306
30358
|
);
|
|
30307
30359
|
}
|
|
30308
30360
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
30309
|
-
const digest =
|
|
30361
|
+
const digest = createHash2("sha1").update(key + GUID).digest("base64");
|
|
30310
30362
|
const headers = [
|
|
30311
30363
|
"HTTP/1.1 101 Switching Protocols",
|
|
30312
30364
|
"Upgrade: websocket",
|
|
@@ -33268,8 +33320,8 @@ var require_utils = __commonJS({
|
|
|
33268
33320
|
var result = transform[inputType][outputType](input);
|
|
33269
33321
|
return result;
|
|
33270
33322
|
};
|
|
33271
|
-
exports2.resolve = function(
|
|
33272
|
-
var parts =
|
|
33323
|
+
exports2.resolve = function(path73) {
|
|
33324
|
+
var parts = path73.split("/");
|
|
33273
33325
|
var result = [];
|
|
33274
33326
|
for (var index = 0; index < parts.length; index++) {
|
|
33275
33327
|
var part = parts[index];
|
|
@@ -39122,18 +39174,18 @@ var require_object = __commonJS({
|
|
|
39122
39174
|
var object = new ZipObject(name, zipObjectContent, o);
|
|
39123
39175
|
this.files[name] = object;
|
|
39124
39176
|
};
|
|
39125
|
-
var parentFolder = function(
|
|
39126
|
-
if (
|
|
39127
|
-
|
|
39177
|
+
var parentFolder = function(path73) {
|
|
39178
|
+
if (path73.slice(-1) === "/") {
|
|
39179
|
+
path73 = path73.substring(0, path73.length - 1);
|
|
39128
39180
|
}
|
|
39129
|
-
var lastSlash =
|
|
39130
|
-
return lastSlash > 0 ?
|
|
39181
|
+
var lastSlash = path73.lastIndexOf("/");
|
|
39182
|
+
return lastSlash > 0 ? path73.substring(0, lastSlash) : "";
|
|
39131
39183
|
};
|
|
39132
|
-
var forceTrailingSlash = function(
|
|
39133
|
-
if (
|
|
39134
|
-
|
|
39184
|
+
var forceTrailingSlash = function(path73) {
|
|
39185
|
+
if (path73.slice(-1) !== "/") {
|
|
39186
|
+
path73 += "/";
|
|
39135
39187
|
}
|
|
39136
|
-
return
|
|
39188
|
+
return path73;
|
|
39137
39189
|
};
|
|
39138
39190
|
var folderAdd = function(name, createFolders) {
|
|
39139
39191
|
createFolders = typeof createFolders !== "undefined" ? createFolders : defaults.createFolders;
|
|
@@ -40135,7 +40187,7 @@ var require_lib3 = __commonJS({
|
|
|
40135
40187
|
// src/run-case/recorder.ts
|
|
40136
40188
|
function startRunCaseRecorder(opts) {
|
|
40137
40189
|
const now = opts.now ?? Date.now;
|
|
40138
|
-
const dir =
|
|
40190
|
+
const dir = import_node_path61.default.dirname(opts.recordPath);
|
|
40139
40191
|
let stream = null;
|
|
40140
40192
|
let closing = false;
|
|
40141
40193
|
let closedSettled = false;
|
|
@@ -40149,8 +40201,8 @@ function startRunCaseRecorder(opts) {
|
|
|
40149
40201
|
});
|
|
40150
40202
|
const ensureStream = () => {
|
|
40151
40203
|
if (stream) return stream;
|
|
40152
|
-
|
|
40153
|
-
stream =
|
|
40204
|
+
import_node_fs48.default.mkdirSync(dir, { recursive: true });
|
|
40205
|
+
stream = import_node_fs48.default.createWriteStream(opts.recordPath, { flags: "a" });
|
|
40154
40206
|
stream.on("close", () => closedResolve());
|
|
40155
40207
|
return stream;
|
|
40156
40208
|
};
|
|
@@ -40175,12 +40227,12 @@ function startRunCaseRecorder(opts) {
|
|
|
40175
40227
|
};
|
|
40176
40228
|
return { tap, close, closed };
|
|
40177
40229
|
}
|
|
40178
|
-
var
|
|
40230
|
+
var import_node_fs48, import_node_path61;
|
|
40179
40231
|
var init_recorder = __esm({
|
|
40180
40232
|
"src/run-case/recorder.ts"() {
|
|
40181
40233
|
"use strict";
|
|
40182
|
-
|
|
40183
|
-
|
|
40234
|
+
import_node_fs48 = __toESM(require("fs"), 1);
|
|
40235
|
+
import_node_path61 = __toESM(require("path"), 1);
|
|
40184
40236
|
}
|
|
40185
40237
|
});
|
|
40186
40238
|
|
|
@@ -40223,7 +40275,7 @@ var init_wire = __esm({
|
|
|
40223
40275
|
// src/run-case/controller.ts
|
|
40224
40276
|
async function runController(opts) {
|
|
40225
40277
|
const now = opts.now ?? Date.now;
|
|
40226
|
-
const cwd = opts.cwd ?? (0,
|
|
40278
|
+
const cwd = opts.cwd ?? (0, import_node_fs49.mkdtempSync)(import_node_path62.default.join(import_node_os22.default.tmpdir(), "clawd-runcase-"));
|
|
40227
40279
|
const ownsCwd = opts.cwd === void 0;
|
|
40228
40280
|
const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
|
|
40229
40281
|
const spawnCtx = { cwd };
|
|
@@ -40384,19 +40436,19 @@ async function runController(opts) {
|
|
|
40384
40436
|
if (sigintHandler) process.off("SIGINT", sigintHandler);
|
|
40385
40437
|
if (ownsCwd) {
|
|
40386
40438
|
try {
|
|
40387
|
-
(0,
|
|
40439
|
+
(0, import_node_fs49.rmSync)(cwd, { recursive: true, force: true });
|
|
40388
40440
|
} catch {
|
|
40389
40441
|
}
|
|
40390
40442
|
}
|
|
40391
40443
|
return exitCode ?? 0;
|
|
40392
40444
|
}
|
|
40393
|
-
var
|
|
40445
|
+
var import_node_fs49, import_node_os22, import_node_path62;
|
|
40394
40446
|
var init_controller = __esm({
|
|
40395
40447
|
"src/run-case/controller.ts"() {
|
|
40396
40448
|
"use strict";
|
|
40397
|
-
|
|
40449
|
+
import_node_fs49 = require("fs");
|
|
40398
40450
|
import_node_os22 = __toESM(require("os"), 1);
|
|
40399
|
-
|
|
40451
|
+
import_node_path62 = __toESM(require("path"), 1);
|
|
40400
40452
|
init_claude();
|
|
40401
40453
|
init_stdout_splitter();
|
|
40402
40454
|
init_permission_stdio();
|
|
@@ -40499,6 +40551,7 @@ var import_node_path = __toESM(require("path"), 1);
|
|
|
40499
40551
|
init_protocol();
|
|
40500
40552
|
var DEFAULT_PORT = 18790;
|
|
40501
40553
|
var DEFAULT_HOST = "127.0.0.1";
|
|
40554
|
+
var DEFAULT_SSHD_PORT = 22422;
|
|
40502
40555
|
var DEFAULT_CLAWOS_API = "https://api.clawos.chat";
|
|
40503
40556
|
var DEFAULT_LOG_ENDPOINT = "https://clawd-prod.cn-hangzhou.log.aliyuncs.com/logstores/app-logs/track";
|
|
40504
40557
|
function resolveLogShipping(raw, cliNoShipping) {
|
|
@@ -40566,6 +40619,14 @@ function parseArgs(argv) {
|
|
|
40566
40619
|
case "--no-log-shipping":
|
|
40567
40620
|
out.noLogShipping = true;
|
|
40568
40621
|
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
|
+
}
|
|
40569
40630
|
default:
|
|
40570
40631
|
if (a.startsWith("--")) throw new Error(`unknown flag: ${a}`);
|
|
40571
40632
|
break;
|
|
@@ -40615,6 +40676,7 @@ function resolveConfig(opts) {
|
|
|
40615
40676
|
fileCfg.logShipping,
|
|
40616
40677
|
args.noLogShipping ?? false
|
|
40617
40678
|
);
|
|
40679
|
+
const sshdPort = args.sshdPort ?? (typeof fileCfg.sshdPort === "number" ? fileCfg.sshdPort : DEFAULT_SSHD_PORT);
|
|
40618
40680
|
return {
|
|
40619
40681
|
port,
|
|
40620
40682
|
host,
|
|
@@ -40628,7 +40690,8 @@ function resolveConfig(opts) {
|
|
|
40628
40690
|
frpcBinary,
|
|
40629
40691
|
mode,
|
|
40630
40692
|
previewPorts,
|
|
40631
|
-
logShipping
|
|
40693
|
+
logShipping,
|
|
40694
|
+
sshdPort
|
|
40632
40695
|
};
|
|
40633
40696
|
}
|
|
40634
40697
|
var HELP_TEXT = `clawd [options]
|
|
@@ -40640,6 +40703,7 @@ var HELP_TEXT = `clawd [options]
|
|
|
40640
40703
|
--clawos-api <url> tunnel register \u63A5\u53E3\u7684 base url\uFF08\u9ED8\u8BA4 https://api.clawos.chat\uFF09
|
|
40641
40704
|
--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
|
|
40642
40705
|
--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
|
|
40643
40707
|
--help / -h \u663E\u793A\u5E2E\u52A9
|
|
40644
40708
|
--version / -v \u663E\u793A\u7248\u672C
|
|
40645
40709
|
|
|
@@ -40658,8 +40722,8 @@ Env (advanced):
|
|
|
40658
40722
|
`;
|
|
40659
40723
|
|
|
40660
40724
|
// src/index.ts
|
|
40661
|
-
var
|
|
40662
|
-
var
|
|
40725
|
+
var import_node_path60 = __toESM(require("path"), 1);
|
|
40726
|
+
var import_node_fs47 = __toESM(require("fs"), 1);
|
|
40663
40727
|
var import_node_os21 = __toESM(require("os"), 1);
|
|
40664
40728
|
|
|
40665
40729
|
// ../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/stringify.js
|
|
@@ -40779,18 +40843,6 @@ function createLogger(opts = {}) {
|
|
|
40779
40843
|
);
|
|
40780
40844
|
return wrap(base);
|
|
40781
40845
|
}
|
|
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
|
-
}
|
|
40794
40846
|
function pinoLevelToString(n) {
|
|
40795
40847
|
if (typeof n !== "number") return null;
|
|
40796
40848
|
if (n >= 50) return "error";
|
|
@@ -42956,18 +43008,9 @@ var SessionManager = class {
|
|
|
42956
43008
|
// 由 observer 监听 jsonl user 行后调 recordRealUserUuid 建立映射;rewind 系列 RPC 在
|
|
42957
43009
|
// 入参 / 出参做转译,保证 UI 看到的 uuid 始终是 events 流里的 synth uuid
|
|
42958
43010
|
realUuidBySynth = /* @__PURE__ */ new Map();
|
|
42959
|
-
//
|
|
42960
|
-
//
|
|
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();
|
|
43011
|
+
// observeScreenIdle 复合条件闸用:sessionId → observer 上次喂出业务事件的时刻(deps.now())。
|
|
43012
|
+
// observerIdleWaitMs 据此判 observer 是否也静止 ≥ idleMs(屏幕静止 AND observer 静止才补 turn_end)。
|
|
43013
|
+
lastObserverEventAt = /* @__PURE__ */ new Map();
|
|
42971
43014
|
// SessionStore 按 scope 派生(root = <dataDir>/sessions/<scopeSubPath>/)。
|
|
42972
43015
|
// default scope 直接复用 deps.store;persona scope(owner / listener)第一次访问时按需创建并缓存。
|
|
42973
43016
|
// 取代旧的 storesByAgent —— agentId 概念由 SessionScope 取代,路径即身份,
|
|
@@ -43307,14 +43350,6 @@ var SessionManager = class {
|
|
|
43307
43350
|
routeFromRunner(frame, target) {
|
|
43308
43351
|
const compressed = compressFrameForWire(frame);
|
|
43309
43352
|
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
|
-
}
|
|
43318
43353
|
if (compressed.type === "session:event" || compressed.type === "session:status") {
|
|
43319
43354
|
const sid = compressed.sessionId;
|
|
43320
43355
|
if (sid) {
|
|
@@ -43588,7 +43623,7 @@ var SessionManager = class {
|
|
|
43588
43623
|
this.runners.delete(args.sessionId);
|
|
43589
43624
|
this.realUuidBySynth.delete(args.sessionId);
|
|
43590
43625
|
this.lastUiSizeBySessionId.delete(args.sessionId);
|
|
43591
|
-
this.
|
|
43626
|
+
this.lastObserverEventAt.delete(args.sessionId);
|
|
43592
43627
|
return { response: { sessionId: args.sessionId }, broadcast };
|
|
43593
43628
|
}
|
|
43594
43629
|
this.deleteOwned(args.sessionId);
|
|
@@ -43618,7 +43653,6 @@ var SessionManager = class {
|
|
|
43618
43653
|
async stop(args) {
|
|
43619
43654
|
const runner = this.runners.get(args.sessionId);
|
|
43620
43655
|
if (!runner) return { response: { ok: true }, broadcast: [] };
|
|
43621
|
-
this.clearPendingTurnEnd(args.sessionId);
|
|
43622
43656
|
const { broadcast } = this.withCollector(() => {
|
|
43623
43657
|
runner.input({ kind: "command", command: { kind: "stop" } });
|
|
43624
43658
|
});
|
|
@@ -43752,7 +43786,6 @@ var SessionManager = class {
|
|
|
43752
43786
|
newSession(args) {
|
|
43753
43787
|
const existingFile = this.getFile(args.sessionId);
|
|
43754
43788
|
const nextToolSessionId = this.deps.mode === "tui" && (existingFile.tool ?? "claude") === "claude" ? v4_default() : void 0;
|
|
43755
|
-
this.clearPendingTurnEnd(args.sessionId);
|
|
43756
43789
|
const runner = this.runners.get(args.sessionId);
|
|
43757
43790
|
if (runner) {
|
|
43758
43791
|
const { value, broadcast } = this.withCollector(() => {
|
|
@@ -43853,7 +43886,6 @@ var SessionManager = class {
|
|
|
43853
43886
|
for (const r of this.runners.values()) {
|
|
43854
43887
|
r.input({ kind: "command", command: { kind: "stop" } });
|
|
43855
43888
|
}
|
|
43856
|
-
this.pendingTurnDurationSignals.clear();
|
|
43857
43889
|
}
|
|
43858
43890
|
// 给 observer 用:拿已存在的 runner
|
|
43859
43891
|
getActive(sessionId) {
|
|
@@ -44070,7 +44102,7 @@ var SessionManager = class {
|
|
|
44070
44102
|
this.runners.delete(args.sessionId);
|
|
44071
44103
|
this.realUuidBySynth.delete(args.sessionId);
|
|
44072
44104
|
this.lastUiSizeBySessionId.delete(args.sessionId);
|
|
44073
|
-
this.
|
|
44105
|
+
this.lastObserverEventAt.delete(args.sessionId);
|
|
44074
44106
|
return { response: { sessionId: args.sessionId }, broadcast };
|
|
44075
44107
|
}
|
|
44076
44108
|
this.storeFor(args.scope).delete(args.sessionId);
|
|
@@ -44316,93 +44348,23 @@ var SessionManager = class {
|
|
|
44316
44348
|
return;
|
|
44317
44349
|
}
|
|
44318
44350
|
}
|
|
44351
|
+
this.lastObserverEventAt.set(sessionId, (this.deps.now ?? Date.now)());
|
|
44319
44352
|
let feedEvents = outEvents;
|
|
44320
44353
|
if (outEvents.some((e) => e.kind === "turn_end")) {
|
|
44321
|
-
const
|
|
44322
|
-
|
|
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 {
|
|
44354
|
+
const ev = this.peekTurnEvidence(runner);
|
|
44355
|
+
if (!ev.turnHasContent) {
|
|
44338
44356
|
feedEvents = outEvents.filter((e) => e.kind !== "turn_end");
|
|
44339
|
-
|
|
44340
|
-
|
|
44341
|
-
|
|
44342
|
-
|
|
44343
|
-
)
|
|
44344
|
-
}
|
|
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
|
-
}
|
|
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
|
+
});
|
|
44351
44363
|
}
|
|
44352
44364
|
}
|
|
44353
44365
|
if (feedEvents.length === 0) return;
|
|
44354
44366
|
runner.feedObserverEvents(feedEvents);
|
|
44355
44367
|
}
|
|
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
|
-
}
|
|
44406
44368
|
// AskUserQuestion 表单回写(plan: clawd-ask-user-question):UI 答完所有 question 后调用。
|
|
44407
44369
|
// - session 不存在 / 无 runner → noop 幂等返回 ok(first-decider-wins)
|
|
44408
44370
|
// - reducer noop(toolUseId 不存在或已答过)也保持幂等返回,handler 不抛
|
|
@@ -44661,6 +44623,70 @@ var SessionManager = class {
|
|
|
44661
44623
|
if (!runner) return;
|
|
44662
44624
|
runner.input({ kind: "ready-detected" });
|
|
44663
44625
|
}
|
|
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
|
+
}
|
|
44664
44690
|
/** toolSessionId → sessionId 反查(遍历 runners);session 数典型 < 10,O(n) 可接受 */
|
|
44665
44691
|
sessionIdByToolSid(toolSessionId) {
|
|
44666
44692
|
for (const [sid, runner] of this.runners) {
|
|
@@ -46170,8 +46196,8 @@ function turnStartInput(text) {
|
|
|
46170
46196
|
const items = [];
|
|
46171
46197
|
let leftover = text;
|
|
46172
46198
|
for (const m2 of text.matchAll(SKILL_RE)) {
|
|
46173
|
-
const [marker, name,
|
|
46174
|
-
items.push({ type: "skill", name, path:
|
|
46199
|
+
const [marker, name, path73] = m2;
|
|
46200
|
+
items.push({ type: "skill", name, path: path73 });
|
|
46175
46201
|
leftover = leftover.replace(marker, "");
|
|
46176
46202
|
}
|
|
46177
46203
|
for (const m2 of text.matchAll(ATTACHMENT_RE2)) {
|
|
@@ -46403,7 +46429,6 @@ var CodexAdapter = class {
|
|
|
46403
46429
|
};
|
|
46404
46430
|
|
|
46405
46431
|
// src/tools/claude-tui.ts
|
|
46406
|
-
var import_node_crypto5 = require("crypto");
|
|
46407
46432
|
var import_node_fs16 = __toESM(require("fs"), 1);
|
|
46408
46433
|
var import_node_os7 = __toESM(require("os"), 1);
|
|
46409
46434
|
var import_node_path14 = __toESM(require("path"), 1);
|
|
@@ -47214,56 +47239,22 @@ function observeScreenIdle(surface, opts) {
|
|
|
47214
47239
|
timer = null;
|
|
47215
47240
|
if (disposed) return;
|
|
47216
47241
|
if (opts.getPopupVisible()) {
|
|
47217
|
-
opts.probeLogger?.info("screen-idle fire suppressed: popup visible", {
|
|
47218
|
-
label: opts.probeLabel
|
|
47219
|
-
});
|
|
47220
47242
|
timer = setTimeout(fire, opts.idleMs);
|
|
47221
47243
|
return;
|
|
47222
47244
|
}
|
|
47223
47245
|
const obsWait = opts.getObserverWaitMs?.() ?? 0;
|
|
47224
47246
|
if (obsWait > 0) {
|
|
47225
|
-
opts.probeLogger?.info("screen-idle fire suppressed: observer not idle", {
|
|
47226
|
-
label: opts.probeLabel,
|
|
47227
|
-
obsWait
|
|
47228
|
-
});
|
|
47229
47247
|
timer = setTimeout(fire, Math.max(obsWait, REWAIT_MIN_MS));
|
|
47230
47248
|
return;
|
|
47231
47249
|
}
|
|
47232
|
-
if (armed)
|
|
47233
|
-
opts.probeLogger?.debug("screen-idle fire noop: already armed", {
|
|
47234
|
-
label: opts.probeLabel
|
|
47235
|
-
});
|
|
47236
|
-
return;
|
|
47237
|
-
}
|
|
47250
|
+
if (armed) return;
|
|
47238
47251
|
armed = true;
|
|
47239
|
-
opts.probeLogger?.info("screen-idle fire triggered \u2192 armed=true, calling onIdle", {
|
|
47240
|
-
label: opts.probeLabel
|
|
47241
|
-
});
|
|
47242
47252
|
opts.onIdle();
|
|
47243
47253
|
};
|
|
47244
47254
|
const unsub = surface.onTick((lines) => {
|
|
47245
47255
|
if (disposed) return;
|
|
47246
47256
|
const snap = snapOf(lines);
|
|
47247
47257
|
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
|
-
}
|
|
47267
47258
|
lastSnap = snap;
|
|
47268
47259
|
armed = false;
|
|
47269
47260
|
clear();
|
|
@@ -47274,38 +47265,9 @@ function observeScreenIdle(surface, opts) {
|
|
|
47274
47265
|
disposed = true;
|
|
47275
47266
|
unsub();
|
|
47276
47267
|
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;
|
|
47290
47268
|
}
|
|
47291
47269
|
};
|
|
47292
47270
|
}
|
|
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
|
-
}
|
|
47309
47271
|
var BYPASS_SETTLE_MS = 300;
|
|
47310
47272
|
var SCREEN_IDLE_MS = 5e3;
|
|
47311
47273
|
function createBootGate(pty, logger) {
|
|
@@ -47380,42 +47342,11 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
|
|
|
47380
47342
|
// 用于 spawn / PtyChildProcess 链路打日志
|
|
47381
47343
|
tuiLogger;
|
|
47382
47344
|
tuiOpts;
|
|
47383
|
-
/**
|
|
47384
|
-
* per-toolSessionId 的 tui 观察者句柄,仅用于 turn_end gate 查询(`canAcceptTurnEnd`)。
|
|
47385
|
-
* onIdle / onPopupTransition 等回调仍走原有闭包(不复用这份 map),本 map 只承担
|
|
47386
|
-
* "manager 需要跨模块查屏幕/弹框状态"这单一职责。
|
|
47387
|
-
*/
|
|
47388
|
-
tuiStates = /* @__PURE__ */ new Map();
|
|
47389
47345
|
constructor(opts = {}) {
|
|
47390
47346
|
super(opts);
|
|
47391
47347
|
this.tuiLogger = opts.logger;
|
|
47392
47348
|
this.tuiOpts = opts;
|
|
47393
47349
|
}
|
|
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
|
-
}
|
|
47419
47350
|
spawn(ctx) {
|
|
47420
47351
|
const args = buildTuiSpawnArgs(ctx, jsonlExistsForCtx(ctx));
|
|
47421
47352
|
const cmd = process.env.CLAUDE_BIN ?? "claude";
|
|
@@ -47473,26 +47404,18 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
|
|
|
47473
47404
|
const screenIdleObserver = observeScreenIdle(surface, {
|
|
47474
47405
|
idleMs: SCREEN_IDLE_MS,
|
|
47475
47406
|
onIdle: () => {
|
|
47476
|
-
if (!ctx.toolSessionId || !this.tuiOpts.
|
|
47477
|
-
this.tuiLogger?.debug("screen-idle \u2192
|
|
47478
|
-
this.tuiOpts.
|
|
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);
|
|
47479
47410
|
},
|
|
47480
47411
|
getPopupVisible: () => popupObserver.visibleKind !== null,
|
|
47481
|
-
//
|
|
47482
|
-
|
|
47483
|
-
|
|
47484
|
-
probeLabel: ctx.toolSessionId ?? "<no-tsid>"
|
|
47485
|
-
} : {}
|
|
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
|
|
47486
47415
|
});
|
|
47487
47416
|
if (ctx.toolSessionId && this.tuiOpts.onSurfaceRegister) {
|
|
47488
47417
|
this.tuiOpts.onSurfaceRegister(ctx.toolSessionId, surface);
|
|
47489
47418
|
}
|
|
47490
|
-
if (ctx.toolSessionId) {
|
|
47491
|
-
this.tuiStates.set(ctx.toolSessionId, {
|
|
47492
|
-
screenIdle: screenIdleObserver,
|
|
47493
|
-
popup: popupObserver
|
|
47494
|
-
});
|
|
47495
|
-
}
|
|
47496
47419
|
let chunkSeq = 0;
|
|
47497
47420
|
if (ctx.toolSessionId && this.tuiOpts.onPtyReplayRegister) {
|
|
47498
47421
|
this.tuiOpts.onPtyReplayRegister(ctx.toolSessionId, async () => {
|
|
@@ -47538,9 +47461,6 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
|
|
|
47538
47461
|
readyObserver.dispose();
|
|
47539
47462
|
popupObserver.dispose();
|
|
47540
47463
|
screenIdleObserver.dispose();
|
|
47541
|
-
if (ctx.toolSessionId) {
|
|
47542
|
-
this.tuiStates.delete(ctx.toolSessionId);
|
|
47543
|
-
}
|
|
47544
47464
|
if (ctx.toolSessionId && this.tuiOpts.onSurfaceUnregister) {
|
|
47545
47465
|
this.tuiOpts.onSurfaceUnregister(ctx.toolSessionId);
|
|
47546
47466
|
}
|
|
@@ -47823,7 +47743,7 @@ async function writeInboxMcpConfig(args) {
|
|
|
47823
47743
|
// src/shift/store.ts
|
|
47824
47744
|
var import_promises = __toESM(require("fs/promises"), 1);
|
|
47825
47745
|
var import_node_path19 = __toESM(require("path"), 1);
|
|
47826
|
-
var
|
|
47746
|
+
var import_node_crypto5 = require("crypto");
|
|
47827
47747
|
|
|
47828
47748
|
// src/shift/constants.ts
|
|
47829
47749
|
var MAX_RUNS_PER_SHIFT = 30;
|
|
@@ -47919,7 +47839,7 @@ function createShiftStore(deps) {
|
|
|
47919
47839
|
const nextRunAtMs = computeNextRunAtMs(input.schedule, now) ?? void 0;
|
|
47920
47840
|
const shift = {
|
|
47921
47841
|
...input,
|
|
47922
|
-
id: (0,
|
|
47842
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
47923
47843
|
createdAtMs: now,
|
|
47924
47844
|
updatedAtMs: now,
|
|
47925
47845
|
state: { nextRunAtMs },
|
|
@@ -48695,13 +48615,13 @@ function mapSkillsListResponse(res) {
|
|
|
48695
48615
|
const r = s ?? {};
|
|
48696
48616
|
const name = str3(r.name);
|
|
48697
48617
|
if (!name) continue;
|
|
48698
|
-
const
|
|
48618
|
+
const path73 = str3(r.path);
|
|
48699
48619
|
const description = str3(r.description);
|
|
48700
48620
|
const isPlugin = name.includes(":");
|
|
48701
48621
|
out.push({
|
|
48702
48622
|
name,
|
|
48703
48623
|
source: isPlugin ? "plugin" : "project",
|
|
48704
|
-
...
|
|
48624
|
+
...path73 ? { path: path73 } : {},
|
|
48705
48625
|
...description ? { description } : {},
|
|
48706
48626
|
...isPlugin ? { plugin: name.split(":")[0] } : {}
|
|
48707
48627
|
});
|
|
@@ -50495,6 +50415,23 @@ var ContactStore = class {
|
|
|
50495
50415
|
this.flush();
|
|
50496
50416
|
return true;
|
|
50497
50417
|
}
|
|
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
|
+
}
|
|
50498
50435
|
flush() {
|
|
50499
50436
|
const file = path30.join(this.dataDir, FILE_NAME);
|
|
50500
50437
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
@@ -50642,7 +50579,9 @@ async function autoReverseContact(args) {
|
|
|
50642
50579
|
connectToken: "",
|
|
50643
50580
|
grants,
|
|
50644
50581
|
addedAt: now(),
|
|
50645
|
-
pinnedAt: null
|
|
50582
|
+
pinnedAt: null,
|
|
50583
|
+
sshAllowed: false,
|
|
50584
|
+
exposedDirs: []
|
|
50646
50585
|
};
|
|
50647
50586
|
args.store.upsert(base);
|
|
50648
50587
|
args.broadcast({ type: "contact:added", contact: base });
|
|
@@ -50871,7 +50810,7 @@ function lookupMime(filePathOrName) {
|
|
|
50871
50810
|
}
|
|
50872
50811
|
|
|
50873
50812
|
// src/attachment/sign-url.ts
|
|
50874
|
-
var
|
|
50813
|
+
var import_node_crypto6 = __toESM(require("crypto"), 1);
|
|
50875
50814
|
var HMAC_ALGO = "sha256";
|
|
50876
50815
|
function base64urlEncode(buf) {
|
|
50877
50816
|
const b2 = typeof buf === "string" ? Buffer.from(buf, "utf8") : buf;
|
|
@@ -50888,7 +50827,7 @@ function decodeAbsPathFromUrl(encoded) {
|
|
|
50888
50827
|
}
|
|
50889
50828
|
function computeSig(secret, absPath, e) {
|
|
50890
50829
|
const msg = e === null ? absPath : `${absPath}|${e}`;
|
|
50891
|
-
return
|
|
50830
|
+
return import_node_crypto6.default.createHmac(HMAC_ALGO, secret).update(msg).digest();
|
|
50892
50831
|
}
|
|
50893
50832
|
function signUrlParts(secret, absPath, ttlSeconds, now = Date.now) {
|
|
50894
50833
|
const e = ttlSeconds === null ? null : Math.floor(now() / 1e3) + ttlSeconds;
|
|
@@ -50923,7 +50862,7 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
|
|
|
50923
50862
|
if (provided.length !== expected.length) {
|
|
50924
50863
|
return { ok: false, code: "BAD_SIG" };
|
|
50925
50864
|
}
|
|
50926
|
-
if (!
|
|
50865
|
+
if (!import_node_crypto6.default.timingSafeEqual(provided, expected)) {
|
|
50927
50866
|
return { ok: false, code: "BAD_SIG" };
|
|
50928
50867
|
}
|
|
50929
50868
|
if (e !== null && now() / 1e3 > e) {
|
|
@@ -50935,7 +50874,7 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
|
|
|
50935
50874
|
// src/attachment/upload.ts
|
|
50936
50875
|
var import_node_fs25 = __toESM(require("fs"), 1);
|
|
50937
50876
|
var import_node_path25 = __toESM(require("path"), 1);
|
|
50938
|
-
var
|
|
50877
|
+
var import_node_crypto7 = __toESM(require("crypto"), 1);
|
|
50939
50878
|
var import_promises2 = require("stream/promises");
|
|
50940
50879
|
var UploadError = class extends Error {
|
|
50941
50880
|
constructor(code, message) {
|
|
@@ -50959,11 +50898,11 @@ async function writeUploadedAttachment(args) {
|
|
|
50959
50898
|
} catch (err) {
|
|
50960
50899
|
throw new UploadError("STORAGE_ERROR", `mkdir failed: ${err.message}`);
|
|
50961
50900
|
}
|
|
50962
|
-
const hasher =
|
|
50901
|
+
const hasher = import_node_crypto7.default.createHash("sha256");
|
|
50963
50902
|
let actualSize = 0;
|
|
50964
50903
|
const tmpPath = import_node_path25.default.join(
|
|
50965
50904
|
attachmentsRoot,
|
|
50966
|
-
`.upload-${process.pid}-${Date.now()}-${
|
|
50905
|
+
`.upload-${process.pid}-${Date.now()}-${import_node_crypto7.default.randomBytes(4).toString("hex")}`
|
|
50967
50906
|
);
|
|
50968
50907
|
try {
|
|
50969
50908
|
await (0, import_promises2.pipeline)(
|
|
@@ -51839,7 +51778,7 @@ function runAttachmentGc(args) {
|
|
|
51839
51778
|
// src/attachment/group.ts
|
|
51840
51779
|
var import_node_fs28 = __toESM(require("fs"), 1);
|
|
51841
51780
|
var import_node_path29 = __toESM(require("path"), 1);
|
|
51842
|
-
var
|
|
51781
|
+
var import_node_crypto8 = __toESM(require("crypto"), 1);
|
|
51843
51782
|
init_protocol();
|
|
51844
51783
|
var GroupFileStore = class {
|
|
51845
51784
|
dataDir;
|
|
@@ -51928,7 +51867,7 @@ var GroupFileStore = class {
|
|
|
51928
51867
|
entries[idx] = next;
|
|
51929
51868
|
} else {
|
|
51930
51869
|
next = {
|
|
51931
|
-
id: `gf-${
|
|
51870
|
+
id: `gf-${import_node_crypto8.default.randomBytes(6).toString("base64url")}`,
|
|
51932
51871
|
relPath: input.relPath,
|
|
51933
51872
|
from: input.from,
|
|
51934
51873
|
label: input.label,
|
|
@@ -52047,7 +51986,7 @@ function readDaemonSourceFromEnv(env = process.env) {
|
|
|
52047
51986
|
// src/tunnel/tunnel-manager.ts
|
|
52048
51987
|
var import_node_fs33 = __toESM(require("fs"), 1);
|
|
52049
51988
|
var import_node_path34 = __toESM(require("path"), 1);
|
|
52050
|
-
var
|
|
51989
|
+
var import_node_crypto9 = __toESM(require("crypto"), 1);
|
|
52051
51990
|
var import_node_child_process9 = require("child_process");
|
|
52052
51991
|
|
|
52053
51992
|
// src/tunnel/tunnel-store.ts
|
|
@@ -52546,7 +52485,7 @@ var TunnelManager = class {
|
|
|
52546
52485
|
override: this.deps.frpcBinaryOverride ?? void 0
|
|
52547
52486
|
});
|
|
52548
52487
|
const tomlPath = import_node_path34.default.join(this.deps.dataDir, "frpc.toml");
|
|
52549
|
-
const proxyName = `clawd-${t.subdomain}-${localPort}-${
|
|
52488
|
+
const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto9.default.randomBytes(3).toString("hex")}`;
|
|
52550
52489
|
const toml = buildFrpcToml({
|
|
52551
52490
|
serverAddr: t.frpsHost,
|
|
52552
52491
|
serverPort: t.frpsPort,
|
|
@@ -52642,29 +52581,510 @@ async function waitForFrpcReady(proc, timeoutMs) {
|
|
|
52642
52581
|
});
|
|
52643
52582
|
}
|
|
52644
52583
|
|
|
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
|
+
|
|
52645
53065
|
// src/tunnel/device-key.ts
|
|
52646
53066
|
var import_node_os14 = __toESM(require("os"), 1);
|
|
52647
|
-
var
|
|
52648
|
-
var
|
|
53067
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
53068
|
+
var import_node_crypto10 = __toESM(require("crypto"), 1);
|
|
52649
53069
|
var DERIVE_SALT = "clawd-tunnel-device-v1";
|
|
52650
53070
|
function deriveStableDeviceKey(opts = {}) {
|
|
52651
53071
|
const hostname = opts.hostname ?? import_node_os14.default.hostname();
|
|
52652
53072
|
const uid = opts.uid ?? (typeof import_node_os14.default.userInfo === "function" ? import_node_os14.default.userInfo().uid : 0);
|
|
52653
53073
|
const home = opts.home ?? import_node_os14.default.homedir();
|
|
52654
|
-
const defaultDataDir =
|
|
52655
|
-
const normalizedDataDir = opts.dataDir ?
|
|
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;
|
|
52656
53076
|
const isDefaultDir = normalizedDataDir == null || normalizedDataDir === defaultDataDir;
|
|
52657
53077
|
const input = isDefaultDir ? `${hostname}::${uid}` : `${hostname}::${uid}::${normalizedDataDir}`;
|
|
52658
|
-
return
|
|
53078
|
+
return import_node_crypto10.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
|
|
52659
53079
|
}
|
|
52660
53080
|
|
|
52661
53081
|
// src/auth-store.ts
|
|
52662
|
-
var
|
|
52663
|
-
var
|
|
52664
|
-
var
|
|
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);
|
|
52665
53085
|
var AUTH_FILE_NAME = "auth.json";
|
|
52666
53086
|
function authFilePath(dataDir) {
|
|
52667
|
-
return
|
|
53087
|
+
return import_node_path40.default.join(dataDir, AUTH_FILE_NAME);
|
|
52668
53088
|
}
|
|
52669
53089
|
function loadOrCreateAuthFile(opts) {
|
|
52670
53090
|
const file = authFilePath(opts.dataDir);
|
|
@@ -52693,14 +53113,14 @@ function loadOrCreateAuthFile(opts) {
|
|
|
52693
53113
|
return next;
|
|
52694
53114
|
}
|
|
52695
53115
|
function defaultGenerateToken() {
|
|
52696
|
-
return
|
|
53116
|
+
return import_node_crypto11.default.randomBytes(32).toString("base64url");
|
|
52697
53117
|
}
|
|
52698
53118
|
function defaultGenerateOwnerPrincipalId() {
|
|
52699
|
-
return `owner-${
|
|
53119
|
+
return `owner-${import_node_crypto11.default.randomUUID()}`;
|
|
52700
53120
|
}
|
|
52701
53121
|
function readAuthFile(file) {
|
|
52702
53122
|
try {
|
|
52703
|
-
const raw =
|
|
53123
|
+
const raw = import_node_fs38.default.readFileSync(file, "utf8");
|
|
52704
53124
|
const parsed = JSON.parse(raw);
|
|
52705
53125
|
if (typeof parsed?.token !== "string" || parsed.token.length === 0) {
|
|
52706
53126
|
return null;
|
|
@@ -52720,25 +53140,25 @@ function readAuthFile(file) {
|
|
|
52720
53140
|
}
|
|
52721
53141
|
}
|
|
52722
53142
|
function writeAuthFile(file, content) {
|
|
52723
|
-
|
|
52724
|
-
|
|
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 });
|
|
52725
53145
|
try {
|
|
52726
|
-
|
|
53146
|
+
import_node_fs38.default.chmodSync(file, 384);
|
|
52727
53147
|
} catch {
|
|
52728
53148
|
}
|
|
52729
53149
|
}
|
|
52730
53150
|
|
|
52731
53151
|
// src/owner-profile.ts
|
|
52732
|
-
var
|
|
53152
|
+
var import_node_fs39 = __toESM(require("fs"), 1);
|
|
52733
53153
|
var import_node_os15 = __toESM(require("os"), 1);
|
|
52734
|
-
var
|
|
53154
|
+
var import_node_path41 = __toESM(require("path"), 1);
|
|
52735
53155
|
var PROFILE_FILENAME = "profile.json";
|
|
52736
53156
|
function loadOwnerDisplayName(dataDir) {
|
|
52737
53157
|
const fallback = import_node_os15.default.userInfo().username;
|
|
52738
|
-
const profilePath =
|
|
53158
|
+
const profilePath = import_node_path41.default.join(dataDir, PROFILE_FILENAME);
|
|
52739
53159
|
let raw;
|
|
52740
53160
|
try {
|
|
52741
|
-
raw =
|
|
53161
|
+
raw = import_node_fs39.default.readFileSync(profilePath, "utf8");
|
|
52742
53162
|
} catch {
|
|
52743
53163
|
return fallback;
|
|
52744
53164
|
}
|
|
@@ -52761,18 +53181,18 @@ function loadOwnerDisplayName(dataDir) {
|
|
|
52761
53181
|
}
|
|
52762
53182
|
|
|
52763
53183
|
// src/feishu-auth/owner-identity-store.ts
|
|
52764
|
-
var
|
|
52765
|
-
var
|
|
53184
|
+
var import_node_fs40 = __toESM(require("fs"), 1);
|
|
53185
|
+
var import_node_path42 = __toESM(require("path"), 1);
|
|
52766
53186
|
var OWNER_IDENTITY_FILE_NAME = "owner-identity.json";
|
|
52767
53187
|
var OwnerIdentityStore = class {
|
|
52768
53188
|
file;
|
|
52769
53189
|
constructor(dataDir) {
|
|
52770
|
-
this.file =
|
|
53190
|
+
this.file = import_node_path42.default.join(dataDir, OWNER_IDENTITY_FILE_NAME);
|
|
52771
53191
|
}
|
|
52772
53192
|
read() {
|
|
52773
53193
|
let raw;
|
|
52774
53194
|
try {
|
|
52775
|
-
raw =
|
|
53195
|
+
raw = import_node_fs40.default.readFileSync(this.file, "utf8");
|
|
52776
53196
|
} catch {
|
|
52777
53197
|
return null;
|
|
52778
53198
|
}
|
|
@@ -52800,16 +53220,16 @@ var OwnerIdentityStore = class {
|
|
|
52800
53220
|
};
|
|
52801
53221
|
}
|
|
52802
53222
|
write(record) {
|
|
52803
|
-
|
|
52804
|
-
|
|
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 });
|
|
52805
53225
|
try {
|
|
52806
|
-
|
|
53226
|
+
import_node_fs40.default.chmodSync(this.file, 384);
|
|
52807
53227
|
} catch {
|
|
52808
53228
|
}
|
|
52809
53229
|
}
|
|
52810
53230
|
clear() {
|
|
52811
53231
|
try {
|
|
52812
|
-
|
|
53232
|
+
import_node_fs40.default.unlinkSync(this.file);
|
|
52813
53233
|
} catch (err) {
|
|
52814
53234
|
const code = err?.code;
|
|
52815
53235
|
if (code !== "ENOENT") throw err;
|
|
@@ -52818,7 +53238,7 @@ var OwnerIdentityStore = class {
|
|
|
52818
53238
|
};
|
|
52819
53239
|
|
|
52820
53240
|
// src/feishu-auth/login-flow.ts
|
|
52821
|
-
var
|
|
53241
|
+
var import_node_crypto12 = __toESM(require("crypto"), 1);
|
|
52822
53242
|
var STATE_TTL_MS = 5 * 60 * 1e3;
|
|
52823
53243
|
var LoginFlow = class {
|
|
52824
53244
|
constructor(deps) {
|
|
@@ -52827,7 +53247,7 @@ var LoginFlow = class {
|
|
|
52827
53247
|
deps;
|
|
52828
53248
|
pendingStates = /* @__PURE__ */ new Map();
|
|
52829
53249
|
start() {
|
|
52830
|
-
const state =
|
|
53250
|
+
const state = import_node_crypto12.default.randomBytes(16).toString("base64url");
|
|
52831
53251
|
const now = (this.deps.now ?? Date.now)();
|
|
52832
53252
|
this.pendingStates.set(state, now);
|
|
52833
53253
|
this.gcExpired(now);
|
|
@@ -52930,9 +53350,9 @@ var CentralClientError = class extends Error {
|
|
|
52930
53350
|
code;
|
|
52931
53351
|
cause;
|
|
52932
53352
|
};
|
|
52933
|
-
async function centralRequest(opts,
|
|
53353
|
+
async function centralRequest(opts, path73, init) {
|
|
52934
53354
|
const f = opts.fetchImpl ?? globalThis.fetch;
|
|
52935
|
-
const url = `${opts.api.replace(/\/+$/, "")}${
|
|
53355
|
+
const url = `${opts.api.replace(/\/+$/, "")}${path73}`;
|
|
52936
53356
|
const ctrl = new AbortController();
|
|
52937
53357
|
const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 15e3);
|
|
52938
53358
|
let res;
|
|
@@ -53074,8 +53494,8 @@ function verifyConnectToken(args) {
|
|
|
53074
53494
|
}
|
|
53075
53495
|
|
|
53076
53496
|
// src/feishu-auth/server-key.ts
|
|
53077
|
-
var
|
|
53078
|
-
var
|
|
53497
|
+
var fs50 = __toESM(require("fs"), 1);
|
|
53498
|
+
var path51 = __toESM(require("path"), 1);
|
|
53079
53499
|
var FILE_NAME2 = "server-signing-key.json";
|
|
53080
53500
|
var ServerKeyStore = class {
|
|
53081
53501
|
constructor(dataDir) {
|
|
@@ -53083,12 +53503,12 @@ var ServerKeyStore = class {
|
|
|
53083
53503
|
}
|
|
53084
53504
|
dataDir;
|
|
53085
53505
|
filePath() {
|
|
53086
|
-
return
|
|
53506
|
+
return path51.join(this.dataDir, FILE_NAME2);
|
|
53087
53507
|
}
|
|
53088
53508
|
/** 读缓存的公钥;无缓存 / 损坏 → null(调用方决定是否触发拉取) */
|
|
53089
53509
|
read() {
|
|
53090
53510
|
try {
|
|
53091
|
-
const raw =
|
|
53511
|
+
const raw = fs50.readFileSync(this.filePath(), "utf8");
|
|
53092
53512
|
const parsed = JSON.parse(raw);
|
|
53093
53513
|
if (typeof parsed.publicKeyPem === "string" && parsed.publicKeyPem.includes("PUBLIC KEY")) {
|
|
53094
53514
|
return parsed.publicKeyPem;
|
|
@@ -53103,12 +53523,12 @@ var ServerKeyStore = class {
|
|
|
53103
53523
|
publicKeyPem,
|
|
53104
53524
|
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
53105
53525
|
};
|
|
53106
|
-
|
|
53107
|
-
|
|
53526
|
+
fs50.mkdirSync(this.dataDir, { recursive: true });
|
|
53527
|
+
fs50.writeFileSync(this.filePath(), JSON.stringify(content, null, 2), { mode: 384 });
|
|
53108
53528
|
}
|
|
53109
53529
|
clear() {
|
|
53110
53530
|
try {
|
|
53111
|
-
|
|
53531
|
+
fs50.unlinkSync(this.filePath());
|
|
53112
53532
|
} catch {
|
|
53113
53533
|
}
|
|
53114
53534
|
}
|
|
@@ -53121,12 +53541,12 @@ init_protocol();
|
|
|
53121
53541
|
init_protocol();
|
|
53122
53542
|
|
|
53123
53543
|
// src/session/fork.ts
|
|
53124
|
-
var
|
|
53544
|
+
var import_node_fs41 = __toESM(require("fs"), 1);
|
|
53125
53545
|
var import_node_os16 = __toESM(require("os"), 1);
|
|
53126
|
-
var
|
|
53546
|
+
var import_node_path43 = __toESM(require("path"), 1);
|
|
53127
53547
|
init_claude_history();
|
|
53128
53548
|
function readJsonlEntries(file) {
|
|
53129
|
-
const raw =
|
|
53549
|
+
const raw = import_node_fs41.default.readFileSync(file, "utf8");
|
|
53130
53550
|
const out = [];
|
|
53131
53551
|
for (const line of raw.split("\n")) {
|
|
53132
53552
|
const t = line.trim();
|
|
@@ -53139,10 +53559,10 @@ function readJsonlEntries(file) {
|
|
|
53139
53559
|
return out;
|
|
53140
53560
|
}
|
|
53141
53561
|
function forkSession(input) {
|
|
53142
|
-
const baseDir = input.baseDir ??
|
|
53143
|
-
const projectDir =
|
|
53144
|
-
const sourceFile =
|
|
53145
|
-
if (!
|
|
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)) {
|
|
53146
53566
|
throw new Error(`fork: source transcript not found: ${sourceFile}`);
|
|
53147
53567
|
}
|
|
53148
53568
|
const entries = readJsonlEntries(sourceFile);
|
|
@@ -53172,9 +53592,9 @@ function forkSession(input) {
|
|
|
53172
53592
|
}
|
|
53173
53593
|
forkedLines.push(JSON.stringify(forked));
|
|
53174
53594
|
}
|
|
53175
|
-
const forkedFilePath =
|
|
53176
|
-
|
|
53177
|
-
|
|
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 });
|
|
53178
53598
|
return { forkedToolSessionId, forkedFilePath };
|
|
53179
53599
|
}
|
|
53180
53600
|
|
|
@@ -53526,7 +53946,7 @@ function buildPermissionHandlers(deps) {
|
|
|
53526
53946
|
}
|
|
53527
53947
|
|
|
53528
53948
|
// src/handlers/history.ts
|
|
53529
|
-
var
|
|
53949
|
+
var path54 = __toESM(require("path"), 1);
|
|
53530
53950
|
init_protocol();
|
|
53531
53951
|
|
|
53532
53952
|
// src/session/recent-dirs.ts
|
|
@@ -53544,7 +53964,7 @@ function listRecentDirs(store, limit = 50) {
|
|
|
53544
53964
|
}
|
|
53545
53965
|
|
|
53546
53966
|
// src/permission/persona-paths.ts
|
|
53547
|
-
var
|
|
53967
|
+
var path53 = __toESM(require("path"), 1);
|
|
53548
53968
|
function getAllowedPersonaIds(grants, action) {
|
|
53549
53969
|
const ids = /* @__PURE__ */ new Set();
|
|
53550
53970
|
for (const g2 of grants) {
|
|
@@ -53557,42 +53977,42 @@ function getAllowedPersonaIds(grants, action) {
|
|
|
53557
53977
|
return ids;
|
|
53558
53978
|
}
|
|
53559
53979
|
function isGuestPathAllowed(grants, absPath, personaRoot, action = "read", userWorkDir) {
|
|
53560
|
-
const target =
|
|
53980
|
+
const target = path53.resolve(absPath);
|
|
53561
53981
|
if (userWorkDir) {
|
|
53562
|
-
const u =
|
|
53563
|
-
const usep = u.endsWith(
|
|
53982
|
+
const u = path53.resolve(userWorkDir);
|
|
53983
|
+
const usep = u.endsWith(path53.sep) ? "" : path53.sep;
|
|
53564
53984
|
if (target === u || target.startsWith(u + usep)) return true;
|
|
53565
53985
|
}
|
|
53566
|
-
const root =
|
|
53567
|
-
const sep3 = root.endsWith(
|
|
53986
|
+
const root = path53.resolve(personaRoot);
|
|
53987
|
+
const sep3 = root.endsWith(path53.sep) ? "" : path53.sep;
|
|
53568
53988
|
if (!target.startsWith(root + sep3)) return false;
|
|
53569
|
-
const rel =
|
|
53989
|
+
const rel = path53.relative(root, target);
|
|
53570
53990
|
if (!rel || rel.startsWith("..")) return false;
|
|
53571
|
-
const personaId = rel.split(
|
|
53991
|
+
const personaId = rel.split(path53.sep)[0];
|
|
53572
53992
|
if (!personaId) return false;
|
|
53573
53993
|
const allowed = getAllowedPersonaIds(grants, action);
|
|
53574
53994
|
if (allowed === "*") return true;
|
|
53575
53995
|
return allowed.has(personaId);
|
|
53576
53996
|
}
|
|
53577
53997
|
function personaIdFromPath(absPath, personaRoot) {
|
|
53578
|
-
const root =
|
|
53579
|
-
const target =
|
|
53580
|
-
const sep3 = root.endsWith(
|
|
53998
|
+
const root = path53.resolve(personaRoot);
|
|
53999
|
+
const target = path53.resolve(absPath);
|
|
54000
|
+
const sep3 = root.endsWith(path53.sep) ? "" : path53.sep;
|
|
53581
54001
|
if (!target.startsWith(root + sep3)) return null;
|
|
53582
|
-
const rel =
|
|
54002
|
+
const rel = path53.relative(root, target);
|
|
53583
54003
|
if (!rel || rel.startsWith("..")) return null;
|
|
53584
|
-
const id = rel.split(
|
|
54004
|
+
const id = rel.split(path53.sep)[0];
|
|
53585
54005
|
return id || null;
|
|
53586
54006
|
}
|
|
53587
54007
|
function isPathWithin(dir, absPath) {
|
|
53588
|
-
const d =
|
|
53589
|
-
const t =
|
|
53590
|
-
const sep3 = d.endsWith(
|
|
54008
|
+
const d = path53.resolve(dir);
|
|
54009
|
+
const t = path53.resolve(absPath);
|
|
54010
|
+
const sep3 = d.endsWith(path53.sep) ? "" : path53.sep;
|
|
53591
54011
|
return t === d || t.startsWith(d + sep3);
|
|
53592
54012
|
}
|
|
53593
54013
|
function isPathInGuestBoundary(personaRoot, personaId, userWorkDir, absPath) {
|
|
53594
54014
|
if (userWorkDir && isPathWithin(userWorkDir, absPath)) return true;
|
|
53595
|
-
return personaIdFromPath(
|
|
54015
|
+
return personaIdFromPath(path53.resolve(absPath), personaRoot) === personaId;
|
|
53596
54016
|
}
|
|
53597
54017
|
|
|
53598
54018
|
// src/handlers/history.ts
|
|
@@ -53618,7 +54038,7 @@ function buildHistoryHandlers(deps) {
|
|
|
53618
54038
|
if (!pid) return false;
|
|
53619
54039
|
return isGuestPathAllowed(
|
|
53620
54040
|
ctx.grants,
|
|
53621
|
-
|
|
54041
|
+
path54.join(personaRoot, pid),
|
|
53622
54042
|
personaRoot,
|
|
53623
54043
|
"read",
|
|
53624
54044
|
userWorkDir
|
|
@@ -53630,7 +54050,7 @@ function buildHistoryHandlers(deps) {
|
|
|
53630
54050
|
};
|
|
53631
54051
|
const list = async (frame, _client, ctx) => {
|
|
53632
54052
|
const args = HistoryListArgs.parse(frame);
|
|
53633
|
-
assertGuestPath(ctx,
|
|
54053
|
+
assertGuestPath(ctx, path54.resolve(args.projectPath), personaRoot, "history:list");
|
|
53634
54054
|
const sessions = await history.listSessions(args);
|
|
53635
54055
|
return { response: { type: "history:list", sessions } };
|
|
53636
54056
|
};
|
|
@@ -53662,13 +54082,13 @@ function buildHistoryHandlers(deps) {
|
|
|
53662
54082
|
};
|
|
53663
54083
|
const subagents = async (frame, _client, ctx) => {
|
|
53664
54084
|
const args = HistorySubagentsArgs.parse(frame);
|
|
53665
|
-
assertGuestPath(ctx,
|
|
54085
|
+
assertGuestPath(ctx, path54.resolve(args.cwd), personaRoot, "history:subagents", usersRoot);
|
|
53666
54086
|
const subs = await history.listSubagents(args);
|
|
53667
54087
|
return { response: { type: "history:subagents", subagents: subs } };
|
|
53668
54088
|
};
|
|
53669
54089
|
const subagentRead = async (frame, _client, ctx) => {
|
|
53670
54090
|
const args = HistorySubagentReadArgs.parse(frame);
|
|
53671
|
-
assertGuestPath(ctx,
|
|
54091
|
+
assertGuestPath(ctx, path54.resolve(args.cwd), personaRoot, "history:subagent-read", usersRoot);
|
|
53672
54092
|
const res = await history.readSubagent(args);
|
|
53673
54093
|
return { response: { type: "history:subagent-read", ...res } };
|
|
53674
54094
|
};
|
|
@@ -53677,7 +54097,7 @@ function buildHistoryHandlers(deps) {
|
|
|
53677
54097
|
if (ctx?.principal.kind === "guest" && personaRoot) {
|
|
53678
54098
|
const userWorkDir = usersRoot ? deriveUserWorkDir(ctx.principal.id, usersRoot) : void 0;
|
|
53679
54099
|
const filtered = dirs.filter(
|
|
53680
|
-
(d) => isGuestPathAllowed(ctx.grants,
|
|
54100
|
+
(d) => isGuestPathAllowed(ctx.grants, path54.resolve(d.cwd), personaRoot, "read", userWorkDir)
|
|
53681
54101
|
);
|
|
53682
54102
|
return { response: { type: "history:recentDirs", dirs: filtered } };
|
|
53683
54103
|
}
|
|
@@ -53694,7 +54114,7 @@ function buildHistoryHandlers(deps) {
|
|
|
53694
54114
|
}
|
|
53695
54115
|
|
|
53696
54116
|
// src/handlers/workspace.ts
|
|
53697
|
-
var
|
|
54117
|
+
var path55 = __toESM(require("path"), 1);
|
|
53698
54118
|
var os16 = __toESM(require("os"), 1);
|
|
53699
54119
|
init_protocol();
|
|
53700
54120
|
init_protocol();
|
|
@@ -53736,22 +54156,22 @@ function buildWorkspaceHandlers(deps) {
|
|
|
53736
54156
|
const args = WorkspaceListArgs.parse(frame);
|
|
53737
54157
|
const isGuest = ctx?.principal.kind === "guest";
|
|
53738
54158
|
const fallbackCwd = isGuest && personaRoot ? personaRoot : os16.homedir();
|
|
53739
|
-
const resolvedCwd =
|
|
53740
|
-
const target = args.path ?
|
|
54159
|
+
const resolvedCwd = path55.resolve(args.cwd ?? fallbackCwd);
|
|
54160
|
+
const target = args.path ? path55.resolve(resolvedCwd, args.path) : resolvedCwd;
|
|
53741
54161
|
assertGuestPath2(ctx, target, personaRoot, "workspace:list", usersRoot);
|
|
53742
54162
|
const res = workspace.list({ ...args, cwd: resolvedCwd });
|
|
53743
54163
|
return { response: { type: "workspace:list", ...res } };
|
|
53744
54164
|
};
|
|
53745
54165
|
const read = async (frame, _client, ctx) => {
|
|
53746
54166
|
const args = WorkspaceReadArgs.parse(frame);
|
|
53747
|
-
const target =
|
|
54167
|
+
const target = path55.isAbsolute(args.path) ? path55.resolve(args.path) : path55.resolve(args.cwd, args.path);
|
|
53748
54168
|
assertGuestPath2(ctx, target, personaRoot, "workspace:read", usersRoot);
|
|
53749
54169
|
const res = workspace.read(args);
|
|
53750
54170
|
return { response: { type: "workspace:read", ...res } };
|
|
53751
54171
|
};
|
|
53752
54172
|
const skillsList = async (frame, _client, ctx) => {
|
|
53753
54173
|
const args = SkillsListArgs.parse(frame);
|
|
53754
|
-
const cwdAbs =
|
|
54174
|
+
const cwdAbs = path55.resolve(args.cwd);
|
|
53755
54175
|
assertGuestPath2(ctx, cwdAbs, personaRoot, "skills:list", usersRoot);
|
|
53756
54176
|
const list2 = await getSkillsForTool(args.tool ?? "claude", cwdAbs);
|
|
53757
54177
|
if (ctx?.principal.kind === "guest" && personaRoot) {
|
|
@@ -53763,7 +54183,7 @@ function buildWorkspaceHandlers(deps) {
|
|
|
53763
54183
|
};
|
|
53764
54184
|
const agentsList = async (frame, _client, ctx) => {
|
|
53765
54185
|
const args = AgentsListArgs.parse(frame);
|
|
53766
|
-
const cwdAbs =
|
|
54186
|
+
const cwdAbs = path55.resolve(args.cwd);
|
|
53767
54187
|
assertGuestPath2(ctx, cwdAbs, personaRoot, "agents:list", usersRoot);
|
|
53768
54188
|
if (args.tool === "codex") {
|
|
53769
54189
|
return { response: { type: "agents:list", agents: [] } };
|
|
@@ -53785,20 +54205,20 @@ function buildWorkspaceHandlers(deps) {
|
|
|
53785
54205
|
}
|
|
53786
54206
|
|
|
53787
54207
|
// src/handlers/git.ts
|
|
53788
|
-
var
|
|
54208
|
+
var path57 = __toESM(require("path"), 1);
|
|
53789
54209
|
init_protocol();
|
|
53790
54210
|
init_protocol();
|
|
53791
54211
|
|
|
53792
54212
|
// src/workspace/git.ts
|
|
53793
|
-
var
|
|
53794
|
-
var
|
|
53795
|
-
var
|
|
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);
|
|
53796
54216
|
var import_node_util = require("util");
|
|
53797
|
-
var pexec = (0, import_node_util.promisify)(
|
|
54217
|
+
var pexec = (0, import_node_util.promisify)(import_node_child_process12.execFile);
|
|
53798
54218
|
function normalizePath(p2) {
|
|
53799
|
-
const resolved =
|
|
54219
|
+
const resolved = import_node_path44.default.resolve(p2);
|
|
53800
54220
|
try {
|
|
53801
|
-
return
|
|
54221
|
+
return import_node_fs42.default.realpathSync(resolved);
|
|
53802
54222
|
} catch {
|
|
53803
54223
|
return resolved;
|
|
53804
54224
|
}
|
|
@@ -53872,7 +54292,7 @@ async function listGitBranches(cwd) {
|
|
|
53872
54292
|
function assertGuestCwd(ctx, cwd, personaRoot, method, usersRoot) {
|
|
53873
54293
|
if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
|
|
53874
54294
|
const userWorkDir = usersRoot ? deriveUserWorkDir(ctx.principal.id, usersRoot) : void 0;
|
|
53875
|
-
if (!isGuestPathAllowed(ctx.grants,
|
|
54295
|
+
if (!isGuestPathAllowed(ctx.grants, path57.resolve(cwd), personaRoot, "read", userWorkDir)) {
|
|
53876
54296
|
throw new ClawdError(
|
|
53877
54297
|
ERROR_CODES.UNAUTHORIZED,
|
|
53878
54298
|
`guest ${ctx.principal.id} cannot ${method} cwd ${cwd}`
|
|
@@ -54205,6 +54625,128 @@ function buildContactHandlers(deps) {
|
|
|
54205
54625
|
};
|
|
54206
54626
|
}
|
|
54207
54627
|
|
|
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
|
+
|
|
54208
54750
|
// src/handlers/whoami.ts
|
|
54209
54751
|
init_protocol();
|
|
54210
54752
|
function buildWhoamiHandler(deps) {
|
|
@@ -54300,7 +54842,7 @@ function buildFeishuAuthHandlers(deps) {
|
|
|
54300
54842
|
|
|
54301
54843
|
// src/handlers/device.ts
|
|
54302
54844
|
init_protocol();
|
|
54303
|
-
function
|
|
54845
|
+
function ensureOwner3(ctx) {
|
|
54304
54846
|
if (!ctx || ctx.principal.kind !== "owner") {
|
|
54305
54847
|
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "UNAUTHORIZED: device:* requires owner ctx");
|
|
54306
54848
|
}
|
|
@@ -54308,14 +54850,14 @@ function ensureOwner2(ctx) {
|
|
|
54308
54850
|
function buildDeviceHandlers(deps) {
|
|
54309
54851
|
const now = deps.now ?? Date.now;
|
|
54310
54852
|
const list = async (_frame, _client, ctx) => {
|
|
54311
|
-
|
|
54853
|
+
ensureOwner3(ctx);
|
|
54312
54854
|
const devices = await deps.listDevices();
|
|
54313
54855
|
return {
|
|
54314
54856
|
response: { type: "device:list:ok", devices }
|
|
54315
54857
|
};
|
|
54316
54858
|
};
|
|
54317
54859
|
const connect = async (frame, _client, ctx) => {
|
|
54318
|
-
|
|
54860
|
+
ensureOwner3(ctx);
|
|
54319
54861
|
const { type: _t, requestId: _r, ...rest } = frame;
|
|
54320
54862
|
const args = DeviceConnectArgsSchema.parse(rest);
|
|
54321
54863
|
const exchanged = await deps.exchange(args.deviceId);
|
|
@@ -54358,7 +54900,9 @@ function buildDeviceHandlers(deps) {
|
|
|
54358
54900
|
connectToken: exchanged.token,
|
|
54359
54901
|
grants: wh.grants,
|
|
54360
54902
|
addedAt: now(),
|
|
54361
|
-
pinnedAt: null
|
|
54903
|
+
pinnedAt: null,
|
|
54904
|
+
sshAllowed: false,
|
|
54905
|
+
exposedDirs: []
|
|
54362
54906
|
};
|
|
54363
54907
|
deps.store.upsert(contact);
|
|
54364
54908
|
deps.broadcast({ type: "contact:added", contact });
|
|
@@ -54514,7 +55058,7 @@ function buildPersonaHandlers(deps) {
|
|
|
54514
55058
|
}
|
|
54515
55059
|
|
|
54516
55060
|
// src/handlers/attachment.ts
|
|
54517
|
-
var
|
|
55061
|
+
var import_node_path46 = __toESM(require("path"), 1);
|
|
54518
55062
|
init_protocol();
|
|
54519
55063
|
init_protocol();
|
|
54520
55064
|
var DEFAULT_TTL_SECONDS = 24 * 3600;
|
|
@@ -54594,12 +55138,12 @@ function buildAttachmentHandlers(deps) {
|
|
|
54594
55138
|
`session ${args.sessionId} scope unresolved`
|
|
54595
55139
|
);
|
|
54596
55140
|
}
|
|
54597
|
-
const cwdAbs =
|
|
54598
|
-
const candidateAbs =
|
|
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);
|
|
54599
55143
|
guardAttachmentPath(ctx, args.sessionId, candidateAbs, "attachment.signUrl", "group-acl");
|
|
54600
55144
|
const entries = deps.groupFileStore.list(scope, args.sessionId);
|
|
54601
55145
|
const entry = entries.find((e) => {
|
|
54602
|
-
const storedAbs =
|
|
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);
|
|
54603
55147
|
return storedAbs === candidateAbs && !e.stale;
|
|
54604
55148
|
});
|
|
54605
55149
|
if (!entry) {
|
|
@@ -54624,7 +55168,7 @@ function buildAttachmentHandlers(deps) {
|
|
|
54624
55168
|
if (!ctx || ctx.principal.kind !== "guest" || !deps.personaRoot || !deps.sessionStore) return;
|
|
54625
55169
|
const f = deps.sessionStore.read(sessionId);
|
|
54626
55170
|
if (!f) return;
|
|
54627
|
-
assertGuestAttachmentPath(ctx,
|
|
55171
|
+
assertGuestAttachmentPath(ctx, import_node_path46.default.resolve(f.cwd), deps.personaRoot, method, deps.usersRoot);
|
|
54628
55172
|
}
|
|
54629
55173
|
const groupAdd = async (frame, _client, ctx) => {
|
|
54630
55174
|
if (!deps.groupFileStore || !deps.getSessionScope) {
|
|
@@ -54639,8 +55183,8 @@ function buildAttachmentHandlers(deps) {
|
|
|
54639
55183
|
if (!scope) {
|
|
54640
55184
|
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, `session ${args.sessionId} not found`);
|
|
54641
55185
|
}
|
|
54642
|
-
const cwdAbs =
|
|
54643
|
-
const candidateAbs =
|
|
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);
|
|
54644
55188
|
guardAttachmentPath(ctx, args.sessionId, candidateAbs, "attachment.groupAdd", "cwd-subtree");
|
|
54645
55189
|
const from = ctx?.principal.kind === "owner" ? "owner" : "agent";
|
|
54646
55190
|
const size = 0;
|
|
@@ -54699,19 +55243,19 @@ function buildAttachmentHandlers(deps) {
|
|
|
54699
55243
|
|
|
54700
55244
|
// src/handlers/extension.ts
|
|
54701
55245
|
var import_promises8 = __toESM(require("fs/promises"), 1);
|
|
54702
|
-
var
|
|
55246
|
+
var import_node_path51 = __toESM(require("path"), 1);
|
|
54703
55247
|
init_protocol();
|
|
54704
55248
|
|
|
54705
55249
|
// src/extension/bundle-zip.ts
|
|
54706
55250
|
var import_promises5 = __toESM(require("fs/promises"), 1);
|
|
54707
|
-
var
|
|
54708
|
-
var
|
|
55251
|
+
var import_node_path47 = __toESM(require("path"), 1);
|
|
55252
|
+
var import_node_crypto13 = __toESM(require("crypto"), 1);
|
|
54709
55253
|
var import_jszip2 = __toESM(require_lib3(), 1);
|
|
54710
55254
|
async function bundleExtensionDir(dir) {
|
|
54711
55255
|
const entries = await listFilesSorted(dir);
|
|
54712
55256
|
const zip = new import_jszip2.default();
|
|
54713
55257
|
for (const rel of entries) {
|
|
54714
|
-
const abs =
|
|
55258
|
+
const abs = import_node_path47.default.join(dir, rel);
|
|
54715
55259
|
const content = await import_promises5.default.readFile(abs);
|
|
54716
55260
|
zip.file(rel, content, { date: FIXED_DATE });
|
|
54717
55261
|
}
|
|
@@ -54720,7 +55264,7 @@ async function bundleExtensionDir(dir) {
|
|
|
54720
55264
|
compression: "DEFLATE",
|
|
54721
55265
|
compressionOptions: { level: 6 }
|
|
54722
55266
|
});
|
|
54723
|
-
const sha256 =
|
|
55267
|
+
const sha256 = import_node_crypto13.default.createHash("sha256").update(buffer).digest("hex");
|
|
54724
55268
|
return { buffer, sha256 };
|
|
54725
55269
|
}
|
|
54726
55270
|
var FIXED_DATE = /* @__PURE__ */ new Date("2020-01-01T00:00:00.000Z");
|
|
@@ -54732,7 +55276,7 @@ async function listFilesSorted(rootDir) {
|
|
|
54732
55276
|
return out;
|
|
54733
55277
|
}
|
|
54734
55278
|
async function walk(absRoot, relPrefix, out) {
|
|
54735
|
-
const dirAbs =
|
|
55279
|
+
const dirAbs = import_node_path47.default.join(absRoot, relPrefix);
|
|
54736
55280
|
const entries = await import_promises5.default.readdir(dirAbs, { withFileTypes: true });
|
|
54737
55281
|
for (const e of entries) {
|
|
54738
55282
|
if (IGNORE_BASENAMES.has(e.name)) continue;
|
|
@@ -54786,25 +55330,25 @@ function computePublishCheck(args) {
|
|
|
54786
55330
|
|
|
54787
55331
|
// src/extension/install-flow.ts
|
|
54788
55332
|
var import_promises6 = __toESM(require("fs/promises"), 1);
|
|
54789
|
-
var
|
|
55333
|
+
var import_node_path49 = __toESM(require("path"), 1);
|
|
54790
55334
|
var import_node_os19 = __toESM(require("os"), 1);
|
|
54791
|
-
var
|
|
55335
|
+
var import_node_crypto14 = __toESM(require("crypto"), 1);
|
|
54792
55336
|
var import_jszip3 = __toESM(require_lib3(), 1);
|
|
54793
55337
|
|
|
54794
55338
|
// src/extension/paths.ts
|
|
54795
55339
|
var import_node_os18 = __toESM(require("os"), 1);
|
|
54796
|
-
var
|
|
55340
|
+
var import_node_path48 = __toESM(require("path"), 1);
|
|
54797
55341
|
function clawdHomeRoot(override) {
|
|
54798
|
-
return override ?? process.env.CLAWD_HOME ??
|
|
55342
|
+
return override ?? process.env.CLAWD_HOME ?? import_node_path48.default.join(import_node_os18.default.homedir(), ".clawd");
|
|
54799
55343
|
}
|
|
54800
55344
|
function extensionsRoot(override) {
|
|
54801
|
-
return
|
|
55345
|
+
return import_node_path48.default.join(clawdHomeRoot(override), "extensions");
|
|
54802
55346
|
}
|
|
54803
55347
|
function publishedChannelsFile(override) {
|
|
54804
|
-
return
|
|
55348
|
+
return import_node_path48.default.join(clawdHomeRoot(override), "extensions-published.json");
|
|
54805
55349
|
}
|
|
54806
55350
|
function bundleCacheRoot(override) {
|
|
54807
|
-
return
|
|
55351
|
+
return import_node_path48.default.join(clawdHomeRoot(override), "extension-bundles");
|
|
54808
55352
|
}
|
|
54809
55353
|
|
|
54810
55354
|
// src/extension/install-flow.ts
|
|
@@ -54817,7 +55361,7 @@ var InstallError = class extends Error {
|
|
|
54817
55361
|
};
|
|
54818
55362
|
async function installFromChannel(args, deps) {
|
|
54819
55363
|
const { channelRef, snapshotHash, bundleZip } = args;
|
|
54820
|
-
const computed =
|
|
55364
|
+
const computed = import_node_crypto14.default.createHash("sha256").update(bundleZip).digest("hex");
|
|
54821
55365
|
if (computed !== snapshotHash) {
|
|
54822
55366
|
throw new InstallError(
|
|
54823
55367
|
"HASH_MISMATCH",
|
|
@@ -54831,7 +55375,7 @@ async function installFromChannel(args, deps) {
|
|
|
54831
55375
|
throw new InstallError("ZIP_INVALID", `failed to load zip: ${e.message}`);
|
|
54832
55376
|
}
|
|
54833
55377
|
for (const name of Object.keys(zip.files)) {
|
|
54834
|
-
if (name.includes("..") || name.startsWith("/") ||
|
|
55378
|
+
if (name.includes("..") || name.startsWith("/") || import_node_path49.default.isAbsolute(name)) {
|
|
54835
55379
|
throw new InstallError("ZIP_INVALID", `unsafe zip entry: ${name}`);
|
|
54836
55380
|
}
|
|
54837
55381
|
}
|
|
@@ -54863,7 +55407,7 @@ async function installFromChannel(args, deps) {
|
|
|
54863
55407
|
);
|
|
54864
55408
|
}
|
|
54865
55409
|
const localExtId = namespacedExtId(ownerSlug, channelRef.ownerPrincipalId);
|
|
54866
|
-
const destDir =
|
|
55410
|
+
const destDir = import_node_path49.default.join(deps.extensionsRoot, localExtId);
|
|
54867
55411
|
let destExists = false;
|
|
54868
55412
|
try {
|
|
54869
55413
|
await import_promises6.default.access(destDir);
|
|
@@ -54877,16 +55421,16 @@ async function installFromChannel(args, deps) {
|
|
|
54877
55421
|
);
|
|
54878
55422
|
}
|
|
54879
55423
|
const stage = await import_promises6.default.mkdtemp(
|
|
54880
|
-
|
|
55424
|
+
import_node_path49.default.join(import_node_os19.default.tmpdir(), `clawd-ext-install-${localExtId}-`)
|
|
54881
55425
|
);
|
|
54882
55426
|
try {
|
|
54883
55427
|
for (const [name, entry] of Object.entries(zip.files)) {
|
|
54884
|
-
const dest =
|
|
55428
|
+
const dest = import_node_path49.default.join(stage, name);
|
|
54885
55429
|
if (entry.dir) {
|
|
54886
55430
|
await import_promises6.default.mkdir(dest, { recursive: true });
|
|
54887
55431
|
continue;
|
|
54888
55432
|
}
|
|
54889
|
-
await import_promises6.default.mkdir(
|
|
55433
|
+
await import_promises6.default.mkdir(import_node_path49.default.dirname(dest), { recursive: true });
|
|
54890
55434
|
if (name === "manifest.json") {
|
|
54891
55435
|
const rewritten = { ...parsed.data, id: localExtId };
|
|
54892
55436
|
await import_promises6.default.writeFile(dest, JSON.stringify(rewritten, null, 2));
|
|
@@ -54907,9 +55451,9 @@ async function installFromChannel(args, deps) {
|
|
|
54907
55451
|
|
|
54908
55452
|
// src/extension/update-flow.ts
|
|
54909
55453
|
var import_promises7 = __toESM(require("fs/promises"), 1);
|
|
54910
|
-
var
|
|
55454
|
+
var import_node_path50 = __toESM(require("path"), 1);
|
|
54911
55455
|
var import_node_os20 = __toESM(require("os"), 1);
|
|
54912
|
-
var
|
|
55456
|
+
var import_node_crypto15 = __toESM(require("crypto"), 1);
|
|
54913
55457
|
var import_jszip4 = __toESM(require_lib3(), 1);
|
|
54914
55458
|
var UpdateError = class extends Error {
|
|
54915
55459
|
constructor(code, message) {
|
|
@@ -54924,11 +55468,11 @@ async function updateFromChannel(args, deps) {
|
|
|
54924
55468
|
channelRef.extId,
|
|
54925
55469
|
channelRef.ownerPrincipalId
|
|
54926
55470
|
);
|
|
54927
|
-
const liveDir =
|
|
55471
|
+
const liveDir = import_node_path50.default.join(deps.extensionsRoot, localExtId);
|
|
54928
55472
|
const prevDir = `${liveDir}.prev`;
|
|
54929
55473
|
let existingVersion;
|
|
54930
55474
|
try {
|
|
54931
|
-
const raw = await import_promises7.default.readFile(
|
|
55475
|
+
const raw = await import_promises7.default.readFile(import_node_path50.default.join(liveDir, "manifest.json"), "utf8");
|
|
54932
55476
|
const parsed2 = ExtensionManifestSchema.safeParse(JSON.parse(raw));
|
|
54933
55477
|
if (!parsed2.success) {
|
|
54934
55478
|
throw new UpdateError(
|
|
@@ -54947,7 +55491,7 @@ async function updateFromChannel(args, deps) {
|
|
|
54947
55491
|
if (e instanceof UpdateError) throw e;
|
|
54948
55492
|
throw e;
|
|
54949
55493
|
}
|
|
54950
|
-
const computed =
|
|
55494
|
+
const computed = import_node_crypto15.default.createHash("sha256").update(bundleZip).digest("hex");
|
|
54951
55495
|
if (computed !== snapshotHash) {
|
|
54952
55496
|
throw new UpdateError(
|
|
54953
55497
|
"HASH_MISMATCH",
|
|
@@ -54961,7 +55505,7 @@ async function updateFromChannel(args, deps) {
|
|
|
54961
55505
|
throw new UpdateError("ZIP_INVALID", `failed to load zip: ${e.message}`);
|
|
54962
55506
|
}
|
|
54963
55507
|
for (const name of Object.keys(zip.files)) {
|
|
54964
|
-
if (name.includes("..") || name.startsWith("/") ||
|
|
55508
|
+
if (name.includes("..") || name.startsWith("/") || import_node_path50.default.isAbsolute(name)) {
|
|
54965
55509
|
throw new UpdateError("ZIP_INVALID", `unsafe zip entry: ${name}`);
|
|
54966
55510
|
}
|
|
54967
55511
|
}
|
|
@@ -54996,16 +55540,16 @@ async function updateFromChannel(args, deps) {
|
|
|
54996
55540
|
await import_promises7.default.rm(prevDir, { recursive: true, force: true });
|
|
54997
55541
|
await import_promises7.default.rename(liveDir, prevDir);
|
|
54998
55542
|
const stage = await import_promises7.default.mkdtemp(
|
|
54999
|
-
|
|
55543
|
+
import_node_path50.default.join(import_node_os20.default.tmpdir(), `clawd-ext-update-${localExtId}-`)
|
|
55000
55544
|
);
|
|
55001
55545
|
try {
|
|
55002
55546
|
for (const [name, entry] of Object.entries(zip.files)) {
|
|
55003
|
-
const dest =
|
|
55547
|
+
const dest = import_node_path50.default.join(stage, name);
|
|
55004
55548
|
if (entry.dir) {
|
|
55005
55549
|
await import_promises7.default.mkdir(dest, { recursive: true });
|
|
55006
55550
|
continue;
|
|
55007
55551
|
}
|
|
55008
|
-
await import_promises7.default.mkdir(
|
|
55552
|
+
await import_promises7.default.mkdir(import_node_path50.default.dirname(dest), { recursive: true });
|
|
55009
55553
|
if (name === "manifest.json") {
|
|
55010
55554
|
const rewritten = { ...parsed.data, id: localExtId };
|
|
55011
55555
|
await import_promises7.default.writeFile(dest, JSON.stringify(rewritten, null, 2));
|
|
@@ -55098,7 +55642,7 @@ async function rewriteManifestVersion(root, extId, newVersion, previousPublished
|
|
|
55098
55642
|
);
|
|
55099
55643
|
}
|
|
55100
55644
|
}
|
|
55101
|
-
const manifestPath =
|
|
55645
|
+
const manifestPath = import_node_path51.default.join(root, extId, "manifest.json");
|
|
55102
55646
|
const manifest = await readManifest(root, extId);
|
|
55103
55647
|
const next = { ...manifest, version: newVersion };
|
|
55104
55648
|
const tmp = `${manifestPath}.tmp`;
|
|
@@ -55106,7 +55650,7 @@ async function rewriteManifestVersion(root, extId, newVersion, previousPublished
|
|
|
55106
55650
|
await import_promises8.default.rename(tmp, manifestPath);
|
|
55107
55651
|
}
|
|
55108
55652
|
async function readManifest(root, extId) {
|
|
55109
|
-
const file =
|
|
55653
|
+
const file = import_node_path51.default.join(root, extId, "manifest.json");
|
|
55110
55654
|
let raw;
|
|
55111
55655
|
try {
|
|
55112
55656
|
raw = await import_promises8.default.readFile(file, "utf8");
|
|
@@ -55197,7 +55741,7 @@ function buildExtensionHandlers(deps) {
|
|
|
55197
55741
|
};
|
|
55198
55742
|
async function buildSnapshotMeta(extId) {
|
|
55199
55743
|
const manifest = await readManifest(deps.root, extId);
|
|
55200
|
-
const { sha256, buffer } = await bundleExtensionDir(
|
|
55744
|
+
const { sha256, buffer } = await bundleExtensionDir(import_node_path51.default.join(deps.root, extId));
|
|
55201
55745
|
return { manifest, contentHash: sha256, buffer };
|
|
55202
55746
|
}
|
|
55203
55747
|
const publish = async (frame, _client, ctx) => {
|
|
@@ -55378,9 +55922,9 @@ function buildExtensionHandlers(deps) {
|
|
|
55378
55922
|
}
|
|
55379
55923
|
|
|
55380
55924
|
// src/app-builder/project-store.ts
|
|
55381
|
-
var
|
|
55382
|
-
var
|
|
55383
|
-
var
|
|
55925
|
+
var import_node_fs44 = require("fs");
|
|
55926
|
+
var import_node_child_process14 = require("child_process");
|
|
55927
|
+
var import_node_path52 = require("path");
|
|
55384
55928
|
init_protocol();
|
|
55385
55929
|
var PROJECTS_DIR = "projects";
|
|
55386
55930
|
var META_FILE = ".clawd-project.json";
|
|
@@ -55394,19 +55938,19 @@ var ProjectStore = class {
|
|
|
55394
55938
|
root;
|
|
55395
55939
|
/** projects/<name>/.clawd-project.json 路径 */
|
|
55396
55940
|
metaPath(name) {
|
|
55397
|
-
return (0,
|
|
55941
|
+
return (0, import_node_path52.join)(this.projectsRoot(), name, META_FILE);
|
|
55398
55942
|
}
|
|
55399
55943
|
/** projects/<name>/ 目录路径(cwd 用) */
|
|
55400
55944
|
projectDir(name) {
|
|
55401
|
-
return (0,
|
|
55945
|
+
return (0, import_node_path52.join)(this.projectsRoot(), name);
|
|
55402
55946
|
}
|
|
55403
55947
|
projectsRoot() {
|
|
55404
|
-
return (0,
|
|
55948
|
+
return (0, import_node_path52.join)(this.root, PROJECTS_DIR);
|
|
55405
55949
|
}
|
|
55406
55950
|
async list() {
|
|
55407
55951
|
let entries;
|
|
55408
55952
|
try {
|
|
55409
|
-
entries = await
|
|
55953
|
+
entries = await import_node_fs44.promises.readdir(this.projectsRoot());
|
|
55410
55954
|
} catch (err) {
|
|
55411
55955
|
if (err.code === "ENOENT") return [];
|
|
55412
55956
|
throw err;
|
|
@@ -55414,7 +55958,7 @@ var ProjectStore = class {
|
|
|
55414
55958
|
const out = [];
|
|
55415
55959
|
for (const name of entries) {
|
|
55416
55960
|
try {
|
|
55417
|
-
const raw = await
|
|
55961
|
+
const raw = await import_node_fs44.promises.readFile(this.metaPath(name), "utf8");
|
|
55418
55962
|
const json = JSON.parse(raw);
|
|
55419
55963
|
let migrated = false;
|
|
55420
55964
|
if (typeof json.devCommand !== "string" || json.devCommand.length === 0) {
|
|
@@ -55425,7 +55969,7 @@ var ProjectStore = class {
|
|
|
55425
55969
|
if (parsed.success) {
|
|
55426
55970
|
out.push(parsed.data);
|
|
55427
55971
|
if (migrated) {
|
|
55428
|
-
void
|
|
55972
|
+
void import_node_fs44.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
|
|
55429
55973
|
});
|
|
55430
55974
|
}
|
|
55431
55975
|
}
|
|
@@ -55469,8 +56013,8 @@ var ProjectStore = class {
|
|
|
55469
56013
|
throw new Error(`invalid name "${name}": ${validated.error.message}`);
|
|
55470
56014
|
}
|
|
55471
56015
|
const dir = this.projectDir(name);
|
|
55472
|
-
await
|
|
55473
|
-
await
|
|
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");
|
|
55474
56018
|
return meta;
|
|
55475
56019
|
}
|
|
55476
56020
|
/**
|
|
@@ -55484,7 +56028,7 @@ var ProjectStore = class {
|
|
|
55484
56028
|
async scaffold(name, templateSrcDir, scaffoldScriptPath) {
|
|
55485
56029
|
const destDir = this.projectDir(name);
|
|
55486
56030
|
return await new Promise((resolve6, reject) => {
|
|
55487
|
-
const child = (0,
|
|
56031
|
+
const child = (0, import_node_child_process14.spawn)("bash", [scaffoldScriptPath, name, templateSrcDir, destDir], {
|
|
55488
56032
|
env: { ...process.env, PATH: process.env.PATH ?? "" },
|
|
55489
56033
|
stdio: ["ignore", "pipe", "pipe"]
|
|
55490
56034
|
});
|
|
@@ -55513,7 +56057,7 @@ var ProjectStore = class {
|
|
|
55513
56057
|
}
|
|
55514
56058
|
async delete(name) {
|
|
55515
56059
|
const dir = this.projectDir(name);
|
|
55516
|
-
await
|
|
56060
|
+
await import_node_fs44.promises.rm(dir, { recursive: true, force: true });
|
|
55517
56061
|
}
|
|
55518
56062
|
async updatePort(name, newPort) {
|
|
55519
56063
|
if (newPort < PROJECT_PORT_MIN || newPort > PROJECT_PORT_MAX) {
|
|
@@ -55529,7 +56073,7 @@ var ProjectStore = class {
|
|
|
55529
56073
|
throw new Error(`port ${newPort} already used / \u5DF2\u88AB project "${conflict.name}" \u5360\u7528`);
|
|
55530
56074
|
}
|
|
55531
56075
|
const updated = { ...target, port: newPort };
|
|
55532
|
-
await
|
|
56076
|
+
await import_node_fs44.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
|
|
55533
56077
|
return updated;
|
|
55534
56078
|
}
|
|
55535
56079
|
/**
|
|
@@ -55546,7 +56090,7 @@ var ProjectStore = class {
|
|
|
55546
56090
|
if (!validated.success) {
|
|
55547
56091
|
throw new Error(`invalid prodUrl "${url}": ${validated.error.message}`);
|
|
55548
56092
|
}
|
|
55549
|
-
await
|
|
56093
|
+
await import_node_fs44.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
|
|
55550
56094
|
return validated.data;
|
|
55551
56095
|
}
|
|
55552
56096
|
/**
|
|
@@ -55567,7 +56111,7 @@ var ProjectStore = class {
|
|
|
55567
56111
|
if (!validated.success) {
|
|
55568
56112
|
throw new Error(`invalid publishJob: ${validated.error.message}`);
|
|
55569
56113
|
}
|
|
55570
|
-
await
|
|
56114
|
+
await import_node_fs44.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
|
|
55571
56115
|
return validated.data;
|
|
55572
56116
|
}
|
|
55573
56117
|
/** 清掉 .clawd-project.json.publishJob 字段。其他字段保持原样。 */
|
|
@@ -55582,13 +56126,13 @@ var ProjectStore = class {
|
|
|
55582
56126
|
if (!validated.success) {
|
|
55583
56127
|
throw new Error(`failed to clear publishJob: ${validated.error.message}`);
|
|
55584
56128
|
}
|
|
55585
|
-
await
|
|
56129
|
+
await import_node_fs44.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
|
|
55586
56130
|
return validated.data;
|
|
55587
56131
|
}
|
|
55588
56132
|
};
|
|
55589
56133
|
|
|
55590
56134
|
// src/app-builder/kill-port.ts
|
|
55591
|
-
var
|
|
56135
|
+
var import_node_child_process15 = require("child_process");
|
|
55592
56136
|
async function killPortOccupants(port, ownedPids, logger) {
|
|
55593
56137
|
let pids;
|
|
55594
56138
|
try {
|
|
@@ -55630,7 +56174,7 @@ async function killPortOccupants(port, ownedPids, logger) {
|
|
|
55630
56174
|
}
|
|
55631
56175
|
function listPidsOnPort(port) {
|
|
55632
56176
|
return new Promise((resolve6, reject) => {
|
|
55633
|
-
(0,
|
|
56177
|
+
(0, import_node_child_process15.execFile)(
|
|
55634
56178
|
"lsof",
|
|
55635
56179
|
["-ti", `:${port}`],
|
|
55636
56180
|
{ timeout: 3e3 },
|
|
@@ -55652,7 +56196,7 @@ function listPidsOnPort(port) {
|
|
|
55652
56196
|
}
|
|
55653
56197
|
|
|
55654
56198
|
// src/app-builder/publish-registry.ts
|
|
55655
|
-
var
|
|
56199
|
+
var import_node_crypto16 = require("crypto");
|
|
55656
56200
|
var PublishJobRegistry = class {
|
|
55657
56201
|
jobs = /* @__PURE__ */ new Map();
|
|
55658
56202
|
has(name) {
|
|
@@ -55669,7 +56213,7 @@ var PublishJobRegistry = class {
|
|
|
55669
56213
|
if (this.jobs.has(args.name)) {
|
|
55670
56214
|
throw new Error(`already publishing: ${args.name}`);
|
|
55671
56215
|
}
|
|
55672
|
-
const jobId = args.jobId ?? `job-${(0,
|
|
56216
|
+
const jobId = args.jobId ?? `job-${(0, import_node_crypto16.randomUUID)()}`;
|
|
55673
56217
|
this.jobs.set(args.name, {
|
|
55674
56218
|
jobId,
|
|
55675
56219
|
name: args.name,
|
|
@@ -55702,9 +56246,9 @@ var PublishJobRegistry = class {
|
|
|
55702
56246
|
};
|
|
55703
56247
|
|
|
55704
56248
|
// src/app-builder/publish-job-runner.ts
|
|
55705
|
-
var
|
|
55706
|
-
var
|
|
55707
|
-
var
|
|
56249
|
+
var import_node_child_process16 = require("child_process");
|
|
56250
|
+
var import_node_fs45 = require("fs");
|
|
56251
|
+
var import_node_path53 = require("path");
|
|
55708
56252
|
|
|
55709
56253
|
// src/app-builder/publish-stage-parser.ts
|
|
55710
56254
|
var STAGE_RE = /^\s*::stage::(build|deploy|verify)\s*$/;
|
|
@@ -55731,19 +56275,19 @@ function tailStderrLines(buf, n) {
|
|
|
55731
56275
|
// src/app-builder/publish-job-runner.ts
|
|
55732
56276
|
async function startPublishJob(deps, args) {
|
|
55733
56277
|
const { registry: registry2, projectDir } = deps;
|
|
55734
|
-
const
|
|
56278
|
+
const spawn15 = deps.spawnImpl ?? import_node_child_process16.spawn;
|
|
55735
56279
|
if (registry2.has(args.name)) {
|
|
55736
56280
|
return { jobId: registry2.get(args.name).jobId, status: "already-publishing" };
|
|
55737
56281
|
}
|
|
55738
56282
|
const projDir = projectDir(args.name);
|
|
55739
|
-
const logPath = (0,
|
|
56283
|
+
const logPath = (0, import_node_path53.join)(projDir, ".publish.log");
|
|
55740
56284
|
let logStream = null;
|
|
55741
56285
|
try {
|
|
55742
|
-
logStream = (0,
|
|
56286
|
+
logStream = (0, import_node_fs45.createWriteStream)(logPath, { flags: "w" });
|
|
55743
56287
|
} catch {
|
|
55744
56288
|
logStream = null;
|
|
55745
56289
|
}
|
|
55746
|
-
const child =
|
|
56290
|
+
const child = spawn15("bash", [args.scriptPath, projDir, args.personaRoot ?? ""], {
|
|
55747
56291
|
cwd: projDir,
|
|
55748
56292
|
env: process.env,
|
|
55749
56293
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -55996,8 +56540,8 @@ async function recoverInterruptedJobs(deps) {
|
|
|
55996
56540
|
|
|
55997
56541
|
// src/handlers/app-builder.ts
|
|
55998
56542
|
init_protocol();
|
|
55999
|
-
var
|
|
56000
|
-
var
|
|
56543
|
+
var import_node_path54 = require("path");
|
|
56544
|
+
var import_node_fs46 = require("fs");
|
|
56001
56545
|
var APP_BUILDER_PERSONAS = ["persona-app-builder", "persona-dataclaw-builder"];
|
|
56002
56546
|
var DEV_SERVER_READY_TIMEOUT_MS = 3e4;
|
|
56003
56547
|
async function recoverInterruptedPublishJobs(store, logger) {
|
|
@@ -56078,7 +56622,7 @@ function buildAppBuilderHandlers(deps) {
|
|
|
56078
56622
|
async function listAllUsersProjects() {
|
|
56079
56623
|
if (!deps.usersRoot || !deps.getStore) return [];
|
|
56080
56624
|
const getStore = deps.getStore;
|
|
56081
|
-
const userIds = await
|
|
56625
|
+
const userIds = await import_node_fs46.promises.readdir(deps.usersRoot).catch(() => []);
|
|
56082
56626
|
const perUser = await Promise.all(
|
|
56083
56627
|
userIds.map((uid) => getStore(uid).list().catch(() => []))
|
|
56084
56628
|
);
|
|
@@ -56154,8 +56698,8 @@ function buildAppBuilderHandlers(deps) {
|
|
|
56154
56698
|
const project = await userStore.create(f.name, reservedPorts);
|
|
56155
56699
|
try {
|
|
56156
56700
|
const personaRoot = deps.resolvePersonaRoot ? deps.resolvePersonaRoot(session.ownerPersonaId ?? "") : deps.personaRoot;
|
|
56157
|
-
const templateSrcDir = (0,
|
|
56158
|
-
const scaffoldScript = (0,
|
|
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");
|
|
56159
56703
|
const scaffoldResult = await userStore.scaffold(project.name, templateSrcDir, scaffoldScript);
|
|
56160
56704
|
deps.logger?.info("app-builder.scaffold.done", {
|
|
56161
56705
|
name: project.name,
|
|
@@ -56376,7 +56920,7 @@ function buildAppBuilderHandlers(deps) {
|
|
|
56376
56920
|
await userStore.clearPublishJob(args.name);
|
|
56377
56921
|
}
|
|
56378
56922
|
const personaRoot = deps.resolvePersonaRoot ? deps.resolvePersonaRoot(boundSession.ownerPersonaId ?? "") : deps.personaRoot;
|
|
56379
|
-
const scriptPath = (0,
|
|
56923
|
+
const scriptPath = (0, import_node_path54.join)(deps.deployKitRoot, "scripts", "publish.sh");
|
|
56380
56924
|
deps.logger?.info("app-builder.publish.start", {
|
|
56381
56925
|
name: args.name,
|
|
56382
56926
|
sessionId: boundSession.sessionId,
|
|
@@ -56520,7 +57064,7 @@ function buildShiftHandlers(deps) {
|
|
|
56520
57064
|
|
|
56521
57065
|
// src/handlers/visitor.ts
|
|
56522
57066
|
init_protocol();
|
|
56523
|
-
function
|
|
57067
|
+
function ensureOwner4(ctx) {
|
|
56524
57068
|
if (!ctx || ctx.principal.kind !== "owner") {
|
|
56525
57069
|
throw new ClawdError(
|
|
56526
57070
|
ERROR_CODES.UNAUTHORIZED,
|
|
@@ -56530,7 +57074,7 @@ function ensureOwner3(ctx) {
|
|
|
56530
57074
|
}
|
|
56531
57075
|
function buildVisitorHandlers(deps) {
|
|
56532
57076
|
const list = async (_frame, _client, ctx) => {
|
|
56533
|
-
|
|
57077
|
+
ensureOwner4(ctx);
|
|
56534
57078
|
return {
|
|
56535
57079
|
response: {
|
|
56536
57080
|
type: "visitor:list",
|
|
@@ -56545,7 +57089,7 @@ function buildVisitorHandlers(deps) {
|
|
|
56545
57089
|
|
|
56546
57090
|
// src/extension/registry.ts
|
|
56547
57091
|
var import_promises9 = __toESM(require("fs/promises"), 1);
|
|
56548
|
-
var
|
|
57092
|
+
var import_node_path55 = __toESM(require("path"), 1);
|
|
56549
57093
|
async function loadAll(root) {
|
|
56550
57094
|
let entries;
|
|
56551
57095
|
try {
|
|
@@ -56558,13 +57102,13 @@ async function loadAll(root) {
|
|
|
56558
57102
|
for (const ent of entries) {
|
|
56559
57103
|
if (!ent.isDirectory()) continue;
|
|
56560
57104
|
if (ent.name.startsWith(".")) continue;
|
|
56561
|
-
records.push(await loadOne(
|
|
57105
|
+
records.push(await loadOne(import_node_path55.default.join(root, ent.name), ent.name));
|
|
56562
57106
|
}
|
|
56563
57107
|
records.sort((a, b2) => a.extId < b2.extId ? -1 : a.extId > b2.extId ? 1 : 0);
|
|
56564
57108
|
return records;
|
|
56565
57109
|
}
|
|
56566
57110
|
async function loadOne(dir, dirName) {
|
|
56567
|
-
const manifestPath =
|
|
57111
|
+
const manifestPath = import_node_path55.default.join(dir, "manifest.json");
|
|
56568
57112
|
let raw;
|
|
56569
57113
|
try {
|
|
56570
57114
|
raw = await import_promises9.default.readFile(manifestPath, "utf8");
|
|
@@ -56609,7 +57153,7 @@ async function loadOne(dir, dirName) {
|
|
|
56609
57153
|
|
|
56610
57154
|
// src/extension/uninstall.ts
|
|
56611
57155
|
var import_promises10 = __toESM(require("fs/promises"), 1);
|
|
56612
|
-
var
|
|
57156
|
+
var import_node_path56 = __toESM(require("path"), 1);
|
|
56613
57157
|
var UninstallError = class extends Error {
|
|
56614
57158
|
constructor(code, message) {
|
|
56615
57159
|
super(message);
|
|
@@ -56618,7 +57162,7 @@ var UninstallError = class extends Error {
|
|
|
56618
57162
|
code;
|
|
56619
57163
|
};
|
|
56620
57164
|
async function uninstall(deps) {
|
|
56621
|
-
const dir =
|
|
57165
|
+
const dir = import_node_path56.default.join(deps.root, deps.extId);
|
|
56622
57166
|
try {
|
|
56623
57167
|
await import_promises10.default.access(dir);
|
|
56624
57168
|
} catch {
|
|
@@ -56629,7 +57173,7 @@ async function uninstall(deps) {
|
|
|
56629
57173
|
}
|
|
56630
57174
|
|
|
56631
57175
|
// src/handlers/index.ts
|
|
56632
|
-
var
|
|
57176
|
+
var import_node_crypto17 = require("crypto");
|
|
56633
57177
|
function buildMethodHandlers(deps) {
|
|
56634
57178
|
return {
|
|
56635
57179
|
...buildSessionHandlers({
|
|
@@ -56662,7 +57206,7 @@ function buildMethodHandlers(deps) {
|
|
|
56662
57206
|
const c = deps.contactStore.get(deviceId);
|
|
56663
57207
|
return c ? { deviceId: c.deviceId, remoteUrl: c.remoteUrl, connectToken: c.connectToken } : null;
|
|
56664
57208
|
},
|
|
56665
|
-
genId: () => (0,
|
|
57209
|
+
genId: () => (0, import_node_crypto17.randomUUID)(),
|
|
56666
57210
|
now: () => Date.now(),
|
|
56667
57211
|
forwardInboxPostToPeer,
|
|
56668
57212
|
logger: deps.logger
|
|
@@ -56673,6 +57217,11 @@ function buildMethodHandlers(deps) {
|
|
|
56673
57217
|
broadcast: deps.broadcastToOwners,
|
|
56674
57218
|
now: () => Date.now()
|
|
56675
57219
|
}),
|
|
57220
|
+
...buildContactSshHandlers({
|
|
57221
|
+
store: deps.contactStore,
|
|
57222
|
+
broadcast: deps.broadcastToOwners,
|
|
57223
|
+
sshdDir: deps.sshdDir
|
|
57224
|
+
}),
|
|
56676
57225
|
whoami: buildWhoamiHandler({
|
|
56677
57226
|
ownerDisplayName: deps.ownerDisplayName,
|
|
56678
57227
|
ownerPrincipalId: deps.ownerPrincipalId,
|
|
@@ -56719,7 +57268,7 @@ function buildMethodHandlers(deps) {
|
|
|
56719
57268
|
}
|
|
56720
57269
|
|
|
56721
57270
|
// src/app-builder/dev-server-supervisor.ts
|
|
56722
|
-
var
|
|
57271
|
+
var import_node_child_process17 = require("child_process");
|
|
56723
57272
|
var import_node_events2 = require("events");
|
|
56724
57273
|
var DEFAULT_READY_PATTERN = /Local:\s+https?:\/\/|Nest application successfully started|server listening on/i;
|
|
56725
57274
|
var DevServerSupervisor = class extends import_node_events2.EventEmitter {
|
|
@@ -56756,7 +57305,7 @@ var DevServerSupervisor = class extends import_node_events2.EventEmitter {
|
|
|
56756
57305
|
tunnelHost: args.tunnelHost,
|
|
56757
57306
|
devCommand: cmd
|
|
56758
57307
|
});
|
|
56759
|
-
const child = (0,
|
|
57308
|
+
const child = (0, import_node_child_process17.spawn)("sh", ["-c", cmd], {
|
|
56760
57309
|
cwd: args.cwd,
|
|
56761
57310
|
env,
|
|
56762
57311
|
stdio: "pipe",
|
|
@@ -57008,6 +57557,12 @@ var METHOD_GRANT_MAP = {
|
|
|
57008
57557
|
"contact:list": ADMIN_ANY,
|
|
57009
57558
|
"contact:pin": ADMIN_ANY,
|
|
57010
57559
|
"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" },
|
|
57011
57566
|
// ---- visitor:* (访客名单,owner-only) ----
|
|
57012
57567
|
// owner 看完整访客名单(含没开会话的);guest 不可调(handler 内再 assertOwner 兜底)。
|
|
57013
57568
|
"visitor:list": ADMIN_ANY,
|
|
@@ -57188,8 +57743,8 @@ async function dispatchRpc(method, frame, client, ctx, deps) {
|
|
|
57188
57743
|
}
|
|
57189
57744
|
|
|
57190
57745
|
// src/extension/runtime.ts
|
|
57191
|
-
var
|
|
57192
|
-
var
|
|
57746
|
+
var import_node_child_process18 = require("child_process");
|
|
57747
|
+
var import_node_path57 = __toESM(require("path"), 1);
|
|
57193
57748
|
var import_promises11 = require("timers/promises");
|
|
57194
57749
|
|
|
57195
57750
|
// src/extension/port-allocator.ts
|
|
@@ -57290,13 +57845,13 @@ var Runtime = class {
|
|
|
57290
57845
|
/\$CLAWOS_EXT_PORT/g,
|
|
57291
57846
|
String(port)
|
|
57292
57847
|
);
|
|
57293
|
-
const dir =
|
|
57848
|
+
const dir = import_node_path57.default.join(this.root, extId);
|
|
57294
57849
|
const env = {
|
|
57295
57850
|
...process.env,
|
|
57296
57851
|
CLAWOS_EXT_PORT: String(port),
|
|
57297
57852
|
CLAWOS_EXT_ID: extId
|
|
57298
57853
|
};
|
|
57299
|
-
const child = (0,
|
|
57854
|
+
const child = (0, import_node_child_process18.spawn)("sh", ["-c", cmd], {
|
|
57300
57855
|
cwd: dir,
|
|
57301
57856
|
env,
|
|
57302
57857
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -57402,7 +57957,7 @@ ${handle.stderrTail}`
|
|
|
57402
57957
|
|
|
57403
57958
|
// src/extension/published-channels.ts
|
|
57404
57959
|
var import_promises12 = __toESM(require("fs/promises"), 1);
|
|
57405
|
-
var
|
|
57960
|
+
var import_node_path58 = __toESM(require("path"), 1);
|
|
57406
57961
|
init_zod();
|
|
57407
57962
|
var PublishedChannelsError = class extends Error {
|
|
57408
57963
|
constructor(code, message) {
|
|
@@ -57501,7 +58056,7 @@ var PublishedChannelStore = class {
|
|
|
57501
58056
|
)
|
|
57502
58057
|
};
|
|
57503
58058
|
const tmp = `${this.filePath}.tmp`;
|
|
57504
|
-
await import_promises12.default.mkdir(
|
|
58059
|
+
await import_promises12.default.mkdir(import_node_path58.default.dirname(this.filePath), { recursive: true });
|
|
57505
58060
|
await import_promises12.default.writeFile(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
57506
58061
|
await import_promises12.default.rename(tmp, this.filePath);
|
|
57507
58062
|
}
|
|
@@ -57509,7 +58064,7 @@ var PublishedChannelStore = class {
|
|
|
57509
58064
|
|
|
57510
58065
|
// src/extension/bundle-cache.ts
|
|
57511
58066
|
var import_promises13 = __toESM(require("fs/promises"), 1);
|
|
57512
|
-
var
|
|
58067
|
+
var import_node_path59 = __toESM(require("path"), 1);
|
|
57513
58068
|
var BundleCache = class {
|
|
57514
58069
|
constructor(rootDir) {
|
|
57515
58070
|
this.rootDir = rootDir;
|
|
@@ -57518,14 +58073,14 @@ var BundleCache = class {
|
|
|
57518
58073
|
/** Atomic write: stage tmp → rename. Caller passes the hex sha256. */
|
|
57519
58074
|
async write(snapshotHash, buffer) {
|
|
57520
58075
|
await import_promises13.default.mkdir(this.rootDir, { recursive: true });
|
|
57521
|
-
const file =
|
|
58076
|
+
const file = import_node_path59.default.join(this.rootDir, `${snapshotHash}.zip`);
|
|
57522
58077
|
const tmp = `${file}.tmp`;
|
|
57523
58078
|
await import_promises13.default.writeFile(tmp, buffer, { mode: 384 });
|
|
57524
58079
|
await import_promises13.default.rename(tmp, file);
|
|
57525
58080
|
}
|
|
57526
58081
|
/** Returns the bundle bytes, or null when the file doesn't exist. */
|
|
57527
58082
|
async read(snapshotHash) {
|
|
57528
|
-
const file =
|
|
58083
|
+
const file = import_node_path59.default.join(this.rootDir, `${snapshotHash}.zip`);
|
|
57529
58084
|
try {
|
|
57530
58085
|
return await import_promises13.default.readFile(file);
|
|
57531
58086
|
} catch (e) {
|
|
@@ -57535,7 +58090,7 @@ var BundleCache = class {
|
|
|
57535
58090
|
}
|
|
57536
58091
|
/** Idempotent — missing file is not an error. */
|
|
57537
58092
|
async delete(snapshotHash) {
|
|
57538
|
-
const file =
|
|
58093
|
+
const file = import_node_path59.default.join(this.rootDir, `${snapshotHash}.zip`);
|
|
57539
58094
|
await import_promises13.default.rm(file, { force: true });
|
|
57540
58095
|
}
|
|
57541
58096
|
};
|
|
@@ -57560,17 +58115,10 @@ async function startDaemon(config) {
|
|
|
57560
58115
|
});
|
|
57561
58116
|
const logger = createLogger({
|
|
57562
58117
|
level: config.logLevel,
|
|
57563
|
-
file:
|
|
58118
|
+
file: import_node_path60.default.join(config.dataDir, "clawd.log"),
|
|
57564
58119
|
logClient
|
|
57565
58120
|
});
|
|
57566
58121
|
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
|
-
});
|
|
57574
58122
|
const stateMgr = new StateFileManager({ dataDir: config.dataDir });
|
|
57575
58123
|
const pre = stateMgr.preflight();
|
|
57576
58124
|
if (pre.status === "active") {
|
|
@@ -57707,8 +58255,8 @@ async function startDaemon(config) {
|
|
|
57707
58255
|
const agents = new AgentsScanner();
|
|
57708
58256
|
const history = new ClaudeHistoryReader();
|
|
57709
58257
|
let transport = null;
|
|
57710
|
-
const personaStore = new PersonaStore(
|
|
57711
|
-
const usersRoot =
|
|
58258
|
+
const personaStore = new PersonaStore(import_node_path60.default.join(config.dataDir, "personas"));
|
|
58259
|
+
const usersRoot = import_node_path60.default.join(config.dataDir, "users");
|
|
57712
58260
|
const defaultsRoot = findDefaultsRoot(logger);
|
|
57713
58261
|
if (defaultsRoot) {
|
|
57714
58262
|
seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
|
|
@@ -57728,17 +58276,17 @@ async function startDaemon(config) {
|
|
|
57728
58276
|
migrateCodexSandbox({ store: personaStore, logger });
|
|
57729
58277
|
const groupFileStore = new GroupFileStore({ dataDir: config.dataDir, logger });
|
|
57730
58278
|
const personaDispatchManager = new PersonaDispatchManager({ genId: () => v4_default() });
|
|
57731
|
-
const here = typeof __dirname === "string" ? __dirname :
|
|
58279
|
+
const here = typeof __dirname === "string" ? __dirname : import_node_path60.default.dirname((0, import_node_url4.fileURLToPath)(import_meta6.url));
|
|
57732
58280
|
const dispatchServerCandidates = [
|
|
57733
|
-
|
|
58281
|
+
import_node_path60.default.join(here, "dispatch", "mcp-server.cjs"),
|
|
57734
58282
|
// 生产 dist/index → dist/dispatch/mcp-server.cjs
|
|
57735
|
-
|
|
58283
|
+
import_node_path60.default.join(here, "..", "dist", "dispatch", "mcp-server.cjs")
|
|
57736
58284
|
// dev tsx src/index → ../dist/dispatch/mcp-server.cjs
|
|
57737
58285
|
];
|
|
57738
|
-
const dispatchServerScriptPath = dispatchServerCandidates.find((p2) =>
|
|
58286
|
+
const dispatchServerScriptPath = dispatchServerCandidates.find((p2) => import_node_fs47.default.existsSync(p2));
|
|
57739
58287
|
let dispatchMcpConfigPath2;
|
|
57740
58288
|
if (dispatchServerScriptPath) {
|
|
57741
|
-
const dispatchLogPath =
|
|
58289
|
+
const dispatchLogPath = import_node_path60.default.join(config.dataDir, "dispatch-mcp-server.log");
|
|
57742
58290
|
dispatchMcpConfigPath2 = writeDispatchMcpConfig({
|
|
57743
58291
|
dataDir: config.dataDir,
|
|
57744
58292
|
serverScriptPath: dispatchServerScriptPath,
|
|
@@ -57755,15 +58303,15 @@ async function startDaemon(config) {
|
|
|
57755
58303
|
});
|
|
57756
58304
|
}
|
|
57757
58305
|
const ticketServerCandidates = [
|
|
57758
|
-
|
|
57759
|
-
|
|
58306
|
+
import_node_path60.default.join(here, "ticket", "mcp-server.cjs"),
|
|
58307
|
+
import_node_path60.default.join(here, "..", "dist", "ticket", "mcp-server.cjs")
|
|
57760
58308
|
];
|
|
57761
|
-
const ticketServerScriptPath = ticketServerCandidates.find((p2) =>
|
|
58309
|
+
const ticketServerScriptPath = ticketServerCandidates.find((p2) => import_node_fs47.default.existsSync(p2));
|
|
57762
58310
|
const ticketOwnerUnionId = feishuIdentity?.identity.unionId ?? "";
|
|
57763
58311
|
const ticketOwnerName = feishuIdentity?.identity.displayName ?? "";
|
|
57764
58312
|
let ticketMcpConfigPath2;
|
|
57765
58313
|
if (ticketServerScriptPath && ticketOwnerUnionId) {
|
|
57766
|
-
const ticketLogPath =
|
|
58314
|
+
const ticketLogPath = import_node_path60.default.join(config.dataDir, "ticket-mcp-server.log");
|
|
57767
58315
|
ticketMcpConfigPath2 = writeTicketMcpConfig({
|
|
57768
58316
|
dataDir: config.dataDir,
|
|
57769
58317
|
serverScriptPath: ticketServerScriptPath,
|
|
@@ -57784,13 +58332,13 @@ async function startDaemon(config) {
|
|
|
57784
58332
|
});
|
|
57785
58333
|
}
|
|
57786
58334
|
const shiftServerCandidates = [
|
|
57787
|
-
|
|
57788
|
-
|
|
58335
|
+
import_node_path60.default.join(here, "shift", "mcp-server.cjs"),
|
|
58336
|
+
import_node_path60.default.join(here, "..", "dist", "shift", "mcp-server.cjs")
|
|
57789
58337
|
];
|
|
57790
|
-
const shiftServerScriptPath = shiftServerCandidates.find((p2) =>
|
|
58338
|
+
const shiftServerScriptPath = shiftServerCandidates.find((p2) => import_node_fs47.default.existsSync(p2));
|
|
57791
58339
|
let shiftMcpConfigPath2;
|
|
57792
58340
|
if (shiftServerScriptPath) {
|
|
57793
|
-
const shiftLogPath =
|
|
58341
|
+
const shiftLogPath = import_node_path60.default.join(config.dataDir, "shift-mcp-server.log");
|
|
57794
58342
|
shiftMcpConfigPath2 = await writeShiftMcpConfig({
|
|
57795
58343
|
dataDir: config.dataDir,
|
|
57796
58344
|
serverScriptPath: shiftServerScriptPath,
|
|
@@ -57808,13 +58356,13 @@ async function startDaemon(config) {
|
|
|
57808
58356
|
);
|
|
57809
58357
|
}
|
|
57810
58358
|
const inboxServerCandidates = [
|
|
57811
|
-
|
|
57812
|
-
|
|
58359
|
+
import_node_path60.default.join(here, "inbox", "mcp-server.cjs"),
|
|
58360
|
+
import_node_path60.default.join(here, "..", "dist", "inbox", "mcp-server.cjs")
|
|
57813
58361
|
];
|
|
57814
|
-
const inboxServerScriptPath = inboxServerCandidates.find((p2) =>
|
|
58362
|
+
const inboxServerScriptPath = inboxServerCandidates.find((p2) => import_node_fs47.default.existsSync(p2));
|
|
57815
58363
|
let inboxMcpConfigPath2;
|
|
57816
58364
|
if (inboxServerScriptPath) {
|
|
57817
|
-
const inboxLogPath =
|
|
58365
|
+
const inboxLogPath = import_node_path60.default.join(config.dataDir, "inbox-mcp-server.log");
|
|
57818
58366
|
inboxMcpConfigPath2 = await writeInboxMcpConfig({
|
|
57819
58367
|
dataDir: config.dataDir,
|
|
57820
58368
|
serverScriptPath: inboxServerScriptPath,
|
|
@@ -57832,7 +58380,7 @@ async function startDaemon(config) {
|
|
|
57832
58380
|
);
|
|
57833
58381
|
}
|
|
57834
58382
|
const shiftStore = createShiftStore({
|
|
57835
|
-
filePath:
|
|
58383
|
+
filePath: import_node_path60.default.join(config.dataDir, "shift.json"),
|
|
57836
58384
|
ownerIdProvider: () => ownerPrincipalId,
|
|
57837
58385
|
now: () => Date.now()
|
|
57838
58386
|
});
|
|
@@ -57847,14 +58395,10 @@ async function startDaemon(config) {
|
|
|
57847
58395
|
// 新布局派生 (sessions/* + personas/<pid>/.clawd/sessions/owner/*)
|
|
57848
58396
|
storeFactory: sessionStoreFactory,
|
|
57849
58397
|
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 } : {},
|
|
57854
58398
|
getAdapter,
|
|
57855
58399
|
historyReader: history,
|
|
57856
58400
|
dataDir: config.dataDir,
|
|
57857
|
-
personaRoot:
|
|
58401
|
+
personaRoot: import_node_path60.default.join(config.dataDir, "personas"),
|
|
57858
58402
|
usersRoot,
|
|
57859
58403
|
personaStore,
|
|
57860
58404
|
ownerDisplayName,
|
|
@@ -57897,10 +58441,10 @@ async function startDaemon(config) {
|
|
|
57897
58441
|
// 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
|
|
57898
58442
|
attachmentGroup: {
|
|
57899
58443
|
onFileEdit: (input) => {
|
|
57900
|
-
const absPath =
|
|
58444
|
+
const absPath = import_node_path60.default.isAbsolute(input.relPath) ? input.relPath : import_node_path60.default.join(input.cwd, input.relPath);
|
|
57901
58445
|
let size = 0;
|
|
57902
58446
|
try {
|
|
57903
|
-
size =
|
|
58447
|
+
size = import_node_fs47.default.statSync(absPath).size;
|
|
57904
58448
|
} catch (err) {
|
|
57905
58449
|
logger.warn("attachment.onFileEdit stat failed", {
|
|
57906
58450
|
sessionId: input.sessionId,
|
|
@@ -57968,10 +58512,10 @@ async function startDaemon(config) {
|
|
|
57968
58512
|
onSurfaceUnregister: (tsid) => manager.unregisterSurface(tsid),
|
|
57969
58513
|
// ReadyGate v2:ReadyDetector emit ready 时投递 reducer 'ready-detected' input
|
|
57970
58514
|
onReady: (tsid) => manager.dispatchReadyDetected(tsid),
|
|
57971
|
-
//
|
|
57972
|
-
|
|
57973
|
-
//
|
|
57974
|
-
|
|
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)
|
|
57975
58519
|
}) : new ClaudeAdapter({ logger, historyReader: new ClaudeHistoryReader() });
|
|
57976
58520
|
registerAdapter("claude", claudeAdapter);
|
|
57977
58521
|
registerAdapter("codex", new CodexAdapter({ logger, historyReader: new CodexHistoryReader() }));
|
|
@@ -58098,11 +58642,11 @@ async function startDaemon(config) {
|
|
|
58098
58642
|
// 'persona/<pid>/owner',default 走 'default'。
|
|
58099
58643
|
getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
|
|
58100
58644
|
// guest path guard:candidate 必须在 personaRoot 子树或调用者自己的 user-dir 下
|
|
58101
|
-
personaRoot:
|
|
58645
|
+
personaRoot: import_node_path60.default.join(config.dataDir, "personas"),
|
|
58102
58646
|
usersRoot
|
|
58103
58647
|
},
|
|
58104
58648
|
// workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
|
|
58105
|
-
personaRoot:
|
|
58649
|
+
personaRoot: import_node_path60.default.join(config.dataDir, "personas"),
|
|
58106
58650
|
// v2 多人 persona 隔离:handler 派生 guest user-dir 放行
|
|
58107
58651
|
usersRoot,
|
|
58108
58652
|
// capability:list / delete handler 依赖
|
|
@@ -58122,6 +58666,9 @@ async function startDaemon(config) {
|
|
|
58122
58666
|
inboxStore,
|
|
58123
58667
|
// 联系人列表 store(device:connect / 自动反向落同一 store)
|
|
58124
58668
|
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"),
|
|
58125
58672
|
// inbox:sendDm 用:sessionId → session 出身(复用 attachment 同款 findOwnedSessionScope)
|
|
58126
58673
|
getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
|
|
58127
58674
|
// contact:removed broadcast;复用 capability:tokenIssued 同款通路
|
|
@@ -58211,11 +58758,11 @@ async function startDaemon(config) {
|
|
|
58211
58758
|
// 发布上线脚手架化 (spec 2026-06-03 §5.2):
|
|
58212
58759
|
// appBuilderPersonaRoot 用于拼 publish.sh 绝对路径(persona-app-builder 安装在
|
|
58213
58760
|
// dataDir/personas/persona-app-builder 之下,extension-kit/scripts/publish.sh 是相对路径)。
|
|
58214
|
-
appBuilderPersonaRoot:
|
|
58761
|
+
appBuilderPersonaRoot: import_node_path60.default.join(config.dataDir, "personas", "persona-app-builder"),
|
|
58215
58762
|
// 共享 deploy-kit 根:scaffold/publish 脚本骨架 + 阿里云凭证单一真源。
|
|
58216
|
-
deployKitRoot:
|
|
58763
|
+
deployKitRoot: import_node_path60.default.join(config.dataDir, "deploy-kit"),
|
|
58217
58764
|
// scaffold/publish 按当前 session 的 persona 解析其安装根,让每个 persona 用自己的模板/注入配置。
|
|
58218
|
-
resolvePersonaRoot: (personaId) =>
|
|
58765
|
+
resolvePersonaRoot: (personaId) => import_node_path60.default.join(config.dataDir, "personas", personaId),
|
|
58219
58766
|
// 发布上线脚手架化 (spec 2026-06-03 §5.2.2):
|
|
58220
58767
|
// 复用 SessionManagerDeps.broadcastFrame 同款 dispatch 逻辑 —— runner 调 manager.send
|
|
58221
58768
|
// 取回 broadcast 帧后逐帧 push 到 transport,跟 manager 自身的 deps 一致。
|
|
@@ -58258,7 +58805,7 @@ async function startDaemon(config) {
|
|
|
58258
58805
|
}
|
|
58259
58806
|
let sourceJsonlPath = "(no transcript yet \u2014 operate from the task description alone)";
|
|
58260
58807
|
if (sourceFile && sourceFile.toolSessionId) {
|
|
58261
|
-
sourceJsonlPath =
|
|
58808
|
+
sourceJsonlPath = import_node_path60.default.join(
|
|
58262
58809
|
import_node_os21.default.homedir(),
|
|
58263
58810
|
".claude",
|
|
58264
58811
|
"projects",
|
|
@@ -58558,8 +59105,8 @@ async function startDaemon(config) {
|
|
|
58558
59105
|
const lines = [
|
|
58559
59106
|
`Tunnel: ${r.url}`,
|
|
58560
59107
|
...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
|
|
58561
|
-
`Frpc config: ${
|
|
58562
|
-
`Frpc log: ${
|
|
59108
|
+
`Frpc config: ${import_node_path60.default.join(config.dataDir, "frpc.toml")}`,
|
|
59109
|
+
`Frpc log: ${import_node_path60.default.join(config.dataDir, "frpc.log")}`
|
|
58563
59110
|
];
|
|
58564
59111
|
const width = Math.max(...lines.map((l) => l.length));
|
|
58565
59112
|
const bar = "\u2550".repeat(width + 4);
|
|
@@ -58572,8 +59119,8 @@ ${bar}
|
|
|
58572
59119
|
|
|
58573
59120
|
`);
|
|
58574
59121
|
try {
|
|
58575
|
-
const connectPath =
|
|
58576
|
-
|
|
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 });
|
|
58577
59124
|
} catch {
|
|
58578
59125
|
}
|
|
58579
59126
|
} catch (err) {
|
|
@@ -58598,6 +59145,22 @@ ${bar}
|
|
|
58598
59145
|
logger.warn("tunnel unavailable, degraded to local mode", { reason: tunnelError });
|
|
58599
59146
|
}
|
|
58600
59147
|
}
|
|
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
|
+
}
|
|
58601
59164
|
void reportDevice();
|
|
58602
59165
|
void fetchServerKey();
|
|
58603
59166
|
const tickAttachmentGc = () => {
|
|
@@ -58632,6 +59195,11 @@ ${bar}
|
|
|
58632
59195
|
if (tunnelMgr) {
|
|
58633
59196
|
await tunnelMgr.stop();
|
|
58634
59197
|
}
|
|
59198
|
+
await sshdMgr.stop().catch((err) => {
|
|
59199
|
+
logger.warn("shutdown.sshd-stop-failed", {
|
|
59200
|
+
error: err instanceof Error ? err.message : String(err)
|
|
59201
|
+
});
|
|
59202
|
+
});
|
|
58635
59203
|
await wss.stop();
|
|
58636
59204
|
stateMgr.delete();
|
|
58637
59205
|
if (logClient) await logClient.dispose();
|
|
@@ -58645,9 +59213,9 @@ ${bar}
|
|
|
58645
59213
|
};
|
|
58646
59214
|
}
|
|
58647
59215
|
function migrateDropPersonsDir(dataDir) {
|
|
58648
|
-
const dir =
|
|
59216
|
+
const dir = import_node_path60.default.join(dataDir, "persons");
|
|
58649
59217
|
try {
|
|
58650
|
-
|
|
59218
|
+
import_node_fs47.default.rmSync(dir, { recursive: true, force: true });
|
|
58651
59219
|
} catch {
|
|
58652
59220
|
}
|
|
58653
59221
|
}
|