@clawos-dev/clawd 0.2.199 → 0.2.201-beta.402.b896f3d
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 +1880 -670
- 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: path76, errorMaps, issueData } = params;
|
|
746
|
+
const fullPath = [...path76, ...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, path76, key) {
|
|
1053
1058
|
this._cachedPath = [];
|
|
1054
1059
|
this.parent = parent;
|
|
1055
1060
|
this.data = value;
|
|
1056
|
-
this._path =
|
|
1061
|
+
this._path = path76;
|
|
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 path76 = req.path;
|
|
6453
|
+
_req.url = typeof path76 === "string" ? path76 : 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(path76) {
|
|
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 < path76.length; i++) {
|
|
6625
|
+
const char = path76[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 path76 of paths) {
|
|
6757
|
+
const parts = parsePath(path76);
|
|
6706
6758
|
if (parts.includes("*")) {
|
|
6707
|
-
redactWildcardPath(obj, parts, censor,
|
|
6759
|
+
redactWildcardPath(obj, parts, censor, path76, 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, path76) => {
|
|
6845
|
+
const fullPath = [...pathArray.slice(0, pathLength), ...path76];
|
|
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 path76 of pathsToClone) {
|
|
6881
|
+
const parts = parsePath(path76);
|
|
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(path76) {
|
|
6934
|
+
if (typeof path76 !== "string") {
|
|
6883
6935
|
throw new Error("Paths must be (non-empty) strings");
|
|
6884
6936
|
}
|
|
6885
|
-
if (
|
|
6937
|
+
if (path76 === "") {
|
|
6886
6938
|
throw new Error("Invalid redaction path ()");
|
|
6887
6939
|
}
|
|
6888
|
-
if (
|
|
6889
|
-
throw new Error(`Invalid redaction path (${
|
|
6940
|
+
if (path76.includes("..")) {
|
|
6941
|
+
throw new Error(`Invalid redaction path (${path76})`);
|
|
6890
6942
|
}
|
|
6891
|
-
if (
|
|
6892
|
-
throw new Error(`Invalid redaction path (${
|
|
6943
|
+
if (path76.includes(",")) {
|
|
6944
|
+
throw new Error(`Invalid redaction path (${path76})`);
|
|
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 < path76.length; i++) {
|
|
6950
|
+
const char = path76[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 (${path76})`);
|
|
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 (${path76})`);
|
|
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 path76 of paths) {
|
|
6977
|
+
validatePath(path76);
|
|
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, path76) => {
|
|
7146
|
+
return censor(value, [k2, ...path76]);
|
|
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 fs69 = require("fs");
|
|
7313
7365
|
var EventEmitter3 = require("events");
|
|
7314
7366
|
var inherits = require("util").inherits;
|
|
7315
|
-
var
|
|
7367
|
+
var path76 = 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) fs69.mkdirSync(path76.dirname(file), { recursive: true });
|
|
7422
|
+
const fd = fs69.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
|
+
fs69.mkdir(path76.dirname(file), { recursive: true }, (err) => {
|
|
7378
7430
|
if (err) return fileOpened(err);
|
|
7379
|
-
|
|
7431
|
+
fs69.open(file, flags, mode, fileOpened);
|
|
7380
7432
|
});
|
|
7381
7433
|
} else {
|
|
7382
|
-
|
|
7434
|
+
fs69.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 = () => fs69.writeSync(this.fd, this._writingBuf);
|
|
7476
|
+
fsWrite = () => fs69.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 fs69.writeSync(this.fd, this._writingBuf);
|
|
7434
7486
|
}
|
|
7435
|
-
return
|
|
7487
|
+
return fs69.writeSync(this.fd, this._writingBuf, "utf8");
|
|
7436
7488
|
};
|
|
7437
7489
|
fsWrite = () => {
|
|
7438
7490
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
7439
|
-
return
|
|
7491
|
+
return fs69.write(this.fd, this._writingBuf, this.release);
|
|
7440
7492
|
}
|
|
7441
|
-
return
|
|
7493
|
+
return fs69.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
|
+
fs69.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
|
+
fs69.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
|
+
fs69.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) ? fs69.writeSync(this.fd, buf) : fs69.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
|
+
fs69.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 = fs69.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) ? fs69.writeSync(this.fd, this._writingBuf) : fs69.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
|
+
fs69.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 = fs69.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
|
+
fs69.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
|
+
fs69.fsync(sonic.fd, closeWrapped);
|
|
7869
7921
|
} catch {
|
|
7870
7922
|
}
|
|
7871
7923
|
function closeWrapped() {
|
|
7872
7924
|
if (sonic.fd !== 1 && sonic.fd !== 2) {
|
|
7873
|
-
|
|
7925
|
+
fs69.close(sonic.fd, done);
|
|
7874
7926
|
} else {
|
|
7875
7927
|
done();
|
|
7876
7928
|
}
|
|
@@ -10234,7 +10286,7 @@ var require_multistream = __commonJS({
|
|
|
10234
10286
|
var require_pino = __commonJS({
|
|
10235
10287
|
"../node_modules/.pnpm/pino@9.14.0/node_modules/pino/pino.js"(exports2, module2) {
|
|
10236
10288
|
"use strict";
|
|
10237
|
-
var
|
|
10289
|
+
var os24 = require("os");
|
|
10238
10290
|
var stdSerializers = require_pino_std_serializers();
|
|
10239
10291
|
var caller = require_caller();
|
|
10240
10292
|
var redaction = require_redaction();
|
|
@@ -10281,7 +10333,7 @@ var require_pino = __commonJS({
|
|
|
10281
10333
|
} = symbols;
|
|
10282
10334
|
var { epochTime, nullTime } = time;
|
|
10283
10335
|
var { pid } = process;
|
|
10284
|
-
var hostname =
|
|
10336
|
+
var hostname = os24.hostname();
|
|
10285
10337
|
var defaultErrorSerializer = stdSerializers.err;
|
|
10286
10338
|
var defaultOptions = {
|
|
10287
10339
|
level: "info",
|
|
@@ -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(path76, added, removed, oldPosInc, options) {
|
|
11061
|
+
var last = path76.lastComponent;
|
|
11010
11062
|
if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
|
|
11011
11063
|
return {
|
|
11012
|
-
oldPos:
|
|
11064
|
+
oldPos: path76.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: path76.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 path76 = 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 (!path76 || content == null) return null;
|
|
11543
|
+
const entry = { path: path76, content };
|
|
11492
11544
|
if (typeof rec3.mtimeMs === "number") entry.mtimeMs = rec3.mtimeMs;
|
|
11493
11545
|
return entry;
|
|
11494
11546
|
}).filter((m2) => m2 !== null);
|
|
@@ -11952,6 +12004,14 @@ var init_claude_history = __esm({
|
|
|
11952
12004
|
}
|
|
11953
12005
|
});
|
|
11954
12006
|
|
|
12007
|
+
// ../protocol/src/index.ts
|
|
12008
|
+
var init_src = __esm({
|
|
12009
|
+
"../protocol/src/index.ts"() {
|
|
12010
|
+
"use strict";
|
|
12011
|
+
init_runtime();
|
|
12012
|
+
}
|
|
12013
|
+
});
|
|
12014
|
+
|
|
11955
12015
|
// src/tools/claude.ts
|
|
11956
12016
|
function probeViaWhich() {
|
|
11957
12017
|
try {
|
|
@@ -12300,10 +12360,10 @@ function parseAttachment(obj) {
|
|
|
12300
12360
|
const memories = raw.map((m2) => {
|
|
12301
12361
|
if (!m2 || typeof m2 !== "object") return null;
|
|
12302
12362
|
const rec3 = m2;
|
|
12303
|
-
const
|
|
12363
|
+
const path76 = typeof rec3.path === "string" ? rec3.path : null;
|
|
12304
12364
|
const content = typeof rec3.content === "string" ? rec3.content : null;
|
|
12305
|
-
if (!
|
|
12306
|
-
const out = { path:
|
|
12365
|
+
if (!path76 || content == null) return null;
|
|
12366
|
+
const out = { path: path76, content };
|
|
12307
12367
|
if (typeof rec3.mtimeMs === "number") out.mtimeMs = rec3.mtimeMs;
|
|
12308
12368
|
return out;
|
|
12309
12369
|
}).filter((m2) => m2 !== null);
|
|
@@ -26775,6 +26835,121 @@ var require_dist = __commonJS({
|
|
|
26775
26835
|
}
|
|
26776
26836
|
});
|
|
26777
26837
|
|
|
26838
|
+
// src/dispatch/peer-forward.ts
|
|
26839
|
+
function wsUrlToHttp(url) {
|
|
26840
|
+
if (url.startsWith("wss://")) return "https://" + url.slice("wss://".length);
|
|
26841
|
+
if (url.startsWith("ws://")) return "http://" + url.slice("ws://".length);
|
|
26842
|
+
return url;
|
|
26843
|
+
}
|
|
26844
|
+
async function forwardDispatchToPeer(args) {
|
|
26845
|
+
const f = args.fetchImpl ?? fetch;
|
|
26846
|
+
const base = wsUrlToHttp(args.contact.remoteUrl).replace(/\/+$/, "");
|
|
26847
|
+
const url = `${base}/rpc/personaDispatch:run`;
|
|
26848
|
+
let res;
|
|
26849
|
+
try {
|
|
26850
|
+
res = await f(url, {
|
|
26851
|
+
method: "POST",
|
|
26852
|
+
headers: {
|
|
26853
|
+
"content-type": "application/json",
|
|
26854
|
+
authorization: `Bearer ${args.contact.connectToken}`
|
|
26855
|
+
},
|
|
26856
|
+
// 注意:不带 targetDeviceId —— B 端据此判定为本地执行(B 角色)。
|
|
26857
|
+
body: JSON.stringify({ targetPersona: args.targetPersona, prompt: args.prompt })
|
|
26858
|
+
});
|
|
26859
|
+
} catch (err) {
|
|
26860
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
26861
|
+
return { kind: "failure", reason: `forward to peer failed: ${msg}` };
|
|
26862
|
+
}
|
|
26863
|
+
let json;
|
|
26864
|
+
try {
|
|
26865
|
+
json = await res.json();
|
|
26866
|
+
} catch {
|
|
26867
|
+
return {
|
|
26868
|
+
kind: "failure",
|
|
26869
|
+
reason: `peer returned non-JSON response (HTTP ${res.status})`
|
|
26870
|
+
};
|
|
26871
|
+
}
|
|
26872
|
+
if (json.ok === false) {
|
|
26873
|
+
return { kind: "failure", reason: `peer rejected: ${json.error}: ${json.message}` };
|
|
26874
|
+
}
|
|
26875
|
+
return json.result.outcome;
|
|
26876
|
+
}
|
|
26877
|
+
async function forwardInboxPostToPeer(args) {
|
|
26878
|
+
const f = args.fetchImpl ?? fetch;
|
|
26879
|
+
const base = wsUrlToHttp(args.contact.remoteUrl).replace(/\/+$/, "");
|
|
26880
|
+
const url = `${base}/rpc/inbox:postMessage`;
|
|
26881
|
+
let res;
|
|
26882
|
+
try {
|
|
26883
|
+
res = await f(url, {
|
|
26884
|
+
method: "POST",
|
|
26885
|
+
headers: {
|
|
26886
|
+
"content-type": "application/json",
|
|
26887
|
+
authorization: `Bearer ${args.contact.connectToken}`
|
|
26888
|
+
},
|
|
26889
|
+
body: JSON.stringify({
|
|
26890
|
+
id: args.id,
|
|
26891
|
+
text: args.text,
|
|
26892
|
+
createdAt: args.createdAt,
|
|
26893
|
+
...args.origin ? { origin: args.origin } : {}
|
|
26894
|
+
})
|
|
26895
|
+
});
|
|
26896
|
+
} catch (err) {
|
|
26897
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
26898
|
+
}
|
|
26899
|
+
let json = null;
|
|
26900
|
+
try {
|
|
26901
|
+
json = await res.json();
|
|
26902
|
+
} catch {
|
|
26903
|
+
return { ok: false, error: `peer non-JSON (HTTP ${res.status})` };
|
|
26904
|
+
}
|
|
26905
|
+
if (res.status < 200 || res.status >= 300 || json?.ok === false) {
|
|
26906
|
+
return { ok: false, error: json?.error ?? `HTTP ${res.status}` };
|
|
26907
|
+
}
|
|
26908
|
+
return { ok: true };
|
|
26909
|
+
}
|
|
26910
|
+
async function forwardContactSshKeyIssueToPeer(args) {
|
|
26911
|
+
const f = args.fetchImpl ?? fetch;
|
|
26912
|
+
const base = wsUrlToHttp(args.contact.remoteUrl).replace(/\/+$/, "");
|
|
26913
|
+
const url = `${base}/rpc/contact:sshKey:issue`;
|
|
26914
|
+
let res;
|
|
26915
|
+
try {
|
|
26916
|
+
res = await f(url, {
|
|
26917
|
+
method: "POST",
|
|
26918
|
+
headers: {
|
|
26919
|
+
"content-type": "application/json",
|
|
26920
|
+
authorization: `Bearer ${args.contact.connectToken}`
|
|
26921
|
+
},
|
|
26922
|
+
body: JSON.stringify({ deviceId: args.selfDeviceIdForRequest })
|
|
26923
|
+
});
|
|
26924
|
+
} catch (err) {
|
|
26925
|
+
return {
|
|
26926
|
+
ok: false,
|
|
26927
|
+
code: "NETWORK",
|
|
26928
|
+
message: err instanceof Error ? err.message : String(err)
|
|
26929
|
+
};
|
|
26930
|
+
}
|
|
26931
|
+
let json = null;
|
|
26932
|
+
try {
|
|
26933
|
+
json = await res.json();
|
|
26934
|
+
} catch {
|
|
26935
|
+
return { ok: false, code: "PROTOCOL", message: `peer non-JSON (HTTP ${res.status})` };
|
|
26936
|
+
}
|
|
26937
|
+
if (!json) {
|
|
26938
|
+
return { ok: false, code: "PROTOCOL", message: `peer returned null (HTTP ${res.status})` };
|
|
26939
|
+
}
|
|
26940
|
+
if (json.ok === false) {
|
|
26941
|
+
const j = json;
|
|
26942
|
+
return { ok: false, code: j.error ?? "UNKNOWN", message: j.message ?? "" };
|
|
26943
|
+
}
|
|
26944
|
+
const r = json.result;
|
|
26945
|
+
return { ok: true, privateKeyPem: r.privateKeyPem, publicKeyLine: r.publicKeyLine };
|
|
26946
|
+
}
|
|
26947
|
+
var init_peer_forward = __esm({
|
|
26948
|
+
"src/dispatch/peer-forward.ts"() {
|
|
26949
|
+
"use strict";
|
|
26950
|
+
}
|
|
26951
|
+
});
|
|
26952
|
+
|
|
26778
26953
|
// ../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/constants.js
|
|
26779
26954
|
var require_constants2 = __commonJS({
|
|
26780
26955
|
"../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/constants.js"(exports2, module2) {
|
|
@@ -28976,9 +29151,9 @@ var require_websocket = __commonJS({
|
|
|
28976
29151
|
var EventEmitter3 = require("events");
|
|
28977
29152
|
var https = require("https");
|
|
28978
29153
|
var http3 = require("http");
|
|
28979
|
-
var
|
|
29154
|
+
var net4 = require("net");
|
|
28980
29155
|
var tls = require("tls");
|
|
28981
|
-
var { randomBytes, createHash:
|
|
29156
|
+
var { randomBytes, createHash: createHash2 } = require("crypto");
|
|
28982
29157
|
var { Duplex, Readable: Readable3 } = require("stream");
|
|
28983
29158
|
var { URL: URL2 } = require("url");
|
|
28984
29159
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -29638,7 +29813,7 @@ var require_websocket = __commonJS({
|
|
|
29638
29813
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
29639
29814
|
return;
|
|
29640
29815
|
}
|
|
29641
|
-
const digest =
|
|
29816
|
+
const digest = createHash2("sha1").update(key + GUID).digest("base64");
|
|
29642
29817
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
29643
29818
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
29644
29819
|
return;
|
|
@@ -29710,12 +29885,12 @@ var require_websocket = __commonJS({
|
|
|
29710
29885
|
}
|
|
29711
29886
|
function netConnect(options) {
|
|
29712
29887
|
options.path = options.socketPath;
|
|
29713
|
-
return
|
|
29888
|
+
return net4.connect(options);
|
|
29714
29889
|
}
|
|
29715
29890
|
function tlsConnect(options) {
|
|
29716
29891
|
options.path = void 0;
|
|
29717
29892
|
if (!options.servername && options.servername !== "") {
|
|
29718
|
-
options.servername =
|
|
29893
|
+
options.servername = net4.isIP(options.host) ? "" : options.host;
|
|
29719
29894
|
}
|
|
29720
29895
|
return tls.connect(options);
|
|
29721
29896
|
}
|
|
@@ -30005,7 +30180,7 @@ var require_websocket_server = __commonJS({
|
|
|
30005
30180
|
var EventEmitter3 = require("events");
|
|
30006
30181
|
var http3 = require("http");
|
|
30007
30182
|
var { Duplex } = require("stream");
|
|
30008
|
-
var { createHash:
|
|
30183
|
+
var { createHash: createHash2 } = require("crypto");
|
|
30009
30184
|
var extension2 = require_extension();
|
|
30010
30185
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
30011
30186
|
var subprotocol2 = require_subprotocol();
|
|
@@ -30306,7 +30481,7 @@ var require_websocket_server = __commonJS({
|
|
|
30306
30481
|
);
|
|
30307
30482
|
}
|
|
30308
30483
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
30309
|
-
const digest =
|
|
30484
|
+
const digest = createHash2("sha1").update(key + GUID).digest("base64");
|
|
30310
30485
|
const headers = [
|
|
30311
30486
|
"HTTP/1.1 101 Switching Protocols",
|
|
30312
30487
|
"Upgrade: websocket",
|
|
@@ -30391,6 +30566,23 @@ var require_websocket_server = __commonJS({
|
|
|
30391
30566
|
}
|
|
30392
30567
|
});
|
|
30393
30568
|
|
|
30569
|
+
// ../node_modules/.pnpm/ws@8.20.0/node_modules/ws/wrapper.mjs
|
|
30570
|
+
var import_stream, import_extension, import_permessage_deflate, import_receiver, import_sender, import_subprotocol, import_websocket, import_websocket_server, wrapper_default;
|
|
30571
|
+
var init_wrapper = __esm({
|
|
30572
|
+
"../node_modules/.pnpm/ws@8.20.0/node_modules/ws/wrapper.mjs"() {
|
|
30573
|
+
"use strict";
|
|
30574
|
+
import_stream = __toESM(require_stream(), 1);
|
|
30575
|
+
import_extension = __toESM(require_extension(), 1);
|
|
30576
|
+
import_permessage_deflate = __toESM(require_permessage_deflate(), 1);
|
|
30577
|
+
import_receiver = __toESM(require_receiver(), 1);
|
|
30578
|
+
import_sender = __toESM(require_sender(), 1);
|
|
30579
|
+
import_subprotocol = __toESM(require_subprotocol(), 1);
|
|
30580
|
+
import_websocket = __toESM(require_websocket(), 1);
|
|
30581
|
+
import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
30582
|
+
wrapper_default = import_websocket.default;
|
|
30583
|
+
}
|
|
30584
|
+
});
|
|
30585
|
+
|
|
30394
30586
|
// ../node_modules/.pnpm/process-nextick-args@2.0.1/node_modules/process-nextick-args/index.js
|
|
30395
30587
|
var require_process_nextick_args = __commonJS({
|
|
30396
30588
|
"../node_modules/.pnpm/process-nextick-args@2.0.1/node_modules/process-nextick-args/index.js"(exports2, module2) {
|
|
@@ -33268,8 +33460,8 @@ var require_utils = __commonJS({
|
|
|
33268
33460
|
var result = transform[inputType][outputType](input);
|
|
33269
33461
|
return result;
|
|
33270
33462
|
};
|
|
33271
|
-
exports2.resolve = function(
|
|
33272
|
-
var parts =
|
|
33463
|
+
exports2.resolve = function(path76) {
|
|
33464
|
+
var parts = path76.split("/");
|
|
33273
33465
|
var result = [];
|
|
33274
33466
|
for (var index = 0; index < parts.length; index++) {
|
|
33275
33467
|
var part = parts[index];
|
|
@@ -39122,18 +39314,18 @@ var require_object = __commonJS({
|
|
|
39122
39314
|
var object = new ZipObject(name, zipObjectContent, o);
|
|
39123
39315
|
this.files[name] = object;
|
|
39124
39316
|
};
|
|
39125
|
-
var parentFolder = function(
|
|
39126
|
-
if (
|
|
39127
|
-
|
|
39317
|
+
var parentFolder = function(path76) {
|
|
39318
|
+
if (path76.slice(-1) === "/") {
|
|
39319
|
+
path76 = path76.substring(0, path76.length - 1);
|
|
39128
39320
|
}
|
|
39129
|
-
var lastSlash =
|
|
39130
|
-
return lastSlash > 0 ?
|
|
39321
|
+
var lastSlash = path76.lastIndexOf("/");
|
|
39322
|
+
return lastSlash > 0 ? path76.substring(0, lastSlash) : "";
|
|
39131
39323
|
};
|
|
39132
|
-
var forceTrailingSlash = function(
|
|
39133
|
-
if (
|
|
39134
|
-
|
|
39324
|
+
var forceTrailingSlash = function(path76) {
|
|
39325
|
+
if (path76.slice(-1) !== "/") {
|
|
39326
|
+
path76 += "/";
|
|
39135
39327
|
}
|
|
39136
|
-
return
|
|
39328
|
+
return path76;
|
|
39137
39329
|
};
|
|
39138
39330
|
var folderAdd = function(name, createFolders) {
|
|
39139
39331
|
createFolders = typeof createFolders !== "undefined" ? createFolders : defaults.createFolders;
|
|
@@ -40135,7 +40327,7 @@ var require_lib3 = __commonJS({
|
|
|
40135
40327
|
// src/run-case/recorder.ts
|
|
40136
40328
|
function startRunCaseRecorder(opts) {
|
|
40137
40329
|
const now = opts.now ?? Date.now;
|
|
40138
|
-
const dir =
|
|
40330
|
+
const dir = import_node_path63.default.dirname(opts.recordPath);
|
|
40139
40331
|
let stream = null;
|
|
40140
40332
|
let closing = false;
|
|
40141
40333
|
let closedSettled = false;
|
|
@@ -40149,8 +40341,8 @@ function startRunCaseRecorder(opts) {
|
|
|
40149
40341
|
});
|
|
40150
40342
|
const ensureStream = () => {
|
|
40151
40343
|
if (stream) return stream;
|
|
40152
|
-
|
|
40153
|
-
stream =
|
|
40344
|
+
import_node_fs50.default.mkdirSync(dir, { recursive: true });
|
|
40345
|
+
stream = import_node_fs50.default.createWriteStream(opts.recordPath, { flags: "a" });
|
|
40154
40346
|
stream.on("close", () => closedResolve());
|
|
40155
40347
|
return stream;
|
|
40156
40348
|
};
|
|
@@ -40175,12 +40367,12 @@ function startRunCaseRecorder(opts) {
|
|
|
40175
40367
|
};
|
|
40176
40368
|
return { tap, close, closed };
|
|
40177
40369
|
}
|
|
40178
|
-
var
|
|
40370
|
+
var import_node_fs50, import_node_path63;
|
|
40179
40371
|
var init_recorder = __esm({
|
|
40180
40372
|
"src/run-case/recorder.ts"() {
|
|
40181
40373
|
"use strict";
|
|
40182
|
-
|
|
40183
|
-
|
|
40374
|
+
import_node_fs50 = __toESM(require("fs"), 1);
|
|
40375
|
+
import_node_path63 = __toESM(require("path"), 1);
|
|
40184
40376
|
}
|
|
40185
40377
|
});
|
|
40186
40378
|
|
|
@@ -40223,7 +40415,7 @@ var init_wire = __esm({
|
|
|
40223
40415
|
// src/run-case/controller.ts
|
|
40224
40416
|
async function runController(opts) {
|
|
40225
40417
|
const now = opts.now ?? Date.now;
|
|
40226
|
-
const cwd = opts.cwd ?? (0,
|
|
40418
|
+
const cwd = opts.cwd ?? (0, import_node_fs51.mkdtempSync)(import_node_path64.default.join(import_node_os22.default.tmpdir(), "clawd-runcase-"));
|
|
40227
40419
|
const ownsCwd = opts.cwd === void 0;
|
|
40228
40420
|
const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
|
|
40229
40421
|
const spawnCtx = { cwd };
|
|
@@ -40384,19 +40576,19 @@ async function runController(opts) {
|
|
|
40384
40576
|
if (sigintHandler) process.off("SIGINT", sigintHandler);
|
|
40385
40577
|
if (ownsCwd) {
|
|
40386
40578
|
try {
|
|
40387
|
-
(0,
|
|
40579
|
+
(0, import_node_fs51.rmSync)(cwd, { recursive: true, force: true });
|
|
40388
40580
|
} catch {
|
|
40389
40581
|
}
|
|
40390
40582
|
}
|
|
40391
40583
|
return exitCode ?? 0;
|
|
40392
40584
|
}
|
|
40393
|
-
var
|
|
40585
|
+
var import_node_fs51, import_node_os22, import_node_path64;
|
|
40394
40586
|
var init_controller = __esm({
|
|
40395
40587
|
"src/run-case/controller.ts"() {
|
|
40396
40588
|
"use strict";
|
|
40397
|
-
|
|
40589
|
+
import_node_fs51 = require("fs");
|
|
40398
40590
|
import_node_os22 = __toESM(require("os"), 1);
|
|
40399
|
-
|
|
40591
|
+
import_node_path64 = __toESM(require("path"), 1);
|
|
40400
40592
|
init_claude();
|
|
40401
40593
|
init_stdout_splitter();
|
|
40402
40594
|
init_permission_stdio();
|
|
@@ -40492,6 +40684,163 @@ stdout \u4E8B\u4EF6\uFF08\u884C JSON\uFF09\uFF1A
|
|
|
40492
40684
|
}
|
|
40493
40685
|
});
|
|
40494
40686
|
|
|
40687
|
+
// src/sshd/sshd-cli-relay.ts
|
|
40688
|
+
var sshd_cli_relay_exports = {};
|
|
40689
|
+
__export(sshd_cli_relay_exports, {
|
|
40690
|
+
sshRelay: () => sshRelay
|
|
40691
|
+
});
|
|
40692
|
+
async function sshRelay(argv) {
|
|
40693
|
+
const args = parseSshRelayArgs(argv);
|
|
40694
|
+
if (args.help) {
|
|
40695
|
+
process.stdout.write(SSH_RELAY_HELP);
|
|
40696
|
+
return 0;
|
|
40697
|
+
}
|
|
40698
|
+
if (!args.peerDeviceId) {
|
|
40699
|
+
process.stderr.write("clawd ssh-relay: missing <peer-device-id>\n" + SSH_RELAY_HELP);
|
|
40700
|
+
return 2;
|
|
40701
|
+
}
|
|
40702
|
+
const dataDir = args.dataDir ?? import_node_path65.default.join(import_node_os23.default.homedir(), ".clawd");
|
|
40703
|
+
const contact = findContact(dataDir, args.peerDeviceId);
|
|
40704
|
+
if (!contact) {
|
|
40705
|
+
process.stderr.write(`clawd ssh-relay: contact ${args.peerDeviceId} not found in ${dataDir}/contacts.json
|
|
40706
|
+
`);
|
|
40707
|
+
return 2;
|
|
40708
|
+
}
|
|
40709
|
+
if (!contact.connectToken) {
|
|
40710
|
+
process.stderr.write(
|
|
40711
|
+
`clawd ssh-relay: contact ${args.peerDeviceId} has no connectToken (auto-reverse \u672A\u6362\u7968)
|
|
40712
|
+
`
|
|
40713
|
+
);
|
|
40714
|
+
return 2;
|
|
40715
|
+
}
|
|
40716
|
+
const baseHttp = wsUrlToHttp(contact.remoteUrl).replace(/\/+$/, "");
|
|
40717
|
+
const wsBase = baseHttp.replace(/^http:/, "ws:").replace(/^https:/, "wss:");
|
|
40718
|
+
const url = `${wsBase}/rpc/ssh-tunnel`;
|
|
40719
|
+
return new Promise((resolve6) => {
|
|
40720
|
+
const ws = new import_websocket.default(url, {
|
|
40721
|
+
headers: {
|
|
40722
|
+
Authorization: `Bearer ${contact.connectToken}`
|
|
40723
|
+
}
|
|
40724
|
+
});
|
|
40725
|
+
let exitCode = 0;
|
|
40726
|
+
let settled = false;
|
|
40727
|
+
const settle = (code) => {
|
|
40728
|
+
if (settled) return;
|
|
40729
|
+
settled = true;
|
|
40730
|
+
exitCode = code;
|
|
40731
|
+
try {
|
|
40732
|
+
ws.close();
|
|
40733
|
+
} catch {
|
|
40734
|
+
}
|
|
40735
|
+
resolve6(exitCode);
|
|
40736
|
+
};
|
|
40737
|
+
ws.on("open", () => {
|
|
40738
|
+
process.stdin.on("data", (chunk) => {
|
|
40739
|
+
if (ws.readyState === ws.OPEN) {
|
|
40740
|
+
ws.send(chunk, { binary: true });
|
|
40741
|
+
}
|
|
40742
|
+
});
|
|
40743
|
+
process.stdin.on("end", () => {
|
|
40744
|
+
try {
|
|
40745
|
+
ws.close(1e3, "stdin end");
|
|
40746
|
+
} catch {
|
|
40747
|
+
}
|
|
40748
|
+
});
|
|
40749
|
+
process.stdin.on("error", () => settle(1));
|
|
40750
|
+
});
|
|
40751
|
+
ws.on("message", (data, isBinary) => {
|
|
40752
|
+
void isBinary;
|
|
40753
|
+
if (Buffer.isBuffer(data)) {
|
|
40754
|
+
process.stdout.write(data);
|
|
40755
|
+
} else if (Array.isArray(data)) {
|
|
40756
|
+
process.stdout.write(Buffer.concat(data));
|
|
40757
|
+
} else if (data instanceof ArrayBuffer) {
|
|
40758
|
+
process.stdout.write(Buffer.from(data));
|
|
40759
|
+
}
|
|
40760
|
+
});
|
|
40761
|
+
ws.on("close", (code) => {
|
|
40762
|
+
settle(code === 1e3 ? 0 : 1);
|
|
40763
|
+
});
|
|
40764
|
+
ws.on("error", (err) => {
|
|
40765
|
+
process.stderr.write(`clawd ssh-relay: ws error ${err.message}
|
|
40766
|
+
`);
|
|
40767
|
+
settle(1);
|
|
40768
|
+
});
|
|
40769
|
+
const onSignal = () => settle(0);
|
|
40770
|
+
process.once("SIGINT", onSignal);
|
|
40771
|
+
process.once("SIGTERM", onSignal);
|
|
40772
|
+
});
|
|
40773
|
+
}
|
|
40774
|
+
function parseSshRelayArgs(argv) {
|
|
40775
|
+
const out = {};
|
|
40776
|
+
for (let i = 0; i < argv.length; i++) {
|
|
40777
|
+
const a = argv[i];
|
|
40778
|
+
if (a === "--help" || a === "-h") {
|
|
40779
|
+
out.help = true;
|
|
40780
|
+
continue;
|
|
40781
|
+
}
|
|
40782
|
+
if (a === "--data-dir") {
|
|
40783
|
+
out.dataDir = argv[++i];
|
|
40784
|
+
continue;
|
|
40785
|
+
}
|
|
40786
|
+
if (a.startsWith("--")) {
|
|
40787
|
+
throw new Error(`unknown flag: ${a}`);
|
|
40788
|
+
}
|
|
40789
|
+
if (!out.peerDeviceId) {
|
|
40790
|
+
out.peerDeviceId = a;
|
|
40791
|
+
}
|
|
40792
|
+
}
|
|
40793
|
+
return out;
|
|
40794
|
+
}
|
|
40795
|
+
function findContact(dataDir, deviceId) {
|
|
40796
|
+
const file = import_node_path65.default.join(dataDir, "contacts.json");
|
|
40797
|
+
let raw;
|
|
40798
|
+
try {
|
|
40799
|
+
raw = import_node_fs52.default.readFileSync(file, "utf8");
|
|
40800
|
+
} catch {
|
|
40801
|
+
return null;
|
|
40802
|
+
}
|
|
40803
|
+
let json;
|
|
40804
|
+
try {
|
|
40805
|
+
json = JSON.parse(raw);
|
|
40806
|
+
} catch {
|
|
40807
|
+
return null;
|
|
40808
|
+
}
|
|
40809
|
+
const arr = json?.contacts;
|
|
40810
|
+
if (!Array.isArray(arr)) return null;
|
|
40811
|
+
for (const entry of arr) {
|
|
40812
|
+
const r = ContactSchema.safeParse(entry);
|
|
40813
|
+
if (r.success && r.data.deviceId === deviceId) return r.data;
|
|
40814
|
+
}
|
|
40815
|
+
return null;
|
|
40816
|
+
}
|
|
40817
|
+
var import_node_fs52, import_node_os23, import_node_path65, SSH_RELAY_HELP;
|
|
40818
|
+
var init_sshd_cli_relay = __esm({
|
|
40819
|
+
"src/sshd/sshd-cli-relay.ts"() {
|
|
40820
|
+
"use strict";
|
|
40821
|
+
import_node_fs52 = __toESM(require("fs"), 1);
|
|
40822
|
+
import_node_os23 = __toESM(require("os"), 1);
|
|
40823
|
+
import_node_path65 = __toESM(require("path"), 1);
|
|
40824
|
+
init_wrapper();
|
|
40825
|
+
init_src();
|
|
40826
|
+
init_peer_forward();
|
|
40827
|
+
SSH_RELAY_HELP = `clawd ssh-relay <peer-device-id> [options]
|
|
40828
|
+
|
|
40829
|
+
WebSocket relay to a peer daemon's /rpc/ssh-tunnel, exposing raw SSH bytes on
|
|
40830
|
+
stdio. Meant to be used as SSH ProxyCommand.
|
|
40831
|
+
|
|
40832
|
+
Options:
|
|
40833
|
+
--data-dir <path> \u6570\u636E\u76EE\u5F55\uFF08\u9ED8\u8BA4 ~/.clawd\uFF09
|
|
40834
|
+
--help / -h \u663E\u793A\u5E2E\u52A9
|
|
40835
|
+
|
|
40836
|
+
Example:
|
|
40837
|
+
ssh -o ProxyCommand='clawd ssh-relay <peer-device-id>' \\
|
|
40838
|
+
-i ~/.clawd/contact-ssh-keys/<peer-device-id>.ed25519 \\
|
|
40839
|
+
$USER@127.0.0.1
|
|
40840
|
+
`;
|
|
40841
|
+
}
|
|
40842
|
+
});
|
|
40843
|
+
|
|
40495
40844
|
// src/config.ts
|
|
40496
40845
|
var import_node_fs = __toESM(require("fs"), 1);
|
|
40497
40846
|
var import_node_os = __toESM(require("os"), 1);
|
|
@@ -40499,6 +40848,7 @@ var import_node_path = __toESM(require("path"), 1);
|
|
|
40499
40848
|
init_protocol();
|
|
40500
40849
|
var DEFAULT_PORT = 18790;
|
|
40501
40850
|
var DEFAULT_HOST = "127.0.0.1";
|
|
40851
|
+
var DEFAULT_SSHD_PORT = 22422;
|
|
40502
40852
|
var DEFAULT_CLAWOS_API = "https://api.clawos.chat";
|
|
40503
40853
|
var DEFAULT_LOG_ENDPOINT = "https://clawd-prod.cn-hangzhou.log.aliyuncs.com/logstores/app-logs/track";
|
|
40504
40854
|
function resolveLogShipping(raw, cliNoShipping) {
|
|
@@ -40566,6 +40916,14 @@ function parseArgs(argv) {
|
|
|
40566
40916
|
case "--no-log-shipping":
|
|
40567
40917
|
out.noLogShipping = true;
|
|
40568
40918
|
break;
|
|
40919
|
+
case "--sshd-port": {
|
|
40920
|
+
const n = Number.parseInt(next() ?? "", 10);
|
|
40921
|
+
if (!Number.isFinite(n) || n <= 0 || n > 65535) {
|
|
40922
|
+
throw new Error(`invalid --sshd-port value: ${argv[i]}`);
|
|
40923
|
+
}
|
|
40924
|
+
out.sshdPort = n;
|
|
40925
|
+
break;
|
|
40926
|
+
}
|
|
40569
40927
|
default:
|
|
40570
40928
|
if (a.startsWith("--")) throw new Error(`unknown flag: ${a}`);
|
|
40571
40929
|
break;
|
|
@@ -40615,6 +40973,7 @@ function resolveConfig(opts) {
|
|
|
40615
40973
|
fileCfg.logShipping,
|
|
40616
40974
|
args.noLogShipping ?? false
|
|
40617
40975
|
);
|
|
40976
|
+
const sshdPort = args.sshdPort ?? (typeof fileCfg.sshdPort === "number" ? fileCfg.sshdPort : DEFAULT_SSHD_PORT);
|
|
40618
40977
|
return {
|
|
40619
40978
|
port,
|
|
40620
40979
|
host,
|
|
@@ -40628,7 +40987,8 @@ function resolveConfig(opts) {
|
|
|
40628
40987
|
frpcBinary,
|
|
40629
40988
|
mode,
|
|
40630
40989
|
previewPorts,
|
|
40631
|
-
logShipping
|
|
40990
|
+
logShipping,
|
|
40991
|
+
sshdPort
|
|
40632
40992
|
};
|
|
40633
40993
|
}
|
|
40634
40994
|
var HELP_TEXT = `clawd [options]
|
|
@@ -40640,6 +41000,7 @@ var HELP_TEXT = `clawd [options]
|
|
|
40640
41000
|
--clawos-api <url> tunnel register \u63A5\u53E3\u7684 base url\uFF08\u9ED8\u8BA4 https://api.clawos.chat\uFF09
|
|
40641
41001
|
--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
41002
|
--no-log-shipping \u7981\u7528 SLS \u65E5\u5FD7\u4E0A\u884C\uFF08\u8986\u76D6 config.json logShipping.mode=off\uFF09
|
|
41003
|
+
--sshd-port <n> Contact SSH \u53CD\u5411\u8BBF\u95EE loopback \u7AEF\u53E3\uFF0C\u9ED8\u8BA4 22422
|
|
40643
41004
|
--help / -h \u663E\u793A\u5E2E\u52A9
|
|
40644
41005
|
--version / -v \u663E\u793A\u7248\u672C
|
|
40645
41006
|
|
|
@@ -40648,6 +41009,10 @@ Subcommands:
|
|
|
40648
41009
|
\u72EC\u7ACB CC \u63A7\u5236\u5668\u8FDB\u7A0B\uFF1A\u5916\u90E8 AI \u901A\u8FC7 stdin/stdout \u884C JSON \u534F\u8BAE
|
|
40649
41010
|
\u9A71\u52A8 CC \u591A\u8F6E\u5BF9\u8BDD\u4E0E\u6743\u9650\u51B3\u7B56\uFF0CIPC \u5168\u7A0B\u5F55\u5230 --record JSONL\u3002
|
|
40650
41011
|
\u8BE6\u89C1 'clawd run-case --help'
|
|
41012
|
+
ssh-relay <peer-device-id> [--data-dir <path>]
|
|
41013
|
+
WebSocket relay to peer daemon's /rpc/ssh-tunnel\uFF0C\u4F5C\u4E3A SSH
|
|
41014
|
+
ProxyCommand \u4F7F\u7528\uFF08Contact SSH tunnel\uFF0CPR#2\uFF09\u3002
|
|
41015
|
+
\u8BE6\u89C1 'clawd ssh-relay --help'
|
|
40651
41016
|
|
|
40652
41017
|
Env (advanced):
|
|
40653
41018
|
CLAWD_FRPC_BIN \u81EA\u5E26 frpc \u4E8C\u8FDB\u5236\u8DEF\u5F84\uFF08\u9ED8\u8BA4\u6309\u9700\u4E0B\u8F7D\u5230 ~/.clawd/bin/frpc\uFF09
|
|
@@ -40658,8 +41023,8 @@ Env (advanced):
|
|
|
40658
41023
|
`;
|
|
40659
41024
|
|
|
40660
41025
|
// src/index.ts
|
|
40661
|
-
var
|
|
40662
|
-
var
|
|
41026
|
+
var import_node_path62 = __toESM(require("path"), 1);
|
|
41027
|
+
var import_node_fs49 = __toESM(require("fs"), 1);
|
|
40663
41028
|
var import_node_os21 = __toESM(require("os"), 1);
|
|
40664
41029
|
|
|
40665
41030
|
// ../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/stringify.js
|
|
@@ -40779,18 +41144,6 @@ function createLogger(opts = {}) {
|
|
|
40779
41144
|
);
|
|
40780
41145
|
return wrap(base);
|
|
40781
41146
|
}
|
|
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
41147
|
function pinoLevelToString(n) {
|
|
40795
41148
|
if (typeof n !== "number") return null;
|
|
40796
41149
|
if (n >= 50) return "error";
|
|
@@ -41370,6 +41723,7 @@ function composeGuestSandbox(base, userWorkDir, spawnCwd) {
|
|
|
41370
41723
|
fsv.denyWrite = unionArr((fsv.denyWrite ?? []).filter((p2) => p2 !== "~/"), [spawnCwd]);
|
|
41371
41724
|
fsv.allowRead = unionArr(fsv.allowRead, [userWorkDir]);
|
|
41372
41725
|
fsv.allowWrite = unionArr(fsv.allowWrite, [userWorkDir]);
|
|
41726
|
+
fsv.allowRead = unionArr(fsv.allowRead, ["~/.clawd/contact-ssh-keys"]);
|
|
41373
41727
|
return s;
|
|
41374
41728
|
}
|
|
41375
41729
|
|
|
@@ -41510,6 +41864,31 @@ deviceId \u76F4\u63A5\u53D6\u81EA token\uFF0C\u4E0D\u8981\u6539\u5199\u6216\u731
|
|
|
41510
41864
|
- \u5931\u8D25\uFF1A\`{ isFailure: true, reason: "..." }\`
|
|
41511
41865
|
- \u62A5\u544A\u5B8C\u540E\u4F60\u7684 session \u5C31\u7ED3\u675F\u4E86\uFF0C\u4E0D\u8981\u7EE7\u7EED\u8F93\u51FA`;
|
|
41512
41866
|
|
|
41867
|
+
// src/sshd/contact-ssh-prompt.ts
|
|
41868
|
+
var CONTACT_SSH_SYSTEM_PROMPT_HINT = `## \u8DE8\u8BBE\u5907\u6587\u4EF6\u8BBF\u95EE\uFF08Contact SSH\uFF09
|
|
41869
|
+
|
|
41870
|
+
\u7528\u6237\u53EF\u80FD\u6709\u591A\u53F0 clawd \u8BBE\u5907\u4E92\u76F8\u6388\u6743\u3002\u4F60\u5728\u8FD9\u53F0\u673A\u5668\u4E0A\u8BBF\u95EE\u5DF2\u6388\u6743\u5BF9\u7AEF\uFF08\u4E0B\u79F0 A\uFF09\u7684\u65B9\u6CD5\uFF1A
|
|
41871
|
+
|
|
41872
|
+
1. \u5217\u51FA\u53EF\u62E8\u7684\u5BF9\u7AEF deviceId\uFF1A\`ls ~/.clawd/contact-ssh-keys/\`
|
|
41873
|
+
\uFF08\u6BCF\u4E2A \`<deviceId>.ed25519\` \u5C31\u662F\u4E00\u628A privkey\uFF0C\u53BB\u6389\u540E\u7F00\u5C31\u662F deviceId\uFF09
|
|
41874
|
+
2. \u7528 SSH \u62E8\u53F7\uFF08\u628A \`<A>\` \u6362\u6210\u5B9E\u9645 deviceId\uFF09\uFF1A
|
|
41875
|
+
\`\`\`bash
|
|
41876
|
+
ssh -o ProxyCommand='clawd ssh-relay <A>' \\
|
|
41877
|
+
-i ~/.clawd/contact-ssh-keys/<A>.ed25519 \\
|
|
41878
|
+
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \\
|
|
41879
|
+
$USER@127.0.0.1 <command>
|
|
41880
|
+
# \u4F8B\u5982\uFF1A... $USER@127.0.0.1 cat /Users/xxx/some/file.md
|
|
41881
|
+
# ... $USER@127.0.0.1 ls -la /Users/xxx/proj
|
|
41882
|
+
\`\`\`
|
|
41883
|
+
|
|
41884
|
+
**A \u6388\u6743\u54EA\u4E9B\u76EE\u5F55\u4E0D\u7528\u4F60\u63D0\u524D\u8BB0**\uFF1AA \u4FA7\u7684 sshd \u62E8\u53F7\u4F1A\u5F3A\u5236\u8D70 sandbox jail\uFF0C\u8FDB\u6C99\u7BB1\u524D\u4F1A\u628A\u6388\u6743\u76EE\u5F55\u5217\u8868\u5199\u5230 stderr\uFF08\u683C\u5F0F \`[clawd-ssh-jail] Access allowed to these directories: ...\`\uFF09\u3002\u4F60\u5373\u4F7F\u731C\u9519\u8DEF\u5F84\u4E5F\u4F1A\u88AB sandbox deny\uFF0C\u4F46 stderr \u91CC\u5C31\u81EA\u5E26\u7B54\u6848\uFF0C\u8BD5\u9519\u4E00\u6B21\u5C31\u5B66\u5230 A \u6388\u6743\u4E86\u54EA\u4E9B\u76EE\u5F55\uFF0C\u4E0B\u6B21\u76F4\u63A5\u7528\u5BF9\u7684\u8DEF\u5F84\u3002
|
|
41885
|
+
|
|
41886
|
+
**\u6CE8\u610F**\uFF1A
|
|
41887
|
+
- \u6388\u6743\u76EE\u5F55\u6570\u636E\u6E90\u5B9E\u65F6\uFF08jail \u6BCF\u6B21\u8BFB A \u4FA7\u6700\u65B0\u914D\u7F6E\uFF09\uFF0CA \u6539\u4E86\u6388\u6743\u540E\u4F60\u4E0B\u4E00\u6B21\u62E8\u53F7\u7ACB\u5373\u611F\u77E5
|
|
41888
|
+
- \u6392\u969C\uFF1A\u672C\u673A debug \u65E5\u5FD7\u5728 \`~/.clawd/contact-ssh.log\`\uFF08\u7528\u6237\u53EF \`tail -f\` \u770B\u6BCF\u4E00\u6B65\uFF09\uFF0C\u4F60\u51FA\u9519\u65F6\u544A\u8BC9\u7528\u6237\u67E5\u8FD9\u4E2A\u6587\u4EF6
|
|
41889
|
+
- \u5982\u679C \`ls ~/.clawd/contact-ssh-keys/\` \u4E3A\u7A7A\uFF0C\u8BF4\u660E\u8FD8\u6CA1\u6709\u5BF9\u7AEF\u6388\u6743\u4F60\u6216 key \u8FD8\u6CA1\u62C9\u5230\uFF08B \u4FA7 daemon \u6BCF 60s \u4E00\u6B21\u81EA\u52A8\u62C9\uFF09\uFF0C\u5982\u5B9E\u544A\u8BC9\u7528\u6237
|
|
41890
|
+
`;
|
|
41891
|
+
|
|
41513
41892
|
// src/session/reducer.ts
|
|
41514
41893
|
function cloneState(s) {
|
|
41515
41894
|
return {
|
|
@@ -41646,6 +42025,9 @@ function buildSpawnContext(state, deps) {
|
|
|
41646
42025
|
if (daemonUrl) {
|
|
41647
42026
|
ctx.extraSystemPrompt = (ctx.extraSystemPrompt ? ctx.extraSystemPrompt + "\n\n" : "") + ATTACHMENT_SHARING_HINT;
|
|
41648
42027
|
}
|
|
42028
|
+
if (meta?.personaMode === "guest") {
|
|
42029
|
+
ctx.extraSystemPrompt = (ctx.extraSystemPrompt ? ctx.extraSystemPrompt + "\n\n" : "") + CONTACT_SSH_SYSTEM_PROMPT_HINT;
|
|
42030
|
+
}
|
|
41649
42031
|
if (meta?.extraSettings) {
|
|
41650
42032
|
ctx.extraSettings = meta.extraSettings;
|
|
41651
42033
|
}
|
|
@@ -42956,18 +43338,9 @@ var SessionManager = class {
|
|
|
42956
43338
|
// 由 observer 监听 jsonl user 行后调 recordRealUserUuid 建立映射;rewind 系列 RPC 在
|
|
42957
43339
|
// 入参 / 出参做转译,保证 UI 看到的 uuid 始终是 events 流里的 synth uuid
|
|
42958
43340
|
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();
|
|
43341
|
+
// observeScreenIdle 复合条件闸用:sessionId → observer 上次喂出业务事件的时刻(deps.now())。
|
|
43342
|
+
// observerIdleWaitMs 据此判 observer 是否也静止 ≥ idleMs(屏幕静止 AND observer 静止才补 turn_end)。
|
|
43343
|
+
lastObserverEventAt = /* @__PURE__ */ new Map();
|
|
42971
43344
|
// SessionStore 按 scope 派生(root = <dataDir>/sessions/<scopeSubPath>/)。
|
|
42972
43345
|
// default scope 直接复用 deps.store;persona scope(owner / listener)第一次访问时按需创建并缓存。
|
|
42973
43346
|
// 取代旧的 storesByAgent —— agentId 概念由 SessionScope 取代,路径即身份,
|
|
@@ -43307,14 +43680,6 @@ var SessionManager = class {
|
|
|
43307
43680
|
routeFromRunner(frame, target) {
|
|
43308
43681
|
const compressed = compressFrameForWire(frame);
|
|
43309
43682
|
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
43683
|
if (compressed.type === "session:event" || compressed.type === "session:status") {
|
|
43319
43684
|
const sid = compressed.sessionId;
|
|
43320
43685
|
if (sid) {
|
|
@@ -43588,7 +43953,7 @@ var SessionManager = class {
|
|
|
43588
43953
|
this.runners.delete(args.sessionId);
|
|
43589
43954
|
this.realUuidBySynth.delete(args.sessionId);
|
|
43590
43955
|
this.lastUiSizeBySessionId.delete(args.sessionId);
|
|
43591
|
-
this.
|
|
43956
|
+
this.lastObserverEventAt.delete(args.sessionId);
|
|
43592
43957
|
return { response: { sessionId: args.sessionId }, broadcast };
|
|
43593
43958
|
}
|
|
43594
43959
|
this.deleteOwned(args.sessionId);
|
|
@@ -43618,7 +43983,6 @@ var SessionManager = class {
|
|
|
43618
43983
|
async stop(args) {
|
|
43619
43984
|
const runner = this.runners.get(args.sessionId);
|
|
43620
43985
|
if (!runner) return { response: { ok: true }, broadcast: [] };
|
|
43621
|
-
this.clearPendingTurnEnd(args.sessionId);
|
|
43622
43986
|
const { broadcast } = this.withCollector(() => {
|
|
43623
43987
|
runner.input({ kind: "command", command: { kind: "stop" } });
|
|
43624
43988
|
});
|
|
@@ -43752,7 +44116,6 @@ var SessionManager = class {
|
|
|
43752
44116
|
newSession(args) {
|
|
43753
44117
|
const existingFile = this.getFile(args.sessionId);
|
|
43754
44118
|
const nextToolSessionId = this.deps.mode === "tui" && (existingFile.tool ?? "claude") === "claude" ? v4_default() : void 0;
|
|
43755
|
-
this.clearPendingTurnEnd(args.sessionId);
|
|
43756
44119
|
const runner = this.runners.get(args.sessionId);
|
|
43757
44120
|
if (runner) {
|
|
43758
44121
|
const { value, broadcast } = this.withCollector(() => {
|
|
@@ -43853,7 +44216,6 @@ var SessionManager = class {
|
|
|
43853
44216
|
for (const r of this.runners.values()) {
|
|
43854
44217
|
r.input({ kind: "command", command: { kind: "stop" } });
|
|
43855
44218
|
}
|
|
43856
|
-
this.pendingTurnDurationSignals.clear();
|
|
43857
44219
|
}
|
|
43858
44220
|
// 给 observer 用:拿已存在的 runner
|
|
43859
44221
|
getActive(sessionId) {
|
|
@@ -44070,7 +44432,7 @@ var SessionManager = class {
|
|
|
44070
44432
|
this.runners.delete(args.sessionId);
|
|
44071
44433
|
this.realUuidBySynth.delete(args.sessionId);
|
|
44072
44434
|
this.lastUiSizeBySessionId.delete(args.sessionId);
|
|
44073
|
-
this.
|
|
44435
|
+
this.lastObserverEventAt.delete(args.sessionId);
|
|
44074
44436
|
return { response: { sessionId: args.sessionId }, broadcast };
|
|
44075
44437
|
}
|
|
44076
44438
|
this.storeFor(args.scope).delete(args.sessionId);
|
|
@@ -44316,93 +44678,23 @@ var SessionManager = class {
|
|
|
44316
44678
|
return;
|
|
44317
44679
|
}
|
|
44318
44680
|
}
|
|
44681
|
+
this.lastObserverEventAt.set(sessionId, (this.deps.now ?? Date.now)());
|
|
44319
44682
|
let feedEvents = outEvents;
|
|
44320
44683
|
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 {
|
|
44684
|
+
const ev = this.peekTurnEvidence(runner);
|
|
44685
|
+
if (!ev.turnHasContent) {
|
|
44338
44686
|
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
|
-
}
|
|
44687
|
+
this.deps.logger?.info("[TE-PROBE] drop spurious observer turn_end", {
|
|
44688
|
+
sessionId,
|
|
44689
|
+
src: "observer",
|
|
44690
|
+
...ev,
|
|
44691
|
+
batchKinds: outEvents.map((e) => e.kind)
|
|
44692
|
+
});
|
|
44351
44693
|
}
|
|
44352
44694
|
}
|
|
44353
44695
|
if (feedEvents.length === 0) return;
|
|
44354
44696
|
runner.feedObserverEvents(feedEvents);
|
|
44355
44697
|
}
|
|
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
44698
|
// AskUserQuestion 表单回写(plan: clawd-ask-user-question):UI 答完所有 question 后调用。
|
|
44407
44699
|
// - session 不存在 / 无 runner → noop 幂等返回 ok(first-decider-wins)
|
|
44408
44700
|
// - reducer noop(toolUseId 不存在或已答过)也保持幂等返回,handler 不抛
|
|
@@ -44661,6 +44953,70 @@ var SessionManager = class {
|
|
|
44661
44953
|
if (!runner) return;
|
|
44662
44954
|
runner.input({ kind: "ready-detected" });
|
|
44663
44955
|
}
|
|
44956
|
+
/**
|
|
44957
|
+
* ClaudeTuiAdapter onTurnIdle callback:屏幕内容静止时**复发**本轮已出现过的权威 turn_end。
|
|
44958
|
+
* 本意:turn_duration 写盘早于尾段正文 → observer 把尾随 text 推进 buffer 盖掉 lastEventKind →
|
|
44959
|
+
* spinner 不熄;屏幕静止时再补一条 turn_end 排到尾随 text 之后。
|
|
44960
|
+
*
|
|
44961
|
+
* Fix A(修 bug1 "UI 还在变却显示结束" / bug2 "发消息后无 spinner"):补偿**只复发不 originate**——
|
|
44962
|
+
* 仅当本轮已出现过 turn_end(turnEndSeenThisTurn,即真有过尾随 text 覆盖场景)才补。turnEndSeenThisTurn
|
|
44963
|
+
* ===false 说明本轮 CC 从未结束过(仍在工作 / 刚发消息没产出 / 漏检弹框等用户),屏幕静止 ≠ turn 结束,
|
|
44964
|
+
* 凭空 inject turn_end 会误灭 spinner——故跳过,让真正的 turn_duration 来时再正常收。
|
|
44965
|
+
* 仅 tui 模式;runner 缺失 noop。turn_end 无 uuid 不参与 dedup。
|
|
44966
|
+
*/
|
|
44967
|
+
dispatchTurnIdle(toolSessionId) {
|
|
44968
|
+
if (this.deps.mode !== "tui") return;
|
|
44969
|
+
const sid = this.sessionIdByToolSid(toolSessionId);
|
|
44970
|
+
const runner = sid ? this.runners.get(sid) : void 0;
|
|
44971
|
+
if (!runner) return;
|
|
44972
|
+
const ev = this.peekTurnEvidence(runner);
|
|
44973
|
+
const willInject = ev.turnEndSeenThisTurn;
|
|
44974
|
+
this.deps.logger?.info("[TE-PROBE] screen-idle compensation", {
|
|
44975
|
+
sessionId: sid,
|
|
44976
|
+
src: "screen-idle",
|
|
44977
|
+
willInject,
|
|
44978
|
+
...ev
|
|
44979
|
+
});
|
|
44980
|
+
if (!willInject) return;
|
|
44981
|
+
runner.input({ kind: "inject-events", events: [{ kind: "turn_end" }] });
|
|
44982
|
+
}
|
|
44983
|
+
/**
|
|
44984
|
+
* 读 runner 当前 turn 态快照,供两个 turn_end 注入源(屏幕静止补偿 / observer turn_duration)
|
|
44985
|
+
* 判断误发 + 打 [TE-PROBE] 日志。
|
|
44986
|
+
* turnEndSeenThisTurn:从 buffer 末尾回扫到最近 user_text 期间是否已出现过 turn_end
|
|
44987
|
+
* (已出现=本轮真结束过、合法尾随;未出现=本轮还没结束过)→ Fix A 复发闸。
|
|
44988
|
+
* turnHasContent:末条是否 assistant 产出(非 user_text/turn_end/空)→ Fix B 空 turn 守卫闸。
|
|
44989
|
+
*/
|
|
44990
|
+
peekTurnEvidence(runner) {
|
|
44991
|
+
const st = runner.getState();
|
|
44992
|
+
const buf = st.buffer;
|
|
44993
|
+
const lastEventKindBefore = buf.length > 0 ? buf[buf.length - 1].event.kind : null;
|
|
44994
|
+
let turnEndSeenThisTurn = false;
|
|
44995
|
+
for (let i = buf.length - 1; i >= 0; i--) {
|
|
44996
|
+
const k2 = buf[i].event.kind;
|
|
44997
|
+
if (k2 === "user_text") break;
|
|
44998
|
+
if (k2 === "turn_end") {
|
|
44999
|
+
turnEndSeenThisTurn = true;
|
|
45000
|
+
break;
|
|
45001
|
+
}
|
|
45002
|
+
}
|
|
45003
|
+
const turnHasContent = lastEventKindBefore !== null && lastEventKindBefore !== "user_text" && lastEventKindBefore !== "turn_end";
|
|
45004
|
+
return { turnOpenBefore: st.turnOpen, lastEventKindBefore, turnEndSeenThisTurn, turnHasContent };
|
|
45005
|
+
}
|
|
45006
|
+
/**
|
|
45007
|
+
* observer 还需静止多久(ms)才满 idleMs,0 = 已满。observeScreenIdle 复合条件闸:屏幕静止后
|
|
45008
|
+
* 精确等这段剩余再补 turn_end —— turn_duration 写盘早于尾段正文,observer 把尾随 text poll 落盘
|
|
45009
|
+
* 期间屏幕可能已静止,仅看屏幕会早 fire(补的 turn_end 盖不到尾随 text 之后)。
|
|
45010
|
+
* 找不到 runner / 从无事件 → 0(不阻塞 fire)。idleMs 由装配处传 SCREEN_IDLE_MS。
|
|
45011
|
+
*/
|
|
45012
|
+
observerIdleWaitMs(toolSessionId, idleMs) {
|
|
45013
|
+
const sid = this.sessionIdByToolSid(toolSessionId);
|
|
45014
|
+
if (!sid) return 0;
|
|
45015
|
+
const last = this.lastObserverEventAt.get(sid);
|
|
45016
|
+
if (last === void 0) return 0;
|
|
45017
|
+
const elapsed = (this.deps.now ?? Date.now)() - last;
|
|
45018
|
+
return Math.max(0, idleMs - elapsed);
|
|
45019
|
+
}
|
|
44664
45020
|
/** toolSessionId → sessionId 反查(遍历 runners);session 数典型 < 10,O(n) 可接受 */
|
|
44665
45021
|
sessionIdByToolSid(toolSessionId) {
|
|
44666
45022
|
for (const [sid, runner] of this.runners) {
|
|
@@ -45809,11 +46165,7 @@ function tryLoadShareUi(logger) {
|
|
|
45809
46165
|
|
|
45810
46166
|
// src/visitor/visitor-token.ts
|
|
45811
46167
|
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
45812
|
-
|
|
45813
|
-
// ../protocol/src/index.ts
|
|
45814
|
-
init_runtime();
|
|
45815
|
-
|
|
45816
|
-
// src/visitor/visitor-token.ts
|
|
46168
|
+
init_src();
|
|
45817
46169
|
function hmac(secret, body) {
|
|
45818
46170
|
return import_node_crypto4.default.createHmac("sha256", secret).update(body).digest("base64url");
|
|
45819
46171
|
}
|
|
@@ -46170,8 +46522,8 @@ function turnStartInput(text) {
|
|
|
46170
46522
|
const items = [];
|
|
46171
46523
|
let leftover = text;
|
|
46172
46524
|
for (const m2 of text.matchAll(SKILL_RE)) {
|
|
46173
|
-
const [marker, name,
|
|
46174
|
-
items.push({ type: "skill", name, path:
|
|
46525
|
+
const [marker, name, path76] = m2;
|
|
46526
|
+
items.push({ type: "skill", name, path: path76 });
|
|
46175
46527
|
leftover = leftover.replace(marker, "");
|
|
46176
46528
|
}
|
|
46177
46529
|
for (const m2 of text.matchAll(ATTACHMENT_RE2)) {
|
|
@@ -46403,7 +46755,6 @@ var CodexAdapter = class {
|
|
|
46403
46755
|
};
|
|
46404
46756
|
|
|
46405
46757
|
// src/tools/claude-tui.ts
|
|
46406
|
-
var import_node_crypto5 = require("crypto");
|
|
46407
46758
|
var import_node_fs16 = __toESM(require("fs"), 1);
|
|
46408
46759
|
var import_node_os7 = __toESM(require("os"), 1);
|
|
46409
46760
|
var import_node_path14 = __toESM(require("path"), 1);
|
|
@@ -47214,56 +47565,22 @@ function observeScreenIdle(surface, opts) {
|
|
|
47214
47565
|
timer = null;
|
|
47215
47566
|
if (disposed) return;
|
|
47216
47567
|
if (opts.getPopupVisible()) {
|
|
47217
|
-
opts.probeLogger?.info("screen-idle fire suppressed: popup visible", {
|
|
47218
|
-
label: opts.probeLabel
|
|
47219
|
-
});
|
|
47220
47568
|
timer = setTimeout(fire, opts.idleMs);
|
|
47221
47569
|
return;
|
|
47222
47570
|
}
|
|
47223
47571
|
const obsWait = opts.getObserverWaitMs?.() ?? 0;
|
|
47224
47572
|
if (obsWait > 0) {
|
|
47225
|
-
opts.probeLogger?.info("screen-idle fire suppressed: observer not idle", {
|
|
47226
|
-
label: opts.probeLabel,
|
|
47227
|
-
obsWait
|
|
47228
|
-
});
|
|
47229
47573
|
timer = setTimeout(fire, Math.max(obsWait, REWAIT_MIN_MS));
|
|
47230
47574
|
return;
|
|
47231
47575
|
}
|
|
47232
|
-
if (armed)
|
|
47233
|
-
opts.probeLogger?.debug("screen-idle fire noop: already armed", {
|
|
47234
|
-
label: opts.probeLabel
|
|
47235
|
-
});
|
|
47236
|
-
return;
|
|
47237
|
-
}
|
|
47576
|
+
if (armed) return;
|
|
47238
47577
|
armed = true;
|
|
47239
|
-
opts.probeLogger?.info("screen-idle fire triggered \u2192 armed=true, calling onIdle", {
|
|
47240
|
-
label: opts.probeLabel
|
|
47241
|
-
});
|
|
47242
47578
|
opts.onIdle();
|
|
47243
47579
|
};
|
|
47244
47580
|
const unsub = surface.onTick((lines) => {
|
|
47245
47581
|
if (disposed) return;
|
|
47246
47582
|
const snap = snapOf(lines);
|
|
47247
47583
|
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
47584
|
lastSnap = snap;
|
|
47268
47585
|
armed = false;
|
|
47269
47586
|
clear();
|
|
@@ -47274,38 +47591,9 @@ function observeScreenIdle(surface, opts) {
|
|
|
47274
47591
|
disposed = true;
|
|
47275
47592
|
unsub();
|
|
47276
47593
|
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
47594
|
}
|
|
47291
47595
|
};
|
|
47292
47596
|
}
|
|
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
47597
|
var BYPASS_SETTLE_MS = 300;
|
|
47310
47598
|
var SCREEN_IDLE_MS = 5e3;
|
|
47311
47599
|
function createBootGate(pty, logger) {
|
|
@@ -47380,42 +47668,11 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
|
|
|
47380
47668
|
// 用于 spawn / PtyChildProcess 链路打日志
|
|
47381
47669
|
tuiLogger;
|
|
47382
47670
|
tuiOpts;
|
|
47383
|
-
/**
|
|
47384
|
-
* per-toolSessionId 的 tui 观察者句柄,仅用于 turn_end gate 查询(`canAcceptTurnEnd`)。
|
|
47385
|
-
* onIdle / onPopupTransition 等回调仍走原有闭包(不复用这份 map),本 map 只承担
|
|
47386
|
-
* "manager 需要跨模块查屏幕/弹框状态"这单一职责。
|
|
47387
|
-
*/
|
|
47388
|
-
tuiStates = /* @__PURE__ */ new Map();
|
|
47389
47671
|
constructor(opts = {}) {
|
|
47390
47672
|
super(opts);
|
|
47391
47673
|
this.tuiLogger = opts.logger;
|
|
47392
47674
|
this.tuiOpts = opts;
|
|
47393
47675
|
}
|
|
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
47676
|
spawn(ctx) {
|
|
47420
47677
|
const args = buildTuiSpawnArgs(ctx, jsonlExistsForCtx(ctx));
|
|
47421
47678
|
const cmd = process.env.CLAUDE_BIN ?? "claude";
|
|
@@ -47473,26 +47730,18 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
|
|
|
47473
47730
|
const screenIdleObserver = observeScreenIdle(surface, {
|
|
47474
47731
|
idleMs: SCREEN_IDLE_MS,
|
|
47475
47732
|
onIdle: () => {
|
|
47476
|
-
if (!ctx.toolSessionId || !this.tuiOpts.
|
|
47477
|
-
this.tuiLogger?.debug("screen-idle \u2192
|
|
47478
|
-
this.tuiOpts.
|
|
47733
|
+
if (!ctx.toolSessionId || !this.tuiOpts.onTurnIdle) return;
|
|
47734
|
+
this.tuiLogger?.debug("screen-idle \u2192 turn_end", { toolSessionId: ctx.toolSessionId });
|
|
47735
|
+
this.tuiOpts.onTurnIdle(ctx.toolSessionId);
|
|
47479
47736
|
},
|
|
47480
47737
|
getPopupVisible: () => popupObserver.visibleKind !== null,
|
|
47481
|
-
//
|
|
47482
|
-
|
|
47483
|
-
|
|
47484
|
-
probeLabel: ctx.toolSessionId ?? "<no-tsid>"
|
|
47485
|
-
} : {}
|
|
47738
|
+
// observer 还需静止多久才满 SCREEN_IDLE_MS(复合条件 AND):屏幕静止后精确等这段剩余再补
|
|
47739
|
+
// turn_end,确保它排在尾段 text + turn_duration 全部 poll 落盘之后 = buffer 末条。
|
|
47740
|
+
getObserverWaitMs: () => ctx.toolSessionId ? this.tuiOpts.getObserverWaitMs?.(ctx.toolSessionId, SCREEN_IDLE_MS) ?? 0 : 0
|
|
47486
47741
|
});
|
|
47487
47742
|
if (ctx.toolSessionId && this.tuiOpts.onSurfaceRegister) {
|
|
47488
47743
|
this.tuiOpts.onSurfaceRegister(ctx.toolSessionId, surface);
|
|
47489
47744
|
}
|
|
47490
|
-
if (ctx.toolSessionId) {
|
|
47491
|
-
this.tuiStates.set(ctx.toolSessionId, {
|
|
47492
|
-
screenIdle: screenIdleObserver,
|
|
47493
|
-
popup: popupObserver
|
|
47494
|
-
});
|
|
47495
|
-
}
|
|
47496
47745
|
let chunkSeq = 0;
|
|
47497
47746
|
if (ctx.toolSessionId && this.tuiOpts.onPtyReplayRegister) {
|
|
47498
47747
|
this.tuiOpts.onPtyReplayRegister(ctx.toolSessionId, async () => {
|
|
@@ -47538,9 +47787,6 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
|
|
|
47538
47787
|
readyObserver.dispose();
|
|
47539
47788
|
popupObserver.dispose();
|
|
47540
47789
|
screenIdleObserver.dispose();
|
|
47541
|
-
if (ctx.toolSessionId) {
|
|
47542
|
-
this.tuiStates.delete(ctx.toolSessionId);
|
|
47543
|
-
}
|
|
47544
47790
|
if (ctx.toolSessionId && this.tuiOpts.onSurfaceUnregister) {
|
|
47545
47791
|
this.tuiOpts.onSurfaceUnregister(ctx.toolSessionId);
|
|
47546
47792
|
}
|
|
@@ -47823,7 +48069,7 @@ async function writeInboxMcpConfig(args) {
|
|
|
47823
48069
|
// src/shift/store.ts
|
|
47824
48070
|
var import_promises = __toESM(require("fs/promises"), 1);
|
|
47825
48071
|
var import_node_path19 = __toESM(require("path"), 1);
|
|
47826
|
-
var
|
|
48072
|
+
var import_node_crypto5 = require("crypto");
|
|
47827
48073
|
|
|
47828
48074
|
// src/shift/constants.ts
|
|
47829
48075
|
var MAX_RUNS_PER_SHIFT = 30;
|
|
@@ -47919,7 +48165,7 @@ function createShiftStore(deps) {
|
|
|
47919
48165
|
const nextRunAtMs = computeNextRunAtMs(input.schedule, now) ?? void 0;
|
|
47920
48166
|
const shift = {
|
|
47921
48167
|
...input,
|
|
47922
|
-
id: (0,
|
|
48168
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
47923
48169
|
createdAtMs: now,
|
|
47924
48170
|
updatedAtMs: now,
|
|
47925
48171
|
state: { nextRunAtMs },
|
|
@@ -48436,78 +48682,8 @@ function buildPersonaDispatchHandlers(deps) {
|
|
|
48436
48682
|
};
|
|
48437
48683
|
}
|
|
48438
48684
|
|
|
48439
|
-
// src/
|
|
48440
|
-
|
|
48441
|
-
if (url.startsWith("wss://")) return "https://" + url.slice("wss://".length);
|
|
48442
|
-
if (url.startsWith("ws://")) return "http://" + url.slice("ws://".length);
|
|
48443
|
-
return url;
|
|
48444
|
-
}
|
|
48445
|
-
async function forwardDispatchToPeer(args) {
|
|
48446
|
-
const f = args.fetchImpl ?? fetch;
|
|
48447
|
-
const base = wsUrlToHttp(args.contact.remoteUrl).replace(/\/+$/, "");
|
|
48448
|
-
const url = `${base}/rpc/personaDispatch:run`;
|
|
48449
|
-
let res;
|
|
48450
|
-
try {
|
|
48451
|
-
res = await f(url, {
|
|
48452
|
-
method: "POST",
|
|
48453
|
-
headers: {
|
|
48454
|
-
"content-type": "application/json",
|
|
48455
|
-
authorization: `Bearer ${args.contact.connectToken}`
|
|
48456
|
-
},
|
|
48457
|
-
// 注意:不带 targetDeviceId —— B 端据此判定为本地执行(B 角色)。
|
|
48458
|
-
body: JSON.stringify({ targetPersona: args.targetPersona, prompt: args.prompt })
|
|
48459
|
-
});
|
|
48460
|
-
} catch (err) {
|
|
48461
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
48462
|
-
return { kind: "failure", reason: `forward to peer failed: ${msg}` };
|
|
48463
|
-
}
|
|
48464
|
-
let json;
|
|
48465
|
-
try {
|
|
48466
|
-
json = await res.json();
|
|
48467
|
-
} catch {
|
|
48468
|
-
return {
|
|
48469
|
-
kind: "failure",
|
|
48470
|
-
reason: `peer returned non-JSON response (HTTP ${res.status})`
|
|
48471
|
-
};
|
|
48472
|
-
}
|
|
48473
|
-
if (json.ok === false) {
|
|
48474
|
-
return { kind: "failure", reason: `peer rejected: ${json.error}: ${json.message}` };
|
|
48475
|
-
}
|
|
48476
|
-
return json.result.outcome;
|
|
48477
|
-
}
|
|
48478
|
-
async function forwardInboxPostToPeer(args) {
|
|
48479
|
-
const f = args.fetchImpl ?? fetch;
|
|
48480
|
-
const base = wsUrlToHttp(args.contact.remoteUrl).replace(/\/+$/, "");
|
|
48481
|
-
const url = `${base}/rpc/inbox:postMessage`;
|
|
48482
|
-
let res;
|
|
48483
|
-
try {
|
|
48484
|
-
res = await f(url, {
|
|
48485
|
-
method: "POST",
|
|
48486
|
-
headers: {
|
|
48487
|
-
"content-type": "application/json",
|
|
48488
|
-
authorization: `Bearer ${args.contact.connectToken}`
|
|
48489
|
-
},
|
|
48490
|
-
body: JSON.stringify({
|
|
48491
|
-
id: args.id,
|
|
48492
|
-
text: args.text,
|
|
48493
|
-
createdAt: args.createdAt,
|
|
48494
|
-
...args.origin ? { origin: args.origin } : {}
|
|
48495
|
-
})
|
|
48496
|
-
});
|
|
48497
|
-
} catch (err) {
|
|
48498
|
-
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
48499
|
-
}
|
|
48500
|
-
let json = null;
|
|
48501
|
-
try {
|
|
48502
|
-
json = await res.json();
|
|
48503
|
-
} catch {
|
|
48504
|
-
return { ok: false, error: `peer non-JSON (HTTP ${res.status})` };
|
|
48505
|
-
}
|
|
48506
|
-
if (res.status < 200 || res.status >= 300 || json?.ok === false) {
|
|
48507
|
-
return { ok: false, error: json?.error ?? `HTTP ${res.status}` };
|
|
48508
|
-
}
|
|
48509
|
-
return { ok: true };
|
|
48510
|
-
}
|
|
48685
|
+
// src/index.ts
|
|
48686
|
+
init_peer_forward();
|
|
48511
48687
|
|
|
48512
48688
|
// src/tools/codex-history.ts
|
|
48513
48689
|
var import_node_child_process5 = require("child_process");
|
|
@@ -48695,13 +48871,13 @@ function mapSkillsListResponse(res) {
|
|
|
48695
48871
|
const r = s ?? {};
|
|
48696
48872
|
const name = str3(r.name);
|
|
48697
48873
|
if (!name) continue;
|
|
48698
|
-
const
|
|
48874
|
+
const path76 = str3(r.path);
|
|
48699
48875
|
const description = str3(r.description);
|
|
48700
48876
|
const isPlugin = name.includes(":");
|
|
48701
48877
|
out.push({
|
|
48702
48878
|
name,
|
|
48703
48879
|
source: isPlugin ? "plugin" : "project",
|
|
48704
|
-
...
|
|
48880
|
+
...path76 ? { path: path76 } : {},
|
|
48705
48881
|
...description ? { description } : {},
|
|
48706
48882
|
...isPlugin ? { plugin: name.split(":")[0] } : {}
|
|
48707
48883
|
});
|
|
@@ -49350,18 +49526,8 @@ function listRegistered() {
|
|
|
49350
49526
|
return [...registry.keys()];
|
|
49351
49527
|
}
|
|
49352
49528
|
|
|
49353
|
-
// ../node_modules/.pnpm/ws@8.20.0/node_modules/ws/wrapper.mjs
|
|
49354
|
-
var import_stream = __toESM(require_stream(), 1);
|
|
49355
|
-
var import_extension = __toESM(require_extension(), 1);
|
|
49356
|
-
var import_permessage_deflate = __toESM(require_permessage_deflate(), 1);
|
|
49357
|
-
var import_receiver = __toESM(require_receiver(), 1);
|
|
49358
|
-
var import_sender = __toESM(require_sender(), 1);
|
|
49359
|
-
var import_subprotocol = __toESM(require_subprotocol(), 1);
|
|
49360
|
-
var import_websocket = __toESM(require_websocket(), 1);
|
|
49361
|
-
var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
49362
|
-
var wrapper_default = import_websocket.default;
|
|
49363
|
-
|
|
49364
49529
|
// src/transport/local-ws-server.ts
|
|
49530
|
+
init_wrapper();
|
|
49365
49531
|
var import_node_http2 = __toESM(require("http"), 1);
|
|
49366
49532
|
|
|
49367
49533
|
// src/transport/preview-proxy.ts
|
|
@@ -49437,6 +49603,18 @@ var LocalWsServer = class {
|
|
|
49437
49603
|
const httpServer = import_node_http2.default.createServer((req, res) => this.handleHttpRequest(req, res));
|
|
49438
49604
|
const wss = new import_websocket_server.default({ noServer: true, clientTracking: true });
|
|
49439
49605
|
httpServer.on("upgrade", (req, socket, head) => {
|
|
49606
|
+
if (this.opts.sshTunnelUpgradeHandler) {
|
|
49607
|
+
const [urlPath] = (req.url ?? "").split("?");
|
|
49608
|
+
if (urlPath === "/rpc/ssh-tunnel") {
|
|
49609
|
+
void this.opts.sshTunnelUpgradeHandler(
|
|
49610
|
+
req,
|
|
49611
|
+
socket,
|
|
49612
|
+
head,
|
|
49613
|
+
wss
|
|
49614
|
+
);
|
|
49615
|
+
return;
|
|
49616
|
+
}
|
|
49617
|
+
}
|
|
49440
49618
|
if (req.url?.startsWith("/preview/")) {
|
|
49441
49619
|
const pathname = (() => {
|
|
49442
49620
|
try {
|
|
@@ -50009,6 +50187,7 @@ function constantTimeEqual2(a, b2) {
|
|
|
50009
50187
|
}
|
|
50010
50188
|
|
|
50011
50189
|
// src/transport/connection-context.ts
|
|
50190
|
+
init_src();
|
|
50012
50191
|
function ownerContext(ownerPrincipalId, displayName) {
|
|
50013
50192
|
return {
|
|
50014
50193
|
principal: makeOwnerPrincipal(ownerPrincipalId, displayName),
|
|
@@ -50062,6 +50241,7 @@ async function authenticate(token, deps) {
|
|
|
50062
50241
|
// src/permission/capability-store.ts
|
|
50063
50242
|
var fs28 = __toESM(require("fs"), 1);
|
|
50064
50243
|
var path28 = __toESM(require("path"), 1);
|
|
50244
|
+
init_src();
|
|
50065
50245
|
var CAPABILITIES_FILE_NAME = "capabilities.json";
|
|
50066
50246
|
var FILE_VERSION = 1;
|
|
50067
50247
|
var CapabilityStore = class {
|
|
@@ -50239,6 +50419,7 @@ function cleanupGuestSessionsForCapability(cap, factory) {
|
|
|
50239
50419
|
// src/inbox/inbox-store.ts
|
|
50240
50420
|
var fs30 = __toESM(require("fs"), 1);
|
|
50241
50421
|
var path29 = __toESM(require("path"), 1);
|
|
50422
|
+
init_src();
|
|
50242
50423
|
var INBOX_SUBDIR = "inbox";
|
|
50243
50424
|
var InboxStore = class {
|
|
50244
50425
|
constructor(dataDir) {
|
|
@@ -50429,6 +50610,7 @@ var InboxManager = class {
|
|
|
50429
50610
|
// src/state/contact-store.ts
|
|
50430
50611
|
var fs31 = __toESM(require("fs"), 1);
|
|
50431
50612
|
var path30 = __toESM(require("path"), 1);
|
|
50613
|
+
init_src();
|
|
50432
50614
|
var FILE_NAME = "contacts.json";
|
|
50433
50615
|
var ContactStore = class {
|
|
50434
50616
|
constructor(dataDir) {
|
|
@@ -50495,6 +50677,23 @@ var ContactStore = class {
|
|
|
50495
50677
|
this.flush();
|
|
50496
50678
|
return true;
|
|
50497
50679
|
}
|
|
50680
|
+
/**
|
|
50681
|
+
* 更新单条 contact 的 SSH 授权(PR: contact-ssh-sandbox)。对齐 setPin pattern:
|
|
50682
|
+
* store 只做原始 mutation,不做业务校验(如"sshAllowed=false 时清空 exposedDirs")——
|
|
50683
|
+
* 那是 handler / UI 的责任。数组语义是完全替换(不 append)。
|
|
50684
|
+
* @returns 是否命中:deviceId 不存在返 false;命中即 flush.
|
|
50685
|
+
*/
|
|
50686
|
+
setSshAccess(deviceId, opts) {
|
|
50687
|
+
const existing = this.contacts.get(deviceId);
|
|
50688
|
+
if (!existing) return false;
|
|
50689
|
+
this.contacts.set(deviceId, {
|
|
50690
|
+
...existing,
|
|
50691
|
+
sshAllowed: opts.sshAllowed,
|
|
50692
|
+
exposedDirs: opts.exposedDirs
|
|
50693
|
+
});
|
|
50694
|
+
this.flush();
|
|
50695
|
+
return true;
|
|
50696
|
+
}
|
|
50498
50697
|
flush() {
|
|
50499
50698
|
const file = path30.join(this.dataDir, FILE_NAME);
|
|
50500
50699
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
@@ -50516,6 +50715,7 @@ var ContactStore = class {
|
|
|
50516
50715
|
};
|
|
50517
50716
|
|
|
50518
50717
|
// src/contact/connect-remote.ts
|
|
50718
|
+
init_wrapper();
|
|
50519
50719
|
var crypto6 = __toESM(require("crypto"), 1);
|
|
50520
50720
|
var HANDSHAKE_TIMEOUT_MS = 5e3;
|
|
50521
50721
|
var RPC_TIMEOUT_MS = 5e3;
|
|
@@ -50642,7 +50842,9 @@ async function autoReverseContact(args) {
|
|
|
50642
50842
|
connectToken: "",
|
|
50643
50843
|
grants,
|
|
50644
50844
|
addedAt: now(),
|
|
50645
|
-
pinnedAt: null
|
|
50845
|
+
pinnedAt: null,
|
|
50846
|
+
sshAllowed: false,
|
|
50847
|
+
exposedDirs: []
|
|
50646
50848
|
};
|
|
50647
50849
|
args.store.upsert(base);
|
|
50648
50850
|
args.broadcast({ type: "contact:added", contact: base });
|
|
@@ -50659,6 +50861,9 @@ async function autoReverseContact(args) {
|
|
|
50659
50861
|
args.broadcast({ type: "contact:added", contact: upgraded });
|
|
50660
50862
|
}
|
|
50661
50863
|
|
|
50864
|
+
// src/index.ts
|
|
50865
|
+
init_src();
|
|
50866
|
+
|
|
50662
50867
|
// src/migrations/2026-05-20-flatten-sessions.ts
|
|
50663
50868
|
var fs32 = __toESM(require("fs"), 1);
|
|
50664
50869
|
var path31 = __toESM(require("path"), 1);
|
|
@@ -50871,7 +51076,7 @@ function lookupMime(filePathOrName) {
|
|
|
50871
51076
|
}
|
|
50872
51077
|
|
|
50873
51078
|
// src/attachment/sign-url.ts
|
|
50874
|
-
var
|
|
51079
|
+
var import_node_crypto6 = __toESM(require("crypto"), 1);
|
|
50875
51080
|
var HMAC_ALGO = "sha256";
|
|
50876
51081
|
function base64urlEncode(buf) {
|
|
50877
51082
|
const b2 = typeof buf === "string" ? Buffer.from(buf, "utf8") : buf;
|
|
@@ -50888,7 +51093,7 @@ function decodeAbsPathFromUrl(encoded) {
|
|
|
50888
51093
|
}
|
|
50889
51094
|
function computeSig(secret, absPath, e) {
|
|
50890
51095
|
const msg = e === null ? absPath : `${absPath}|${e}`;
|
|
50891
|
-
return
|
|
51096
|
+
return import_node_crypto6.default.createHmac(HMAC_ALGO, secret).update(msg).digest();
|
|
50892
51097
|
}
|
|
50893
51098
|
function signUrlParts(secret, absPath, ttlSeconds, now = Date.now) {
|
|
50894
51099
|
const e = ttlSeconds === null ? null : Math.floor(now() / 1e3) + ttlSeconds;
|
|
@@ -50923,7 +51128,7 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
|
|
|
50923
51128
|
if (provided.length !== expected.length) {
|
|
50924
51129
|
return { ok: false, code: "BAD_SIG" };
|
|
50925
51130
|
}
|
|
50926
|
-
if (!
|
|
51131
|
+
if (!import_node_crypto6.default.timingSafeEqual(provided, expected)) {
|
|
50927
51132
|
return { ok: false, code: "BAD_SIG" };
|
|
50928
51133
|
}
|
|
50929
51134
|
if (e !== null && now() / 1e3 > e) {
|
|
@@ -50935,7 +51140,7 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
|
|
|
50935
51140
|
// src/attachment/upload.ts
|
|
50936
51141
|
var import_node_fs25 = __toESM(require("fs"), 1);
|
|
50937
51142
|
var import_node_path25 = __toESM(require("path"), 1);
|
|
50938
|
-
var
|
|
51143
|
+
var import_node_crypto7 = __toESM(require("crypto"), 1);
|
|
50939
51144
|
var import_promises2 = require("stream/promises");
|
|
50940
51145
|
var UploadError = class extends Error {
|
|
50941
51146
|
constructor(code, message) {
|
|
@@ -50959,11 +51164,11 @@ async function writeUploadedAttachment(args) {
|
|
|
50959
51164
|
} catch (err) {
|
|
50960
51165
|
throw new UploadError("STORAGE_ERROR", `mkdir failed: ${err.message}`);
|
|
50961
51166
|
}
|
|
50962
|
-
const hasher =
|
|
51167
|
+
const hasher = import_node_crypto7.default.createHash("sha256");
|
|
50963
51168
|
let actualSize = 0;
|
|
50964
51169
|
const tmpPath = import_node_path25.default.join(
|
|
50965
51170
|
attachmentsRoot,
|
|
50966
|
-
`.upload-${process.pid}-${Date.now()}-${
|
|
51171
|
+
`.upload-${process.pid}-${Date.now()}-${import_node_crypto7.default.randomBytes(4).toString("hex")}`
|
|
50967
51172
|
);
|
|
50968
51173
|
try {
|
|
50969
51174
|
await (0, import_promises2.pipeline)(
|
|
@@ -51041,6 +51246,7 @@ var import_promises3 = __toESM(require("fs/promises"), 1);
|
|
|
51041
51246
|
var import_node_path26 = __toESM(require("path"), 1);
|
|
51042
51247
|
var import_node_os12 = __toESM(require("os"), 1);
|
|
51043
51248
|
var import_jszip = __toESM(require_lib3(), 1);
|
|
51249
|
+
init_src();
|
|
51044
51250
|
var ImportError = class extends Error {
|
|
51045
51251
|
constructor(code, message) {
|
|
51046
51252
|
super(message);
|
|
@@ -51839,7 +52045,7 @@ function runAttachmentGc(args) {
|
|
|
51839
52045
|
// src/attachment/group.ts
|
|
51840
52046
|
var import_node_fs28 = __toESM(require("fs"), 1);
|
|
51841
52047
|
var import_node_path29 = __toESM(require("path"), 1);
|
|
51842
|
-
var
|
|
52048
|
+
var import_node_crypto8 = __toESM(require("crypto"), 1);
|
|
51843
52049
|
init_protocol();
|
|
51844
52050
|
var GroupFileStore = class {
|
|
51845
52051
|
dataDir;
|
|
@@ -51928,7 +52134,7 @@ var GroupFileStore = class {
|
|
|
51928
52134
|
entries[idx] = next;
|
|
51929
52135
|
} else {
|
|
51930
52136
|
next = {
|
|
51931
|
-
id: `gf-${
|
|
52137
|
+
id: `gf-${import_node_crypto8.default.randomBytes(6).toString("base64url")}`,
|
|
51932
52138
|
relPath: input.relPath,
|
|
51933
52139
|
from: input.from,
|
|
51934
52140
|
label: input.label,
|
|
@@ -52047,7 +52253,7 @@ function readDaemonSourceFromEnv(env = process.env) {
|
|
|
52047
52253
|
// src/tunnel/tunnel-manager.ts
|
|
52048
52254
|
var import_node_fs33 = __toESM(require("fs"), 1);
|
|
52049
52255
|
var import_node_path34 = __toESM(require("path"), 1);
|
|
52050
|
-
var
|
|
52256
|
+
var import_node_crypto9 = __toESM(require("crypto"), 1);
|
|
52051
52257
|
var import_node_child_process9 = require("child_process");
|
|
52052
52258
|
|
|
52053
52259
|
// src/tunnel/tunnel-store.ts
|
|
@@ -52546,7 +52752,7 @@ var TunnelManager = class {
|
|
|
52546
52752
|
override: this.deps.frpcBinaryOverride ?? void 0
|
|
52547
52753
|
});
|
|
52548
52754
|
const tomlPath = import_node_path34.default.join(this.deps.dataDir, "frpc.toml");
|
|
52549
|
-
const proxyName = `clawd-${t.subdomain}-${localPort}-${
|
|
52755
|
+
const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto9.default.randomBytes(3).toString("hex")}`;
|
|
52550
52756
|
const toml = buildFrpcToml({
|
|
52551
52757
|
serverAddr: t.frpsHost,
|
|
52552
52758
|
serverPort: t.frpsPort,
|
|
@@ -52642,29 +52848,795 @@ async function waitForFrpcReady(proc, timeoutMs) {
|
|
|
52642
52848
|
});
|
|
52643
52849
|
}
|
|
52644
52850
|
|
|
52851
|
+
// src/sshd/sshd-manager.ts
|
|
52852
|
+
var import_node_fs36 = __toESM(require("fs"), 1);
|
|
52853
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
52854
|
+
var import_node_child_process11 = require("child_process");
|
|
52855
|
+
|
|
52856
|
+
// src/sshd/sshd-config.ts
|
|
52857
|
+
function buildSshdConfig(input) {
|
|
52858
|
+
const lines = [
|
|
52859
|
+
`ListenAddress ${input.listenAddress}`,
|
|
52860
|
+
`Port ${input.port}`,
|
|
52861
|
+
`HostKey ${input.hostKeyPath}`,
|
|
52862
|
+
`PidFile ${input.pidFilePath}`,
|
|
52863
|
+
`AuthorizedKeysFile ${input.authorizedKeysFile}`,
|
|
52864
|
+
`PubkeyAuthentication yes`,
|
|
52865
|
+
`PasswordAuthentication no`,
|
|
52866
|
+
`ChallengeResponseAuthentication no`,
|
|
52867
|
+
`KbdInteractiveAuthentication no`,
|
|
52868
|
+
`PermitRootLogin no`,
|
|
52869
|
+
`StrictModes no`,
|
|
52870
|
+
`UsePAM no`,
|
|
52871
|
+
`LogLevel INFO`,
|
|
52872
|
+
`Subsystem sftp internal-sftp`
|
|
52873
|
+
];
|
|
52874
|
+
return lines.join("\n") + "\n";
|
|
52875
|
+
}
|
|
52876
|
+
|
|
52877
|
+
// src/sshd/sshd-process.ts
|
|
52878
|
+
var import_node_fs34 = __toESM(require("fs"), 1);
|
|
52879
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
52880
|
+
var import_node_child_process10 = require("child_process");
|
|
52881
|
+
function sshdPidFilePath(dataDir) {
|
|
52882
|
+
return import_node_path35.default.join(dataDir, "sshd", "sshd.pid");
|
|
52883
|
+
}
|
|
52884
|
+
function writeSshdPid(dataDir, pid) {
|
|
52885
|
+
try {
|
|
52886
|
+
const p2 = sshdPidFilePath(dataDir);
|
|
52887
|
+
import_node_fs34.default.mkdirSync(import_node_path35.default.dirname(p2), { recursive: true, mode: 448 });
|
|
52888
|
+
import_node_fs34.default.writeFileSync(p2, String(pid), { mode: 384 });
|
|
52889
|
+
} catch {
|
|
52890
|
+
}
|
|
52891
|
+
}
|
|
52892
|
+
function clearSshdPid(dataDir) {
|
|
52893
|
+
try {
|
|
52894
|
+
import_node_fs34.default.unlinkSync(sshdPidFilePath(dataDir));
|
|
52895
|
+
} catch {
|
|
52896
|
+
}
|
|
52897
|
+
}
|
|
52898
|
+
function defaultIsPidAlive2(pid) {
|
|
52899
|
+
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
52900
|
+
try {
|
|
52901
|
+
process.kill(pid, 0);
|
|
52902
|
+
return true;
|
|
52903
|
+
} catch (err) {
|
|
52904
|
+
const code = err.code;
|
|
52905
|
+
return code === "EPERM";
|
|
52906
|
+
}
|
|
52907
|
+
}
|
|
52908
|
+
function defaultReadPidFile2(file) {
|
|
52909
|
+
try {
|
|
52910
|
+
return import_node_fs34.default.readFileSync(file, "utf8");
|
|
52911
|
+
} catch {
|
|
52912
|
+
return null;
|
|
52913
|
+
}
|
|
52914
|
+
}
|
|
52915
|
+
function defaultKillPid2(pid, signal) {
|
|
52916
|
+
try {
|
|
52917
|
+
process.kill(pid, signal);
|
|
52918
|
+
} catch {
|
|
52919
|
+
}
|
|
52920
|
+
}
|
|
52921
|
+
function defaultSleep2(ms) {
|
|
52922
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
52923
|
+
}
|
|
52924
|
+
async function killStaleSshd(deps) {
|
|
52925
|
+
const pidFile = sshdPidFilePath(deps.dataDir);
|
|
52926
|
+
const configPath = import_node_path35.default.join(deps.dataDir, "sshd", "sshd_config");
|
|
52927
|
+
const readPidFile = deps.readPidFileImpl ?? defaultReadPidFile2;
|
|
52928
|
+
const isAlive = deps.isPidAliveImpl ?? defaultIsPidAlive2;
|
|
52929
|
+
const killPid = deps.killPidImpl ?? defaultKillPid2;
|
|
52930
|
+
const scanPids = deps.scanSshdPidsImpl ?? ((cp) => defaultScanSshdPidsByCmdline(cp, deps.logger));
|
|
52931
|
+
const sleep2 = deps.sleepImpl ?? defaultSleep2;
|
|
52932
|
+
const victims = /* @__PURE__ */ new Set();
|
|
52933
|
+
const raw = readPidFile(pidFile);
|
|
52934
|
+
if (raw) {
|
|
52935
|
+
const pid = parseInt(raw.trim(), 10);
|
|
52936
|
+
if (Number.isFinite(pid) && pid > 0 && pid !== deps.ownPid && isAlive(pid)) {
|
|
52937
|
+
victims.add(pid);
|
|
52938
|
+
}
|
|
52939
|
+
}
|
|
52940
|
+
try {
|
|
52941
|
+
const scanned = await scanPids(configPath);
|
|
52942
|
+
for (const pid of scanned) {
|
|
52943
|
+
if (pid > 0 && pid !== deps.ownPid && isAlive(pid)) victims.add(pid);
|
|
52944
|
+
}
|
|
52945
|
+
} catch (e) {
|
|
52946
|
+
deps.logger?.warn("sshd: stale-sshd cmdline scan failed", { err: e.message });
|
|
52947
|
+
}
|
|
52948
|
+
if (victims.size === 0) {
|
|
52949
|
+
try {
|
|
52950
|
+
import_node_fs34.default.unlinkSync(pidFile);
|
|
52951
|
+
} catch {
|
|
52952
|
+
}
|
|
52953
|
+
return;
|
|
52954
|
+
}
|
|
52955
|
+
for (const pid of victims) {
|
|
52956
|
+
deps.logger?.warn("sshd: killing stale sshd before respawn", { pid });
|
|
52957
|
+
killPid(pid, "SIGKILL");
|
|
52958
|
+
}
|
|
52959
|
+
await sleep2(deps.reapWaitMs ?? 300);
|
|
52960
|
+
try {
|
|
52961
|
+
import_node_fs34.default.unlinkSync(pidFile);
|
|
52962
|
+
} catch {
|
|
52963
|
+
}
|
|
52964
|
+
}
|
|
52965
|
+
async function defaultScanSshdPidsByCmdline(configPath, logger) {
|
|
52966
|
+
if (process.platform === "win32") return [];
|
|
52967
|
+
return new Promise((resolve6) => {
|
|
52968
|
+
const ps = (0, import_node_child_process10.spawn)("ps", ["-axo", "pid=,command="], { stdio: ["ignore", "pipe", "ignore"] });
|
|
52969
|
+
let buf = "";
|
|
52970
|
+
ps.stdout.on("data", (c) => {
|
|
52971
|
+
buf += c.toString();
|
|
52972
|
+
});
|
|
52973
|
+
ps.on("exit", () => {
|
|
52974
|
+
const pids = [];
|
|
52975
|
+
for (const line of buf.split("\n")) {
|
|
52976
|
+
const m2 = /^\s*(\d+)\s+(.*)$/.exec(line);
|
|
52977
|
+
if (!m2) continue;
|
|
52978
|
+
const cmd = m2[2];
|
|
52979
|
+
if (!/\bsshd\b/.test(cmd)) continue;
|
|
52980
|
+
if (!cmd.includes(configPath)) continue;
|
|
52981
|
+
const pid = parseInt(m2[1], 10);
|
|
52982
|
+
if (Number.isFinite(pid) && pid > 0) pids.push(pid);
|
|
52983
|
+
}
|
|
52984
|
+
resolve6(pids);
|
|
52985
|
+
});
|
|
52986
|
+
ps.on("error", (e) => {
|
|
52987
|
+
logger?.warn("sshd: ps scan failed", { err: e.message });
|
|
52988
|
+
resolve6([]);
|
|
52989
|
+
});
|
|
52990
|
+
});
|
|
52991
|
+
}
|
|
52992
|
+
|
|
52993
|
+
// src/sshd/jail-script.ts
|
|
52994
|
+
var import_node_fs35 = __toESM(require("fs"), 1);
|
|
52995
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
52996
|
+
var CLAWD_SSH_JAIL_SCRIPT = String.raw`#!/usr/bin/env bash
|
|
52997
|
+
# clawd-ssh-jail — SSH reverse access sandbox wrapper (managed by clawd; do not edit)
|
|
52998
|
+
#
|
|
52999
|
+
# 由 sshd authorized_keys 的 command= 强制入口调用。
|
|
53000
|
+
# 用法: sshd 会以 \`clawd-ssh-jail <deviceId>\` 起本脚本;$SSH_ORIGINAL_COMMAND = client
|
|
53001
|
+
# 真实请求(interactive shell 时为空)。
|
|
53002
|
+
#
|
|
53003
|
+
# 职责:
|
|
53004
|
+
# 1. 读 ~/.clawd/contacts.json 找 contact.exposedDirs
|
|
53005
|
+
# 2. macOS 用 sandbox-exec + sbpl; Linux 用 bwrap
|
|
53006
|
+
# 3. exec 沙箱 shell
|
|
53007
|
+
|
|
53008
|
+
set -euo pipefail
|
|
53009
|
+
|
|
53010
|
+
# 用户可读审计日志:追加到 ~/.clawd/contact-ssh.log(跟 daemon TS 侧 contact-ssh-log.ts 共写)
|
|
53011
|
+
CONTACT_SSH_LOG="\${HOME}/.clawd/contact-ssh.log"
|
|
53012
|
+
log_line() {
|
|
53013
|
+
# 参数: level tag msg
|
|
53014
|
+
local ts
|
|
53015
|
+
ts=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ" 2>/dev/null || date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
53016
|
+
printf '[%s] [%s] [%s] %s\n' "$ts" "$1" "$2" "$3" >> "$CONTACT_SSH_LOG" 2>/dev/null || true
|
|
53017
|
+
}
|
|
53018
|
+
|
|
53019
|
+
DEVICE_ID="\${1:-}"
|
|
53020
|
+
if [ -z "$DEVICE_ID" ]; then
|
|
53021
|
+
log_line ERROR jail.entered "clawd-ssh-jail 缺 deviceId 参数"
|
|
53022
|
+
echo "clawd-ssh-jail: missing deviceId" >&2
|
|
53023
|
+
exit 1
|
|
53024
|
+
fi
|
|
53025
|
+
|
|
53026
|
+
log_line INFO jail.entered "clawd-ssh-jail 起来 device=\${DEVICE_ID} cmd=\${SSH_ORIGINAL_COMMAND:-<interactive-shell>}"
|
|
53027
|
+
|
|
53028
|
+
CONTACTS="\${HOME}/.clawd/contacts.json"
|
|
53029
|
+
if [ ! -f "$CONTACTS" ]; then
|
|
53030
|
+
log_line ERROR jail.entered "contacts.json 不存在,无法查 exposedDirs"
|
|
53031
|
+
echo "clawd-ssh-jail: contacts.json missing" >&2
|
|
53032
|
+
exit 1
|
|
53033
|
+
fi
|
|
53034
|
+
|
|
53035
|
+
# 读 contact 的 exposedDirs (mac/linux 都自带 python3)
|
|
53036
|
+
EXPOSED_JSON=$(python3 -c "
|
|
53037
|
+
import json, sys
|
|
53038
|
+
with open('$CONTACTS') as f:
|
|
53039
|
+
data = json.load(f)
|
|
53040
|
+
for c in data.get('contacts', []):
|
|
53041
|
+
if c.get('deviceId') == '$DEVICE_ID':
|
|
53042
|
+
if not c.get('sshAllowed'):
|
|
53043
|
+
print('DENIED', file=sys.stderr); sys.exit(2)
|
|
53044
|
+
for d in c.get('exposedDirs', []):
|
|
53045
|
+
print(d)
|
|
53046
|
+
sys.exit(0)
|
|
53047
|
+
sys.exit(3)
|
|
53048
|
+
")
|
|
53049
|
+
|
|
53050
|
+
if [ -z "$EXPOSED_JSON" ]; then
|
|
53051
|
+
log_line ERROR jail.entered "contact \${DEVICE_ID} 不在 store 或 exposedDirs 为空"
|
|
53052
|
+
echo "clawd-ssh-jail: contact not found or no exposed dirs" >&2
|
|
53053
|
+
exit 1
|
|
53054
|
+
fi
|
|
53055
|
+
log_line INFO jail.entered "exposedDirs 白名单已加载,进沙箱 exec shell"
|
|
53056
|
+
|
|
53057
|
+
# 进沙箱前告知 SSH client 授权目录列表(发到 stderr)—— CC 或其他 SSH client 试错一次就能
|
|
53058
|
+
# 学到 exposedDirs 具体值,无需 A 侧向 B 侧同步(授权数据实时的 single source of truth)。
|
|
53059
|
+
# 每次拨号运行时读,A 改 exposedDirs 后 B 侧下一次拨号立即感知。
|
|
53060
|
+
echo "[clawd-ssh-jail] Access allowed to these directories:" >&2
|
|
53061
|
+
while IFS= read -r d; do
|
|
53062
|
+
echo " - $d" >&2
|
|
53063
|
+
done <<< "$EXPOSED_JSON"
|
|
53064
|
+
echo "[clawd-ssh-jail] Anything outside these paths will be sandbox-denied." >&2
|
|
53065
|
+
|
|
53066
|
+
# 校验路径安全(bash 侧二次防御)
|
|
53067
|
+
while IFS= read -r line; do
|
|
53068
|
+
case "$line" in
|
|
53069
|
+
/*) : ;;
|
|
53070
|
+
*) echo "clawd-ssh-jail: bad path: $line" >&2; exit 1 ;;
|
|
53071
|
+
esac
|
|
53072
|
+
case "$line" in
|
|
53073
|
+
*[\"\'\`\$\;\|\&\(\)\{\}\[\]\<\>\*\?]*)
|
|
53074
|
+
echo "clawd-ssh-jail: unsafe path: $line" >&2; exit 1 ;;
|
|
53075
|
+
esac
|
|
53076
|
+
done <<< "$EXPOSED_JSON"
|
|
53077
|
+
|
|
53078
|
+
CMD="\${SSH_ORIGINAL_COMMAND:-}"
|
|
53079
|
+
if [ -z "$CMD" ]; then
|
|
53080
|
+
SHELL_CMD=(bash --login)
|
|
53081
|
+
else
|
|
53082
|
+
SHELL_CMD=(bash -c "$CMD")
|
|
53083
|
+
fi
|
|
53084
|
+
|
|
53085
|
+
case "$(uname -s)" in
|
|
53086
|
+
Darwin)
|
|
53087
|
+
POLICY="(version 1)
|
|
53088
|
+
(deny default)
|
|
53089
|
+
(allow process*)
|
|
53090
|
+
(allow signal (target self))
|
|
53091
|
+
(allow sysctl-read)
|
|
53092
|
+
(allow mach-lookup)
|
|
53093
|
+
(allow file-read-metadata)
|
|
53094
|
+
(allow network*)
|
|
53095
|
+
(allow file-read* (subpath \"/usr\"))
|
|
53096
|
+
(allow file-read* (subpath \"/bin\"))
|
|
53097
|
+
(allow file-read* (subpath \"/sbin\"))
|
|
53098
|
+
(allow file-read* (subpath \"/System\"))
|
|
53099
|
+
(allow file-read* (subpath \"/Library\"))
|
|
53100
|
+
(allow file-read* (subpath \"/etc\"))
|
|
53101
|
+
(allow file-read* (subpath \"/private/etc\"))"
|
|
53102
|
+
while IFS= read -r d; do
|
|
53103
|
+
POLICY+="
|
|
53104
|
+
(allow file-read* file-write* (subpath \"$d\"))"
|
|
53105
|
+
done <<< "$EXPOSED_JSON"
|
|
53106
|
+
exec sandbox-exec -p "$POLICY" "\${SHELL_CMD[@]}"
|
|
53107
|
+
;;
|
|
53108
|
+
Linux)
|
|
53109
|
+
BWRAP_ARGS=(
|
|
53110
|
+
--unshare-user --unshare-ipc --unshare-pid --unshare-uts
|
|
53111
|
+
--die-with-parent
|
|
53112
|
+
--proc /proc --dev /dev --tmpfs /tmp
|
|
53113
|
+
--ro-bind /usr /usr --ro-bind /bin /bin --ro-bind /sbin /sbin
|
|
53114
|
+
--ro-bind /lib /lib --ro-bind /etc /etc
|
|
53115
|
+
)
|
|
53116
|
+
if [ -d /lib64 ]; then BWRAP_ARGS+=(--ro-bind /lib64 /lib64); fi
|
|
53117
|
+
while IFS= read -r d; do
|
|
53118
|
+
BWRAP_ARGS+=(--bind "$d" "$d")
|
|
53119
|
+
done <<< "$EXPOSED_JSON"
|
|
53120
|
+
exec bwrap "\${BWRAP_ARGS[@]}" "\${SHELL_CMD[@]}"
|
|
53121
|
+
;;
|
|
53122
|
+
*)
|
|
53123
|
+
echo "clawd-ssh-jail: unsupported OS $(uname -s)" >&2
|
|
53124
|
+
exit 1
|
|
53125
|
+
;;
|
|
53126
|
+
esac
|
|
53127
|
+
`;
|
|
53128
|
+
function ensureJailScript(dataDir) {
|
|
53129
|
+
const binDir = import_node_path36.default.join(dataDir, "bin");
|
|
53130
|
+
import_node_fs35.default.mkdirSync(binDir, { recursive: true, mode: 493 });
|
|
53131
|
+
const target = import_node_path36.default.join(binDir, "clawd-ssh-jail");
|
|
53132
|
+
import_node_fs35.default.writeFileSync(target, CLAWD_SSH_JAIL_SCRIPT, { mode: 493 });
|
|
53133
|
+
return target;
|
|
53134
|
+
}
|
|
53135
|
+
|
|
53136
|
+
// src/sshd/sshd-manager.ts
|
|
53137
|
+
var SshdManager = class {
|
|
53138
|
+
constructor(deps) {
|
|
53139
|
+
this.deps = deps;
|
|
53140
|
+
this.sshdDir = import_node_path37.default.join(deps.dataDir, "sshd");
|
|
53141
|
+
this.startupTimeoutMs = deps.startupTimeoutMs ?? 15e3;
|
|
53142
|
+
}
|
|
53143
|
+
deps;
|
|
53144
|
+
proc = null;
|
|
53145
|
+
sshdDir;
|
|
53146
|
+
stopping = false;
|
|
53147
|
+
exitHookInstalled = false;
|
|
53148
|
+
startupTimeoutMs;
|
|
53149
|
+
get port() {
|
|
53150
|
+
return this.deps.port;
|
|
53151
|
+
}
|
|
53152
|
+
async start() {
|
|
53153
|
+
const { logger } = this.deps;
|
|
53154
|
+
await (this.deps.killStaleImpl ?? killStaleSshd)({
|
|
53155
|
+
dataDir: this.deps.dataDir,
|
|
53156
|
+
ownPid: process.pid,
|
|
53157
|
+
logger
|
|
53158
|
+
});
|
|
53159
|
+
import_node_fs36.default.mkdirSync(this.sshdDir, { recursive: true, mode: 448 });
|
|
53160
|
+
import_node_fs36.default.mkdirSync(import_node_path37.default.join(this.sshdDir, "authorized_keys.d"), { recursive: true, mode: 448 });
|
|
53161
|
+
ensureJailScript(this.deps.dataDir);
|
|
53162
|
+
const hostKeyPath = import_node_path37.default.join(this.sshdDir, "host_key");
|
|
53163
|
+
if (!import_node_fs36.default.existsSync(hostKeyPath)) {
|
|
53164
|
+
await this.generateHostKey(hostKeyPath);
|
|
53165
|
+
}
|
|
53166
|
+
const akFile = import_node_path37.default.join(this.sshdDir, "authorized_keys.d", "clawd-contacts");
|
|
53167
|
+
if (!import_node_fs36.default.existsSync(akFile)) {
|
|
53168
|
+
import_node_fs36.default.writeFileSync(akFile, "", { mode: 384 });
|
|
53169
|
+
}
|
|
53170
|
+
const configPath = import_node_path37.default.join(this.sshdDir, "sshd_config");
|
|
53171
|
+
const config = buildSshdConfig({
|
|
53172
|
+
listenAddress: "127.0.0.1",
|
|
53173
|
+
port: this.deps.port,
|
|
53174
|
+
hostKeyPath,
|
|
53175
|
+
authorizedKeysFile: akFile,
|
|
53176
|
+
pidFilePath: import_node_path37.default.join(this.sshdDir, "sshd.pid")
|
|
53177
|
+
});
|
|
53178
|
+
import_node_fs36.default.writeFileSync(configPath, config, { mode: 384 });
|
|
53179
|
+
const sshdBin = this.deps.sshdBin ?? "/usr/sbin/sshd";
|
|
53180
|
+
const proc = (this.deps.spawnImpl ?? import_node_child_process11.spawn)(sshdBin, ["-D", "-e", "-f", configPath], {
|
|
53181
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
53182
|
+
});
|
|
53183
|
+
const logStream = import_node_fs36.default.createWriteStream(import_node_path37.default.join(this.sshdDir, "sshd.log"), {
|
|
53184
|
+
flags: "a",
|
|
53185
|
+
mode: 384
|
|
53186
|
+
});
|
|
53187
|
+
logStream.on("error", () => {
|
|
53188
|
+
});
|
|
53189
|
+
const tee = (c) => {
|
|
53190
|
+
logStream.write(String(c));
|
|
53191
|
+
};
|
|
53192
|
+
proc.stdout?.on("data", tee);
|
|
53193
|
+
proc.stderr?.on("data", tee);
|
|
53194
|
+
proc.once("exit", () => logStream.end());
|
|
53195
|
+
const ready = await waitForSshdReady(proc, this.startupTimeoutMs);
|
|
53196
|
+
if (!ready.ok) {
|
|
53197
|
+
try {
|
|
53198
|
+
proc.kill("SIGTERM");
|
|
53199
|
+
} catch {
|
|
53200
|
+
}
|
|
53201
|
+
const tail = ready.output.slice(-500);
|
|
53202
|
+
const msg = tail ? `${ready.error}
|
|
53203
|
+
${tail}` : ready.error;
|
|
53204
|
+
throw new Error(msg);
|
|
53205
|
+
}
|
|
53206
|
+
if (typeof proc.pid === "number") writeSshdPid(this.deps.dataDir, proc.pid);
|
|
53207
|
+
this.proc = proc;
|
|
53208
|
+
this.installProcessExitHandlersIfNeeded();
|
|
53209
|
+
this.attachExitListener(proc);
|
|
53210
|
+
logger?.info("sshd: up", { port: this.deps.port, pid: proc.pid ?? null });
|
|
53211
|
+
return { port: this.deps.port };
|
|
53212
|
+
}
|
|
53213
|
+
async stop() {
|
|
53214
|
+
this.stopping = true;
|
|
53215
|
+
const proc = this.proc;
|
|
53216
|
+
this.proc = null;
|
|
53217
|
+
if (!proc) {
|
|
53218
|
+
clearSshdPid(this.deps.dataDir);
|
|
53219
|
+
return;
|
|
53220
|
+
}
|
|
53221
|
+
proc.kill("SIGTERM");
|
|
53222
|
+
await new Promise((resolve6) => {
|
|
53223
|
+
const t = setTimeout(() => {
|
|
53224
|
+
try {
|
|
53225
|
+
proc.kill("SIGKILL");
|
|
53226
|
+
} catch {
|
|
53227
|
+
}
|
|
53228
|
+
resolve6();
|
|
53229
|
+
}, 5e3);
|
|
53230
|
+
proc.once("exit", () => {
|
|
53231
|
+
clearTimeout(t);
|
|
53232
|
+
resolve6();
|
|
53233
|
+
});
|
|
53234
|
+
});
|
|
53235
|
+
clearSshdPid(this.deps.dataDir);
|
|
53236
|
+
}
|
|
53237
|
+
killSync() {
|
|
53238
|
+
const proc = this.proc;
|
|
53239
|
+
this.proc = null;
|
|
53240
|
+
clearSshdPid(this.deps.dataDir);
|
|
53241
|
+
if (!proc) return;
|
|
53242
|
+
try {
|
|
53243
|
+
proc.kill("SIGTERM");
|
|
53244
|
+
} catch {
|
|
53245
|
+
}
|
|
53246
|
+
}
|
|
53247
|
+
attachExitListener(proc) {
|
|
53248
|
+
proc.on("exit", (code) => {
|
|
53249
|
+
this.deps.logger?.warn("sshd exited", { code });
|
|
53250
|
+
if (this.stopping) return;
|
|
53251
|
+
this.proc = null;
|
|
53252
|
+
this.deps.onSshdExit?.({ code });
|
|
53253
|
+
});
|
|
53254
|
+
}
|
|
53255
|
+
installProcessExitHandlersIfNeeded() {
|
|
53256
|
+
if (this.exitHookInstalled) return;
|
|
53257
|
+
if (this.deps.installProcessExitHandlers !== true) return;
|
|
53258
|
+
this.exitHookInstalled = true;
|
|
53259
|
+
const sync = () => this.killSync();
|
|
53260
|
+
process.once("exit", sync);
|
|
53261
|
+
process.once("SIGHUP", sync);
|
|
53262
|
+
process.once("uncaughtException", sync);
|
|
53263
|
+
}
|
|
53264
|
+
async generateHostKey(hostKeyPath) {
|
|
53265
|
+
const keygenBin = this.deps.keygenBin ?? "/usr/bin/ssh-keygen";
|
|
53266
|
+
await new Promise((resolve6, reject) => {
|
|
53267
|
+
const p2 = (this.deps.spawnImpl ?? import_node_child_process11.spawn)(
|
|
53268
|
+
keygenBin,
|
|
53269
|
+
["-t", "ed25519", "-f", hostKeyPath, "-N", "", "-q"],
|
|
53270
|
+
{ stdio: "ignore" }
|
|
53271
|
+
);
|
|
53272
|
+
p2.on("exit", (code) => code === 0 ? resolve6() : reject(new Error(`ssh-keygen exit ${code}`)));
|
|
53273
|
+
p2.on("error", reject);
|
|
53274
|
+
});
|
|
53275
|
+
try {
|
|
53276
|
+
import_node_fs36.default.chmodSync(hostKeyPath, 384);
|
|
53277
|
+
} catch {
|
|
53278
|
+
}
|
|
53279
|
+
}
|
|
53280
|
+
};
|
|
53281
|
+
async function waitForSshdReady(proc, timeoutMs) {
|
|
53282
|
+
return new Promise((resolve6) => {
|
|
53283
|
+
let settled = false;
|
|
53284
|
+
let buf = "";
|
|
53285
|
+
const finish = (r) => {
|
|
53286
|
+
if (settled) return;
|
|
53287
|
+
settled = true;
|
|
53288
|
+
cleanup();
|
|
53289
|
+
resolve6(r);
|
|
53290
|
+
};
|
|
53291
|
+
const onData = (chunk) => {
|
|
53292
|
+
buf += String(chunk);
|
|
53293
|
+
if (/Server listening on/i.test(buf)) finish({ ok: true });
|
|
53294
|
+
if (/fatal:/i.test(buf) || /error: Bind to port/i.test(buf)) {
|
|
53295
|
+
finish({ ok: false, error: "sshd startup failed", output: buf });
|
|
53296
|
+
}
|
|
53297
|
+
};
|
|
53298
|
+
const onExit = (code) => finish({ ok: false, error: `sshd exited before ready (code=${code})`, output: buf });
|
|
53299
|
+
const onErr = (err) => finish({ ok: false, error: `sshd spawn error: ${err.message}`, output: buf });
|
|
53300
|
+
const cleanup = () => {
|
|
53301
|
+
proc.stdout?.off("data", onData);
|
|
53302
|
+
proc.stderr?.off("data", onData);
|
|
53303
|
+
proc.off("exit", onExit);
|
|
53304
|
+
proc.off("error", onErr);
|
|
53305
|
+
clearTimeout(timer);
|
|
53306
|
+
};
|
|
53307
|
+
proc.stdout?.on("data", onData);
|
|
53308
|
+
proc.stderr?.on("data", onData);
|
|
53309
|
+
proc.on("exit", onExit);
|
|
53310
|
+
proc.on("error", onErr);
|
|
53311
|
+
const timer = setTimeout(
|
|
53312
|
+
() => finish({ ok: false, error: `sshd startup timeout after ${timeoutMs}ms`, output: buf }),
|
|
53313
|
+
timeoutMs
|
|
53314
|
+
);
|
|
53315
|
+
});
|
|
53316
|
+
}
|
|
53317
|
+
|
|
53318
|
+
// src/sshd/authorized-keys.ts
|
|
53319
|
+
var import_node_fs37 = __toESM(require("fs"), 1);
|
|
53320
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
53321
|
+
var JAIL_BIN_PATH_ENV = "CLAWD_JAIL_BIN_PATH";
|
|
53322
|
+
var AUTHORIZED_KEYS_FILE = "clawd-contacts";
|
|
53323
|
+
function jailBinPath() {
|
|
53324
|
+
return process.env[JAIL_BIN_PATH_ENV] ?? import_node_path38.default.join(process.env.HOME ?? "", ".clawd", "bin", "clawd-ssh-jail");
|
|
53325
|
+
}
|
|
53326
|
+
function rebuildAuthorizedKeys(store, sshdDir) {
|
|
53327
|
+
const akDir = import_node_path38.default.join(sshdDir, "authorized_keys.d");
|
|
53328
|
+
const target = import_node_path38.default.join(akDir, AUTHORIZED_KEYS_FILE);
|
|
53329
|
+
import_node_fs37.default.mkdirSync(akDir, { recursive: true, mode: 448 });
|
|
53330
|
+
const lines = ["# managed by clawd; do not edit", ""];
|
|
53331
|
+
for (const c of store.list()) {
|
|
53332
|
+
if (!c.sshAllowed) continue;
|
|
53333
|
+
const safe = /^[A-Za-z0-9_.-]+$/.test(c.deviceId);
|
|
53334
|
+
if (!safe) continue;
|
|
53335
|
+
const pubkey = readIssuedPubkey(sshdDir, c.deviceId);
|
|
53336
|
+
if (!pubkey) continue;
|
|
53337
|
+
const bin = jailBinPath();
|
|
53338
|
+
lines.push(`command="${bin} ${c.deviceId}",restrict ${pubkey.trim()}`);
|
|
53339
|
+
lines.push(`# contact:${c.deviceId}`);
|
|
53340
|
+
}
|
|
53341
|
+
const body = lines.join("\n") + "\n";
|
|
53342
|
+
const tmp = `${target}.tmp-${process.pid}-${Date.now()}`;
|
|
53343
|
+
import_node_fs37.default.writeFileSync(tmp, body, { mode: 384 });
|
|
53344
|
+
import_node_fs37.default.renameSync(tmp, target);
|
|
53345
|
+
}
|
|
53346
|
+
function readIssuedPubkey(sshdDir, deviceId) {
|
|
53347
|
+
const safeId = deviceId.replace(/[\/\\]/g, "_");
|
|
53348
|
+
const p2 = import_node_path38.default.join(sshdDir, "keys", `${safeId}.ed25519.pub`);
|
|
53349
|
+
try {
|
|
53350
|
+
return import_node_fs37.default.readFileSync(p2, "utf8");
|
|
53351
|
+
} catch {
|
|
53352
|
+
return null;
|
|
53353
|
+
}
|
|
53354
|
+
}
|
|
53355
|
+
|
|
53356
|
+
// src/sshd/contact-key-puller.ts
|
|
53357
|
+
var import_node_fs39 = __toESM(require("fs"), 1);
|
|
53358
|
+
var import_node_path40 = __toESM(require("path"), 1);
|
|
53359
|
+
init_peer_forward();
|
|
53360
|
+
|
|
53361
|
+
// src/sshd/contact-ssh-log.ts
|
|
53362
|
+
var import_node_fs38 = __toESM(require("fs"), 1);
|
|
53363
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
53364
|
+
function createContactSshLog(dataDir) {
|
|
53365
|
+
const file = import_node_path39.default.join(dataDir, "contact-ssh.log");
|
|
53366
|
+
function append(level, tag, message, meta) {
|
|
53367
|
+
const time = (/* @__PURE__ */ new Date()).toISOString();
|
|
53368
|
+
let line = `[${time}] [${level}] [${tag}] ${message}`;
|
|
53369
|
+
if (meta && Object.keys(meta).length > 0) {
|
|
53370
|
+
try {
|
|
53371
|
+
line += " " + JSON.stringify(meta);
|
|
53372
|
+
} catch {
|
|
53373
|
+
line += " [meta-serialize-failed]";
|
|
53374
|
+
}
|
|
53375
|
+
}
|
|
53376
|
+
line += "\n";
|
|
53377
|
+
try {
|
|
53378
|
+
import_node_fs38.default.mkdirSync(import_node_path39.default.dirname(file), { recursive: true });
|
|
53379
|
+
import_node_fs38.default.appendFileSync(file, line, { mode: 384 });
|
|
53380
|
+
} catch {
|
|
53381
|
+
}
|
|
53382
|
+
}
|
|
53383
|
+
return {
|
|
53384
|
+
info: (tag, message, meta) => append("INFO", tag, message, meta),
|
|
53385
|
+
warn: (tag, message, meta) => append("WARN", tag, message, meta),
|
|
53386
|
+
error: (tag, message, meta) => append("ERROR", tag, message, meta)
|
|
53387
|
+
};
|
|
53388
|
+
}
|
|
53389
|
+
var nullContactSshLog = {
|
|
53390
|
+
info: () => {
|
|
53391
|
+
},
|
|
53392
|
+
warn: () => {
|
|
53393
|
+
},
|
|
53394
|
+
error: () => {
|
|
53395
|
+
}
|
|
53396
|
+
};
|
|
53397
|
+
|
|
53398
|
+
// src/sshd/contact-key-puller.ts
|
|
53399
|
+
var CONTACT_KEYS_DIR = "contact-ssh-keys";
|
|
53400
|
+
function safeContactKeyPath(dataDir, deviceId) {
|
|
53401
|
+
const safeId = deviceId.replace(/[\/\\]/g, "_");
|
|
53402
|
+
return import_node_path40.default.join(dataDir, CONTACT_KEYS_DIR, `${safeId}.ed25519`);
|
|
53403
|
+
}
|
|
53404
|
+
async function pullContactSshKeyOnce(deps) {
|
|
53405
|
+
const forward = deps.forwardImpl ?? ((c) => forwardContactSshKeyIssueToPeer({
|
|
53406
|
+
contact: { remoteUrl: c.remoteUrl, connectToken: c.connectToken },
|
|
53407
|
+
selfDeviceIdForRequest: c.deviceId
|
|
53408
|
+
}));
|
|
53409
|
+
const contacts = deps.store.list().filter((c) => c.connectToken.length > 0);
|
|
53410
|
+
const results = await Promise.all(
|
|
53411
|
+
contacts.map(async (c) => {
|
|
53412
|
+
try {
|
|
53413
|
+
const r = await forward(c);
|
|
53414
|
+
return { contact: c, result: r };
|
|
53415
|
+
} catch (err) {
|
|
53416
|
+
return {
|
|
53417
|
+
contact: c,
|
|
53418
|
+
result: {
|
|
53419
|
+
ok: false,
|
|
53420
|
+
code: "NETWORK",
|
|
53421
|
+
message: err instanceof Error ? err.message : String(err)
|
|
53422
|
+
}
|
|
53423
|
+
};
|
|
53424
|
+
}
|
|
53425
|
+
})
|
|
53426
|
+
);
|
|
53427
|
+
const errors = [];
|
|
53428
|
+
let pulled = 0;
|
|
53429
|
+
const sshLog = deps.sshLog ?? nullContactSshLog;
|
|
53430
|
+
for (const { contact, result } of results) {
|
|
53431
|
+
if (result.ok) {
|
|
53432
|
+
writeKeyFile(deps.dataDir, contact.deviceId, result.privateKeyPem);
|
|
53433
|
+
pulled++;
|
|
53434
|
+
deps.logger?.info("contact-key-puller: pulled", { deviceId: contact.deviceId });
|
|
53435
|
+
sshLog.info("key.pull.success", "B \u4FA7\u4ECE A \u62C9\u5230 privkey \u5E76\u843D\u76D8", {
|
|
53436
|
+
peerDeviceId: contact.deviceId,
|
|
53437
|
+
peerDisplayName: contact.displayName
|
|
53438
|
+
});
|
|
53439
|
+
} else if (result.code === "UNAUTHORIZED" || result.code === "FORBIDDEN") {
|
|
53440
|
+
const hadStale = removeKeyFile(deps.dataDir, contact.deviceId);
|
|
53441
|
+
sshLog.info("key.pull.rejected", "A \u4FA7\u672A\u6388\u6743\u6211 SSH\uFF08\u6B63\u5E38\u72B6\u6001\uFF1B\u8F6E\u8BE2\u7EE7\u7EED\uFF09", {
|
|
53442
|
+
peerDeviceId: contact.deviceId,
|
|
53443
|
+
peerDisplayName: contact.displayName,
|
|
53444
|
+
clearedStalePrivkey: hadStale
|
|
53445
|
+
});
|
|
53446
|
+
} else {
|
|
53447
|
+
errors.push({
|
|
53448
|
+
deviceId: contact.deviceId,
|
|
53449
|
+
code: result.code,
|
|
53450
|
+
message: result.message
|
|
53451
|
+
});
|
|
53452
|
+
deps.logger?.warn("contact-key-puller: pull failed", {
|
|
53453
|
+
deviceId: contact.deviceId,
|
|
53454
|
+
code: result.code,
|
|
53455
|
+
message: result.message
|
|
53456
|
+
});
|
|
53457
|
+
sshLog.warn("key.pull.error", "\u62C9 privkey \u65F6\u7F51\u7EDC/\u534F\u8BAE\u9519\u8BEF", {
|
|
53458
|
+
peerDeviceId: contact.deviceId,
|
|
53459
|
+
code: result.code,
|
|
53460
|
+
message: result.message
|
|
53461
|
+
});
|
|
53462
|
+
}
|
|
53463
|
+
}
|
|
53464
|
+
return { pulled, errors };
|
|
53465
|
+
}
|
|
53466
|
+
function writeKeyFile(dataDir, deviceId, pem) {
|
|
53467
|
+
const p2 = safeContactKeyPath(dataDir, deviceId);
|
|
53468
|
+
import_node_fs39.default.mkdirSync(import_node_path40.default.dirname(p2), { recursive: true, mode: 448 });
|
|
53469
|
+
import_node_fs39.default.writeFileSync(p2, pem, { mode: 384 });
|
|
53470
|
+
}
|
|
53471
|
+
function removeKeyFile(dataDir, deviceId) {
|
|
53472
|
+
try {
|
|
53473
|
+
import_node_fs39.default.unlinkSync(safeContactKeyPath(dataDir, deviceId));
|
|
53474
|
+
return true;
|
|
53475
|
+
} catch {
|
|
53476
|
+
return false;
|
|
53477
|
+
}
|
|
53478
|
+
}
|
|
53479
|
+
var ContactKeyPuller = class {
|
|
53480
|
+
constructor(deps) {
|
|
53481
|
+
this.deps = deps;
|
|
53482
|
+
}
|
|
53483
|
+
deps;
|
|
53484
|
+
timer = null;
|
|
53485
|
+
async start() {
|
|
53486
|
+
const interval = this.deps.intervalMs ?? 6e4;
|
|
53487
|
+
void this.tick();
|
|
53488
|
+
this.timer = setInterval(() => void this.tick(), interval);
|
|
53489
|
+
this.timer.unref();
|
|
53490
|
+
}
|
|
53491
|
+
stop() {
|
|
53492
|
+
if (this.timer) {
|
|
53493
|
+
clearInterval(this.timer);
|
|
53494
|
+
this.timer = null;
|
|
53495
|
+
}
|
|
53496
|
+
}
|
|
53497
|
+
async tick() {
|
|
53498
|
+
try {
|
|
53499
|
+
await pullContactSshKeyOnce(this.deps);
|
|
53500
|
+
} catch (err) {
|
|
53501
|
+
this.deps.logger?.warn("contact-key-puller: tick failed", {
|
|
53502
|
+
err: err instanceof Error ? err.message : String(err)
|
|
53503
|
+
});
|
|
53504
|
+
}
|
|
53505
|
+
}
|
|
53506
|
+
};
|
|
53507
|
+
|
|
53508
|
+
// src/sshd/ssh-tunnel-relay.ts
|
|
53509
|
+
var import_node_net2 = __toESM(require("net"), 1);
|
|
53510
|
+
async function handleSshTunnelUpgrade(req, socket, head, deps) {
|
|
53511
|
+
const sshLog = deps.sshLog ?? nullContactSshLog;
|
|
53512
|
+
const clientAddr = (req.socket && "remoteAddress" in req.socket ? req.socket.remoteAddress : null) ?? "unknown";
|
|
53513
|
+
const auth = req.headers.authorization ?? "";
|
|
53514
|
+
const m2 = /^Bearer\s+(.+)$/i.exec(auth);
|
|
53515
|
+
if (!m2) {
|
|
53516
|
+
sshLog.warn("tunnel.auth-failed", "/rpc/ssh-tunnel \u8BF7\u6C42\u7F3A Bearer token", {
|
|
53517
|
+
clientAddr
|
|
53518
|
+
});
|
|
53519
|
+
socket.write(
|
|
53520
|
+
"HTTP/1.1 401 Unauthorized\r\nContent-Length: 0\r\n\r\n"
|
|
53521
|
+
);
|
|
53522
|
+
socket.destroy();
|
|
53523
|
+
return;
|
|
53524
|
+
}
|
|
53525
|
+
const token = m2[1].trim();
|
|
53526
|
+
let ok = false;
|
|
53527
|
+
try {
|
|
53528
|
+
ok = await deps.authorize(token);
|
|
53529
|
+
} catch (err) {
|
|
53530
|
+
deps.logger?.warn("ssh-tunnel: authorize threw", {
|
|
53531
|
+
err: err instanceof Error ? err.message : String(err)
|
|
53532
|
+
});
|
|
53533
|
+
}
|
|
53534
|
+
if (!ok) {
|
|
53535
|
+
sshLog.warn("tunnel.auth-failed", "/rpc/ssh-tunnel Bearer token \u9A8C\u8BC1\u5931\u8D25", {
|
|
53536
|
+
clientAddr
|
|
53537
|
+
});
|
|
53538
|
+
socket.write(
|
|
53539
|
+
"HTTP/1.1 401 Unauthorized\r\nContent-Length: 0\r\n\r\n"
|
|
53540
|
+
);
|
|
53541
|
+
socket.destroy();
|
|
53542
|
+
return;
|
|
53543
|
+
}
|
|
53544
|
+
sshLog.info("tunnel.auth-ok", "/rpc/ssh-tunnel \u8BA4\u8BC1\u901A\u8FC7\uFF0C\u5347\u7EA7 WS", {
|
|
53545
|
+
clientAddr
|
|
53546
|
+
});
|
|
53547
|
+
deps.wss.handleUpgrade(req, socket, head, (ws) => {
|
|
53548
|
+
pumpWsToSshd(ws, deps, clientAddr);
|
|
53549
|
+
});
|
|
53550
|
+
}
|
|
53551
|
+
function pumpWsToSshd(ws, deps, clientAddr) {
|
|
53552
|
+
const sshLog = deps.sshLog ?? nullContactSshLog;
|
|
53553
|
+
const tcp = import_node_net2.default.connect(deps.sshdPort, "127.0.0.1");
|
|
53554
|
+
let tcpConnected = false;
|
|
53555
|
+
let bytesFromWs = 0;
|
|
53556
|
+
let bytesToWs = 0;
|
|
53557
|
+
const startedAt = Date.now();
|
|
53558
|
+
const cleanup = (reason) => {
|
|
53559
|
+
try {
|
|
53560
|
+
ws.close(1e3, reason);
|
|
53561
|
+
} catch {
|
|
53562
|
+
}
|
|
53563
|
+
try {
|
|
53564
|
+
tcp.destroy();
|
|
53565
|
+
} catch {
|
|
53566
|
+
}
|
|
53567
|
+
sshLog.info("tunnel.disconnected", "SSH tunnel \u4F1A\u8BDD\u7ED3\u675F", {
|
|
53568
|
+
clientAddr,
|
|
53569
|
+
reason,
|
|
53570
|
+
bytesFromWs,
|
|
53571
|
+
bytesToWs,
|
|
53572
|
+
durationMs: Date.now() - startedAt
|
|
53573
|
+
});
|
|
53574
|
+
};
|
|
53575
|
+
tcp.once("connect", () => {
|
|
53576
|
+
tcpConnected = true;
|
|
53577
|
+
deps.logger?.info("ssh-tunnel: tcp connected to sshd", { port: deps.sshdPort });
|
|
53578
|
+
sshLog.info("tunnel.connected", "WS \u2194 \u672C\u673A sshd raw byte relay \u5DF2\u5C31\u7EEA", {
|
|
53579
|
+
clientAddr,
|
|
53580
|
+
sshdPort: deps.sshdPort
|
|
53581
|
+
});
|
|
53582
|
+
});
|
|
53583
|
+
tcp.on("data", (chunk) => {
|
|
53584
|
+
if (ws.readyState === ws.OPEN) {
|
|
53585
|
+
bytesToWs += chunk.length;
|
|
53586
|
+
ws.send(chunk, { binary: true });
|
|
53587
|
+
}
|
|
53588
|
+
});
|
|
53589
|
+
tcp.on("error", (err) => {
|
|
53590
|
+
deps.logger?.warn("ssh-tunnel: tcp error", { err: err.message, tcpConnected });
|
|
53591
|
+
sshLog.error(
|
|
53592
|
+
tcpConnected ? "tunnel.tcp-error" : "tunnel.tcp-connect-failed",
|
|
53593
|
+
tcpConnected ? "WS \u2194 sshd relay \u671F\u95F4 TCP \u4FA7\u51FA\u9519" : "\u65E0\u6CD5\u8FDE\u5230\u672C\u673A sshd\uFF08sshd \u672A\u8D77\uFF1F\u7AEF\u53E3\u9519\uFF1F\uFF09",
|
|
53594
|
+
{ clientAddr, sshdPort: deps.sshdPort, err: err.message }
|
|
53595
|
+
);
|
|
53596
|
+
cleanup("tcp error");
|
|
53597
|
+
});
|
|
53598
|
+
tcp.on("end", () => cleanup("tcp end"));
|
|
53599
|
+
tcp.on("close", () => cleanup("tcp close"));
|
|
53600
|
+
ws.on("message", (data) => {
|
|
53601
|
+
let buf = null;
|
|
53602
|
+
if (Buffer.isBuffer(data)) buf = data;
|
|
53603
|
+
else if (Array.isArray(data)) buf = Buffer.concat(data);
|
|
53604
|
+
else if (data instanceof ArrayBuffer) buf = Buffer.from(data);
|
|
53605
|
+
if (buf) {
|
|
53606
|
+
bytesFromWs += buf.length;
|
|
53607
|
+
tcp.write(buf);
|
|
53608
|
+
}
|
|
53609
|
+
});
|
|
53610
|
+
ws.on("close", () => cleanup("ws close"));
|
|
53611
|
+
ws.on("error", (err) => {
|
|
53612
|
+
deps.logger?.warn("ssh-tunnel: ws error", { err: err.message });
|
|
53613
|
+
cleanup("ws error");
|
|
53614
|
+
});
|
|
53615
|
+
}
|
|
53616
|
+
|
|
52645
53617
|
// src/tunnel/device-key.ts
|
|
52646
53618
|
var import_node_os14 = __toESM(require("os"), 1);
|
|
52647
|
-
var
|
|
52648
|
-
var
|
|
53619
|
+
var import_node_path41 = __toESM(require("path"), 1);
|
|
53620
|
+
var import_node_crypto10 = __toESM(require("crypto"), 1);
|
|
52649
53621
|
var DERIVE_SALT = "clawd-tunnel-device-v1";
|
|
52650
53622
|
function deriveStableDeviceKey(opts = {}) {
|
|
52651
53623
|
const hostname = opts.hostname ?? import_node_os14.default.hostname();
|
|
52652
53624
|
const uid = opts.uid ?? (typeof import_node_os14.default.userInfo === "function" ? import_node_os14.default.userInfo().uid : 0);
|
|
52653
53625
|
const home = opts.home ?? import_node_os14.default.homedir();
|
|
52654
|
-
const defaultDataDir =
|
|
52655
|
-
const normalizedDataDir = opts.dataDir ?
|
|
53626
|
+
const defaultDataDir = import_node_path41.default.resolve(import_node_path41.default.join(home, ".clawd"));
|
|
53627
|
+
const normalizedDataDir = opts.dataDir ? import_node_path41.default.resolve(opts.dataDir) : null;
|
|
52656
53628
|
const isDefaultDir = normalizedDataDir == null || normalizedDataDir === defaultDataDir;
|
|
52657
53629
|
const input = isDefaultDir ? `${hostname}::${uid}` : `${hostname}::${uid}::${normalizedDataDir}`;
|
|
52658
|
-
return
|
|
53630
|
+
return import_node_crypto10.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
|
|
52659
53631
|
}
|
|
52660
53632
|
|
|
52661
53633
|
// src/auth-store.ts
|
|
52662
|
-
var
|
|
52663
|
-
var
|
|
52664
|
-
var
|
|
53634
|
+
var import_node_fs40 = __toESM(require("fs"), 1);
|
|
53635
|
+
var import_node_path42 = __toESM(require("path"), 1);
|
|
53636
|
+
var import_node_crypto11 = __toESM(require("crypto"), 1);
|
|
52665
53637
|
var AUTH_FILE_NAME = "auth.json";
|
|
52666
53638
|
function authFilePath(dataDir) {
|
|
52667
|
-
return
|
|
53639
|
+
return import_node_path42.default.join(dataDir, AUTH_FILE_NAME);
|
|
52668
53640
|
}
|
|
52669
53641
|
function loadOrCreateAuthFile(opts) {
|
|
52670
53642
|
const file = authFilePath(opts.dataDir);
|
|
@@ -52693,14 +53665,14 @@ function loadOrCreateAuthFile(opts) {
|
|
|
52693
53665
|
return next;
|
|
52694
53666
|
}
|
|
52695
53667
|
function defaultGenerateToken() {
|
|
52696
|
-
return
|
|
53668
|
+
return import_node_crypto11.default.randomBytes(32).toString("base64url");
|
|
52697
53669
|
}
|
|
52698
53670
|
function defaultGenerateOwnerPrincipalId() {
|
|
52699
|
-
return `owner-${
|
|
53671
|
+
return `owner-${import_node_crypto11.default.randomUUID()}`;
|
|
52700
53672
|
}
|
|
52701
53673
|
function readAuthFile(file) {
|
|
52702
53674
|
try {
|
|
52703
|
-
const raw =
|
|
53675
|
+
const raw = import_node_fs40.default.readFileSync(file, "utf8");
|
|
52704
53676
|
const parsed = JSON.parse(raw);
|
|
52705
53677
|
if (typeof parsed?.token !== "string" || parsed.token.length === 0) {
|
|
52706
53678
|
return null;
|
|
@@ -52720,25 +53692,25 @@ function readAuthFile(file) {
|
|
|
52720
53692
|
}
|
|
52721
53693
|
}
|
|
52722
53694
|
function writeAuthFile(file, content) {
|
|
52723
|
-
|
|
52724
|
-
|
|
53695
|
+
import_node_fs40.default.mkdirSync(import_node_path42.default.dirname(file), { recursive: true });
|
|
53696
|
+
import_node_fs40.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
|
|
52725
53697
|
try {
|
|
52726
|
-
|
|
53698
|
+
import_node_fs40.default.chmodSync(file, 384);
|
|
52727
53699
|
} catch {
|
|
52728
53700
|
}
|
|
52729
53701
|
}
|
|
52730
53702
|
|
|
52731
53703
|
// src/owner-profile.ts
|
|
52732
|
-
var
|
|
53704
|
+
var import_node_fs41 = __toESM(require("fs"), 1);
|
|
52733
53705
|
var import_node_os15 = __toESM(require("os"), 1);
|
|
52734
|
-
var
|
|
53706
|
+
var import_node_path43 = __toESM(require("path"), 1);
|
|
52735
53707
|
var PROFILE_FILENAME = "profile.json";
|
|
52736
53708
|
function loadOwnerDisplayName(dataDir) {
|
|
52737
53709
|
const fallback = import_node_os15.default.userInfo().username;
|
|
52738
|
-
const profilePath =
|
|
53710
|
+
const profilePath = import_node_path43.default.join(dataDir, PROFILE_FILENAME);
|
|
52739
53711
|
let raw;
|
|
52740
53712
|
try {
|
|
52741
|
-
raw =
|
|
53713
|
+
raw = import_node_fs41.default.readFileSync(profilePath, "utf8");
|
|
52742
53714
|
} catch {
|
|
52743
53715
|
return fallback;
|
|
52744
53716
|
}
|
|
@@ -52761,18 +53733,18 @@ function loadOwnerDisplayName(dataDir) {
|
|
|
52761
53733
|
}
|
|
52762
53734
|
|
|
52763
53735
|
// src/feishu-auth/owner-identity-store.ts
|
|
52764
|
-
var
|
|
52765
|
-
var
|
|
53736
|
+
var import_node_fs42 = __toESM(require("fs"), 1);
|
|
53737
|
+
var import_node_path44 = __toESM(require("path"), 1);
|
|
52766
53738
|
var OWNER_IDENTITY_FILE_NAME = "owner-identity.json";
|
|
52767
53739
|
var OwnerIdentityStore = class {
|
|
52768
53740
|
file;
|
|
52769
53741
|
constructor(dataDir) {
|
|
52770
|
-
this.file =
|
|
53742
|
+
this.file = import_node_path44.default.join(dataDir, OWNER_IDENTITY_FILE_NAME);
|
|
52771
53743
|
}
|
|
52772
53744
|
read() {
|
|
52773
53745
|
let raw;
|
|
52774
53746
|
try {
|
|
52775
|
-
raw =
|
|
53747
|
+
raw = import_node_fs42.default.readFileSync(this.file, "utf8");
|
|
52776
53748
|
} catch {
|
|
52777
53749
|
return null;
|
|
52778
53750
|
}
|
|
@@ -52800,16 +53772,16 @@ var OwnerIdentityStore = class {
|
|
|
52800
53772
|
};
|
|
52801
53773
|
}
|
|
52802
53774
|
write(record) {
|
|
52803
|
-
|
|
52804
|
-
|
|
53775
|
+
import_node_fs42.default.mkdirSync(import_node_path44.default.dirname(this.file), { recursive: true });
|
|
53776
|
+
import_node_fs42.default.writeFileSync(this.file, JSON.stringify(record, null, 2), { mode: 384 });
|
|
52805
53777
|
try {
|
|
52806
|
-
|
|
53778
|
+
import_node_fs42.default.chmodSync(this.file, 384);
|
|
52807
53779
|
} catch {
|
|
52808
53780
|
}
|
|
52809
53781
|
}
|
|
52810
53782
|
clear() {
|
|
52811
53783
|
try {
|
|
52812
|
-
|
|
53784
|
+
import_node_fs42.default.unlinkSync(this.file);
|
|
52813
53785
|
} catch (err) {
|
|
52814
53786
|
const code = err?.code;
|
|
52815
53787
|
if (code !== "ENOENT") throw err;
|
|
@@ -52818,7 +53790,7 @@ var OwnerIdentityStore = class {
|
|
|
52818
53790
|
};
|
|
52819
53791
|
|
|
52820
53792
|
// src/feishu-auth/login-flow.ts
|
|
52821
|
-
var
|
|
53793
|
+
var import_node_crypto12 = __toESM(require("crypto"), 1);
|
|
52822
53794
|
var STATE_TTL_MS = 5 * 60 * 1e3;
|
|
52823
53795
|
var LoginFlow = class {
|
|
52824
53796
|
constructor(deps) {
|
|
@@ -52827,7 +53799,7 @@ var LoginFlow = class {
|
|
|
52827
53799
|
deps;
|
|
52828
53800
|
pendingStates = /* @__PURE__ */ new Map();
|
|
52829
53801
|
start() {
|
|
52830
|
-
const state =
|
|
53802
|
+
const state = import_node_crypto12.default.randomBytes(16).toString("base64url");
|
|
52831
53803
|
const now = (this.deps.now ?? Date.now)();
|
|
52832
53804
|
this.pendingStates.set(state, now);
|
|
52833
53805
|
this.gcExpired(now);
|
|
@@ -52930,9 +53902,9 @@ var CentralClientError = class extends Error {
|
|
|
52930
53902
|
code;
|
|
52931
53903
|
cause;
|
|
52932
53904
|
};
|
|
52933
|
-
async function centralRequest(opts,
|
|
53905
|
+
async function centralRequest(opts, path76, init) {
|
|
52934
53906
|
const f = opts.fetchImpl ?? globalThis.fetch;
|
|
52935
|
-
const url = `${opts.api.replace(/\/+$/, "")}${
|
|
53907
|
+
const url = `${opts.api.replace(/\/+$/, "")}${path76}`;
|
|
52936
53908
|
const ctrl = new AbortController();
|
|
52937
53909
|
const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 15e3);
|
|
52938
53910
|
let res;
|
|
@@ -53074,8 +54046,8 @@ function verifyConnectToken(args) {
|
|
|
53074
54046
|
}
|
|
53075
54047
|
|
|
53076
54048
|
// src/feishu-auth/server-key.ts
|
|
53077
|
-
var
|
|
53078
|
-
var
|
|
54049
|
+
var fs52 = __toESM(require("fs"), 1);
|
|
54050
|
+
var path53 = __toESM(require("path"), 1);
|
|
53079
54051
|
var FILE_NAME2 = "server-signing-key.json";
|
|
53080
54052
|
var ServerKeyStore = class {
|
|
53081
54053
|
constructor(dataDir) {
|
|
@@ -53083,12 +54055,12 @@ var ServerKeyStore = class {
|
|
|
53083
54055
|
}
|
|
53084
54056
|
dataDir;
|
|
53085
54057
|
filePath() {
|
|
53086
|
-
return
|
|
54058
|
+
return path53.join(this.dataDir, FILE_NAME2);
|
|
53087
54059
|
}
|
|
53088
54060
|
/** 读缓存的公钥;无缓存 / 损坏 → null(调用方决定是否触发拉取) */
|
|
53089
54061
|
read() {
|
|
53090
54062
|
try {
|
|
53091
|
-
const raw =
|
|
54063
|
+
const raw = fs52.readFileSync(this.filePath(), "utf8");
|
|
53092
54064
|
const parsed = JSON.parse(raw);
|
|
53093
54065
|
if (typeof parsed.publicKeyPem === "string" && parsed.publicKeyPem.includes("PUBLIC KEY")) {
|
|
53094
54066
|
return parsed.publicKeyPem;
|
|
@@ -53103,12 +54075,12 @@ var ServerKeyStore = class {
|
|
|
53103
54075
|
publicKeyPem,
|
|
53104
54076
|
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
53105
54077
|
};
|
|
53106
|
-
|
|
53107
|
-
|
|
54078
|
+
fs52.mkdirSync(this.dataDir, { recursive: true });
|
|
54079
|
+
fs52.writeFileSync(this.filePath(), JSON.stringify(content, null, 2), { mode: 384 });
|
|
53108
54080
|
}
|
|
53109
54081
|
clear() {
|
|
53110
54082
|
try {
|
|
53111
|
-
|
|
54083
|
+
fs52.unlinkSync(this.filePath());
|
|
53112
54084
|
} catch {
|
|
53113
54085
|
}
|
|
53114
54086
|
}
|
|
@@ -53121,12 +54093,12 @@ init_protocol();
|
|
|
53121
54093
|
init_protocol();
|
|
53122
54094
|
|
|
53123
54095
|
// src/session/fork.ts
|
|
53124
|
-
var
|
|
54096
|
+
var import_node_fs43 = __toESM(require("fs"), 1);
|
|
53125
54097
|
var import_node_os16 = __toESM(require("os"), 1);
|
|
53126
|
-
var
|
|
54098
|
+
var import_node_path45 = __toESM(require("path"), 1);
|
|
53127
54099
|
init_claude_history();
|
|
53128
54100
|
function readJsonlEntries(file) {
|
|
53129
|
-
const raw =
|
|
54101
|
+
const raw = import_node_fs43.default.readFileSync(file, "utf8");
|
|
53130
54102
|
const out = [];
|
|
53131
54103
|
for (const line of raw.split("\n")) {
|
|
53132
54104
|
const t = line.trim();
|
|
@@ -53139,10 +54111,10 @@ function readJsonlEntries(file) {
|
|
|
53139
54111
|
return out;
|
|
53140
54112
|
}
|
|
53141
54113
|
function forkSession(input) {
|
|
53142
|
-
const baseDir = input.baseDir ??
|
|
53143
|
-
const projectDir =
|
|
53144
|
-
const sourceFile =
|
|
53145
|
-
if (!
|
|
54114
|
+
const baseDir = input.baseDir ?? import_node_path45.default.join(import_node_os16.default.homedir(), ".claude");
|
|
54115
|
+
const projectDir = import_node_path45.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
|
|
54116
|
+
const sourceFile = import_node_path45.default.join(projectDir, `${input.toolSessionId}.jsonl`);
|
|
54117
|
+
if (!import_node_fs43.default.existsSync(sourceFile)) {
|
|
53146
54118
|
throw new Error(`fork: source transcript not found: ${sourceFile}`);
|
|
53147
54119
|
}
|
|
53148
54120
|
const entries = readJsonlEntries(sourceFile);
|
|
@@ -53172,9 +54144,9 @@ function forkSession(input) {
|
|
|
53172
54144
|
}
|
|
53173
54145
|
forkedLines.push(JSON.stringify(forked));
|
|
53174
54146
|
}
|
|
53175
|
-
const forkedFilePath =
|
|
53176
|
-
|
|
53177
|
-
|
|
54147
|
+
const forkedFilePath = import_node_path45.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
|
|
54148
|
+
import_node_fs43.default.mkdirSync(projectDir, { recursive: true });
|
|
54149
|
+
import_node_fs43.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
|
|
53178
54150
|
return { forkedToolSessionId, forkedFilePath };
|
|
53179
54151
|
}
|
|
53180
54152
|
|
|
@@ -53526,7 +54498,7 @@ function buildPermissionHandlers(deps) {
|
|
|
53526
54498
|
}
|
|
53527
54499
|
|
|
53528
54500
|
// src/handlers/history.ts
|
|
53529
|
-
var
|
|
54501
|
+
var path56 = __toESM(require("path"), 1);
|
|
53530
54502
|
init_protocol();
|
|
53531
54503
|
|
|
53532
54504
|
// src/session/recent-dirs.ts
|
|
@@ -53544,7 +54516,7 @@ function listRecentDirs(store, limit = 50) {
|
|
|
53544
54516
|
}
|
|
53545
54517
|
|
|
53546
54518
|
// src/permission/persona-paths.ts
|
|
53547
|
-
var
|
|
54519
|
+
var path55 = __toESM(require("path"), 1);
|
|
53548
54520
|
function getAllowedPersonaIds(grants, action) {
|
|
53549
54521
|
const ids = /* @__PURE__ */ new Set();
|
|
53550
54522
|
for (const g2 of grants) {
|
|
@@ -53557,42 +54529,42 @@ function getAllowedPersonaIds(grants, action) {
|
|
|
53557
54529
|
return ids;
|
|
53558
54530
|
}
|
|
53559
54531
|
function isGuestPathAllowed(grants, absPath, personaRoot, action = "read", userWorkDir) {
|
|
53560
|
-
const target =
|
|
54532
|
+
const target = path55.resolve(absPath);
|
|
53561
54533
|
if (userWorkDir) {
|
|
53562
|
-
const u =
|
|
53563
|
-
const usep = u.endsWith(
|
|
54534
|
+
const u = path55.resolve(userWorkDir);
|
|
54535
|
+
const usep = u.endsWith(path55.sep) ? "" : path55.sep;
|
|
53564
54536
|
if (target === u || target.startsWith(u + usep)) return true;
|
|
53565
54537
|
}
|
|
53566
|
-
const root =
|
|
53567
|
-
const sep3 = root.endsWith(
|
|
54538
|
+
const root = path55.resolve(personaRoot);
|
|
54539
|
+
const sep3 = root.endsWith(path55.sep) ? "" : path55.sep;
|
|
53568
54540
|
if (!target.startsWith(root + sep3)) return false;
|
|
53569
|
-
const rel =
|
|
54541
|
+
const rel = path55.relative(root, target);
|
|
53570
54542
|
if (!rel || rel.startsWith("..")) return false;
|
|
53571
|
-
const personaId = rel.split(
|
|
54543
|
+
const personaId = rel.split(path55.sep)[0];
|
|
53572
54544
|
if (!personaId) return false;
|
|
53573
54545
|
const allowed = getAllowedPersonaIds(grants, action);
|
|
53574
54546
|
if (allowed === "*") return true;
|
|
53575
54547
|
return allowed.has(personaId);
|
|
53576
54548
|
}
|
|
53577
54549
|
function personaIdFromPath(absPath, personaRoot) {
|
|
53578
|
-
const root =
|
|
53579
|
-
const target =
|
|
53580
|
-
const sep3 = root.endsWith(
|
|
54550
|
+
const root = path55.resolve(personaRoot);
|
|
54551
|
+
const target = path55.resolve(absPath);
|
|
54552
|
+
const sep3 = root.endsWith(path55.sep) ? "" : path55.sep;
|
|
53581
54553
|
if (!target.startsWith(root + sep3)) return null;
|
|
53582
|
-
const rel =
|
|
54554
|
+
const rel = path55.relative(root, target);
|
|
53583
54555
|
if (!rel || rel.startsWith("..")) return null;
|
|
53584
|
-
const id = rel.split(
|
|
54556
|
+
const id = rel.split(path55.sep)[0];
|
|
53585
54557
|
return id || null;
|
|
53586
54558
|
}
|
|
53587
54559
|
function isPathWithin(dir, absPath) {
|
|
53588
|
-
const d =
|
|
53589
|
-
const t =
|
|
53590
|
-
const sep3 = d.endsWith(
|
|
54560
|
+
const d = path55.resolve(dir);
|
|
54561
|
+
const t = path55.resolve(absPath);
|
|
54562
|
+
const sep3 = d.endsWith(path55.sep) ? "" : path55.sep;
|
|
53591
54563
|
return t === d || t.startsWith(d + sep3);
|
|
53592
54564
|
}
|
|
53593
54565
|
function isPathInGuestBoundary(personaRoot, personaId, userWorkDir, absPath) {
|
|
53594
54566
|
if (userWorkDir && isPathWithin(userWorkDir, absPath)) return true;
|
|
53595
|
-
return personaIdFromPath(
|
|
54567
|
+
return personaIdFromPath(path55.resolve(absPath), personaRoot) === personaId;
|
|
53596
54568
|
}
|
|
53597
54569
|
|
|
53598
54570
|
// src/handlers/history.ts
|
|
@@ -53618,7 +54590,7 @@ function buildHistoryHandlers(deps) {
|
|
|
53618
54590
|
if (!pid) return false;
|
|
53619
54591
|
return isGuestPathAllowed(
|
|
53620
54592
|
ctx.grants,
|
|
53621
|
-
|
|
54593
|
+
path56.join(personaRoot, pid),
|
|
53622
54594
|
personaRoot,
|
|
53623
54595
|
"read",
|
|
53624
54596
|
userWorkDir
|
|
@@ -53630,7 +54602,7 @@ function buildHistoryHandlers(deps) {
|
|
|
53630
54602
|
};
|
|
53631
54603
|
const list = async (frame, _client, ctx) => {
|
|
53632
54604
|
const args = HistoryListArgs.parse(frame);
|
|
53633
|
-
assertGuestPath(ctx,
|
|
54605
|
+
assertGuestPath(ctx, path56.resolve(args.projectPath), personaRoot, "history:list");
|
|
53634
54606
|
const sessions = await history.listSessions(args);
|
|
53635
54607
|
return { response: { type: "history:list", sessions } };
|
|
53636
54608
|
};
|
|
@@ -53662,13 +54634,13 @@ function buildHistoryHandlers(deps) {
|
|
|
53662
54634
|
};
|
|
53663
54635
|
const subagents = async (frame, _client, ctx) => {
|
|
53664
54636
|
const args = HistorySubagentsArgs.parse(frame);
|
|
53665
|
-
assertGuestPath(ctx,
|
|
54637
|
+
assertGuestPath(ctx, path56.resolve(args.cwd), personaRoot, "history:subagents", usersRoot);
|
|
53666
54638
|
const subs = await history.listSubagents(args);
|
|
53667
54639
|
return { response: { type: "history:subagents", subagents: subs } };
|
|
53668
54640
|
};
|
|
53669
54641
|
const subagentRead = async (frame, _client, ctx) => {
|
|
53670
54642
|
const args = HistorySubagentReadArgs.parse(frame);
|
|
53671
|
-
assertGuestPath(ctx,
|
|
54643
|
+
assertGuestPath(ctx, path56.resolve(args.cwd), personaRoot, "history:subagent-read", usersRoot);
|
|
53672
54644
|
const res = await history.readSubagent(args);
|
|
53673
54645
|
return { response: { type: "history:subagent-read", ...res } };
|
|
53674
54646
|
};
|
|
@@ -53677,7 +54649,7 @@ function buildHistoryHandlers(deps) {
|
|
|
53677
54649
|
if (ctx?.principal.kind === "guest" && personaRoot) {
|
|
53678
54650
|
const userWorkDir = usersRoot ? deriveUserWorkDir(ctx.principal.id, usersRoot) : void 0;
|
|
53679
54651
|
const filtered = dirs.filter(
|
|
53680
|
-
(d) => isGuestPathAllowed(ctx.grants,
|
|
54652
|
+
(d) => isGuestPathAllowed(ctx.grants, path56.resolve(d.cwd), personaRoot, "read", userWorkDir)
|
|
53681
54653
|
);
|
|
53682
54654
|
return { response: { type: "history:recentDirs", dirs: filtered } };
|
|
53683
54655
|
}
|
|
@@ -53694,7 +54666,7 @@ function buildHistoryHandlers(deps) {
|
|
|
53694
54666
|
}
|
|
53695
54667
|
|
|
53696
54668
|
// src/handlers/workspace.ts
|
|
53697
|
-
var
|
|
54669
|
+
var path57 = __toESM(require("path"), 1);
|
|
53698
54670
|
var os16 = __toESM(require("os"), 1);
|
|
53699
54671
|
init_protocol();
|
|
53700
54672
|
init_protocol();
|
|
@@ -53736,22 +54708,22 @@ function buildWorkspaceHandlers(deps) {
|
|
|
53736
54708
|
const args = WorkspaceListArgs.parse(frame);
|
|
53737
54709
|
const isGuest = ctx?.principal.kind === "guest";
|
|
53738
54710
|
const fallbackCwd = isGuest && personaRoot ? personaRoot : os16.homedir();
|
|
53739
|
-
const resolvedCwd =
|
|
53740
|
-
const target = args.path ?
|
|
54711
|
+
const resolvedCwd = path57.resolve(args.cwd ?? fallbackCwd);
|
|
54712
|
+
const target = args.path ? path57.resolve(resolvedCwd, args.path) : resolvedCwd;
|
|
53741
54713
|
assertGuestPath2(ctx, target, personaRoot, "workspace:list", usersRoot);
|
|
53742
54714
|
const res = workspace.list({ ...args, cwd: resolvedCwd });
|
|
53743
54715
|
return { response: { type: "workspace:list", ...res } };
|
|
53744
54716
|
};
|
|
53745
54717
|
const read = async (frame, _client, ctx) => {
|
|
53746
54718
|
const args = WorkspaceReadArgs.parse(frame);
|
|
53747
|
-
const target =
|
|
54719
|
+
const target = path57.isAbsolute(args.path) ? path57.resolve(args.path) : path57.resolve(args.cwd, args.path);
|
|
53748
54720
|
assertGuestPath2(ctx, target, personaRoot, "workspace:read", usersRoot);
|
|
53749
54721
|
const res = workspace.read(args);
|
|
53750
54722
|
return { response: { type: "workspace:read", ...res } };
|
|
53751
54723
|
};
|
|
53752
54724
|
const skillsList = async (frame, _client, ctx) => {
|
|
53753
54725
|
const args = SkillsListArgs.parse(frame);
|
|
53754
|
-
const cwdAbs =
|
|
54726
|
+
const cwdAbs = path57.resolve(args.cwd);
|
|
53755
54727
|
assertGuestPath2(ctx, cwdAbs, personaRoot, "skills:list", usersRoot);
|
|
53756
54728
|
const list2 = await getSkillsForTool(args.tool ?? "claude", cwdAbs);
|
|
53757
54729
|
if (ctx?.principal.kind === "guest" && personaRoot) {
|
|
@@ -53763,7 +54735,7 @@ function buildWorkspaceHandlers(deps) {
|
|
|
53763
54735
|
};
|
|
53764
54736
|
const agentsList = async (frame, _client, ctx) => {
|
|
53765
54737
|
const args = AgentsListArgs.parse(frame);
|
|
53766
|
-
const cwdAbs =
|
|
54738
|
+
const cwdAbs = path57.resolve(args.cwd);
|
|
53767
54739
|
assertGuestPath2(ctx, cwdAbs, personaRoot, "agents:list", usersRoot);
|
|
53768
54740
|
if (args.tool === "codex") {
|
|
53769
54741
|
return { response: { type: "agents:list", agents: [] } };
|
|
@@ -53785,20 +54757,20 @@ function buildWorkspaceHandlers(deps) {
|
|
|
53785
54757
|
}
|
|
53786
54758
|
|
|
53787
54759
|
// src/handlers/git.ts
|
|
53788
|
-
var
|
|
54760
|
+
var path59 = __toESM(require("path"), 1);
|
|
53789
54761
|
init_protocol();
|
|
53790
54762
|
init_protocol();
|
|
53791
54763
|
|
|
53792
54764
|
// src/workspace/git.ts
|
|
53793
|
-
var
|
|
53794
|
-
var
|
|
53795
|
-
var
|
|
54765
|
+
var import_node_child_process12 = require("child_process");
|
|
54766
|
+
var import_node_fs44 = __toESM(require("fs"), 1);
|
|
54767
|
+
var import_node_path46 = __toESM(require("path"), 1);
|
|
53796
54768
|
var import_node_util = require("util");
|
|
53797
|
-
var pexec = (0, import_node_util.promisify)(
|
|
54769
|
+
var pexec = (0, import_node_util.promisify)(import_node_child_process12.execFile);
|
|
53798
54770
|
function normalizePath(p2) {
|
|
53799
|
-
const resolved =
|
|
54771
|
+
const resolved = import_node_path46.default.resolve(p2);
|
|
53800
54772
|
try {
|
|
53801
|
-
return
|
|
54773
|
+
return import_node_fs44.default.realpathSync(resolved);
|
|
53802
54774
|
} catch {
|
|
53803
54775
|
return resolved;
|
|
53804
54776
|
}
|
|
@@ -53872,7 +54844,7 @@ async function listGitBranches(cwd) {
|
|
|
53872
54844
|
function assertGuestCwd(ctx, cwd, personaRoot, method, usersRoot) {
|
|
53873
54845
|
if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
|
|
53874
54846
|
const userWorkDir = usersRoot ? deriveUserWorkDir(ctx.principal.id, usersRoot) : void 0;
|
|
53875
|
-
if (!isGuestPathAllowed(ctx.grants,
|
|
54847
|
+
if (!isGuestPathAllowed(ctx.grants, path59.resolve(cwd), personaRoot, "read", userWorkDir)) {
|
|
53876
54848
|
throw new ClawdError(
|
|
53877
54849
|
ERROR_CODES.UNAUTHORIZED,
|
|
53878
54850
|
`guest ${ctx.principal.id} cannot ${method} cwd ${cwd}`
|
|
@@ -53922,6 +54894,7 @@ function buildCapabilitiesHandlers(deps) {
|
|
|
53922
54894
|
|
|
53923
54895
|
// src/handlers/capability.ts
|
|
53924
54896
|
init_zod();
|
|
54897
|
+
init_src();
|
|
53925
54898
|
init_protocol();
|
|
53926
54899
|
var DeleteArgsSchema = external_exports.object({
|
|
53927
54900
|
capabilityId: external_exports.string().min(1)
|
|
@@ -53960,6 +54933,7 @@ function buildCapabilityHandlers(deps) {
|
|
|
53960
54933
|
}
|
|
53961
54934
|
|
|
53962
54935
|
// src/handlers/inbox.ts
|
|
54936
|
+
init_src();
|
|
53963
54937
|
init_protocol();
|
|
53964
54938
|
function resolvePeerDeviceId(ctx, argsPeerDeviceId) {
|
|
53965
54939
|
if (ctx.principal.kind === "owner") {
|
|
@@ -54138,6 +55112,7 @@ function buildInboxHandlers(deps) {
|
|
|
54138
55112
|
}
|
|
54139
55113
|
|
|
54140
55114
|
// src/handlers/contact.ts
|
|
55115
|
+
init_src();
|
|
54141
55116
|
init_protocol();
|
|
54142
55117
|
function ensureOwner(ctx) {
|
|
54143
55118
|
if (!ctx || ctx.principal.kind !== "owner") {
|
|
@@ -54205,7 +55180,157 @@ function buildContactHandlers(deps) {
|
|
|
54205
55180
|
};
|
|
54206
55181
|
}
|
|
54207
55182
|
|
|
55183
|
+
// src/handlers/contact-ssh.ts
|
|
55184
|
+
init_src();
|
|
55185
|
+
init_protocol();
|
|
55186
|
+
|
|
55187
|
+
// src/sshd/key-issue.ts
|
|
55188
|
+
var import_node_fs45 = __toESM(require("fs"), 1);
|
|
55189
|
+
var import_node_path47 = __toESM(require("path"), 1);
|
|
55190
|
+
var import_node_child_process13 = require("child_process");
|
|
55191
|
+
function safeDeviceId(deviceId) {
|
|
55192
|
+
return deviceId.replace(/[\/\\]/g, "_");
|
|
55193
|
+
}
|
|
55194
|
+
async function issueContactSshKey(deviceId, sshdDir, opts = {}) {
|
|
55195
|
+
const safeId = safeDeviceId(deviceId);
|
|
55196
|
+
const keysDir = import_node_path47.default.join(sshdDir, "keys");
|
|
55197
|
+
import_node_fs45.default.mkdirSync(keysDir, { recursive: true, mode: 448 });
|
|
55198
|
+
const privPath = import_node_path47.default.join(keysDir, `${safeId}.ed25519`);
|
|
55199
|
+
const pubPath = `${privPath}.pub`;
|
|
55200
|
+
if (import_node_fs45.default.existsSync(privPath) && import_node_fs45.default.existsSync(pubPath)) {
|
|
55201
|
+
return {
|
|
55202
|
+
privateKeyPem: import_node_fs45.default.readFileSync(privPath, "utf8"),
|
|
55203
|
+
publicKeyLine: import_node_fs45.default.readFileSync(pubPath, "utf8").trim()
|
|
55204
|
+
};
|
|
55205
|
+
}
|
|
55206
|
+
const bin = opts.keygenBin ?? "/usr/bin/ssh-keygen";
|
|
55207
|
+
await new Promise((resolve6, reject) => {
|
|
55208
|
+
const p2 = (opts.spawnImpl ?? import_node_child_process13.spawn)(
|
|
55209
|
+
bin,
|
|
55210
|
+
["-t", "ed25519", "-f", privPath, "-N", "", "-q", "-C", `clawd-contact-${safeId}`],
|
|
55211
|
+
{ stdio: "ignore" }
|
|
55212
|
+
);
|
|
55213
|
+
p2.on("exit", (code) => code === 0 ? resolve6() : reject(new Error(`ssh-keygen exit ${code}`)));
|
|
55214
|
+
p2.on("error", reject);
|
|
55215
|
+
});
|
|
55216
|
+
try {
|
|
55217
|
+
import_node_fs45.default.chmodSync(privPath, 384);
|
|
55218
|
+
} catch {
|
|
55219
|
+
}
|
|
55220
|
+
try {
|
|
55221
|
+
import_node_fs45.default.chmodSync(pubPath, 420);
|
|
55222
|
+
} catch {
|
|
55223
|
+
}
|
|
55224
|
+
return {
|
|
55225
|
+
privateKeyPem: import_node_fs45.default.readFileSync(privPath, "utf8"),
|
|
55226
|
+
publicKeyLine: import_node_fs45.default.readFileSync(pubPath, "utf8").trim()
|
|
55227
|
+
};
|
|
55228
|
+
}
|
|
55229
|
+
|
|
55230
|
+
// src/handlers/contact-ssh.ts
|
|
55231
|
+
function ensureOwner2(ctx) {
|
|
55232
|
+
if (!ctx || ctx.principal.kind !== "owner") {
|
|
55233
|
+
throw new ClawdError(
|
|
55234
|
+
ERROR_CODES.UNAUTHORIZED,
|
|
55235
|
+
"UNAUTHORIZED: contact:setSshAccess requires owner ctx"
|
|
55236
|
+
);
|
|
55237
|
+
}
|
|
55238
|
+
}
|
|
55239
|
+
function ensureGuest(ctx) {
|
|
55240
|
+
if (!ctx || ctx.principal.kind !== "guest") {
|
|
55241
|
+
throw new ClawdError(
|
|
55242
|
+
ERROR_CODES.UNAUTHORIZED,
|
|
55243
|
+
"UNAUTHORIZED: contact:sshKey:issue requires guest ctx"
|
|
55244
|
+
);
|
|
55245
|
+
}
|
|
55246
|
+
return ctx.principal.id;
|
|
55247
|
+
}
|
|
55248
|
+
function buildContactSshHandlers(deps) {
|
|
55249
|
+
const sshLog = deps.sshLog ?? nullContactSshLog;
|
|
55250
|
+
const setSshAccess = async (frame, _client, ctx) => {
|
|
55251
|
+
ensureOwner2(ctx);
|
|
55252
|
+
const { type: _t, requestId: _r, ...rest } = frame;
|
|
55253
|
+
const args = ContactSetSshAccessArgsSchema.parse(rest);
|
|
55254
|
+
const hit = deps.store.setSshAccess(args.deviceId, {
|
|
55255
|
+
sshAllowed: args.sshAllowed,
|
|
55256
|
+
exposedDirs: args.exposedDirs
|
|
55257
|
+
});
|
|
55258
|
+
if (!hit) {
|
|
55259
|
+
sshLog.warn("authz.setSshAccess", "owner \u5C1D\u8BD5\u6539 SSH \u6388\u6743\u4F46 contact \u4E0D\u5728 store", {
|
|
55260
|
+
peerDeviceId: args.deviceId
|
|
55261
|
+
});
|
|
55262
|
+
throw new ClawdError(
|
|
55263
|
+
ERROR_CODES.CONTACT_NOT_FOUND,
|
|
55264
|
+
`CONTACT_NOT_FOUND: contact ${args.deviceId} not in store`
|
|
55265
|
+
);
|
|
55266
|
+
}
|
|
55267
|
+
rebuildAuthorizedKeys(deps.store, deps.sshdDir);
|
|
55268
|
+
sshLog.info("authz.setSshAccess", "owner \u6539\u4E86 SSH \u6388\u6743 \u2192 authorized_keys \u5DF2\u91CD\u5EFA", {
|
|
55269
|
+
peerDeviceId: args.deviceId,
|
|
55270
|
+
sshAllowed: args.sshAllowed,
|
|
55271
|
+
exposedDirs: args.exposedDirs
|
|
55272
|
+
});
|
|
55273
|
+
deps.broadcast({
|
|
55274
|
+
type: "contact:ssh-access-updated",
|
|
55275
|
+
deviceId: args.deviceId,
|
|
55276
|
+
sshAllowed: args.sshAllowed,
|
|
55277
|
+
exposedDirs: args.exposedDirs
|
|
55278
|
+
});
|
|
55279
|
+
return {
|
|
55280
|
+
response: {
|
|
55281
|
+
type: "contact:setSshAccess:ok",
|
|
55282
|
+
deviceId: args.deviceId,
|
|
55283
|
+
sshAllowed: args.sshAllowed,
|
|
55284
|
+
exposedDirs: args.exposedDirs
|
|
55285
|
+
}
|
|
55286
|
+
};
|
|
55287
|
+
};
|
|
55288
|
+
const sshKeyIssue = async (frame, _client, ctx) => {
|
|
55289
|
+
const callerDeviceId = ensureGuest(ctx);
|
|
55290
|
+
const { type: _t, requestId: _r, ...rest } = frame;
|
|
55291
|
+
ContactSshKeyIssueArgsSchema.parse(rest);
|
|
55292
|
+
const contact = deps.store.get(callerDeviceId);
|
|
55293
|
+
if (!contact || !contact.sshAllowed) {
|
|
55294
|
+
sshLog.info(
|
|
55295
|
+
"key.issue.rejected",
|
|
55296
|
+
contact ? "guest \u8BF7\u6C42 SSH key \u4F46 owner \u672A\u6388\u6743" : "guest \u8BF7\u6C42 SSH key \u4F46\u4E0D\u5728\u6211\u7684 contactStore",
|
|
55297
|
+
{
|
|
55298
|
+
peerDeviceId: callerDeviceId,
|
|
55299
|
+
peerDisplayName: contact?.displayName
|
|
55300
|
+
}
|
|
55301
|
+
);
|
|
55302
|
+
throw new ClawdError(
|
|
55303
|
+
ERROR_CODES.UNAUTHORIZED,
|
|
55304
|
+
`UNAUTHORIZED: contact ${callerDeviceId} not authorized for SSH`
|
|
55305
|
+
);
|
|
55306
|
+
}
|
|
55307
|
+
const { privateKeyPem, publicKeyLine } = await issueContactSshKey(callerDeviceId, deps.sshdDir);
|
|
55308
|
+
rebuildAuthorizedKeys(deps.store, deps.sshdDir);
|
|
55309
|
+
sshLog.info(
|
|
55310
|
+
"key.issue.success",
|
|
55311
|
+
"A \u4FA7\u53D1 privkey \u7ED9 guest\uFF08\u5E42\u7B49\uFF09\uFF0Cauthorized_keys \u5DF2\u91CD\u5EFA",
|
|
55312
|
+
{
|
|
55313
|
+
peerDeviceId: callerDeviceId,
|
|
55314
|
+
peerDisplayName: contact.displayName,
|
|
55315
|
+
publicKeyFingerprint: publicKeyLine.slice(0, 32) + "..."
|
|
55316
|
+
}
|
|
55317
|
+
);
|
|
55318
|
+
return {
|
|
55319
|
+
response: {
|
|
55320
|
+
type: "contact:sshKey:issue:ok",
|
|
55321
|
+
privateKeyPem,
|
|
55322
|
+
publicKeyLine
|
|
55323
|
+
}
|
|
55324
|
+
};
|
|
55325
|
+
};
|
|
55326
|
+
return {
|
|
55327
|
+
"contact:setSshAccess": setSshAccess,
|
|
55328
|
+
"contact:sshKey:issue": sshKeyIssue
|
|
55329
|
+
};
|
|
55330
|
+
}
|
|
55331
|
+
|
|
54208
55332
|
// src/handlers/whoami.ts
|
|
55333
|
+
init_src();
|
|
54209
55334
|
init_protocol();
|
|
54210
55335
|
function buildWhoamiHandler(deps) {
|
|
54211
55336
|
return async (_frame, _client, ctx) => {
|
|
@@ -54299,8 +55424,9 @@ function buildFeishuAuthHandlers(deps) {
|
|
|
54299
55424
|
}
|
|
54300
55425
|
|
|
54301
55426
|
// src/handlers/device.ts
|
|
55427
|
+
init_src();
|
|
54302
55428
|
init_protocol();
|
|
54303
|
-
function
|
|
55429
|
+
function ensureOwner3(ctx) {
|
|
54304
55430
|
if (!ctx || ctx.principal.kind !== "owner") {
|
|
54305
55431
|
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "UNAUTHORIZED: device:* requires owner ctx");
|
|
54306
55432
|
}
|
|
@@ -54308,14 +55434,14 @@ function ensureOwner2(ctx) {
|
|
|
54308
55434
|
function buildDeviceHandlers(deps) {
|
|
54309
55435
|
const now = deps.now ?? Date.now;
|
|
54310
55436
|
const list = async (_frame, _client, ctx) => {
|
|
54311
|
-
|
|
55437
|
+
ensureOwner3(ctx);
|
|
54312
55438
|
const devices = await deps.listDevices();
|
|
54313
55439
|
return {
|
|
54314
55440
|
response: { type: "device:list:ok", devices }
|
|
54315
55441
|
};
|
|
54316
55442
|
};
|
|
54317
55443
|
const connect = async (frame, _client, ctx) => {
|
|
54318
|
-
|
|
55444
|
+
ensureOwner3(ctx);
|
|
54319
55445
|
const { type: _t, requestId: _r, ...rest } = frame;
|
|
54320
55446
|
const args = DeviceConnectArgsSchema.parse(rest);
|
|
54321
55447
|
const exchanged = await deps.exchange(args.deviceId);
|
|
@@ -54358,7 +55484,9 @@ function buildDeviceHandlers(deps) {
|
|
|
54358
55484
|
connectToken: exchanged.token,
|
|
54359
55485
|
grants: wh.grants,
|
|
54360
55486
|
addedAt: now(),
|
|
54361
|
-
pinnedAt: null
|
|
55487
|
+
pinnedAt: null,
|
|
55488
|
+
sshAllowed: false,
|
|
55489
|
+
exposedDirs: []
|
|
54362
55490
|
};
|
|
54363
55491
|
deps.store.upsert(contact);
|
|
54364
55492
|
deps.broadcast({ type: "contact:added", contact });
|
|
@@ -54514,7 +55642,7 @@ function buildPersonaHandlers(deps) {
|
|
|
54514
55642
|
}
|
|
54515
55643
|
|
|
54516
55644
|
// src/handlers/attachment.ts
|
|
54517
|
-
var
|
|
55645
|
+
var import_node_path48 = __toESM(require("path"), 1);
|
|
54518
55646
|
init_protocol();
|
|
54519
55647
|
init_protocol();
|
|
54520
55648
|
var DEFAULT_TTL_SECONDS = 24 * 3600;
|
|
@@ -54594,12 +55722,12 @@ function buildAttachmentHandlers(deps) {
|
|
|
54594
55722
|
`session ${args.sessionId} scope unresolved`
|
|
54595
55723
|
);
|
|
54596
55724
|
}
|
|
54597
|
-
const cwdAbs =
|
|
54598
|
-
const candidateAbs =
|
|
55725
|
+
const cwdAbs = import_node_path48.default.resolve(sessionFile.cwd);
|
|
55726
|
+
const candidateAbs = import_node_path48.default.isAbsolute(args.relPath) ? import_node_path48.default.resolve(args.relPath) : import_node_path48.default.resolve(cwdAbs, args.relPath);
|
|
54599
55727
|
guardAttachmentPath(ctx, args.sessionId, candidateAbs, "attachment.signUrl", "group-acl");
|
|
54600
55728
|
const entries = deps.groupFileStore.list(scope, args.sessionId);
|
|
54601
55729
|
const entry = entries.find((e) => {
|
|
54602
|
-
const storedAbs =
|
|
55730
|
+
const storedAbs = import_node_path48.default.isAbsolute(e.relPath) ? import_node_path48.default.resolve(e.relPath) : import_node_path48.default.resolve(cwdAbs, e.relPath);
|
|
54603
55731
|
return storedAbs === candidateAbs && !e.stale;
|
|
54604
55732
|
});
|
|
54605
55733
|
if (!entry) {
|
|
@@ -54624,7 +55752,7 @@ function buildAttachmentHandlers(deps) {
|
|
|
54624
55752
|
if (!ctx || ctx.principal.kind !== "guest" || !deps.personaRoot || !deps.sessionStore) return;
|
|
54625
55753
|
const f = deps.sessionStore.read(sessionId);
|
|
54626
55754
|
if (!f) return;
|
|
54627
|
-
assertGuestAttachmentPath(ctx,
|
|
55755
|
+
assertGuestAttachmentPath(ctx, import_node_path48.default.resolve(f.cwd), deps.personaRoot, method, deps.usersRoot);
|
|
54628
55756
|
}
|
|
54629
55757
|
const groupAdd = async (frame, _client, ctx) => {
|
|
54630
55758
|
if (!deps.groupFileStore || !deps.getSessionScope) {
|
|
@@ -54639,8 +55767,8 @@ function buildAttachmentHandlers(deps) {
|
|
|
54639
55767
|
if (!scope) {
|
|
54640
55768
|
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, `session ${args.sessionId} not found`);
|
|
54641
55769
|
}
|
|
54642
|
-
const cwdAbs =
|
|
54643
|
-
const candidateAbs =
|
|
55770
|
+
const cwdAbs = import_node_path48.default.resolve(deps.sessionStore?.read(args.sessionId)?.cwd ?? ".");
|
|
55771
|
+
const candidateAbs = import_node_path48.default.isAbsolute(args.relPath) ? import_node_path48.default.resolve(args.relPath) : import_node_path48.default.resolve(cwdAbs, args.relPath);
|
|
54644
55772
|
guardAttachmentPath(ctx, args.sessionId, candidateAbs, "attachment.groupAdd", "cwd-subtree");
|
|
54645
55773
|
const from = ctx?.principal.kind === "owner" ? "owner" : "agent";
|
|
54646
55774
|
const size = 0;
|
|
@@ -54699,19 +55827,20 @@ function buildAttachmentHandlers(deps) {
|
|
|
54699
55827
|
|
|
54700
55828
|
// src/handlers/extension.ts
|
|
54701
55829
|
var import_promises8 = __toESM(require("fs/promises"), 1);
|
|
54702
|
-
var
|
|
55830
|
+
var import_node_path53 = __toESM(require("path"), 1);
|
|
54703
55831
|
init_protocol();
|
|
55832
|
+
init_src();
|
|
54704
55833
|
|
|
54705
55834
|
// src/extension/bundle-zip.ts
|
|
54706
55835
|
var import_promises5 = __toESM(require("fs/promises"), 1);
|
|
54707
|
-
var
|
|
54708
|
-
var
|
|
55836
|
+
var import_node_path49 = __toESM(require("path"), 1);
|
|
55837
|
+
var import_node_crypto13 = __toESM(require("crypto"), 1);
|
|
54709
55838
|
var import_jszip2 = __toESM(require_lib3(), 1);
|
|
54710
55839
|
async function bundleExtensionDir(dir) {
|
|
54711
55840
|
const entries = await listFilesSorted(dir);
|
|
54712
55841
|
const zip = new import_jszip2.default();
|
|
54713
55842
|
for (const rel of entries) {
|
|
54714
|
-
const abs =
|
|
55843
|
+
const abs = import_node_path49.default.join(dir, rel);
|
|
54715
55844
|
const content = await import_promises5.default.readFile(abs);
|
|
54716
55845
|
zip.file(rel, content, { date: FIXED_DATE });
|
|
54717
55846
|
}
|
|
@@ -54720,7 +55849,7 @@ async function bundleExtensionDir(dir) {
|
|
|
54720
55849
|
compression: "DEFLATE",
|
|
54721
55850
|
compressionOptions: { level: 6 }
|
|
54722
55851
|
});
|
|
54723
|
-
const sha256 =
|
|
55852
|
+
const sha256 = import_node_crypto13.default.createHash("sha256").update(buffer).digest("hex");
|
|
54724
55853
|
return { buffer, sha256 };
|
|
54725
55854
|
}
|
|
54726
55855
|
var FIXED_DATE = /* @__PURE__ */ new Date("2020-01-01T00:00:00.000Z");
|
|
@@ -54732,7 +55861,7 @@ async function listFilesSorted(rootDir) {
|
|
|
54732
55861
|
return out;
|
|
54733
55862
|
}
|
|
54734
55863
|
async function walk(absRoot, relPrefix, out) {
|
|
54735
|
-
const dirAbs =
|
|
55864
|
+
const dirAbs = import_node_path49.default.join(absRoot, relPrefix);
|
|
54736
55865
|
const entries = await import_promises5.default.readdir(dirAbs, { withFileTypes: true });
|
|
54737
55866
|
for (const e of entries) {
|
|
54738
55867
|
if (IGNORE_BASENAMES.has(e.name)) continue;
|
|
@@ -54746,6 +55875,8 @@ async function walk(absRoot, relPrefix, out) {
|
|
|
54746
55875
|
}
|
|
54747
55876
|
|
|
54748
55877
|
// src/extension/publish-check.ts
|
|
55878
|
+
init_src();
|
|
55879
|
+
init_src();
|
|
54749
55880
|
function computePublishCheck(args) {
|
|
54750
55881
|
const { localHash, localVersion, head } = args;
|
|
54751
55882
|
if (head === null) {
|
|
@@ -54786,25 +55917,27 @@ function computePublishCheck(args) {
|
|
|
54786
55917
|
|
|
54787
55918
|
// src/extension/install-flow.ts
|
|
54788
55919
|
var import_promises6 = __toESM(require("fs/promises"), 1);
|
|
54789
|
-
var
|
|
55920
|
+
var import_node_path51 = __toESM(require("path"), 1);
|
|
54790
55921
|
var import_node_os19 = __toESM(require("os"), 1);
|
|
54791
|
-
var
|
|
55922
|
+
var import_node_crypto14 = __toESM(require("crypto"), 1);
|
|
54792
55923
|
var import_jszip3 = __toESM(require_lib3(), 1);
|
|
55924
|
+
init_src();
|
|
54793
55925
|
|
|
54794
55926
|
// src/extension/paths.ts
|
|
54795
55927
|
var import_node_os18 = __toESM(require("os"), 1);
|
|
54796
|
-
var
|
|
55928
|
+
var import_node_path50 = __toESM(require("path"), 1);
|
|
55929
|
+
init_src();
|
|
54797
55930
|
function clawdHomeRoot(override) {
|
|
54798
|
-
return override ?? process.env.CLAWD_HOME ??
|
|
55931
|
+
return override ?? process.env.CLAWD_HOME ?? import_node_path50.default.join(import_node_os18.default.homedir(), ".clawd");
|
|
54799
55932
|
}
|
|
54800
55933
|
function extensionsRoot(override) {
|
|
54801
|
-
return
|
|
55934
|
+
return import_node_path50.default.join(clawdHomeRoot(override), "extensions");
|
|
54802
55935
|
}
|
|
54803
55936
|
function publishedChannelsFile(override) {
|
|
54804
|
-
return
|
|
55937
|
+
return import_node_path50.default.join(clawdHomeRoot(override), "extensions-published.json");
|
|
54805
55938
|
}
|
|
54806
55939
|
function bundleCacheRoot(override) {
|
|
54807
|
-
return
|
|
55940
|
+
return import_node_path50.default.join(clawdHomeRoot(override), "extension-bundles");
|
|
54808
55941
|
}
|
|
54809
55942
|
|
|
54810
55943
|
// src/extension/install-flow.ts
|
|
@@ -54817,7 +55950,7 @@ var InstallError = class extends Error {
|
|
|
54817
55950
|
};
|
|
54818
55951
|
async function installFromChannel(args, deps) {
|
|
54819
55952
|
const { channelRef, snapshotHash, bundleZip } = args;
|
|
54820
|
-
const computed =
|
|
55953
|
+
const computed = import_node_crypto14.default.createHash("sha256").update(bundleZip).digest("hex");
|
|
54821
55954
|
if (computed !== snapshotHash) {
|
|
54822
55955
|
throw new InstallError(
|
|
54823
55956
|
"HASH_MISMATCH",
|
|
@@ -54831,7 +55964,7 @@ async function installFromChannel(args, deps) {
|
|
|
54831
55964
|
throw new InstallError("ZIP_INVALID", `failed to load zip: ${e.message}`);
|
|
54832
55965
|
}
|
|
54833
55966
|
for (const name of Object.keys(zip.files)) {
|
|
54834
|
-
if (name.includes("..") || name.startsWith("/") ||
|
|
55967
|
+
if (name.includes("..") || name.startsWith("/") || import_node_path51.default.isAbsolute(name)) {
|
|
54835
55968
|
throw new InstallError("ZIP_INVALID", `unsafe zip entry: ${name}`);
|
|
54836
55969
|
}
|
|
54837
55970
|
}
|
|
@@ -54863,7 +55996,7 @@ async function installFromChannel(args, deps) {
|
|
|
54863
55996
|
);
|
|
54864
55997
|
}
|
|
54865
55998
|
const localExtId = namespacedExtId(ownerSlug, channelRef.ownerPrincipalId);
|
|
54866
|
-
const destDir =
|
|
55999
|
+
const destDir = import_node_path51.default.join(deps.extensionsRoot, localExtId);
|
|
54867
56000
|
let destExists = false;
|
|
54868
56001
|
try {
|
|
54869
56002
|
await import_promises6.default.access(destDir);
|
|
@@ -54877,16 +56010,16 @@ async function installFromChannel(args, deps) {
|
|
|
54877
56010
|
);
|
|
54878
56011
|
}
|
|
54879
56012
|
const stage = await import_promises6.default.mkdtemp(
|
|
54880
|
-
|
|
56013
|
+
import_node_path51.default.join(import_node_os19.default.tmpdir(), `clawd-ext-install-${localExtId}-`)
|
|
54881
56014
|
);
|
|
54882
56015
|
try {
|
|
54883
56016
|
for (const [name, entry] of Object.entries(zip.files)) {
|
|
54884
|
-
const dest =
|
|
56017
|
+
const dest = import_node_path51.default.join(stage, name);
|
|
54885
56018
|
if (entry.dir) {
|
|
54886
56019
|
await import_promises6.default.mkdir(dest, { recursive: true });
|
|
54887
56020
|
continue;
|
|
54888
56021
|
}
|
|
54889
|
-
await import_promises6.default.mkdir(
|
|
56022
|
+
await import_promises6.default.mkdir(import_node_path51.default.dirname(dest), { recursive: true });
|
|
54890
56023
|
if (name === "manifest.json") {
|
|
54891
56024
|
const rewritten = { ...parsed.data, id: localExtId };
|
|
54892
56025
|
await import_promises6.default.writeFile(dest, JSON.stringify(rewritten, null, 2));
|
|
@@ -54907,10 +56040,11 @@ async function installFromChannel(args, deps) {
|
|
|
54907
56040
|
|
|
54908
56041
|
// src/extension/update-flow.ts
|
|
54909
56042
|
var import_promises7 = __toESM(require("fs/promises"), 1);
|
|
54910
|
-
var
|
|
56043
|
+
var import_node_path52 = __toESM(require("path"), 1);
|
|
54911
56044
|
var import_node_os20 = __toESM(require("os"), 1);
|
|
54912
|
-
var
|
|
56045
|
+
var import_node_crypto15 = __toESM(require("crypto"), 1);
|
|
54913
56046
|
var import_jszip4 = __toESM(require_lib3(), 1);
|
|
56047
|
+
init_src();
|
|
54914
56048
|
var UpdateError = class extends Error {
|
|
54915
56049
|
constructor(code, message) {
|
|
54916
56050
|
super(message);
|
|
@@ -54924,11 +56058,11 @@ async function updateFromChannel(args, deps) {
|
|
|
54924
56058
|
channelRef.extId,
|
|
54925
56059
|
channelRef.ownerPrincipalId
|
|
54926
56060
|
);
|
|
54927
|
-
const liveDir =
|
|
56061
|
+
const liveDir = import_node_path52.default.join(deps.extensionsRoot, localExtId);
|
|
54928
56062
|
const prevDir = `${liveDir}.prev`;
|
|
54929
56063
|
let existingVersion;
|
|
54930
56064
|
try {
|
|
54931
|
-
const raw = await import_promises7.default.readFile(
|
|
56065
|
+
const raw = await import_promises7.default.readFile(import_node_path52.default.join(liveDir, "manifest.json"), "utf8");
|
|
54932
56066
|
const parsed2 = ExtensionManifestSchema.safeParse(JSON.parse(raw));
|
|
54933
56067
|
if (!parsed2.success) {
|
|
54934
56068
|
throw new UpdateError(
|
|
@@ -54947,7 +56081,7 @@ async function updateFromChannel(args, deps) {
|
|
|
54947
56081
|
if (e instanceof UpdateError) throw e;
|
|
54948
56082
|
throw e;
|
|
54949
56083
|
}
|
|
54950
|
-
const computed =
|
|
56084
|
+
const computed = import_node_crypto15.default.createHash("sha256").update(bundleZip).digest("hex");
|
|
54951
56085
|
if (computed !== snapshotHash) {
|
|
54952
56086
|
throw new UpdateError(
|
|
54953
56087
|
"HASH_MISMATCH",
|
|
@@ -54961,7 +56095,7 @@ async function updateFromChannel(args, deps) {
|
|
|
54961
56095
|
throw new UpdateError("ZIP_INVALID", `failed to load zip: ${e.message}`);
|
|
54962
56096
|
}
|
|
54963
56097
|
for (const name of Object.keys(zip.files)) {
|
|
54964
|
-
if (name.includes("..") || name.startsWith("/") ||
|
|
56098
|
+
if (name.includes("..") || name.startsWith("/") || import_node_path52.default.isAbsolute(name)) {
|
|
54965
56099
|
throw new UpdateError("ZIP_INVALID", `unsafe zip entry: ${name}`);
|
|
54966
56100
|
}
|
|
54967
56101
|
}
|
|
@@ -54996,16 +56130,16 @@ async function updateFromChannel(args, deps) {
|
|
|
54996
56130
|
await import_promises7.default.rm(prevDir, { recursive: true, force: true });
|
|
54997
56131
|
await import_promises7.default.rename(liveDir, prevDir);
|
|
54998
56132
|
const stage = await import_promises7.default.mkdtemp(
|
|
54999
|
-
|
|
56133
|
+
import_node_path52.default.join(import_node_os20.default.tmpdir(), `clawd-ext-update-${localExtId}-`)
|
|
55000
56134
|
);
|
|
55001
56135
|
try {
|
|
55002
56136
|
for (const [name, entry] of Object.entries(zip.files)) {
|
|
55003
|
-
const dest =
|
|
56137
|
+
const dest = import_node_path52.default.join(stage, name);
|
|
55004
56138
|
if (entry.dir) {
|
|
55005
56139
|
await import_promises7.default.mkdir(dest, { recursive: true });
|
|
55006
56140
|
continue;
|
|
55007
56141
|
}
|
|
55008
|
-
await import_promises7.default.mkdir(
|
|
56142
|
+
await import_promises7.default.mkdir(import_node_path52.default.dirname(dest), { recursive: true });
|
|
55009
56143
|
if (name === "manifest.json") {
|
|
55010
56144
|
const rewritten = { ...parsed.data, id: localExtId };
|
|
55011
56145
|
await import_promises7.default.writeFile(dest, JSON.stringify(rewritten, null, 2));
|
|
@@ -55056,6 +56190,7 @@ async function rollback(deps, localExtId, liveDir, prevDir) {
|
|
|
55056
56190
|
}
|
|
55057
56191
|
|
|
55058
56192
|
// src/handlers/extension.ts
|
|
56193
|
+
init_src();
|
|
55059
56194
|
function pickChannelRef(frame) {
|
|
55060
56195
|
const ref = frame.channelRef;
|
|
55061
56196
|
const parsed = ChannelRefSchema.safeParse(ref);
|
|
@@ -55098,7 +56233,7 @@ async function rewriteManifestVersion(root, extId, newVersion, previousPublished
|
|
|
55098
56233
|
);
|
|
55099
56234
|
}
|
|
55100
56235
|
}
|
|
55101
|
-
const manifestPath =
|
|
56236
|
+
const manifestPath = import_node_path53.default.join(root, extId, "manifest.json");
|
|
55102
56237
|
const manifest = await readManifest(root, extId);
|
|
55103
56238
|
const next = { ...manifest, version: newVersion };
|
|
55104
56239
|
const tmp = `${manifestPath}.tmp`;
|
|
@@ -55106,7 +56241,7 @@ async function rewriteManifestVersion(root, extId, newVersion, previousPublished
|
|
|
55106
56241
|
await import_promises8.default.rename(tmp, manifestPath);
|
|
55107
56242
|
}
|
|
55108
56243
|
async function readManifest(root, extId) {
|
|
55109
|
-
const file =
|
|
56244
|
+
const file = import_node_path53.default.join(root, extId, "manifest.json");
|
|
55110
56245
|
let raw;
|
|
55111
56246
|
try {
|
|
55112
56247
|
raw = await import_promises8.default.readFile(file, "utf8");
|
|
@@ -55197,7 +56332,7 @@ function buildExtensionHandlers(deps) {
|
|
|
55197
56332
|
};
|
|
55198
56333
|
async function buildSnapshotMeta(extId) {
|
|
55199
56334
|
const manifest = await readManifest(deps.root, extId);
|
|
55200
|
-
const { sha256, buffer } = await bundleExtensionDir(
|
|
56335
|
+
const { sha256, buffer } = await bundleExtensionDir(import_node_path53.default.join(deps.root, extId));
|
|
55201
56336
|
return { manifest, contentHash: sha256, buffer };
|
|
55202
56337
|
}
|
|
55203
56338
|
const publish = async (frame, _client, ctx) => {
|
|
@@ -55378,9 +56513,9 @@ function buildExtensionHandlers(deps) {
|
|
|
55378
56513
|
}
|
|
55379
56514
|
|
|
55380
56515
|
// src/app-builder/project-store.ts
|
|
55381
|
-
var
|
|
55382
|
-
var
|
|
55383
|
-
var
|
|
56516
|
+
var import_node_fs46 = require("fs");
|
|
56517
|
+
var import_node_child_process14 = require("child_process");
|
|
56518
|
+
var import_node_path54 = require("path");
|
|
55384
56519
|
init_protocol();
|
|
55385
56520
|
var PROJECTS_DIR = "projects";
|
|
55386
56521
|
var META_FILE = ".clawd-project.json";
|
|
@@ -55394,19 +56529,19 @@ var ProjectStore = class {
|
|
|
55394
56529
|
root;
|
|
55395
56530
|
/** projects/<name>/.clawd-project.json 路径 */
|
|
55396
56531
|
metaPath(name) {
|
|
55397
|
-
return (0,
|
|
56532
|
+
return (0, import_node_path54.join)(this.projectsRoot(), name, META_FILE);
|
|
55398
56533
|
}
|
|
55399
56534
|
/** projects/<name>/ 目录路径(cwd 用) */
|
|
55400
56535
|
projectDir(name) {
|
|
55401
|
-
return (0,
|
|
56536
|
+
return (0, import_node_path54.join)(this.projectsRoot(), name);
|
|
55402
56537
|
}
|
|
55403
56538
|
projectsRoot() {
|
|
55404
|
-
return (0,
|
|
56539
|
+
return (0, import_node_path54.join)(this.root, PROJECTS_DIR);
|
|
55405
56540
|
}
|
|
55406
56541
|
async list() {
|
|
55407
56542
|
let entries;
|
|
55408
56543
|
try {
|
|
55409
|
-
entries = await
|
|
56544
|
+
entries = await import_node_fs46.promises.readdir(this.projectsRoot());
|
|
55410
56545
|
} catch (err) {
|
|
55411
56546
|
if (err.code === "ENOENT") return [];
|
|
55412
56547
|
throw err;
|
|
@@ -55414,7 +56549,7 @@ var ProjectStore = class {
|
|
|
55414
56549
|
const out = [];
|
|
55415
56550
|
for (const name of entries) {
|
|
55416
56551
|
try {
|
|
55417
|
-
const raw = await
|
|
56552
|
+
const raw = await import_node_fs46.promises.readFile(this.metaPath(name), "utf8");
|
|
55418
56553
|
const json = JSON.parse(raw);
|
|
55419
56554
|
let migrated = false;
|
|
55420
56555
|
if (typeof json.devCommand !== "string" || json.devCommand.length === 0) {
|
|
@@ -55425,7 +56560,7 @@ var ProjectStore = class {
|
|
|
55425
56560
|
if (parsed.success) {
|
|
55426
56561
|
out.push(parsed.data);
|
|
55427
56562
|
if (migrated) {
|
|
55428
|
-
void
|
|
56563
|
+
void import_node_fs46.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
|
|
55429
56564
|
});
|
|
55430
56565
|
}
|
|
55431
56566
|
}
|
|
@@ -55469,8 +56604,8 @@ var ProjectStore = class {
|
|
|
55469
56604
|
throw new Error(`invalid name "${name}": ${validated.error.message}`);
|
|
55470
56605
|
}
|
|
55471
56606
|
const dir = this.projectDir(name);
|
|
55472
|
-
await
|
|
55473
|
-
await
|
|
56607
|
+
await import_node_fs46.promises.mkdir(dir, { recursive: true });
|
|
56608
|
+
await import_node_fs46.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
|
|
55474
56609
|
return meta;
|
|
55475
56610
|
}
|
|
55476
56611
|
/**
|
|
@@ -55484,7 +56619,7 @@ var ProjectStore = class {
|
|
|
55484
56619
|
async scaffold(name, templateSrcDir, scaffoldScriptPath) {
|
|
55485
56620
|
const destDir = this.projectDir(name);
|
|
55486
56621
|
return await new Promise((resolve6, reject) => {
|
|
55487
|
-
const child = (0,
|
|
56622
|
+
const child = (0, import_node_child_process14.spawn)("bash", [scaffoldScriptPath, name, templateSrcDir, destDir], {
|
|
55488
56623
|
env: { ...process.env, PATH: process.env.PATH ?? "" },
|
|
55489
56624
|
stdio: ["ignore", "pipe", "pipe"]
|
|
55490
56625
|
});
|
|
@@ -55513,7 +56648,7 @@ var ProjectStore = class {
|
|
|
55513
56648
|
}
|
|
55514
56649
|
async delete(name) {
|
|
55515
56650
|
const dir = this.projectDir(name);
|
|
55516
|
-
await
|
|
56651
|
+
await import_node_fs46.promises.rm(dir, { recursive: true, force: true });
|
|
55517
56652
|
}
|
|
55518
56653
|
async updatePort(name, newPort) {
|
|
55519
56654
|
if (newPort < PROJECT_PORT_MIN || newPort > PROJECT_PORT_MAX) {
|
|
@@ -55529,7 +56664,7 @@ var ProjectStore = class {
|
|
|
55529
56664
|
throw new Error(`port ${newPort} already used / \u5DF2\u88AB project "${conflict.name}" \u5360\u7528`);
|
|
55530
56665
|
}
|
|
55531
56666
|
const updated = { ...target, port: newPort };
|
|
55532
|
-
await
|
|
56667
|
+
await import_node_fs46.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
|
|
55533
56668
|
return updated;
|
|
55534
56669
|
}
|
|
55535
56670
|
/**
|
|
@@ -55546,7 +56681,7 @@ var ProjectStore = class {
|
|
|
55546
56681
|
if (!validated.success) {
|
|
55547
56682
|
throw new Error(`invalid prodUrl "${url}": ${validated.error.message}`);
|
|
55548
56683
|
}
|
|
55549
|
-
await
|
|
56684
|
+
await import_node_fs46.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
|
|
55550
56685
|
return validated.data;
|
|
55551
56686
|
}
|
|
55552
56687
|
/**
|
|
@@ -55567,7 +56702,7 @@ var ProjectStore = class {
|
|
|
55567
56702
|
if (!validated.success) {
|
|
55568
56703
|
throw new Error(`invalid publishJob: ${validated.error.message}`);
|
|
55569
56704
|
}
|
|
55570
|
-
await
|
|
56705
|
+
await import_node_fs46.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
|
|
55571
56706
|
return validated.data;
|
|
55572
56707
|
}
|
|
55573
56708
|
/** 清掉 .clawd-project.json.publishJob 字段。其他字段保持原样。 */
|
|
@@ -55582,13 +56717,13 @@ var ProjectStore = class {
|
|
|
55582
56717
|
if (!validated.success) {
|
|
55583
56718
|
throw new Error(`failed to clear publishJob: ${validated.error.message}`);
|
|
55584
56719
|
}
|
|
55585
|
-
await
|
|
56720
|
+
await import_node_fs46.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
|
|
55586
56721
|
return validated.data;
|
|
55587
56722
|
}
|
|
55588
56723
|
};
|
|
55589
56724
|
|
|
55590
56725
|
// src/app-builder/kill-port.ts
|
|
55591
|
-
var
|
|
56726
|
+
var import_node_child_process15 = require("child_process");
|
|
55592
56727
|
async function killPortOccupants(port, ownedPids, logger) {
|
|
55593
56728
|
let pids;
|
|
55594
56729
|
try {
|
|
@@ -55630,7 +56765,7 @@ async function killPortOccupants(port, ownedPids, logger) {
|
|
|
55630
56765
|
}
|
|
55631
56766
|
function listPidsOnPort(port) {
|
|
55632
56767
|
return new Promise((resolve6, reject) => {
|
|
55633
|
-
(0,
|
|
56768
|
+
(0, import_node_child_process15.execFile)(
|
|
55634
56769
|
"lsof",
|
|
55635
56770
|
["-ti", `:${port}`],
|
|
55636
56771
|
{ timeout: 3e3 },
|
|
@@ -55652,7 +56787,7 @@ function listPidsOnPort(port) {
|
|
|
55652
56787
|
}
|
|
55653
56788
|
|
|
55654
56789
|
// src/app-builder/publish-registry.ts
|
|
55655
|
-
var
|
|
56790
|
+
var import_node_crypto16 = require("crypto");
|
|
55656
56791
|
var PublishJobRegistry = class {
|
|
55657
56792
|
jobs = /* @__PURE__ */ new Map();
|
|
55658
56793
|
has(name) {
|
|
@@ -55669,7 +56804,7 @@ var PublishJobRegistry = class {
|
|
|
55669
56804
|
if (this.jobs.has(args.name)) {
|
|
55670
56805
|
throw new Error(`already publishing: ${args.name}`);
|
|
55671
56806
|
}
|
|
55672
|
-
const jobId = args.jobId ?? `job-${(0,
|
|
56807
|
+
const jobId = args.jobId ?? `job-${(0, import_node_crypto16.randomUUID)()}`;
|
|
55673
56808
|
this.jobs.set(args.name, {
|
|
55674
56809
|
jobId,
|
|
55675
56810
|
name: args.name,
|
|
@@ -55702,9 +56837,9 @@ var PublishJobRegistry = class {
|
|
|
55702
56837
|
};
|
|
55703
56838
|
|
|
55704
56839
|
// src/app-builder/publish-job-runner.ts
|
|
55705
|
-
var
|
|
55706
|
-
var
|
|
55707
|
-
var
|
|
56840
|
+
var import_node_child_process16 = require("child_process");
|
|
56841
|
+
var import_node_fs47 = require("fs");
|
|
56842
|
+
var import_node_path55 = require("path");
|
|
55708
56843
|
|
|
55709
56844
|
// src/app-builder/publish-stage-parser.ts
|
|
55710
56845
|
var STAGE_RE = /^\s*::stage::(build|deploy|verify)\s*$/;
|
|
@@ -55731,19 +56866,19 @@ function tailStderrLines(buf, n) {
|
|
|
55731
56866
|
// src/app-builder/publish-job-runner.ts
|
|
55732
56867
|
async function startPublishJob(deps, args) {
|
|
55733
56868
|
const { registry: registry2, projectDir } = deps;
|
|
55734
|
-
const
|
|
56869
|
+
const spawn15 = deps.spawnImpl ?? import_node_child_process16.spawn;
|
|
55735
56870
|
if (registry2.has(args.name)) {
|
|
55736
56871
|
return { jobId: registry2.get(args.name).jobId, status: "already-publishing" };
|
|
55737
56872
|
}
|
|
55738
56873
|
const projDir = projectDir(args.name);
|
|
55739
|
-
const logPath = (0,
|
|
56874
|
+
const logPath = (0, import_node_path55.join)(projDir, ".publish.log");
|
|
55740
56875
|
let logStream = null;
|
|
55741
56876
|
try {
|
|
55742
|
-
logStream = (0,
|
|
56877
|
+
logStream = (0, import_node_fs47.createWriteStream)(logPath, { flags: "w" });
|
|
55743
56878
|
} catch {
|
|
55744
56879
|
logStream = null;
|
|
55745
56880
|
}
|
|
55746
|
-
const child =
|
|
56881
|
+
const child = spawn15("bash", [args.scriptPath, projDir, args.personaRoot ?? ""], {
|
|
55747
56882
|
cwd: projDir,
|
|
55748
56883
|
env: process.env,
|
|
55749
56884
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -55996,8 +57131,8 @@ async function recoverInterruptedJobs(deps) {
|
|
|
55996
57131
|
|
|
55997
57132
|
// src/handlers/app-builder.ts
|
|
55998
57133
|
init_protocol();
|
|
55999
|
-
var
|
|
56000
|
-
var
|
|
57134
|
+
var import_node_path56 = require("path");
|
|
57135
|
+
var import_node_fs48 = require("fs");
|
|
56001
57136
|
var APP_BUILDER_PERSONAS = ["persona-app-builder", "persona-dataclaw-builder"];
|
|
56002
57137
|
var DEV_SERVER_READY_TIMEOUT_MS = 3e4;
|
|
56003
57138
|
async function recoverInterruptedPublishJobs(store, logger) {
|
|
@@ -56078,7 +57213,7 @@ function buildAppBuilderHandlers(deps) {
|
|
|
56078
57213
|
async function listAllUsersProjects() {
|
|
56079
57214
|
if (!deps.usersRoot || !deps.getStore) return [];
|
|
56080
57215
|
const getStore = deps.getStore;
|
|
56081
|
-
const userIds = await
|
|
57216
|
+
const userIds = await import_node_fs48.promises.readdir(deps.usersRoot).catch(() => []);
|
|
56082
57217
|
const perUser = await Promise.all(
|
|
56083
57218
|
userIds.map((uid) => getStore(uid).list().catch(() => []))
|
|
56084
57219
|
);
|
|
@@ -56154,8 +57289,8 @@ function buildAppBuilderHandlers(deps) {
|
|
|
56154
57289
|
const project = await userStore.create(f.name, reservedPorts);
|
|
56155
57290
|
try {
|
|
56156
57291
|
const personaRoot = deps.resolvePersonaRoot ? deps.resolvePersonaRoot(session.ownerPersonaId ?? "") : deps.personaRoot;
|
|
56157
|
-
const templateSrcDir = (0,
|
|
56158
|
-
const scaffoldScript = (0,
|
|
57292
|
+
const templateSrcDir = (0, import_node_path56.join)(personaRoot, "extension-kit", "examples", DEFAULT_TEMPLATE);
|
|
57293
|
+
const scaffoldScript = (0, import_node_path56.join)(deps.deployKitRoot, "scripts", "new-extension.sh");
|
|
56159
57294
|
const scaffoldResult = await userStore.scaffold(project.name, templateSrcDir, scaffoldScript);
|
|
56160
57295
|
deps.logger?.info("app-builder.scaffold.done", {
|
|
56161
57296
|
name: project.name,
|
|
@@ -56376,7 +57511,7 @@ function buildAppBuilderHandlers(deps) {
|
|
|
56376
57511
|
await userStore.clearPublishJob(args.name);
|
|
56377
57512
|
}
|
|
56378
57513
|
const personaRoot = deps.resolvePersonaRoot ? deps.resolvePersonaRoot(boundSession.ownerPersonaId ?? "") : deps.personaRoot;
|
|
56379
|
-
const scriptPath = (0,
|
|
57514
|
+
const scriptPath = (0, import_node_path56.join)(deps.deployKitRoot, "scripts", "publish.sh");
|
|
56380
57515
|
deps.logger?.info("app-builder.publish.start", {
|
|
56381
57516
|
name: args.name,
|
|
56382
57517
|
sessionId: boundSession.sessionId,
|
|
@@ -56520,7 +57655,7 @@ function buildShiftHandlers(deps) {
|
|
|
56520
57655
|
|
|
56521
57656
|
// src/handlers/visitor.ts
|
|
56522
57657
|
init_protocol();
|
|
56523
|
-
function
|
|
57658
|
+
function ensureOwner4(ctx) {
|
|
56524
57659
|
if (!ctx || ctx.principal.kind !== "owner") {
|
|
56525
57660
|
throw new ClawdError(
|
|
56526
57661
|
ERROR_CODES.UNAUTHORIZED,
|
|
@@ -56530,7 +57665,7 @@ function ensureOwner3(ctx) {
|
|
|
56530
57665
|
}
|
|
56531
57666
|
function buildVisitorHandlers(deps) {
|
|
56532
57667
|
const list = async (_frame, _client, ctx) => {
|
|
56533
|
-
|
|
57668
|
+
ensureOwner4(ctx);
|
|
56534
57669
|
return {
|
|
56535
57670
|
response: {
|
|
56536
57671
|
type: "visitor:list",
|
|
@@ -56545,7 +57680,8 @@ function buildVisitorHandlers(deps) {
|
|
|
56545
57680
|
|
|
56546
57681
|
// src/extension/registry.ts
|
|
56547
57682
|
var import_promises9 = __toESM(require("fs/promises"), 1);
|
|
56548
|
-
var
|
|
57683
|
+
var import_node_path57 = __toESM(require("path"), 1);
|
|
57684
|
+
init_src();
|
|
56549
57685
|
async function loadAll(root) {
|
|
56550
57686
|
let entries;
|
|
56551
57687
|
try {
|
|
@@ -56558,13 +57694,13 @@ async function loadAll(root) {
|
|
|
56558
57694
|
for (const ent of entries) {
|
|
56559
57695
|
if (!ent.isDirectory()) continue;
|
|
56560
57696
|
if (ent.name.startsWith(".")) continue;
|
|
56561
|
-
records.push(await loadOne(
|
|
57697
|
+
records.push(await loadOne(import_node_path57.default.join(root, ent.name), ent.name));
|
|
56562
57698
|
}
|
|
56563
57699
|
records.sort((a, b2) => a.extId < b2.extId ? -1 : a.extId > b2.extId ? 1 : 0);
|
|
56564
57700
|
return records;
|
|
56565
57701
|
}
|
|
56566
57702
|
async function loadOne(dir, dirName) {
|
|
56567
|
-
const manifestPath =
|
|
57703
|
+
const manifestPath = import_node_path57.default.join(dir, "manifest.json");
|
|
56568
57704
|
let raw;
|
|
56569
57705
|
try {
|
|
56570
57706
|
raw = await import_promises9.default.readFile(manifestPath, "utf8");
|
|
@@ -56609,7 +57745,7 @@ async function loadOne(dir, dirName) {
|
|
|
56609
57745
|
|
|
56610
57746
|
// src/extension/uninstall.ts
|
|
56611
57747
|
var import_promises10 = __toESM(require("fs/promises"), 1);
|
|
56612
|
-
var
|
|
57748
|
+
var import_node_path58 = __toESM(require("path"), 1);
|
|
56613
57749
|
var UninstallError = class extends Error {
|
|
56614
57750
|
constructor(code, message) {
|
|
56615
57751
|
super(message);
|
|
@@ -56618,7 +57754,7 @@ var UninstallError = class extends Error {
|
|
|
56618
57754
|
code;
|
|
56619
57755
|
};
|
|
56620
57756
|
async function uninstall(deps) {
|
|
56621
|
-
const dir =
|
|
57757
|
+
const dir = import_node_path58.default.join(deps.root, deps.extId);
|
|
56622
57758
|
try {
|
|
56623
57759
|
await import_promises10.default.access(dir);
|
|
56624
57760
|
} catch {
|
|
@@ -56629,7 +57765,8 @@ async function uninstall(deps) {
|
|
|
56629
57765
|
}
|
|
56630
57766
|
|
|
56631
57767
|
// src/handlers/index.ts
|
|
56632
|
-
var
|
|
57768
|
+
var import_node_crypto17 = require("crypto");
|
|
57769
|
+
init_peer_forward();
|
|
56633
57770
|
function buildMethodHandlers(deps) {
|
|
56634
57771
|
return {
|
|
56635
57772
|
...buildSessionHandlers({
|
|
@@ -56662,7 +57799,7 @@ function buildMethodHandlers(deps) {
|
|
|
56662
57799
|
const c = deps.contactStore.get(deviceId);
|
|
56663
57800
|
return c ? { deviceId: c.deviceId, remoteUrl: c.remoteUrl, connectToken: c.connectToken } : null;
|
|
56664
57801
|
},
|
|
56665
|
-
genId: () => (0,
|
|
57802
|
+
genId: () => (0, import_node_crypto17.randomUUID)(),
|
|
56666
57803
|
now: () => Date.now(),
|
|
56667
57804
|
forwardInboxPostToPeer,
|
|
56668
57805
|
logger: deps.logger
|
|
@@ -56673,6 +57810,12 @@ function buildMethodHandlers(deps) {
|
|
|
56673
57810
|
broadcast: deps.broadcastToOwners,
|
|
56674
57811
|
now: () => Date.now()
|
|
56675
57812
|
}),
|
|
57813
|
+
...buildContactSshHandlers({
|
|
57814
|
+
store: deps.contactStore,
|
|
57815
|
+
broadcast: deps.broadcastToOwners,
|
|
57816
|
+
sshdDir: deps.sshdDir,
|
|
57817
|
+
sshLog: deps.contactSshLog
|
|
57818
|
+
}),
|
|
56676
57819
|
whoami: buildWhoamiHandler({
|
|
56677
57820
|
ownerDisplayName: deps.ownerDisplayName,
|
|
56678
57821
|
ownerPrincipalId: deps.ownerPrincipalId,
|
|
@@ -56719,7 +57862,7 @@ function buildMethodHandlers(deps) {
|
|
|
56719
57862
|
}
|
|
56720
57863
|
|
|
56721
57864
|
// src/app-builder/dev-server-supervisor.ts
|
|
56722
|
-
var
|
|
57865
|
+
var import_node_child_process17 = require("child_process");
|
|
56723
57866
|
var import_node_events2 = require("events");
|
|
56724
57867
|
var DEFAULT_READY_PATTERN = /Local:\s+https?:\/\/|Nest application successfully started|server listening on/i;
|
|
56725
57868
|
var DevServerSupervisor = class extends import_node_events2.EventEmitter {
|
|
@@ -56756,7 +57899,7 @@ var DevServerSupervisor = class extends import_node_events2.EventEmitter {
|
|
|
56756
57899
|
tunnelHost: args.tunnelHost,
|
|
56757
57900
|
devCommand: cmd
|
|
56758
57901
|
});
|
|
56759
|
-
const child = (0,
|
|
57902
|
+
const child = (0, import_node_child_process17.spawn)("sh", ["-c", cmd], {
|
|
56760
57903
|
cwd: args.cwd,
|
|
56761
57904
|
env,
|
|
56762
57905
|
stdio: "pipe",
|
|
@@ -57008,6 +58151,12 @@ var METHOD_GRANT_MAP = {
|
|
|
57008
58151
|
"contact:list": ADMIN_ANY,
|
|
57009
58152
|
"contact:pin": ADMIN_ANY,
|
|
57010
58153
|
"contact:remove": ADMIN_ANY,
|
|
58154
|
+
// contact:setSshAccess (owner UI 配 SSH 授权):ADMIN_ANY
|
|
58155
|
+
// contact:sshKey:issue (guest daemon 拉自己的 privkey):public — handler 内校
|
|
58156
|
+
// ctx.principal.kind==='guest' + store.get(callerId).sshAllowed
|
|
58157
|
+
// (对齐 inbox:postMessage 的"能连上=有 auth,业务在 handler 校"模式)
|
|
58158
|
+
"contact:setSshAccess": ADMIN_ANY,
|
|
58159
|
+
"contact:sshKey:issue": { kind: "public" },
|
|
57011
58160
|
// ---- visitor:* (访客名单,owner-only) ----
|
|
57012
58161
|
// owner 看完整访客名单(含没开会话的);guest 不可调(handler 内再 assertOwner 兜底)。
|
|
57013
58162
|
"visitor:list": ADMIN_ANY,
|
|
@@ -57188,12 +58337,13 @@ async function dispatchRpc(method, frame, client, ctx, deps) {
|
|
|
57188
58337
|
}
|
|
57189
58338
|
|
|
57190
58339
|
// src/extension/runtime.ts
|
|
57191
|
-
var
|
|
57192
|
-
var
|
|
58340
|
+
var import_node_child_process18 = require("child_process");
|
|
58341
|
+
var import_node_path59 = __toESM(require("path"), 1);
|
|
57193
58342
|
var import_promises11 = require("timers/promises");
|
|
58343
|
+
init_src();
|
|
57194
58344
|
|
|
57195
58345
|
// src/extension/port-allocator.ts
|
|
57196
|
-
var
|
|
58346
|
+
var import_node_net3 = __toESM(require("net"), 1);
|
|
57197
58347
|
var PortExhaustedError = class extends Error {
|
|
57198
58348
|
constructor(min, max) {
|
|
57199
58349
|
super(`no free port in [${min},${max}]`);
|
|
@@ -57206,7 +58356,7 @@ var PortExhaustedError = class extends Error {
|
|
|
57206
58356
|
};
|
|
57207
58357
|
function probe(port) {
|
|
57208
58358
|
return new Promise((resolve6) => {
|
|
57209
|
-
const srv =
|
|
58359
|
+
const srv = import_node_net3.default.createServer();
|
|
57210
58360
|
srv.once("error", () => resolve6(false));
|
|
57211
58361
|
srv.once("listening", () => {
|
|
57212
58362
|
srv.close(() => resolve6(true));
|
|
@@ -57290,13 +58440,13 @@ var Runtime = class {
|
|
|
57290
58440
|
/\$CLAWOS_EXT_PORT/g,
|
|
57291
58441
|
String(port)
|
|
57292
58442
|
);
|
|
57293
|
-
const dir =
|
|
58443
|
+
const dir = import_node_path59.default.join(this.root, extId);
|
|
57294
58444
|
const env = {
|
|
57295
58445
|
...process.env,
|
|
57296
58446
|
CLAWOS_EXT_PORT: String(port),
|
|
57297
58447
|
CLAWOS_EXT_ID: extId
|
|
57298
58448
|
};
|
|
57299
|
-
const child = (0,
|
|
58449
|
+
const child = (0, import_node_child_process18.spawn)("sh", ["-c", cmd], {
|
|
57300
58450
|
cwd: dir,
|
|
57301
58451
|
env,
|
|
57302
58452
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -57402,7 +58552,8 @@ ${handle.stderrTail}`
|
|
|
57402
58552
|
|
|
57403
58553
|
// src/extension/published-channels.ts
|
|
57404
58554
|
var import_promises12 = __toESM(require("fs/promises"), 1);
|
|
57405
|
-
var
|
|
58555
|
+
var import_node_path60 = __toESM(require("path"), 1);
|
|
58556
|
+
init_src();
|
|
57406
58557
|
init_zod();
|
|
57407
58558
|
var PublishedChannelsError = class extends Error {
|
|
57408
58559
|
constructor(code, message) {
|
|
@@ -57501,7 +58652,7 @@ var PublishedChannelStore = class {
|
|
|
57501
58652
|
)
|
|
57502
58653
|
};
|
|
57503
58654
|
const tmp = `${this.filePath}.tmp`;
|
|
57504
|
-
await import_promises12.default.mkdir(
|
|
58655
|
+
await import_promises12.default.mkdir(import_node_path60.default.dirname(this.filePath), { recursive: true });
|
|
57505
58656
|
await import_promises12.default.writeFile(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
57506
58657
|
await import_promises12.default.rename(tmp, this.filePath);
|
|
57507
58658
|
}
|
|
@@ -57509,7 +58660,7 @@ var PublishedChannelStore = class {
|
|
|
57509
58660
|
|
|
57510
58661
|
// src/extension/bundle-cache.ts
|
|
57511
58662
|
var import_promises13 = __toESM(require("fs/promises"), 1);
|
|
57512
|
-
var
|
|
58663
|
+
var import_node_path61 = __toESM(require("path"), 1);
|
|
57513
58664
|
var BundleCache = class {
|
|
57514
58665
|
constructor(rootDir) {
|
|
57515
58666
|
this.rootDir = rootDir;
|
|
@@ -57518,14 +58669,14 @@ var BundleCache = class {
|
|
|
57518
58669
|
/** Atomic write: stage tmp → rename. Caller passes the hex sha256. */
|
|
57519
58670
|
async write(snapshotHash, buffer) {
|
|
57520
58671
|
await import_promises13.default.mkdir(this.rootDir, { recursive: true });
|
|
57521
|
-
const file =
|
|
58672
|
+
const file = import_node_path61.default.join(this.rootDir, `${snapshotHash}.zip`);
|
|
57522
58673
|
const tmp = `${file}.tmp`;
|
|
57523
58674
|
await import_promises13.default.writeFile(tmp, buffer, { mode: 384 });
|
|
57524
58675
|
await import_promises13.default.rename(tmp, file);
|
|
57525
58676
|
}
|
|
57526
58677
|
/** Returns the bundle bytes, or null when the file doesn't exist. */
|
|
57527
58678
|
async read(snapshotHash) {
|
|
57528
|
-
const file =
|
|
58679
|
+
const file = import_node_path61.default.join(this.rootDir, `${snapshotHash}.zip`);
|
|
57529
58680
|
try {
|
|
57530
58681
|
return await import_promises13.default.readFile(file);
|
|
57531
58682
|
} catch (e) {
|
|
@@ -57535,7 +58686,7 @@ var BundleCache = class {
|
|
|
57535
58686
|
}
|
|
57536
58687
|
/** Idempotent — missing file is not an error. */
|
|
57537
58688
|
async delete(snapshotHash) {
|
|
57538
|
-
const file =
|
|
58689
|
+
const file = import_node_path61.default.join(this.rootDir, `${snapshotHash}.zip`);
|
|
57539
58690
|
await import_promises13.default.rm(file, { force: true });
|
|
57540
58691
|
}
|
|
57541
58692
|
};
|
|
@@ -57560,17 +58711,10 @@ async function startDaemon(config) {
|
|
|
57560
58711
|
});
|
|
57561
58712
|
const logger = createLogger({
|
|
57562
58713
|
level: config.logLevel,
|
|
57563
|
-
file:
|
|
58714
|
+
file: import_node_path62.default.join(config.dataDir, "clawd.log"),
|
|
57564
58715
|
logClient
|
|
57565
58716
|
});
|
|
57566
58717
|
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
58718
|
const stateMgr = new StateFileManager({ dataDir: config.dataDir });
|
|
57575
58719
|
const pre = stateMgr.preflight();
|
|
57576
58720
|
if (pre.status === "active") {
|
|
@@ -57707,8 +58851,8 @@ async function startDaemon(config) {
|
|
|
57707
58851
|
const agents = new AgentsScanner();
|
|
57708
58852
|
const history = new ClaudeHistoryReader();
|
|
57709
58853
|
let transport = null;
|
|
57710
|
-
const personaStore = new PersonaStore(
|
|
57711
|
-
const usersRoot =
|
|
58854
|
+
const personaStore = new PersonaStore(import_node_path62.default.join(config.dataDir, "personas"));
|
|
58855
|
+
const usersRoot = import_node_path62.default.join(config.dataDir, "users");
|
|
57712
58856
|
const defaultsRoot = findDefaultsRoot(logger);
|
|
57713
58857
|
if (defaultsRoot) {
|
|
57714
58858
|
seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
|
|
@@ -57728,17 +58872,17 @@ async function startDaemon(config) {
|
|
|
57728
58872
|
migrateCodexSandbox({ store: personaStore, logger });
|
|
57729
58873
|
const groupFileStore = new GroupFileStore({ dataDir: config.dataDir, logger });
|
|
57730
58874
|
const personaDispatchManager = new PersonaDispatchManager({ genId: () => v4_default() });
|
|
57731
|
-
const here = typeof __dirname === "string" ? __dirname :
|
|
58875
|
+
const here = typeof __dirname === "string" ? __dirname : import_node_path62.default.dirname((0, import_node_url4.fileURLToPath)(import_meta6.url));
|
|
57732
58876
|
const dispatchServerCandidates = [
|
|
57733
|
-
|
|
58877
|
+
import_node_path62.default.join(here, "dispatch", "mcp-server.cjs"),
|
|
57734
58878
|
// 生产 dist/index → dist/dispatch/mcp-server.cjs
|
|
57735
|
-
|
|
58879
|
+
import_node_path62.default.join(here, "..", "dist", "dispatch", "mcp-server.cjs")
|
|
57736
58880
|
// dev tsx src/index → ../dist/dispatch/mcp-server.cjs
|
|
57737
58881
|
];
|
|
57738
|
-
const dispatchServerScriptPath = dispatchServerCandidates.find((p2) =>
|
|
58882
|
+
const dispatchServerScriptPath = dispatchServerCandidates.find((p2) => import_node_fs49.default.existsSync(p2));
|
|
57739
58883
|
let dispatchMcpConfigPath2;
|
|
57740
58884
|
if (dispatchServerScriptPath) {
|
|
57741
|
-
const dispatchLogPath =
|
|
58885
|
+
const dispatchLogPath = import_node_path62.default.join(config.dataDir, "dispatch-mcp-server.log");
|
|
57742
58886
|
dispatchMcpConfigPath2 = writeDispatchMcpConfig({
|
|
57743
58887
|
dataDir: config.dataDir,
|
|
57744
58888
|
serverScriptPath: dispatchServerScriptPath,
|
|
@@ -57755,15 +58899,15 @@ async function startDaemon(config) {
|
|
|
57755
58899
|
});
|
|
57756
58900
|
}
|
|
57757
58901
|
const ticketServerCandidates = [
|
|
57758
|
-
|
|
57759
|
-
|
|
58902
|
+
import_node_path62.default.join(here, "ticket", "mcp-server.cjs"),
|
|
58903
|
+
import_node_path62.default.join(here, "..", "dist", "ticket", "mcp-server.cjs")
|
|
57760
58904
|
];
|
|
57761
|
-
const ticketServerScriptPath = ticketServerCandidates.find((p2) =>
|
|
58905
|
+
const ticketServerScriptPath = ticketServerCandidates.find((p2) => import_node_fs49.default.existsSync(p2));
|
|
57762
58906
|
const ticketOwnerUnionId = feishuIdentity?.identity.unionId ?? "";
|
|
57763
58907
|
const ticketOwnerName = feishuIdentity?.identity.displayName ?? "";
|
|
57764
58908
|
let ticketMcpConfigPath2;
|
|
57765
58909
|
if (ticketServerScriptPath && ticketOwnerUnionId) {
|
|
57766
|
-
const ticketLogPath =
|
|
58910
|
+
const ticketLogPath = import_node_path62.default.join(config.dataDir, "ticket-mcp-server.log");
|
|
57767
58911
|
ticketMcpConfigPath2 = writeTicketMcpConfig({
|
|
57768
58912
|
dataDir: config.dataDir,
|
|
57769
58913
|
serverScriptPath: ticketServerScriptPath,
|
|
@@ -57784,13 +58928,13 @@ async function startDaemon(config) {
|
|
|
57784
58928
|
});
|
|
57785
58929
|
}
|
|
57786
58930
|
const shiftServerCandidates = [
|
|
57787
|
-
|
|
57788
|
-
|
|
58931
|
+
import_node_path62.default.join(here, "shift", "mcp-server.cjs"),
|
|
58932
|
+
import_node_path62.default.join(here, "..", "dist", "shift", "mcp-server.cjs")
|
|
57789
58933
|
];
|
|
57790
|
-
const shiftServerScriptPath = shiftServerCandidates.find((p2) =>
|
|
58934
|
+
const shiftServerScriptPath = shiftServerCandidates.find((p2) => import_node_fs49.default.existsSync(p2));
|
|
57791
58935
|
let shiftMcpConfigPath2;
|
|
57792
58936
|
if (shiftServerScriptPath) {
|
|
57793
|
-
const shiftLogPath =
|
|
58937
|
+
const shiftLogPath = import_node_path62.default.join(config.dataDir, "shift-mcp-server.log");
|
|
57794
58938
|
shiftMcpConfigPath2 = await writeShiftMcpConfig({
|
|
57795
58939
|
dataDir: config.dataDir,
|
|
57796
58940
|
serverScriptPath: shiftServerScriptPath,
|
|
@@ -57808,13 +58952,13 @@ async function startDaemon(config) {
|
|
|
57808
58952
|
);
|
|
57809
58953
|
}
|
|
57810
58954
|
const inboxServerCandidates = [
|
|
57811
|
-
|
|
57812
|
-
|
|
58955
|
+
import_node_path62.default.join(here, "inbox", "mcp-server.cjs"),
|
|
58956
|
+
import_node_path62.default.join(here, "..", "dist", "inbox", "mcp-server.cjs")
|
|
57813
58957
|
];
|
|
57814
|
-
const inboxServerScriptPath = inboxServerCandidates.find((p2) =>
|
|
58958
|
+
const inboxServerScriptPath = inboxServerCandidates.find((p2) => import_node_fs49.default.existsSync(p2));
|
|
57815
58959
|
let inboxMcpConfigPath2;
|
|
57816
58960
|
if (inboxServerScriptPath) {
|
|
57817
|
-
const inboxLogPath =
|
|
58961
|
+
const inboxLogPath = import_node_path62.default.join(config.dataDir, "inbox-mcp-server.log");
|
|
57818
58962
|
inboxMcpConfigPath2 = await writeInboxMcpConfig({
|
|
57819
58963
|
dataDir: config.dataDir,
|
|
57820
58964
|
serverScriptPath: inboxServerScriptPath,
|
|
@@ -57832,7 +58976,7 @@ async function startDaemon(config) {
|
|
|
57832
58976
|
);
|
|
57833
58977
|
}
|
|
57834
58978
|
const shiftStore = createShiftStore({
|
|
57835
|
-
filePath:
|
|
58979
|
+
filePath: import_node_path62.default.join(config.dataDir, "shift.json"),
|
|
57836
58980
|
ownerIdProvider: () => ownerPrincipalId,
|
|
57837
58981
|
now: () => Date.now()
|
|
57838
58982
|
});
|
|
@@ -57847,14 +58991,10 @@ async function startDaemon(config) {
|
|
|
57847
58991
|
// 新布局派生 (sessions/* + personas/<pid>/.clawd/sessions/owner/*)
|
|
57848
58992
|
storeFactory: sessionStoreFactory,
|
|
57849
58993
|
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
58994
|
getAdapter,
|
|
57855
58995
|
historyReader: history,
|
|
57856
58996
|
dataDir: config.dataDir,
|
|
57857
|
-
personaRoot:
|
|
58997
|
+
personaRoot: import_node_path62.default.join(config.dataDir, "personas"),
|
|
57858
58998
|
usersRoot,
|
|
57859
58999
|
personaStore,
|
|
57860
59000
|
ownerDisplayName,
|
|
@@ -57897,10 +59037,10 @@ async function startDaemon(config) {
|
|
|
57897
59037
|
// 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
|
|
57898
59038
|
attachmentGroup: {
|
|
57899
59039
|
onFileEdit: (input) => {
|
|
57900
|
-
const absPath =
|
|
59040
|
+
const absPath = import_node_path62.default.isAbsolute(input.relPath) ? input.relPath : import_node_path62.default.join(input.cwd, input.relPath);
|
|
57901
59041
|
let size = 0;
|
|
57902
59042
|
try {
|
|
57903
|
-
size =
|
|
59043
|
+
size = import_node_fs49.default.statSync(absPath).size;
|
|
57904
59044
|
} catch (err) {
|
|
57905
59045
|
logger.warn("attachment.onFileEdit stat failed", {
|
|
57906
59046
|
sessionId: input.sessionId,
|
|
@@ -57968,10 +59108,10 @@ async function startDaemon(config) {
|
|
|
57968
59108
|
onSurfaceUnregister: (tsid) => manager.unregisterSurface(tsid),
|
|
57969
59109
|
// ReadyGate v2:ReadyDetector emit ready 时投递 reducer 'ready-detected' input
|
|
57970
59110
|
onReady: (tsid) => manager.dispatchReadyDetected(tsid),
|
|
57971
|
-
//
|
|
57972
|
-
|
|
57973
|
-
//
|
|
57974
|
-
|
|
59111
|
+
// 屏幕静止 → 补权威 turn_end(修 turn_duration 尾随 text 覆盖 lastEventKind)
|
|
59112
|
+
onTurnIdle: (tsid) => manager.dispatchTurnIdle(tsid),
|
|
59113
|
+
// 复合条件闸:observer 还需静止多久才满 idleMs(屏幕静止后精确等这段剩余再补 turn_end)
|
|
59114
|
+
getObserverWaitMs: (tsid, idleMs) => manager.observerIdleWaitMs(tsid, idleMs)
|
|
57975
59115
|
}) : new ClaudeAdapter({ logger, historyReader: new ClaudeHistoryReader() });
|
|
57976
59116
|
registerAdapter("claude", claudeAdapter);
|
|
57977
59117
|
registerAdapter("codex", new CodexAdapter({ logger, historyReader: new CodexHistoryReader() }));
|
|
@@ -58055,6 +59195,7 @@ async function startDaemon(config) {
|
|
|
58055
59195
|
const effectivePreviewPorts = Array.from(
|
|
58056
59196
|
/* @__PURE__ */ new Set([...config.previewPorts ?? [], ...appBuilderPortRange])
|
|
58057
59197
|
);
|
|
59198
|
+
const sshLog = createContactSshLog(config.dataDir);
|
|
58058
59199
|
const makeHandlers = () => buildMethodHandlers({
|
|
58059
59200
|
manager,
|
|
58060
59201
|
workspace,
|
|
@@ -58098,11 +59239,11 @@ async function startDaemon(config) {
|
|
|
58098
59239
|
// 'persona/<pid>/owner',default 走 'default'。
|
|
58099
59240
|
getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
|
|
58100
59241
|
// guest path guard:candidate 必须在 personaRoot 子树或调用者自己的 user-dir 下
|
|
58101
|
-
personaRoot:
|
|
59242
|
+
personaRoot: import_node_path62.default.join(config.dataDir, "personas"),
|
|
58102
59243
|
usersRoot
|
|
58103
59244
|
},
|
|
58104
59245
|
// workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
|
|
58105
|
-
personaRoot:
|
|
59246
|
+
personaRoot: import_node_path62.default.join(config.dataDir, "personas"),
|
|
58106
59247
|
// v2 多人 persona 隔离:handler 派生 guest user-dir 放行
|
|
58107
59248
|
usersRoot,
|
|
58108
59249
|
// capability:list / delete handler 依赖
|
|
@@ -58122,6 +59263,10 @@ async function startDaemon(config) {
|
|
|
58122
59263
|
inboxStore,
|
|
58123
59264
|
// 联系人列表 store(device:connect / 自动反向落同一 store)
|
|
58124
59265
|
contactStore,
|
|
59266
|
+
// <dataDir>/sshd 绝对路径 —— contact-ssh handlers 用它拼 authorized_keys / keys/ 子路径
|
|
59267
|
+
// Task 10 会加 SshdManager 起 sshd;handlers wire 提前挂 sshdDir 让 typecheck 过
|
|
59268
|
+
sshdDir: import_node_path62.default.join(config.dataDir, "sshd"),
|
|
59269
|
+
contactSshLog: sshLog,
|
|
58125
59270
|
// inbox:sendDm 用:sessionId → session 出身(复用 attachment 同款 findOwnedSessionScope)
|
|
58126
59271
|
getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
|
|
58127
59272
|
// contact:removed broadcast;复用 capability:tokenIssued 同款通路
|
|
@@ -58211,11 +59356,11 @@ async function startDaemon(config) {
|
|
|
58211
59356
|
// 发布上线脚手架化 (spec 2026-06-03 §5.2):
|
|
58212
59357
|
// appBuilderPersonaRoot 用于拼 publish.sh 绝对路径(persona-app-builder 安装在
|
|
58213
59358
|
// dataDir/personas/persona-app-builder 之下,extension-kit/scripts/publish.sh 是相对路径)。
|
|
58214
|
-
appBuilderPersonaRoot:
|
|
59359
|
+
appBuilderPersonaRoot: import_node_path62.default.join(config.dataDir, "personas", "persona-app-builder"),
|
|
58215
59360
|
// 共享 deploy-kit 根:scaffold/publish 脚本骨架 + 阿里云凭证单一真源。
|
|
58216
|
-
deployKitRoot:
|
|
59361
|
+
deployKitRoot: import_node_path62.default.join(config.dataDir, "deploy-kit"),
|
|
58217
59362
|
// scaffold/publish 按当前 session 的 persona 解析其安装根,让每个 persona 用自己的模板/注入配置。
|
|
58218
|
-
resolvePersonaRoot: (personaId) =>
|
|
59363
|
+
resolvePersonaRoot: (personaId) => import_node_path62.default.join(config.dataDir, "personas", personaId),
|
|
58219
59364
|
// 发布上线脚手架化 (spec 2026-06-03 §5.2.2):
|
|
58220
59365
|
// 复用 SessionManagerDeps.broadcastFrame 同款 dispatch 逻辑 —— runner 调 manager.send
|
|
58221
59366
|
// 取回 broadcast 帧后逐帧 push 到 transport,跟 manager 自身的 deps 一致。
|
|
@@ -58258,7 +59403,7 @@ async function startDaemon(config) {
|
|
|
58258
59403
|
}
|
|
58259
59404
|
let sourceJsonlPath = "(no transcript yet \u2014 operate from the task description alone)";
|
|
58260
59405
|
if (sourceFile && sourceFile.toolSessionId) {
|
|
58261
|
-
sourceJsonlPath =
|
|
59406
|
+
sourceJsonlPath = import_node_path62.default.join(
|
|
58262
59407
|
import_node_os21.default.homedir(),
|
|
58263
59408
|
".claude",
|
|
58264
59409
|
"projects",
|
|
@@ -58447,6 +59592,36 @@ async function startDaemon(config) {
|
|
|
58447
59592
|
httpRequestHandler: httpRouter,
|
|
58448
59593
|
// app-builder build 模式:/preview/<port>/ HMR websocket upgrade 转发的端口白名单(同 http-router 配置)
|
|
58449
59594
|
previewPorts: effectivePreviewPorts,
|
|
59595
|
+
// Contact SSH tunnel (PR: contact-ssh-tunnel): /rpc/ssh-tunnel WS 前置拦截 →
|
|
59596
|
+
// Bearer auth (owner token / connect token / capability token 三选一) → net.connect 到本机 sshd →
|
|
59597
|
+
// raw byte relay. 用于 B 侧 `clawd ssh-relay` CLI 拨号(SSH ProxyCommand)
|
|
59598
|
+
sshTunnelUpgradeHandler: async (req, socket, head, wss2) => {
|
|
59599
|
+
await handleSshTunnelUpgrade(req, socket, head, {
|
|
59600
|
+
wss: wss2,
|
|
59601
|
+
sshdPort: config.sshdPort,
|
|
59602
|
+
logger,
|
|
59603
|
+
sshLog,
|
|
59604
|
+
authorize: async (token) => {
|
|
59605
|
+
if (!token) return false;
|
|
59606
|
+
if (resolvedAuthToken && token === resolvedAuthToken) return true;
|
|
59607
|
+
const cap = capabilityRegistry.verifyToken(token);
|
|
59608
|
+
if (cap.ok) return true;
|
|
59609
|
+
const vis = verifyVisitorToken(authFile.visitorTokenSecret, token, Math.floor(Date.now() / 1e3));
|
|
59610
|
+
if (vis.ok) return true;
|
|
59611
|
+
const publicKeyPem = serverKeyStore.read();
|
|
59612
|
+
if (publicKeyPem) {
|
|
59613
|
+
const r = verifyConnectToken({
|
|
59614
|
+
token,
|
|
59615
|
+
publicKeyPem,
|
|
59616
|
+
expectedDeviceId: authFile.deviceId
|
|
59617
|
+
});
|
|
59618
|
+
if (r.ok) return true;
|
|
59619
|
+
}
|
|
59620
|
+
return false;
|
|
59621
|
+
}
|
|
59622
|
+
});
|
|
59623
|
+
return true;
|
|
59624
|
+
},
|
|
58450
59625
|
// 订阅成功后给该 client 重放 in-flight pendingQuestions(plan: clawd-question-server-truth)。
|
|
58451
59626
|
// daemon 是 pendingQuestions 的唯一 source of truth;新 client 接入 / 刷新页面时
|
|
58452
59627
|
// 把当前所有未决 question 以 session:question 帧定向回放,让 UI 不再误显示 Ended。
|
|
@@ -58558,8 +59733,8 @@ async function startDaemon(config) {
|
|
|
58558
59733
|
const lines = [
|
|
58559
59734
|
`Tunnel: ${r.url}`,
|
|
58560
59735
|
...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
|
|
58561
|
-
`Frpc config: ${
|
|
58562
|
-
`Frpc log: ${
|
|
59736
|
+
`Frpc config: ${import_node_path62.default.join(config.dataDir, "frpc.toml")}`,
|
|
59737
|
+
`Frpc log: ${import_node_path62.default.join(config.dataDir, "frpc.log")}`
|
|
58563
59738
|
];
|
|
58564
59739
|
const width = Math.max(...lines.map((l) => l.length));
|
|
58565
59740
|
const bar = "\u2550".repeat(width + 4);
|
|
@@ -58572,8 +59747,8 @@ ${bar}
|
|
|
58572
59747
|
|
|
58573
59748
|
`);
|
|
58574
59749
|
try {
|
|
58575
|
-
const connectPath =
|
|
58576
|
-
|
|
59750
|
+
const connectPath = import_node_path62.default.join(config.dataDir, "connect.txt");
|
|
59751
|
+
import_node_fs49.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
|
|
58577
59752
|
} catch {
|
|
58578
59753
|
}
|
|
58579
59754
|
} catch (err) {
|
|
@@ -58598,6 +59773,30 @@ ${bar}
|
|
|
58598
59773
|
logger.warn("tunnel unavailable, degraded to local mode", { reason: tunnelError });
|
|
58599
59774
|
}
|
|
58600
59775
|
}
|
|
59776
|
+
const sshdMgr = new SshdManager({
|
|
59777
|
+
dataDir: config.dataDir,
|
|
59778
|
+
port: config.sshdPort,
|
|
59779
|
+
logger,
|
|
59780
|
+
installProcessExitHandlers: true,
|
|
59781
|
+
onSshdExit: (info) => logger.warn("sshd exited unexpectedly", info)
|
|
59782
|
+
});
|
|
59783
|
+
try {
|
|
59784
|
+
await sshdMgr.start();
|
|
59785
|
+
rebuildAuthorizedKeys(contactStore, import_node_path62.default.join(config.dataDir, "sshd"));
|
|
59786
|
+
logger.info("sshd: contact-ssh sandbox ready", { port: config.sshdPort });
|
|
59787
|
+
} catch (err) {
|
|
59788
|
+
logger.warn("sshd start failed; contact SSH grant will not work until fixed", {
|
|
59789
|
+
err: err.message
|
|
59790
|
+
});
|
|
59791
|
+
}
|
|
59792
|
+
const contactKeyPuller = new ContactKeyPuller({
|
|
59793
|
+
store: contactStore,
|
|
59794
|
+
dataDir: config.dataDir,
|
|
59795
|
+
logger,
|
|
59796
|
+
intervalMs: 6e4,
|
|
59797
|
+
sshLog
|
|
59798
|
+
});
|
|
59799
|
+
void contactKeyPuller.start();
|
|
58601
59800
|
void reportDevice();
|
|
58602
59801
|
void fetchServerKey();
|
|
58603
59802
|
const tickAttachmentGc = () => {
|
|
@@ -58632,6 +59831,12 @@ ${bar}
|
|
|
58632
59831
|
if (tunnelMgr) {
|
|
58633
59832
|
await tunnelMgr.stop();
|
|
58634
59833
|
}
|
|
59834
|
+
contactKeyPuller.stop();
|
|
59835
|
+
await sshdMgr.stop().catch((err) => {
|
|
59836
|
+
logger.warn("shutdown.sshd-stop-failed", {
|
|
59837
|
+
error: err instanceof Error ? err.message : String(err)
|
|
59838
|
+
});
|
|
59839
|
+
});
|
|
58635
59840
|
await wss.stop();
|
|
58636
59841
|
stateMgr.delete();
|
|
58637
59842
|
if (logClient) await logClient.dispose();
|
|
@@ -58645,9 +59850,9 @@ ${bar}
|
|
|
58645
59850
|
};
|
|
58646
59851
|
}
|
|
58647
59852
|
function migrateDropPersonsDir(dataDir) {
|
|
58648
|
-
const dir =
|
|
59853
|
+
const dir = import_node_path62.default.join(dataDir, "persons");
|
|
58649
59854
|
try {
|
|
58650
|
-
|
|
59855
|
+
import_node_fs49.default.rmSync(dir, { recursive: true, force: true });
|
|
58651
59856
|
} catch {
|
|
58652
59857
|
}
|
|
58653
59858
|
}
|
|
@@ -58660,6 +59865,11 @@ async function main() {
|
|
|
58660
59865
|
const code = await runCase2(argv.slice(1));
|
|
58661
59866
|
process.exit(code);
|
|
58662
59867
|
}
|
|
59868
|
+
if (argv[0] === "ssh-relay") {
|
|
59869
|
+
const { sshRelay: sshRelay2 } = await Promise.resolve().then(() => (init_sshd_cli_relay(), sshd_cli_relay_exports));
|
|
59870
|
+
const code = await sshRelay2(argv.slice(1));
|
|
59871
|
+
process.exit(code);
|
|
59872
|
+
}
|
|
58663
59873
|
const parsed = parseArgs(argv);
|
|
58664
59874
|
if (parsed.help) {
|
|
58665
59875
|
process.stdout.write(HELP_TEXT);
|