@clawos-dev/clawd 0.2.200 → 0.2.201-beta.403.73f7019
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs
CHANGED
|
@@ -742,8 +742,8 @@ var init_parseUtil = __esm({
|
|
|
742
742
|
init_errors2();
|
|
743
743
|
init_en();
|
|
744
744
|
makeIssue = (params) => {
|
|
745
|
-
const { data, path:
|
|
746
|
-
const fullPath = [...
|
|
745
|
+
const { data, path: path76, errorMaps, issueData } = params;
|
|
746
|
+
const fullPath = [...path76, ...issueData.path || []];
|
|
747
747
|
const fullIssue = {
|
|
748
748
|
...issueData,
|
|
749
749
|
path: fullPath
|
|
@@ -1054,11 +1054,11 @@ var init_types = __esm({
|
|
|
1054
1054
|
init_parseUtil();
|
|
1055
1055
|
init_util();
|
|
1056
1056
|
ParseInputLazyPath = class {
|
|
1057
|
-
constructor(parent, value,
|
|
1057
|
+
constructor(parent, value, path76, key) {
|
|
1058
1058
|
this._cachedPath = [];
|
|
1059
1059
|
this.parent = parent;
|
|
1060
1060
|
this.data = value;
|
|
1061
|
-
this._path =
|
|
1061
|
+
this._path = path76;
|
|
1062
1062
|
this._key = key;
|
|
1063
1063
|
}
|
|
1064
1064
|
get path() {
|
|
@@ -6449,8 +6449,8 @@ var require_req = __commonJS({
|
|
|
6449
6449
|
if (req.originalUrl) {
|
|
6450
6450
|
_req.url = req.originalUrl;
|
|
6451
6451
|
} else {
|
|
6452
|
-
const
|
|
6453
|
-
_req.url = typeof
|
|
6452
|
+
const path76 = req.path;
|
|
6453
|
+
_req.url = typeof path76 === "string" ? path76 : req.url ? req.url.path || req.url : void 0;
|
|
6454
6454
|
}
|
|
6455
6455
|
if (req.query) {
|
|
6456
6456
|
_req.query = req.query;
|
|
@@ -6615,14 +6615,14 @@ var require_redact = __commonJS({
|
|
|
6615
6615
|
}
|
|
6616
6616
|
return obj;
|
|
6617
6617
|
}
|
|
6618
|
-
function parsePath(
|
|
6618
|
+
function parsePath(path76) {
|
|
6619
6619
|
const parts = [];
|
|
6620
6620
|
let current = "";
|
|
6621
6621
|
let inBrackets = false;
|
|
6622
6622
|
let inQuotes = false;
|
|
6623
6623
|
let quoteChar = "";
|
|
6624
|
-
for (let i = 0; i <
|
|
6625
|
-
const char =
|
|
6624
|
+
for (let i = 0; i < path76.length; i++) {
|
|
6625
|
+
const char = path76[i];
|
|
6626
6626
|
if (!inBrackets && char === ".") {
|
|
6627
6627
|
if (current) {
|
|
6628
6628
|
parts.push(current);
|
|
@@ -6753,10 +6753,10 @@ var require_redact = __commonJS({
|
|
|
6753
6753
|
return current;
|
|
6754
6754
|
}
|
|
6755
6755
|
function redactPaths(obj, paths, censor, remove = false) {
|
|
6756
|
-
for (const
|
|
6757
|
-
const parts = parsePath(
|
|
6756
|
+
for (const path76 of paths) {
|
|
6757
|
+
const parts = parsePath(path76);
|
|
6758
6758
|
if (parts.includes("*")) {
|
|
6759
|
-
redactWildcardPath(obj, parts, censor,
|
|
6759
|
+
redactWildcardPath(obj, parts, censor, path76, remove);
|
|
6760
6760
|
} else {
|
|
6761
6761
|
if (remove) {
|
|
6762
6762
|
removeKey(obj, parts);
|
|
@@ -6841,8 +6841,8 @@ var require_redact = __commonJS({
|
|
|
6841
6841
|
}
|
|
6842
6842
|
} else {
|
|
6843
6843
|
if (afterWildcard.includes("*")) {
|
|
6844
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
6845
|
-
const fullPath = [...pathArray.slice(0, pathLength), ...
|
|
6844
|
+
const wrappedCensor = typeof censor === "function" ? (value, path76) => {
|
|
6845
|
+
const fullPath = [...pathArray.slice(0, pathLength), ...path76];
|
|
6846
6846
|
return censor(value, fullPath);
|
|
6847
6847
|
} : censor;
|
|
6848
6848
|
redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
|
|
@@ -6877,8 +6877,8 @@ var require_redact = __commonJS({
|
|
|
6877
6877
|
return null;
|
|
6878
6878
|
}
|
|
6879
6879
|
const pathStructure = /* @__PURE__ */ new Map();
|
|
6880
|
-
for (const
|
|
6881
|
-
const parts = parsePath(
|
|
6880
|
+
for (const path76 of pathsToClone) {
|
|
6881
|
+
const parts = parsePath(path76);
|
|
6882
6882
|
let current = pathStructure;
|
|
6883
6883
|
for (let i = 0; i < parts.length; i++) {
|
|
6884
6884
|
const part = parts[i];
|
|
@@ -6930,24 +6930,24 @@ var require_redact = __commonJS({
|
|
|
6930
6930
|
}
|
|
6931
6931
|
return cloneSelectively(obj, pathStructure);
|
|
6932
6932
|
}
|
|
6933
|
-
function validatePath(
|
|
6934
|
-
if (typeof
|
|
6933
|
+
function validatePath(path76) {
|
|
6934
|
+
if (typeof path76 !== "string") {
|
|
6935
6935
|
throw new Error("Paths must be (non-empty) strings");
|
|
6936
6936
|
}
|
|
6937
|
-
if (
|
|
6937
|
+
if (path76 === "") {
|
|
6938
6938
|
throw new Error("Invalid redaction path ()");
|
|
6939
6939
|
}
|
|
6940
|
-
if (
|
|
6941
|
-
throw new Error(`Invalid redaction path (${
|
|
6940
|
+
if (path76.includes("..")) {
|
|
6941
|
+
throw new Error(`Invalid redaction path (${path76})`);
|
|
6942
6942
|
}
|
|
6943
|
-
if (
|
|
6944
|
-
throw new Error(`Invalid redaction path (${
|
|
6943
|
+
if (path76.includes(",")) {
|
|
6944
|
+
throw new Error(`Invalid redaction path (${path76})`);
|
|
6945
6945
|
}
|
|
6946
6946
|
let bracketCount = 0;
|
|
6947
6947
|
let inQuotes = false;
|
|
6948
6948
|
let quoteChar = "";
|
|
6949
|
-
for (let i = 0; i <
|
|
6950
|
-
const char =
|
|
6949
|
+
for (let i = 0; i < path76.length; i++) {
|
|
6950
|
+
const char = path76[i];
|
|
6951
6951
|
if ((char === '"' || char === "'") && bracketCount > 0) {
|
|
6952
6952
|
if (!inQuotes) {
|
|
6953
6953
|
inQuotes = true;
|
|
@@ -6961,20 +6961,20 @@ var require_redact = __commonJS({
|
|
|
6961
6961
|
} else if (char === "]" && !inQuotes) {
|
|
6962
6962
|
bracketCount--;
|
|
6963
6963
|
if (bracketCount < 0) {
|
|
6964
|
-
throw new Error(`Invalid redaction path (${
|
|
6964
|
+
throw new Error(`Invalid redaction path (${path76})`);
|
|
6965
6965
|
}
|
|
6966
6966
|
}
|
|
6967
6967
|
}
|
|
6968
6968
|
if (bracketCount !== 0) {
|
|
6969
|
-
throw new Error(`Invalid redaction path (${
|
|
6969
|
+
throw new Error(`Invalid redaction path (${path76})`);
|
|
6970
6970
|
}
|
|
6971
6971
|
}
|
|
6972
6972
|
function validatePaths(paths) {
|
|
6973
6973
|
if (!Array.isArray(paths)) {
|
|
6974
6974
|
throw new TypeError("paths must be an array");
|
|
6975
6975
|
}
|
|
6976
|
-
for (const
|
|
6977
|
-
validatePath(
|
|
6976
|
+
for (const path76 of paths) {
|
|
6977
|
+
validatePath(path76);
|
|
6978
6978
|
}
|
|
6979
6979
|
}
|
|
6980
6980
|
function slowRedact(options = {}) {
|
|
@@ -7142,8 +7142,8 @@ var require_redaction = __commonJS({
|
|
|
7142
7142
|
if (shape[k2] === null) {
|
|
7143
7143
|
o[k2] = (value) => topCensor(value, [k2]);
|
|
7144
7144
|
} else {
|
|
7145
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
7146
|
-
return censor(value, [k2, ...
|
|
7145
|
+
const wrappedCensor = typeof censor === "function" ? (value, path76) => {
|
|
7146
|
+
return censor(value, [k2, ...path76]);
|
|
7147
7147
|
} : censor;
|
|
7148
7148
|
o[k2] = Redact({
|
|
7149
7149
|
paths: shape[k2],
|
|
@@ -7361,10 +7361,10 @@ var require_atomic_sleep = __commonJS({
|
|
|
7361
7361
|
var require_sonic_boom = __commonJS({
|
|
7362
7362
|
"../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
|
|
7363
7363
|
"use strict";
|
|
7364
|
-
var
|
|
7364
|
+
var fs69 = require("fs");
|
|
7365
7365
|
var EventEmitter3 = require("events");
|
|
7366
7366
|
var inherits = require("util").inherits;
|
|
7367
|
-
var
|
|
7367
|
+
var path76 = require("path");
|
|
7368
7368
|
var sleep2 = require_atomic_sleep();
|
|
7369
7369
|
var assert = require("assert");
|
|
7370
7370
|
var BUSY_WRITE_TIMEOUT = 100;
|
|
@@ -7418,20 +7418,20 @@ var require_sonic_boom = __commonJS({
|
|
|
7418
7418
|
const mode = sonic.mode;
|
|
7419
7419
|
if (sonic.sync) {
|
|
7420
7420
|
try {
|
|
7421
|
-
if (sonic.mkdir)
|
|
7422
|
-
const fd =
|
|
7421
|
+
if (sonic.mkdir) fs69.mkdirSync(path76.dirname(file), { recursive: true });
|
|
7422
|
+
const fd = fs69.openSync(file, flags, mode);
|
|
7423
7423
|
fileOpened(null, fd);
|
|
7424
7424
|
} catch (err) {
|
|
7425
7425
|
fileOpened(err);
|
|
7426
7426
|
throw err;
|
|
7427
7427
|
}
|
|
7428
7428
|
} else if (sonic.mkdir) {
|
|
7429
|
-
|
|
7429
|
+
fs69.mkdir(path76.dirname(file), { recursive: true }, (err) => {
|
|
7430
7430
|
if (err) return fileOpened(err);
|
|
7431
|
-
|
|
7431
|
+
fs69.open(file, flags, mode, fileOpened);
|
|
7432
7432
|
});
|
|
7433
7433
|
} else {
|
|
7434
|
-
|
|
7434
|
+
fs69.open(file, flags, mode, fileOpened);
|
|
7435
7435
|
}
|
|
7436
7436
|
}
|
|
7437
7437
|
function SonicBoom(opts) {
|
|
@@ -7472,8 +7472,8 @@ var require_sonic_boom = __commonJS({
|
|
|
7472
7472
|
this.flush = flushBuffer;
|
|
7473
7473
|
this.flushSync = flushBufferSync;
|
|
7474
7474
|
this._actualWrite = actualWriteBuffer;
|
|
7475
|
-
fsWriteSync = () =>
|
|
7476
|
-
fsWrite = () =>
|
|
7475
|
+
fsWriteSync = () => fs69.writeSync(this.fd, this._writingBuf);
|
|
7476
|
+
fsWrite = () => fs69.write(this.fd, this._writingBuf, this.release);
|
|
7477
7477
|
} else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
|
|
7478
7478
|
this._writingBuf = "";
|
|
7479
7479
|
this.write = write;
|
|
@@ -7482,15 +7482,15 @@ var require_sonic_boom = __commonJS({
|
|
|
7482
7482
|
this._actualWrite = actualWrite;
|
|
7483
7483
|
fsWriteSync = () => {
|
|
7484
7484
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
7485
|
-
return
|
|
7485
|
+
return fs69.writeSync(this.fd, this._writingBuf);
|
|
7486
7486
|
}
|
|
7487
|
-
return
|
|
7487
|
+
return fs69.writeSync(this.fd, this._writingBuf, "utf8");
|
|
7488
7488
|
};
|
|
7489
7489
|
fsWrite = () => {
|
|
7490
7490
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
7491
|
-
return
|
|
7491
|
+
return fs69.write(this.fd, this._writingBuf, this.release);
|
|
7492
7492
|
}
|
|
7493
|
-
return
|
|
7493
|
+
return fs69.write(this.fd, this._writingBuf, "utf8", this.release);
|
|
7494
7494
|
};
|
|
7495
7495
|
} else {
|
|
7496
7496
|
throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
|
|
@@ -7547,7 +7547,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7547
7547
|
}
|
|
7548
7548
|
}
|
|
7549
7549
|
if (this._fsync) {
|
|
7550
|
-
|
|
7550
|
+
fs69.fsyncSync(this.fd);
|
|
7551
7551
|
}
|
|
7552
7552
|
const len = this._len;
|
|
7553
7553
|
if (this._reopening) {
|
|
@@ -7661,7 +7661,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7661
7661
|
const onDrain = () => {
|
|
7662
7662
|
if (!this._fsync) {
|
|
7663
7663
|
try {
|
|
7664
|
-
|
|
7664
|
+
fs69.fsync(this.fd, (err) => {
|
|
7665
7665
|
this._flushPending = false;
|
|
7666
7666
|
cb(err);
|
|
7667
7667
|
});
|
|
@@ -7763,7 +7763,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7763
7763
|
const fd = this.fd;
|
|
7764
7764
|
this.once("ready", () => {
|
|
7765
7765
|
if (fd !== this.fd) {
|
|
7766
|
-
|
|
7766
|
+
fs69.close(fd, (err) => {
|
|
7767
7767
|
if (err) {
|
|
7768
7768
|
return this.emit("error", err);
|
|
7769
7769
|
}
|
|
@@ -7812,7 +7812,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7812
7812
|
buf = this._bufs[0];
|
|
7813
7813
|
}
|
|
7814
7814
|
try {
|
|
7815
|
-
const n = Buffer.isBuffer(buf) ?
|
|
7815
|
+
const n = Buffer.isBuffer(buf) ? fs69.writeSync(this.fd, buf) : fs69.writeSync(this.fd, buf, "utf8");
|
|
7816
7816
|
const releasedBufObj = releaseWritingBuf(buf, this._len, n);
|
|
7817
7817
|
buf = releasedBufObj.writingBuf;
|
|
7818
7818
|
this._len = releasedBufObj.len;
|
|
@@ -7828,7 +7828,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7828
7828
|
}
|
|
7829
7829
|
}
|
|
7830
7830
|
try {
|
|
7831
|
-
|
|
7831
|
+
fs69.fsyncSync(this.fd);
|
|
7832
7832
|
} catch {
|
|
7833
7833
|
}
|
|
7834
7834
|
}
|
|
@@ -7849,7 +7849,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7849
7849
|
buf = mergeBuf(this._bufs[0], this._lens[0]);
|
|
7850
7850
|
}
|
|
7851
7851
|
try {
|
|
7852
|
-
const n =
|
|
7852
|
+
const n = fs69.writeSync(this.fd, buf);
|
|
7853
7853
|
buf = buf.subarray(n);
|
|
7854
7854
|
this._len = Math.max(this._len - n, 0);
|
|
7855
7855
|
if (buf.length <= 0) {
|
|
@@ -7877,13 +7877,13 @@ var require_sonic_boom = __commonJS({
|
|
|
7877
7877
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
|
|
7878
7878
|
if (this.sync) {
|
|
7879
7879
|
try {
|
|
7880
|
-
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");
|
|
7881
7881
|
release(null, written);
|
|
7882
7882
|
} catch (err) {
|
|
7883
7883
|
release(err);
|
|
7884
7884
|
}
|
|
7885
7885
|
} else {
|
|
7886
|
-
|
|
7886
|
+
fs69.write(this.fd, this._writingBuf, release);
|
|
7887
7887
|
}
|
|
7888
7888
|
}
|
|
7889
7889
|
function actualWriteBuffer() {
|
|
@@ -7892,7 +7892,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7892
7892
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
|
|
7893
7893
|
if (this.sync) {
|
|
7894
7894
|
try {
|
|
7895
|
-
const written =
|
|
7895
|
+
const written = fs69.writeSync(this.fd, this._writingBuf);
|
|
7896
7896
|
release(null, written);
|
|
7897
7897
|
} catch (err) {
|
|
7898
7898
|
release(err);
|
|
@@ -7901,7 +7901,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7901
7901
|
if (kCopyBuffer) {
|
|
7902
7902
|
this._writingBuf = Buffer.from(this._writingBuf);
|
|
7903
7903
|
}
|
|
7904
|
-
|
|
7904
|
+
fs69.write(this.fd, this._writingBuf, release);
|
|
7905
7905
|
}
|
|
7906
7906
|
}
|
|
7907
7907
|
function actualClose(sonic) {
|
|
@@ -7917,12 +7917,12 @@ var require_sonic_boom = __commonJS({
|
|
|
7917
7917
|
sonic._lens = [];
|
|
7918
7918
|
assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
|
|
7919
7919
|
try {
|
|
7920
|
-
|
|
7920
|
+
fs69.fsync(sonic.fd, closeWrapped);
|
|
7921
7921
|
} catch {
|
|
7922
7922
|
}
|
|
7923
7923
|
function closeWrapped() {
|
|
7924
7924
|
if (sonic.fd !== 1 && sonic.fd !== 2) {
|
|
7925
|
-
|
|
7925
|
+
fs69.close(sonic.fd, done);
|
|
7926
7926
|
} else {
|
|
7927
7927
|
done();
|
|
7928
7928
|
}
|
|
@@ -10286,7 +10286,7 @@ var require_multistream = __commonJS({
|
|
|
10286
10286
|
var require_pino = __commonJS({
|
|
10287
10287
|
"../node_modules/.pnpm/pino@9.14.0/node_modules/pino/pino.js"(exports2, module2) {
|
|
10288
10288
|
"use strict";
|
|
10289
|
-
var
|
|
10289
|
+
var os24 = require("os");
|
|
10290
10290
|
var stdSerializers = require_pino_std_serializers();
|
|
10291
10291
|
var caller = require_caller();
|
|
10292
10292
|
var redaction = require_redaction();
|
|
@@ -10333,7 +10333,7 @@ var require_pino = __commonJS({
|
|
|
10333
10333
|
} = symbols;
|
|
10334
10334
|
var { epochTime, nullTime } = time;
|
|
10335
10335
|
var { pid } = process;
|
|
10336
|
-
var hostname =
|
|
10336
|
+
var hostname = os24.hostname();
|
|
10337
10337
|
var defaultErrorSerializer = stdSerializers.err;
|
|
10338
10338
|
var defaultOptions = {
|
|
10339
10339
|
level: "info",
|
|
@@ -11057,11 +11057,11 @@ var init_lib = __esm({
|
|
|
11057
11057
|
}
|
|
11058
11058
|
}
|
|
11059
11059
|
},
|
|
11060
|
-
addToPath: function addToPath(
|
|
11061
|
-
var last =
|
|
11060
|
+
addToPath: function addToPath(path76, added, removed, oldPosInc, options) {
|
|
11061
|
+
var last = path76.lastComponent;
|
|
11062
11062
|
if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
|
|
11063
11063
|
return {
|
|
11064
|
-
oldPos:
|
|
11064
|
+
oldPos: path76.oldPos + oldPosInc,
|
|
11065
11065
|
lastComponent: {
|
|
11066
11066
|
count: last.count + 1,
|
|
11067
11067
|
added,
|
|
@@ -11071,7 +11071,7 @@ var init_lib = __esm({
|
|
|
11071
11071
|
};
|
|
11072
11072
|
} else {
|
|
11073
11073
|
return {
|
|
11074
|
-
oldPos:
|
|
11074
|
+
oldPos: path76.oldPos + oldPosInc,
|
|
11075
11075
|
lastComponent: {
|
|
11076
11076
|
count: 1,
|
|
11077
11077
|
added,
|
|
@@ -11537,10 +11537,10 @@ function attachmentToHistoryMessage(o, ts) {
|
|
|
11537
11537
|
const memories = raw.map((m2) => {
|
|
11538
11538
|
if (!m2 || typeof m2 !== "object") return null;
|
|
11539
11539
|
const rec3 = m2;
|
|
11540
|
-
const
|
|
11540
|
+
const path76 = typeof rec3.path === "string" ? rec3.path : null;
|
|
11541
11541
|
const content = typeof rec3.content === "string" ? rec3.content : null;
|
|
11542
|
-
if (!
|
|
11543
|
-
const entry = { path:
|
|
11542
|
+
if (!path76 || content == null) return null;
|
|
11543
|
+
const entry = { path: path76, content };
|
|
11544
11544
|
if (typeof rec3.mtimeMs === "number") entry.mtimeMs = rec3.mtimeMs;
|
|
11545
11545
|
return entry;
|
|
11546
11546
|
}).filter((m2) => m2 !== null);
|
|
@@ -12004,6 +12004,14 @@ var init_claude_history = __esm({
|
|
|
12004
12004
|
}
|
|
12005
12005
|
});
|
|
12006
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
|
+
|
|
12007
12015
|
// src/tools/claude.ts
|
|
12008
12016
|
function probeViaWhich() {
|
|
12009
12017
|
try {
|
|
@@ -12352,10 +12360,10 @@ function parseAttachment(obj) {
|
|
|
12352
12360
|
const memories = raw.map((m2) => {
|
|
12353
12361
|
if (!m2 || typeof m2 !== "object") return null;
|
|
12354
12362
|
const rec3 = m2;
|
|
12355
|
-
const
|
|
12363
|
+
const path76 = typeof rec3.path === "string" ? rec3.path : null;
|
|
12356
12364
|
const content = typeof rec3.content === "string" ? rec3.content : null;
|
|
12357
|
-
if (!
|
|
12358
|
-
const out = { path:
|
|
12365
|
+
if (!path76 || content == null) return null;
|
|
12366
|
+
const out = { path: path76, content };
|
|
12359
12367
|
if (typeof rec3.mtimeMs === "number") out.mtimeMs = rec3.mtimeMs;
|
|
12360
12368
|
return out;
|
|
12361
12369
|
}).filter((m2) => m2 !== null);
|
|
@@ -26827,6 +26835,121 @@ var require_dist = __commonJS({
|
|
|
26827
26835
|
}
|
|
26828
26836
|
});
|
|
26829
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
|
+
|
|
26830
26953
|
// ../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/constants.js
|
|
26831
26954
|
var require_constants2 = __commonJS({
|
|
26832
26955
|
"../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/constants.js"(exports2, module2) {
|
|
@@ -29028,9 +29151,9 @@ var require_websocket = __commonJS({
|
|
|
29028
29151
|
var EventEmitter3 = require("events");
|
|
29029
29152
|
var https = require("https");
|
|
29030
29153
|
var http3 = require("http");
|
|
29031
|
-
var
|
|
29154
|
+
var net4 = require("net");
|
|
29032
29155
|
var tls = require("tls");
|
|
29033
|
-
var { randomBytes, createHash:
|
|
29156
|
+
var { randomBytes, createHash: createHash2 } = require("crypto");
|
|
29034
29157
|
var { Duplex, Readable: Readable3 } = require("stream");
|
|
29035
29158
|
var { URL: URL2 } = require("url");
|
|
29036
29159
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -29690,7 +29813,7 @@ var require_websocket = __commonJS({
|
|
|
29690
29813
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
29691
29814
|
return;
|
|
29692
29815
|
}
|
|
29693
|
-
const digest =
|
|
29816
|
+
const digest = createHash2("sha1").update(key + GUID).digest("base64");
|
|
29694
29817
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
29695
29818
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
29696
29819
|
return;
|
|
@@ -29762,12 +29885,12 @@ var require_websocket = __commonJS({
|
|
|
29762
29885
|
}
|
|
29763
29886
|
function netConnect(options) {
|
|
29764
29887
|
options.path = options.socketPath;
|
|
29765
|
-
return
|
|
29888
|
+
return net4.connect(options);
|
|
29766
29889
|
}
|
|
29767
29890
|
function tlsConnect(options) {
|
|
29768
29891
|
options.path = void 0;
|
|
29769
29892
|
if (!options.servername && options.servername !== "") {
|
|
29770
|
-
options.servername =
|
|
29893
|
+
options.servername = net4.isIP(options.host) ? "" : options.host;
|
|
29771
29894
|
}
|
|
29772
29895
|
return tls.connect(options);
|
|
29773
29896
|
}
|
|
@@ -30057,7 +30180,7 @@ var require_websocket_server = __commonJS({
|
|
|
30057
30180
|
var EventEmitter3 = require("events");
|
|
30058
30181
|
var http3 = require("http");
|
|
30059
30182
|
var { Duplex } = require("stream");
|
|
30060
|
-
var { createHash:
|
|
30183
|
+
var { createHash: createHash2 } = require("crypto");
|
|
30061
30184
|
var extension2 = require_extension();
|
|
30062
30185
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
30063
30186
|
var subprotocol2 = require_subprotocol();
|
|
@@ -30358,7 +30481,7 @@ var require_websocket_server = __commonJS({
|
|
|
30358
30481
|
);
|
|
30359
30482
|
}
|
|
30360
30483
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
30361
|
-
const digest =
|
|
30484
|
+
const digest = createHash2("sha1").update(key + GUID).digest("base64");
|
|
30362
30485
|
const headers = [
|
|
30363
30486
|
"HTTP/1.1 101 Switching Protocols",
|
|
30364
30487
|
"Upgrade: websocket",
|
|
@@ -30443,6 +30566,23 @@ var require_websocket_server = __commonJS({
|
|
|
30443
30566
|
}
|
|
30444
30567
|
});
|
|
30445
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
|
+
|
|
30446
30586
|
// ../node_modules/.pnpm/process-nextick-args@2.0.1/node_modules/process-nextick-args/index.js
|
|
30447
30587
|
var require_process_nextick_args = __commonJS({
|
|
30448
30588
|
"../node_modules/.pnpm/process-nextick-args@2.0.1/node_modules/process-nextick-args/index.js"(exports2, module2) {
|
|
@@ -33320,8 +33460,8 @@ var require_utils = __commonJS({
|
|
|
33320
33460
|
var result = transform[inputType][outputType](input);
|
|
33321
33461
|
return result;
|
|
33322
33462
|
};
|
|
33323
|
-
exports2.resolve = function(
|
|
33324
|
-
var parts =
|
|
33463
|
+
exports2.resolve = function(path76) {
|
|
33464
|
+
var parts = path76.split("/");
|
|
33325
33465
|
var result = [];
|
|
33326
33466
|
for (var index = 0; index < parts.length; index++) {
|
|
33327
33467
|
var part = parts[index];
|
|
@@ -39174,18 +39314,18 @@ var require_object = __commonJS({
|
|
|
39174
39314
|
var object = new ZipObject(name, zipObjectContent, o);
|
|
39175
39315
|
this.files[name] = object;
|
|
39176
39316
|
};
|
|
39177
|
-
var parentFolder = function(
|
|
39178
|
-
if (
|
|
39179
|
-
|
|
39317
|
+
var parentFolder = function(path76) {
|
|
39318
|
+
if (path76.slice(-1) === "/") {
|
|
39319
|
+
path76 = path76.substring(0, path76.length - 1);
|
|
39180
39320
|
}
|
|
39181
|
-
var lastSlash =
|
|
39182
|
-
return lastSlash > 0 ?
|
|
39321
|
+
var lastSlash = path76.lastIndexOf("/");
|
|
39322
|
+
return lastSlash > 0 ? path76.substring(0, lastSlash) : "";
|
|
39183
39323
|
};
|
|
39184
|
-
var forceTrailingSlash = function(
|
|
39185
|
-
if (
|
|
39186
|
-
|
|
39324
|
+
var forceTrailingSlash = function(path76) {
|
|
39325
|
+
if (path76.slice(-1) !== "/") {
|
|
39326
|
+
path76 += "/";
|
|
39187
39327
|
}
|
|
39188
|
-
return
|
|
39328
|
+
return path76;
|
|
39189
39329
|
};
|
|
39190
39330
|
var folderAdd = function(name, createFolders) {
|
|
39191
39331
|
createFolders = typeof createFolders !== "undefined" ? createFolders : defaults.createFolders;
|
|
@@ -40187,7 +40327,7 @@ var require_lib3 = __commonJS({
|
|
|
40187
40327
|
// src/run-case/recorder.ts
|
|
40188
40328
|
function startRunCaseRecorder(opts) {
|
|
40189
40329
|
const now = opts.now ?? Date.now;
|
|
40190
|
-
const dir =
|
|
40330
|
+
const dir = import_node_path63.default.dirname(opts.recordPath);
|
|
40191
40331
|
let stream = null;
|
|
40192
40332
|
let closing = false;
|
|
40193
40333
|
let closedSettled = false;
|
|
@@ -40201,8 +40341,8 @@ function startRunCaseRecorder(opts) {
|
|
|
40201
40341
|
});
|
|
40202
40342
|
const ensureStream = () => {
|
|
40203
40343
|
if (stream) return stream;
|
|
40204
|
-
|
|
40205
|
-
stream =
|
|
40344
|
+
import_node_fs50.default.mkdirSync(dir, { recursive: true });
|
|
40345
|
+
stream = import_node_fs50.default.createWriteStream(opts.recordPath, { flags: "a" });
|
|
40206
40346
|
stream.on("close", () => closedResolve());
|
|
40207
40347
|
return stream;
|
|
40208
40348
|
};
|
|
@@ -40227,12 +40367,12 @@ function startRunCaseRecorder(opts) {
|
|
|
40227
40367
|
};
|
|
40228
40368
|
return { tap, close, closed };
|
|
40229
40369
|
}
|
|
40230
|
-
var
|
|
40370
|
+
var import_node_fs50, import_node_path63;
|
|
40231
40371
|
var init_recorder = __esm({
|
|
40232
40372
|
"src/run-case/recorder.ts"() {
|
|
40233
40373
|
"use strict";
|
|
40234
|
-
|
|
40235
|
-
|
|
40374
|
+
import_node_fs50 = __toESM(require("fs"), 1);
|
|
40375
|
+
import_node_path63 = __toESM(require("path"), 1);
|
|
40236
40376
|
}
|
|
40237
40377
|
});
|
|
40238
40378
|
|
|
@@ -40275,7 +40415,7 @@ var init_wire = __esm({
|
|
|
40275
40415
|
// src/run-case/controller.ts
|
|
40276
40416
|
async function runController(opts) {
|
|
40277
40417
|
const now = opts.now ?? Date.now;
|
|
40278
|
-
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-"));
|
|
40279
40419
|
const ownsCwd = opts.cwd === void 0;
|
|
40280
40420
|
const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
|
|
40281
40421
|
const spawnCtx = { cwd };
|
|
@@ -40436,19 +40576,19 @@ async function runController(opts) {
|
|
|
40436
40576
|
if (sigintHandler) process.off("SIGINT", sigintHandler);
|
|
40437
40577
|
if (ownsCwd) {
|
|
40438
40578
|
try {
|
|
40439
|
-
(0,
|
|
40579
|
+
(0, import_node_fs51.rmSync)(cwd, { recursive: true, force: true });
|
|
40440
40580
|
} catch {
|
|
40441
40581
|
}
|
|
40442
40582
|
}
|
|
40443
40583
|
return exitCode ?? 0;
|
|
40444
40584
|
}
|
|
40445
|
-
var
|
|
40585
|
+
var import_node_fs51, import_node_os22, import_node_path64;
|
|
40446
40586
|
var init_controller = __esm({
|
|
40447
40587
|
"src/run-case/controller.ts"() {
|
|
40448
40588
|
"use strict";
|
|
40449
|
-
|
|
40589
|
+
import_node_fs51 = require("fs");
|
|
40450
40590
|
import_node_os22 = __toESM(require("os"), 1);
|
|
40451
|
-
|
|
40591
|
+
import_node_path64 = __toESM(require("path"), 1);
|
|
40452
40592
|
init_claude();
|
|
40453
40593
|
init_stdout_splitter();
|
|
40454
40594
|
init_permission_stdio();
|
|
@@ -40544,6 +40684,163 @@ stdout \u4E8B\u4EF6\uFF08\u884C JSON\uFF09\uFF1A
|
|
|
40544
40684
|
}
|
|
40545
40685
|
});
|
|
40546
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
|
+
|
|
40547
40844
|
// src/config.ts
|
|
40548
40845
|
var import_node_fs = __toESM(require("fs"), 1);
|
|
40549
40846
|
var import_node_os = __toESM(require("os"), 1);
|
|
@@ -40712,6 +41009,10 @@ Subcommands:
|
|
|
40712
41009
|
\u72EC\u7ACB CC \u63A7\u5236\u5668\u8FDB\u7A0B\uFF1A\u5916\u90E8 AI \u901A\u8FC7 stdin/stdout \u884C JSON \u534F\u8BAE
|
|
40713
41010
|
\u9A71\u52A8 CC \u591A\u8F6E\u5BF9\u8BDD\u4E0E\u6743\u9650\u51B3\u7B56\uFF0CIPC \u5168\u7A0B\u5F55\u5230 --record JSONL\u3002
|
|
40714
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'
|
|
40715
41016
|
|
|
40716
41017
|
Env (advanced):
|
|
40717
41018
|
CLAWD_FRPC_BIN \u81EA\u5E26 frpc \u4E8C\u8FDB\u5236\u8DEF\u5F84\uFF08\u9ED8\u8BA4\u6309\u9700\u4E0B\u8F7D\u5230 ~/.clawd/bin/frpc\uFF09
|
|
@@ -40722,8 +41023,8 @@ Env (advanced):
|
|
|
40722
41023
|
`;
|
|
40723
41024
|
|
|
40724
41025
|
// src/index.ts
|
|
40725
|
-
var
|
|
40726
|
-
var
|
|
41026
|
+
var import_node_path62 = __toESM(require("path"), 1);
|
|
41027
|
+
var import_node_fs49 = __toESM(require("fs"), 1);
|
|
40727
41028
|
var import_node_os21 = __toESM(require("os"), 1);
|
|
40728
41029
|
|
|
40729
41030
|
// ../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/stringify.js
|
|
@@ -40843,18 +41144,6 @@ function createLogger(opts = {}) {
|
|
|
40843
41144
|
);
|
|
40844
41145
|
return wrap(base);
|
|
40845
41146
|
}
|
|
40846
|
-
function createFileOnlyLogger(opts) {
|
|
40847
|
-
const level = opts.level ?? "debug";
|
|
40848
|
-
try {
|
|
40849
|
-
import_node_fs2.default.mkdirSync(import_node_path2.default.dirname(opts.file), { recursive: true });
|
|
40850
|
-
} catch {
|
|
40851
|
-
}
|
|
40852
|
-
const base = (0, import_pino.default)(
|
|
40853
|
-
{ level, base: {} },
|
|
40854
|
-
import_pino.default.destination({ dest: opts.file, mkdir: true, sync: true })
|
|
40855
|
-
);
|
|
40856
|
-
return wrap(base);
|
|
40857
|
-
}
|
|
40858
41147
|
function pinoLevelToString(n) {
|
|
40859
41148
|
if (typeof n !== "number") return null;
|
|
40860
41149
|
if (n >= 50) return "error";
|
|
@@ -41434,6 +41723,7 @@ function composeGuestSandbox(base, userWorkDir, spawnCwd) {
|
|
|
41434
41723
|
fsv.denyWrite = unionArr((fsv.denyWrite ?? []).filter((p2) => p2 !== "~/"), [spawnCwd]);
|
|
41435
41724
|
fsv.allowRead = unionArr(fsv.allowRead, [userWorkDir]);
|
|
41436
41725
|
fsv.allowWrite = unionArr(fsv.allowWrite, [userWorkDir]);
|
|
41726
|
+
fsv.allowRead = unionArr(fsv.allowRead, ["~/.clawd/contact-ssh-keys"]);
|
|
41437
41727
|
return s;
|
|
41438
41728
|
}
|
|
41439
41729
|
|
|
@@ -41574,6 +41864,31 @@ deviceId \u76F4\u63A5\u53D6\u81EA token\uFF0C\u4E0D\u8981\u6539\u5199\u6216\u731
|
|
|
41574
41864
|
- \u5931\u8D25\uFF1A\`{ isFailure: true, reason: "..." }\`
|
|
41575
41865
|
- \u62A5\u544A\u5B8C\u540E\u4F60\u7684 session \u5C31\u7ED3\u675F\u4E86\uFF0C\u4E0D\u8981\u7EE7\u7EED\u8F93\u51FA`;
|
|
41576
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
|
+
|
|
41577
41892
|
// src/session/reducer.ts
|
|
41578
41893
|
function cloneState(s) {
|
|
41579
41894
|
return {
|
|
@@ -41710,6 +42025,9 @@ function buildSpawnContext(state, deps) {
|
|
|
41710
42025
|
if (daemonUrl) {
|
|
41711
42026
|
ctx.extraSystemPrompt = (ctx.extraSystemPrompt ? ctx.extraSystemPrompt + "\n\n" : "") + ATTACHMENT_SHARING_HINT;
|
|
41712
42027
|
}
|
|
42028
|
+
if (meta?.personaMode === "guest") {
|
|
42029
|
+
ctx.extraSystemPrompt = (ctx.extraSystemPrompt ? ctx.extraSystemPrompt + "\n\n" : "") + CONTACT_SSH_SYSTEM_PROMPT_HINT;
|
|
42030
|
+
}
|
|
41713
42031
|
if (meta?.extraSettings) {
|
|
41714
42032
|
ctx.extraSettings = meta.extraSettings;
|
|
41715
42033
|
}
|
|
@@ -43020,18 +43338,9 @@ var SessionManager = class {
|
|
|
43020
43338
|
// 由 observer 监听 jsonl user 行后调 recordRealUserUuid 建立映射;rewind 系列 RPC 在
|
|
43021
43339
|
// 入参 / 出参做转译,保证 UI 看到的 uuid 始终是 events 流里的 synth uuid
|
|
43022
43340
|
realUuidBySynth = /* @__PURE__ */ new Map();
|
|
43023
|
-
//
|
|
43024
|
-
//
|
|
43025
|
-
|
|
43026
|
-
// 语义澄清:`turn_duration` 是 CC 报的原始信号("本轮 API 调用完了"),**不代表 turn 真的结束**
|
|
43027
|
-
//(背景 agent 可能还在跑)。屏幕 5s 稳定 + 无 popup 才是"确认信号"。
|
|
43028
|
-
// 两者 AND 后 daemon 才产生真正的 `turn_end` 事件送给 reducer。
|
|
43029
|
-
//
|
|
43030
|
-
// pending 不设截止时间:屏幕稳定的时机由 CC UI 决定,可能几秒也可能几分钟。观察者以
|
|
43031
|
-
// "屏幕真的稳定"作为触发点,signal-driven 而非 timer-driven。
|
|
43032
|
-
//
|
|
43033
|
-
// 清理点:newSession / stop / session-delete / stopAll。
|
|
43034
|
-
pendingTurnDurationSignals = /* @__PURE__ */ new Set();
|
|
43341
|
+
// observeScreenIdle 复合条件闸用:sessionId → observer 上次喂出业务事件的时刻(deps.now())。
|
|
43342
|
+
// observerIdleWaitMs 据此判 observer 是否也静止 ≥ idleMs(屏幕静止 AND observer 静止才补 turn_end)。
|
|
43343
|
+
lastObserverEventAt = /* @__PURE__ */ new Map();
|
|
43035
43344
|
// SessionStore 按 scope 派生(root = <dataDir>/sessions/<scopeSubPath>/)。
|
|
43036
43345
|
// default scope 直接复用 deps.store;persona scope(owner / listener)第一次访问时按需创建并缓存。
|
|
43037
43346
|
// 取代旧的 storesByAgent —— agentId 概念由 SessionScope 取代,路径即身份,
|
|
@@ -43371,14 +43680,6 @@ var SessionManager = class {
|
|
|
43371
43680
|
routeFromRunner(frame, target) {
|
|
43372
43681
|
const compressed = compressFrameForWire(frame);
|
|
43373
43682
|
if (!compressed) return;
|
|
43374
|
-
if (compressed.type === "session:status") {
|
|
43375
|
-
const s = compressed;
|
|
43376
|
-
this.deps.screenIdleProbeLogger?.info("session:status wire emit", {
|
|
43377
|
-
sessionId: s.sessionId,
|
|
43378
|
-
status: s.status,
|
|
43379
|
-
target
|
|
43380
|
-
});
|
|
43381
|
-
}
|
|
43382
43683
|
if (compressed.type === "session:event" || compressed.type === "session:status") {
|
|
43383
43684
|
const sid = compressed.sessionId;
|
|
43384
43685
|
if (sid) {
|
|
@@ -43652,7 +43953,7 @@ var SessionManager = class {
|
|
|
43652
43953
|
this.runners.delete(args.sessionId);
|
|
43653
43954
|
this.realUuidBySynth.delete(args.sessionId);
|
|
43654
43955
|
this.lastUiSizeBySessionId.delete(args.sessionId);
|
|
43655
|
-
this.
|
|
43956
|
+
this.lastObserverEventAt.delete(args.sessionId);
|
|
43656
43957
|
return { response: { sessionId: args.sessionId }, broadcast };
|
|
43657
43958
|
}
|
|
43658
43959
|
this.deleteOwned(args.sessionId);
|
|
@@ -43682,7 +43983,6 @@ var SessionManager = class {
|
|
|
43682
43983
|
async stop(args) {
|
|
43683
43984
|
const runner = this.runners.get(args.sessionId);
|
|
43684
43985
|
if (!runner) return { response: { ok: true }, broadcast: [] };
|
|
43685
|
-
this.clearPendingTurnEnd(args.sessionId);
|
|
43686
43986
|
const { broadcast } = this.withCollector(() => {
|
|
43687
43987
|
runner.input({ kind: "command", command: { kind: "stop" } });
|
|
43688
43988
|
});
|
|
@@ -43816,7 +44116,6 @@ var SessionManager = class {
|
|
|
43816
44116
|
newSession(args) {
|
|
43817
44117
|
const existingFile = this.getFile(args.sessionId);
|
|
43818
44118
|
const nextToolSessionId = this.deps.mode === "tui" && (existingFile.tool ?? "claude") === "claude" ? v4_default() : void 0;
|
|
43819
|
-
this.clearPendingTurnEnd(args.sessionId);
|
|
43820
44119
|
const runner = this.runners.get(args.sessionId);
|
|
43821
44120
|
if (runner) {
|
|
43822
44121
|
const { value, broadcast } = this.withCollector(() => {
|
|
@@ -43917,7 +44216,6 @@ var SessionManager = class {
|
|
|
43917
44216
|
for (const r of this.runners.values()) {
|
|
43918
44217
|
r.input({ kind: "command", command: { kind: "stop" } });
|
|
43919
44218
|
}
|
|
43920
|
-
this.pendingTurnDurationSignals.clear();
|
|
43921
44219
|
}
|
|
43922
44220
|
// 给 observer 用:拿已存在的 runner
|
|
43923
44221
|
getActive(sessionId) {
|
|
@@ -44134,7 +44432,7 @@ var SessionManager = class {
|
|
|
44134
44432
|
this.runners.delete(args.sessionId);
|
|
44135
44433
|
this.realUuidBySynth.delete(args.sessionId);
|
|
44136
44434
|
this.lastUiSizeBySessionId.delete(args.sessionId);
|
|
44137
|
-
this.
|
|
44435
|
+
this.lastObserverEventAt.delete(args.sessionId);
|
|
44138
44436
|
return { response: { sessionId: args.sessionId }, broadcast };
|
|
44139
44437
|
}
|
|
44140
44438
|
this.storeFor(args.scope).delete(args.sessionId);
|
|
@@ -44380,93 +44678,23 @@ var SessionManager = class {
|
|
|
44380
44678
|
return;
|
|
44381
44679
|
}
|
|
44382
44680
|
}
|
|
44681
|
+
this.lastObserverEventAt.set(sessionId, (this.deps.now ?? Date.now)());
|
|
44383
44682
|
let feedEvents = outEvents;
|
|
44384
44683
|
if (outEvents.some((e) => e.kind === "turn_end")) {
|
|
44385
|
-
const
|
|
44386
|
-
|
|
44387
|
-
const adapter = this.deps.getAdapter(runnerState.file.tool ?? "claude");
|
|
44388
|
-
const gateOpen = !adapter.canAcceptTurnEnd || !toolSessionId ? true : adapter.canAcceptTurnEnd(toolSessionId);
|
|
44389
|
-
this.deps.screenIdleProbeLogger?.info("turn_duration signal received", {
|
|
44390
|
-
sessionId,
|
|
44391
|
-
toolSessionId,
|
|
44392
|
-
batchKinds: outEvents.map((e) => e.kind),
|
|
44393
|
-
gateOpen,
|
|
44394
|
-
hasCanAcceptGate: !!adapter.canAcceptTurnEnd
|
|
44395
|
-
});
|
|
44396
|
-
if (gateOpen) {
|
|
44397
|
-
this.deps.screenIdleProbeLogger?.info(
|
|
44398
|
-
"turn_duration \u2192 turn_end confirmed (gate open) \u2192 fed to reducer",
|
|
44399
|
-
{ sessionId, toolSessionId }
|
|
44400
|
-
);
|
|
44401
|
-
} else {
|
|
44684
|
+
const ev = this.peekTurnEvidence(runner);
|
|
44685
|
+
if (!ev.turnHasContent) {
|
|
44402
44686
|
feedEvents = outEvents.filter((e) => e.kind !== "turn_end");
|
|
44403
|
-
|
|
44404
|
-
|
|
44405
|
-
|
|
44406
|
-
|
|
44407
|
-
)
|
|
44408
|
-
}
|
|
44409
|
-
this.pendingTurnDurationSignals.add(sessionId);
|
|
44410
|
-
this.deps.screenIdleProbeLogger?.info(
|
|
44411
|
-
"turn_duration pending: gate closed \u2192 waiting for screen-idle signal",
|
|
44412
|
-
{ sessionId, toolSessionId }
|
|
44413
|
-
);
|
|
44414
|
-
}
|
|
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
|
+
});
|
|
44415
44693
|
}
|
|
44416
44694
|
}
|
|
44417
44695
|
if (feedEvents.length === 0) return;
|
|
44418
44696
|
runner.feedObserverEvents(feedEvents);
|
|
44419
44697
|
}
|
|
44420
|
-
/**
|
|
44421
|
-
* `ClaudeTuiAdapter.observeScreenIdle` fire triggered → armed=true 时调用(一次性触发点)。
|
|
44422
|
-
*
|
|
44423
|
-
* 语义:屏幕真的稳定了 5s。查有没有 pending 的 turn_duration 信号:
|
|
44424
|
-
* - 有 → 之前收到的 turn_duration 被屏幕稳定**确认**了,flush 成 turn_end 进 reducer
|
|
44425
|
-
* - 无 → 屏幕稳定但从没收到 turn_duration → noop(不生成 turn_end;补偿路径的错误做法)
|
|
44426
|
-
*
|
|
44427
|
-
* pending 集合是**必要前提**:turn_end 只能来自"observer 收 turn_duration + 屏幕后续稳定"
|
|
44428
|
-
* 双源确认,任何一个缺少都不能 emit。这跟 PR #962 拆掉的 dispatchTurnIdle "屏幕静止就补
|
|
44429
|
-
* turn_end" 语义不同。
|
|
44430
|
-
*
|
|
44431
|
-
* 仅 TUI 模式;SDK / codex 没有屏幕信号也就不会触发本方法。
|
|
44432
|
-
*/
|
|
44433
|
-
notifyScreenIdle(toolSessionId) {
|
|
44434
|
-
if (this.deps.mode !== "tui") return;
|
|
44435
|
-
const sid = this.sessionIdByToolSid(toolSessionId);
|
|
44436
|
-
if (!sid) {
|
|
44437
|
-
this.deps.screenIdleProbeLogger?.warn("notifyScreenIdle: no session for toolSessionId", {
|
|
44438
|
-
toolSessionId
|
|
44439
|
-
});
|
|
44440
|
-
return;
|
|
44441
|
-
}
|
|
44442
|
-
if (!this.pendingTurnDurationSignals.has(sid)) {
|
|
44443
|
-
this.deps.screenIdleProbeLogger?.info(
|
|
44444
|
-
"notifyScreenIdle: no pending turn_duration \u2192 noop",
|
|
44445
|
-
{ sessionId: sid, toolSessionId }
|
|
44446
|
-
);
|
|
44447
|
-
return;
|
|
44448
|
-
}
|
|
44449
|
-
const runner = this.runners.get(sid);
|
|
44450
|
-
if (!runner) {
|
|
44451
|
-
this.pendingTurnDurationSignals.delete(sid);
|
|
44452
|
-
this.deps.screenIdleProbeLogger?.warn(
|
|
44453
|
-
"notifyScreenIdle: pending but no runner \u2192 cleared without inject",
|
|
44454
|
-
{ sessionId: sid, toolSessionId }
|
|
44455
|
-
);
|
|
44456
|
-
return;
|
|
44457
|
-
}
|
|
44458
|
-
this.pendingTurnDurationSignals.delete(sid);
|
|
44459
|
-
this.deps.screenIdleProbeLogger?.info(
|
|
44460
|
-
"notifyScreenIdle: pending turn_duration + screen idle confirmed \u2192 inject turn_end",
|
|
44461
|
-
{ sessionId: sid, toolSessionId }
|
|
44462
|
-
);
|
|
44463
|
-
runner.input({ kind: "inject-events", events: [{ kind: "turn_end" }] });
|
|
44464
|
-
}
|
|
44465
|
-
clearPendingTurnEnd(sessionId) {
|
|
44466
|
-
if (this.pendingTurnDurationSignals.delete(sessionId)) {
|
|
44467
|
-
this.deps.screenIdleProbeLogger?.info("pending turn_duration cleared", { sessionId });
|
|
44468
|
-
}
|
|
44469
|
-
}
|
|
44470
44698
|
// AskUserQuestion 表单回写(plan: clawd-ask-user-question):UI 答完所有 question 后调用。
|
|
44471
44699
|
// - session 不存在 / 无 runner → noop 幂等返回 ok(first-decider-wins)
|
|
44472
44700
|
// - reducer noop(toolUseId 不存在或已答过)也保持幂等返回,handler 不抛
|
|
@@ -44725,6 +44953,70 @@ var SessionManager = class {
|
|
|
44725
44953
|
if (!runner) return;
|
|
44726
44954
|
runner.input({ kind: "ready-detected" });
|
|
44727
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
|
+
}
|
|
44728
45020
|
/** toolSessionId → sessionId 反查(遍历 runners);session 数典型 < 10,O(n) 可接受 */
|
|
44729
45021
|
sessionIdByToolSid(toolSessionId) {
|
|
44730
45022
|
for (const [sid, runner] of this.runners) {
|
|
@@ -45873,11 +46165,7 @@ function tryLoadShareUi(logger) {
|
|
|
45873
46165
|
|
|
45874
46166
|
// src/visitor/visitor-token.ts
|
|
45875
46167
|
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
45876
|
-
|
|
45877
|
-
// ../protocol/src/index.ts
|
|
45878
|
-
init_runtime();
|
|
45879
|
-
|
|
45880
|
-
// src/visitor/visitor-token.ts
|
|
46168
|
+
init_src();
|
|
45881
46169
|
function hmac(secret, body) {
|
|
45882
46170
|
return import_node_crypto4.default.createHmac("sha256", secret).update(body).digest("base64url");
|
|
45883
46171
|
}
|
|
@@ -46234,8 +46522,8 @@ function turnStartInput(text) {
|
|
|
46234
46522
|
const items = [];
|
|
46235
46523
|
let leftover = text;
|
|
46236
46524
|
for (const m2 of text.matchAll(SKILL_RE)) {
|
|
46237
|
-
const [marker, name,
|
|
46238
|
-
items.push({ type: "skill", name, path:
|
|
46525
|
+
const [marker, name, path76] = m2;
|
|
46526
|
+
items.push({ type: "skill", name, path: path76 });
|
|
46239
46527
|
leftover = leftover.replace(marker, "");
|
|
46240
46528
|
}
|
|
46241
46529
|
for (const m2 of text.matchAll(ATTACHMENT_RE2)) {
|
|
@@ -46467,7 +46755,6 @@ var CodexAdapter = class {
|
|
|
46467
46755
|
};
|
|
46468
46756
|
|
|
46469
46757
|
// src/tools/claude-tui.ts
|
|
46470
|
-
var import_node_crypto5 = require("crypto");
|
|
46471
46758
|
var import_node_fs16 = __toESM(require("fs"), 1);
|
|
46472
46759
|
var import_node_os7 = __toESM(require("os"), 1);
|
|
46473
46760
|
var import_node_path14 = __toESM(require("path"), 1);
|
|
@@ -47278,56 +47565,22 @@ function observeScreenIdle(surface, opts) {
|
|
|
47278
47565
|
timer = null;
|
|
47279
47566
|
if (disposed) return;
|
|
47280
47567
|
if (opts.getPopupVisible()) {
|
|
47281
|
-
opts.probeLogger?.info("screen-idle fire suppressed: popup visible", {
|
|
47282
|
-
label: opts.probeLabel
|
|
47283
|
-
});
|
|
47284
47568
|
timer = setTimeout(fire, opts.idleMs);
|
|
47285
47569
|
return;
|
|
47286
47570
|
}
|
|
47287
47571
|
const obsWait = opts.getObserverWaitMs?.() ?? 0;
|
|
47288
47572
|
if (obsWait > 0) {
|
|
47289
|
-
opts.probeLogger?.info("screen-idle fire suppressed: observer not idle", {
|
|
47290
|
-
label: opts.probeLabel,
|
|
47291
|
-
obsWait
|
|
47292
|
-
});
|
|
47293
47573
|
timer = setTimeout(fire, Math.max(obsWait, REWAIT_MIN_MS));
|
|
47294
47574
|
return;
|
|
47295
47575
|
}
|
|
47296
|
-
if (armed)
|
|
47297
|
-
opts.probeLogger?.debug("screen-idle fire noop: already armed", {
|
|
47298
|
-
label: opts.probeLabel
|
|
47299
|
-
});
|
|
47300
|
-
return;
|
|
47301
|
-
}
|
|
47576
|
+
if (armed) return;
|
|
47302
47577
|
armed = true;
|
|
47303
|
-
opts.probeLogger?.info("screen-idle fire triggered \u2192 armed=true, calling onIdle", {
|
|
47304
|
-
label: opts.probeLabel
|
|
47305
|
-
});
|
|
47306
47578
|
opts.onIdle();
|
|
47307
47579
|
};
|
|
47308
47580
|
const unsub = surface.onTick((lines) => {
|
|
47309
47581
|
if (disposed) return;
|
|
47310
47582
|
const snap = snapOf(lines);
|
|
47311
47583
|
if (snap === lastSnap) return;
|
|
47312
|
-
if (opts.probeLogger) {
|
|
47313
|
-
const prev = lastSnap;
|
|
47314
|
-
const meta = {
|
|
47315
|
-
label: opts.probeLabel,
|
|
47316
|
-
prevHash: prev === null ? null : shortHash(prev),
|
|
47317
|
-
nextHash: shortHash(snap),
|
|
47318
|
-
prevLen: prev?.length ?? 0,
|
|
47319
|
-
nextLen: snap.length
|
|
47320
|
-
};
|
|
47321
|
-
if (prev !== null) {
|
|
47322
|
-
const diff2 = firstLineDiff(prev, snap);
|
|
47323
|
-
if (diff2) {
|
|
47324
|
-
meta.diffRow = diff2.row;
|
|
47325
|
-
meta.prevRow = diff2.prev;
|
|
47326
|
-
meta.nextRow = diff2.next;
|
|
47327
|
-
}
|
|
47328
|
-
}
|
|
47329
|
-
opts.probeLogger.info("screen-idle tick snap changed", meta);
|
|
47330
|
-
}
|
|
47331
47584
|
lastSnap = snap;
|
|
47332
47585
|
armed = false;
|
|
47333
47586
|
clear();
|
|
@@ -47338,38 +47591,9 @@ function observeScreenIdle(surface, opts) {
|
|
|
47338
47591
|
disposed = true;
|
|
47339
47592
|
unsub();
|
|
47340
47593
|
clear();
|
|
47341
|
-
},
|
|
47342
|
-
isIdle() {
|
|
47343
|
-
const popupVisible = opts.getPopupVisible();
|
|
47344
|
-
const idle = armed && !popupVisible;
|
|
47345
|
-
if (opts.probeLogger) {
|
|
47346
|
-
opts.probeLogger.info("screen-idle isIdle check", {
|
|
47347
|
-
label: opts.probeLabel,
|
|
47348
|
-
idle,
|
|
47349
|
-
armed,
|
|
47350
|
-
popupVisible
|
|
47351
|
-
});
|
|
47352
|
-
}
|
|
47353
|
-
return idle;
|
|
47354
47594
|
}
|
|
47355
47595
|
};
|
|
47356
47596
|
}
|
|
47357
|
-
function shortHash(s) {
|
|
47358
|
-
return (0, import_node_crypto5.createHash)("sha1").update(s).digest("hex").slice(0, 8);
|
|
47359
|
-
}
|
|
47360
|
-
function firstLineDiff(prev, next) {
|
|
47361
|
-
const p2 = prev.split("\n");
|
|
47362
|
-
const n = next.split("\n");
|
|
47363
|
-
const rows = Math.max(p2.length, n.length);
|
|
47364
|
-
for (let i = 0; i < rows; i++) {
|
|
47365
|
-
const pl = p2[i] ?? "";
|
|
47366
|
-
const nl = n[i] ?? "";
|
|
47367
|
-
if (pl !== nl) {
|
|
47368
|
-
return { row: i, prev: pl.slice(0, 60), next: nl.slice(0, 60) };
|
|
47369
|
-
}
|
|
47370
|
-
}
|
|
47371
|
-
return null;
|
|
47372
|
-
}
|
|
47373
47597
|
var BYPASS_SETTLE_MS = 300;
|
|
47374
47598
|
var SCREEN_IDLE_MS = 5e3;
|
|
47375
47599
|
function createBootGate(pty, logger) {
|
|
@@ -47444,42 +47668,11 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
|
|
|
47444
47668
|
// 用于 spawn / PtyChildProcess 链路打日志
|
|
47445
47669
|
tuiLogger;
|
|
47446
47670
|
tuiOpts;
|
|
47447
|
-
/**
|
|
47448
|
-
* per-toolSessionId 的 tui 观察者句柄,仅用于 turn_end gate 查询(`canAcceptTurnEnd`)。
|
|
47449
|
-
* onIdle / onPopupTransition 等回调仍走原有闭包(不复用这份 map),本 map 只承担
|
|
47450
|
-
* "manager 需要跨模块查屏幕/弹框状态"这单一职责。
|
|
47451
|
-
*/
|
|
47452
|
-
tuiStates = /* @__PURE__ */ new Map();
|
|
47453
47671
|
constructor(opts = {}) {
|
|
47454
47672
|
super(opts);
|
|
47455
47673
|
this.tuiLogger = opts.logger;
|
|
47456
47674
|
this.tuiOpts = opts;
|
|
47457
47675
|
}
|
|
47458
|
-
/**
|
|
47459
|
-
* TUI adapter 的 turn_end 权威判定:屏幕已 idle 且非弹框态才放行。
|
|
47460
|
-
*
|
|
47461
|
-
* `feedObserverEvents` 收到 observer 回灌 `turn_end` 时调用。屏幕仍在变(如后台 agent 在跑)
|
|
47462
|
-
* 时 drop 掉 turn_end,避免 `system/turn_duration` JSONL 帧误触发 running-idle 状态转换。
|
|
47463
|
-
*
|
|
47464
|
-
* 未跟踪的 toolSessionId(spawn 前 / spawn 失败 / 已 dispose)视为 pass —— gate 只 drop
|
|
47465
|
-
* "有证据判定为伪信号"的场景,不做 unknown → block。
|
|
47466
|
-
*/
|
|
47467
|
-
canAcceptTurnEnd(toolSessionId) {
|
|
47468
|
-
const state = this.tuiStates.get(toolSessionId);
|
|
47469
|
-
if (!state) {
|
|
47470
|
-
this.tuiOpts.screenIdleProbeLogger?.info(
|
|
47471
|
-
"canAcceptTurnEnd: no tuiState \u2192 pass (\u672A\u8DDF\u8E2A)",
|
|
47472
|
-
{ toolSessionId }
|
|
47473
|
-
);
|
|
47474
|
-
return true;
|
|
47475
|
-
}
|
|
47476
|
-
const result = state.screenIdle.isIdle();
|
|
47477
|
-
this.tuiOpts.screenIdleProbeLogger?.info("canAcceptTurnEnd", {
|
|
47478
|
-
toolSessionId,
|
|
47479
|
-
result
|
|
47480
|
-
});
|
|
47481
|
-
return result;
|
|
47482
|
-
}
|
|
47483
47676
|
spawn(ctx) {
|
|
47484
47677
|
const args = buildTuiSpawnArgs(ctx, jsonlExistsForCtx(ctx));
|
|
47485
47678
|
const cmd = process.env.CLAUDE_BIN ?? "claude";
|
|
@@ -47537,26 +47730,18 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
|
|
|
47537
47730
|
const screenIdleObserver = observeScreenIdle(surface, {
|
|
47538
47731
|
idleMs: SCREEN_IDLE_MS,
|
|
47539
47732
|
onIdle: () => {
|
|
47540
|
-
if (!ctx.toolSessionId || !this.tuiOpts.
|
|
47541
|
-
this.tuiLogger?.debug("screen-idle \u2192
|
|
47542
|
-
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);
|
|
47543
47736
|
},
|
|
47544
47737
|
getPopupVisible: () => popupObserver.visibleKind !== null,
|
|
47545
|
-
//
|
|
47546
|
-
|
|
47547
|
-
|
|
47548
|
-
probeLabel: ctx.toolSessionId ?? "<no-tsid>"
|
|
47549
|
-
} : {}
|
|
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
|
|
47550
47741
|
});
|
|
47551
47742
|
if (ctx.toolSessionId && this.tuiOpts.onSurfaceRegister) {
|
|
47552
47743
|
this.tuiOpts.onSurfaceRegister(ctx.toolSessionId, surface);
|
|
47553
47744
|
}
|
|
47554
|
-
if (ctx.toolSessionId) {
|
|
47555
|
-
this.tuiStates.set(ctx.toolSessionId, {
|
|
47556
|
-
screenIdle: screenIdleObserver,
|
|
47557
|
-
popup: popupObserver
|
|
47558
|
-
});
|
|
47559
|
-
}
|
|
47560
47745
|
let chunkSeq = 0;
|
|
47561
47746
|
if (ctx.toolSessionId && this.tuiOpts.onPtyReplayRegister) {
|
|
47562
47747
|
this.tuiOpts.onPtyReplayRegister(ctx.toolSessionId, async () => {
|
|
@@ -47602,9 +47787,6 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
|
|
|
47602
47787
|
readyObserver.dispose();
|
|
47603
47788
|
popupObserver.dispose();
|
|
47604
47789
|
screenIdleObserver.dispose();
|
|
47605
|
-
if (ctx.toolSessionId) {
|
|
47606
|
-
this.tuiStates.delete(ctx.toolSessionId);
|
|
47607
|
-
}
|
|
47608
47790
|
if (ctx.toolSessionId && this.tuiOpts.onSurfaceUnregister) {
|
|
47609
47791
|
this.tuiOpts.onSurfaceUnregister(ctx.toolSessionId);
|
|
47610
47792
|
}
|
|
@@ -47887,7 +48069,7 @@ async function writeInboxMcpConfig(args) {
|
|
|
47887
48069
|
// src/shift/store.ts
|
|
47888
48070
|
var import_promises = __toESM(require("fs/promises"), 1);
|
|
47889
48071
|
var import_node_path19 = __toESM(require("path"), 1);
|
|
47890
|
-
var
|
|
48072
|
+
var import_node_crypto5 = require("crypto");
|
|
47891
48073
|
|
|
47892
48074
|
// src/shift/constants.ts
|
|
47893
48075
|
var MAX_RUNS_PER_SHIFT = 30;
|
|
@@ -47983,7 +48165,7 @@ function createShiftStore(deps) {
|
|
|
47983
48165
|
const nextRunAtMs = computeNextRunAtMs(input.schedule, now) ?? void 0;
|
|
47984
48166
|
const shift = {
|
|
47985
48167
|
...input,
|
|
47986
|
-
id: (0,
|
|
48168
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
47987
48169
|
createdAtMs: now,
|
|
47988
48170
|
updatedAtMs: now,
|
|
47989
48171
|
state: { nextRunAtMs },
|
|
@@ -48500,78 +48682,8 @@ function buildPersonaDispatchHandlers(deps) {
|
|
|
48500
48682
|
};
|
|
48501
48683
|
}
|
|
48502
48684
|
|
|
48503
|
-
// src/
|
|
48504
|
-
|
|
48505
|
-
if (url.startsWith("wss://")) return "https://" + url.slice("wss://".length);
|
|
48506
|
-
if (url.startsWith("ws://")) return "http://" + url.slice("ws://".length);
|
|
48507
|
-
return url;
|
|
48508
|
-
}
|
|
48509
|
-
async function forwardDispatchToPeer(args) {
|
|
48510
|
-
const f = args.fetchImpl ?? fetch;
|
|
48511
|
-
const base = wsUrlToHttp(args.contact.remoteUrl).replace(/\/+$/, "");
|
|
48512
|
-
const url = `${base}/rpc/personaDispatch:run`;
|
|
48513
|
-
let res;
|
|
48514
|
-
try {
|
|
48515
|
-
res = await f(url, {
|
|
48516
|
-
method: "POST",
|
|
48517
|
-
headers: {
|
|
48518
|
-
"content-type": "application/json",
|
|
48519
|
-
authorization: `Bearer ${args.contact.connectToken}`
|
|
48520
|
-
},
|
|
48521
|
-
// 注意:不带 targetDeviceId —— B 端据此判定为本地执行(B 角色)。
|
|
48522
|
-
body: JSON.stringify({ targetPersona: args.targetPersona, prompt: args.prompt })
|
|
48523
|
-
});
|
|
48524
|
-
} catch (err) {
|
|
48525
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
48526
|
-
return { kind: "failure", reason: `forward to peer failed: ${msg}` };
|
|
48527
|
-
}
|
|
48528
|
-
let json;
|
|
48529
|
-
try {
|
|
48530
|
-
json = await res.json();
|
|
48531
|
-
} catch {
|
|
48532
|
-
return {
|
|
48533
|
-
kind: "failure",
|
|
48534
|
-
reason: `peer returned non-JSON response (HTTP ${res.status})`
|
|
48535
|
-
};
|
|
48536
|
-
}
|
|
48537
|
-
if (json.ok === false) {
|
|
48538
|
-
return { kind: "failure", reason: `peer rejected: ${json.error}: ${json.message}` };
|
|
48539
|
-
}
|
|
48540
|
-
return json.result.outcome;
|
|
48541
|
-
}
|
|
48542
|
-
async function forwardInboxPostToPeer(args) {
|
|
48543
|
-
const f = args.fetchImpl ?? fetch;
|
|
48544
|
-
const base = wsUrlToHttp(args.contact.remoteUrl).replace(/\/+$/, "");
|
|
48545
|
-
const url = `${base}/rpc/inbox:postMessage`;
|
|
48546
|
-
let res;
|
|
48547
|
-
try {
|
|
48548
|
-
res = await f(url, {
|
|
48549
|
-
method: "POST",
|
|
48550
|
-
headers: {
|
|
48551
|
-
"content-type": "application/json",
|
|
48552
|
-
authorization: `Bearer ${args.contact.connectToken}`
|
|
48553
|
-
},
|
|
48554
|
-
body: JSON.stringify({
|
|
48555
|
-
id: args.id,
|
|
48556
|
-
text: args.text,
|
|
48557
|
-
createdAt: args.createdAt,
|
|
48558
|
-
...args.origin ? { origin: args.origin } : {}
|
|
48559
|
-
})
|
|
48560
|
-
});
|
|
48561
|
-
} catch (err) {
|
|
48562
|
-
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
48563
|
-
}
|
|
48564
|
-
let json = null;
|
|
48565
|
-
try {
|
|
48566
|
-
json = await res.json();
|
|
48567
|
-
} catch {
|
|
48568
|
-
return { ok: false, error: `peer non-JSON (HTTP ${res.status})` };
|
|
48569
|
-
}
|
|
48570
|
-
if (res.status < 200 || res.status >= 300 || json?.ok === false) {
|
|
48571
|
-
return { ok: false, error: json?.error ?? `HTTP ${res.status}` };
|
|
48572
|
-
}
|
|
48573
|
-
return { ok: true };
|
|
48574
|
-
}
|
|
48685
|
+
// src/index.ts
|
|
48686
|
+
init_peer_forward();
|
|
48575
48687
|
|
|
48576
48688
|
// src/tools/codex-history.ts
|
|
48577
48689
|
var import_node_child_process5 = require("child_process");
|
|
@@ -48759,13 +48871,13 @@ function mapSkillsListResponse(res) {
|
|
|
48759
48871
|
const r = s ?? {};
|
|
48760
48872
|
const name = str3(r.name);
|
|
48761
48873
|
if (!name) continue;
|
|
48762
|
-
const
|
|
48874
|
+
const path76 = str3(r.path);
|
|
48763
48875
|
const description = str3(r.description);
|
|
48764
48876
|
const isPlugin = name.includes(":");
|
|
48765
48877
|
out.push({
|
|
48766
48878
|
name,
|
|
48767
48879
|
source: isPlugin ? "plugin" : "project",
|
|
48768
|
-
...
|
|
48880
|
+
...path76 ? { path: path76 } : {},
|
|
48769
48881
|
...description ? { description } : {},
|
|
48770
48882
|
...isPlugin ? { plugin: name.split(":")[0] } : {}
|
|
48771
48883
|
});
|
|
@@ -49414,18 +49526,8 @@ function listRegistered() {
|
|
|
49414
49526
|
return [...registry.keys()];
|
|
49415
49527
|
}
|
|
49416
49528
|
|
|
49417
|
-
// ../node_modules/.pnpm/ws@8.20.0/node_modules/ws/wrapper.mjs
|
|
49418
|
-
var import_stream = __toESM(require_stream(), 1);
|
|
49419
|
-
var import_extension = __toESM(require_extension(), 1);
|
|
49420
|
-
var import_permessage_deflate = __toESM(require_permessage_deflate(), 1);
|
|
49421
|
-
var import_receiver = __toESM(require_receiver(), 1);
|
|
49422
|
-
var import_sender = __toESM(require_sender(), 1);
|
|
49423
|
-
var import_subprotocol = __toESM(require_subprotocol(), 1);
|
|
49424
|
-
var import_websocket = __toESM(require_websocket(), 1);
|
|
49425
|
-
var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
49426
|
-
var wrapper_default = import_websocket.default;
|
|
49427
|
-
|
|
49428
49529
|
// src/transport/local-ws-server.ts
|
|
49530
|
+
init_wrapper();
|
|
49429
49531
|
var import_node_http2 = __toESM(require("http"), 1);
|
|
49430
49532
|
|
|
49431
49533
|
// src/transport/preview-proxy.ts
|
|
@@ -49501,6 +49603,18 @@ var LocalWsServer = class {
|
|
|
49501
49603
|
const httpServer = import_node_http2.default.createServer((req, res) => this.handleHttpRequest(req, res));
|
|
49502
49604
|
const wss = new import_websocket_server.default({ noServer: true, clientTracking: true });
|
|
49503
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
|
+
}
|
|
49504
49618
|
if (req.url?.startsWith("/preview/")) {
|
|
49505
49619
|
const pathname = (() => {
|
|
49506
49620
|
try {
|
|
@@ -50073,6 +50187,7 @@ function constantTimeEqual2(a, b2) {
|
|
|
50073
50187
|
}
|
|
50074
50188
|
|
|
50075
50189
|
// src/transport/connection-context.ts
|
|
50190
|
+
init_src();
|
|
50076
50191
|
function ownerContext(ownerPrincipalId, displayName) {
|
|
50077
50192
|
return {
|
|
50078
50193
|
principal: makeOwnerPrincipal(ownerPrincipalId, displayName),
|
|
@@ -50126,6 +50241,7 @@ async function authenticate(token, deps) {
|
|
|
50126
50241
|
// src/permission/capability-store.ts
|
|
50127
50242
|
var fs28 = __toESM(require("fs"), 1);
|
|
50128
50243
|
var path28 = __toESM(require("path"), 1);
|
|
50244
|
+
init_src();
|
|
50129
50245
|
var CAPABILITIES_FILE_NAME = "capabilities.json";
|
|
50130
50246
|
var FILE_VERSION = 1;
|
|
50131
50247
|
var CapabilityStore = class {
|
|
@@ -50303,6 +50419,7 @@ function cleanupGuestSessionsForCapability(cap, factory) {
|
|
|
50303
50419
|
// src/inbox/inbox-store.ts
|
|
50304
50420
|
var fs30 = __toESM(require("fs"), 1);
|
|
50305
50421
|
var path29 = __toESM(require("path"), 1);
|
|
50422
|
+
init_src();
|
|
50306
50423
|
var INBOX_SUBDIR = "inbox";
|
|
50307
50424
|
var InboxStore = class {
|
|
50308
50425
|
constructor(dataDir) {
|
|
@@ -50493,6 +50610,7 @@ var InboxManager = class {
|
|
|
50493
50610
|
// src/state/contact-store.ts
|
|
50494
50611
|
var fs31 = __toESM(require("fs"), 1);
|
|
50495
50612
|
var path30 = __toESM(require("path"), 1);
|
|
50613
|
+
init_src();
|
|
50496
50614
|
var FILE_NAME = "contacts.json";
|
|
50497
50615
|
var ContactStore = class {
|
|
50498
50616
|
constructor(dataDir) {
|
|
@@ -50597,6 +50715,7 @@ var ContactStore = class {
|
|
|
50597
50715
|
};
|
|
50598
50716
|
|
|
50599
50717
|
// src/contact/connect-remote.ts
|
|
50718
|
+
init_wrapper();
|
|
50600
50719
|
var crypto6 = __toESM(require("crypto"), 1);
|
|
50601
50720
|
var HANDSHAKE_TIMEOUT_MS = 5e3;
|
|
50602
50721
|
var RPC_TIMEOUT_MS = 5e3;
|
|
@@ -50742,6 +50861,9 @@ async function autoReverseContact(args) {
|
|
|
50742
50861
|
args.broadcast({ type: "contact:added", contact: upgraded });
|
|
50743
50862
|
}
|
|
50744
50863
|
|
|
50864
|
+
// src/index.ts
|
|
50865
|
+
init_src();
|
|
50866
|
+
|
|
50745
50867
|
// src/migrations/2026-05-20-flatten-sessions.ts
|
|
50746
50868
|
var fs32 = __toESM(require("fs"), 1);
|
|
50747
50869
|
var path31 = __toESM(require("path"), 1);
|
|
@@ -50954,7 +51076,7 @@ function lookupMime(filePathOrName) {
|
|
|
50954
51076
|
}
|
|
50955
51077
|
|
|
50956
51078
|
// src/attachment/sign-url.ts
|
|
50957
|
-
var
|
|
51079
|
+
var import_node_crypto6 = __toESM(require("crypto"), 1);
|
|
50958
51080
|
var HMAC_ALGO = "sha256";
|
|
50959
51081
|
function base64urlEncode(buf) {
|
|
50960
51082
|
const b2 = typeof buf === "string" ? Buffer.from(buf, "utf8") : buf;
|
|
@@ -50971,7 +51093,7 @@ function decodeAbsPathFromUrl(encoded) {
|
|
|
50971
51093
|
}
|
|
50972
51094
|
function computeSig(secret, absPath, e) {
|
|
50973
51095
|
const msg = e === null ? absPath : `${absPath}|${e}`;
|
|
50974
|
-
return
|
|
51096
|
+
return import_node_crypto6.default.createHmac(HMAC_ALGO, secret).update(msg).digest();
|
|
50975
51097
|
}
|
|
50976
51098
|
function signUrlParts(secret, absPath, ttlSeconds, now = Date.now) {
|
|
50977
51099
|
const e = ttlSeconds === null ? null : Math.floor(now() / 1e3) + ttlSeconds;
|
|
@@ -51006,7 +51128,7 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
|
|
|
51006
51128
|
if (provided.length !== expected.length) {
|
|
51007
51129
|
return { ok: false, code: "BAD_SIG" };
|
|
51008
51130
|
}
|
|
51009
|
-
if (!
|
|
51131
|
+
if (!import_node_crypto6.default.timingSafeEqual(provided, expected)) {
|
|
51010
51132
|
return { ok: false, code: "BAD_SIG" };
|
|
51011
51133
|
}
|
|
51012
51134
|
if (e !== null && now() / 1e3 > e) {
|
|
@@ -51018,7 +51140,7 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
|
|
|
51018
51140
|
// src/attachment/upload.ts
|
|
51019
51141
|
var import_node_fs25 = __toESM(require("fs"), 1);
|
|
51020
51142
|
var import_node_path25 = __toESM(require("path"), 1);
|
|
51021
|
-
var
|
|
51143
|
+
var import_node_crypto7 = __toESM(require("crypto"), 1);
|
|
51022
51144
|
var import_promises2 = require("stream/promises");
|
|
51023
51145
|
var UploadError = class extends Error {
|
|
51024
51146
|
constructor(code, message) {
|
|
@@ -51042,11 +51164,11 @@ async function writeUploadedAttachment(args) {
|
|
|
51042
51164
|
} catch (err) {
|
|
51043
51165
|
throw new UploadError("STORAGE_ERROR", `mkdir failed: ${err.message}`);
|
|
51044
51166
|
}
|
|
51045
|
-
const hasher =
|
|
51167
|
+
const hasher = import_node_crypto7.default.createHash("sha256");
|
|
51046
51168
|
let actualSize = 0;
|
|
51047
51169
|
const tmpPath = import_node_path25.default.join(
|
|
51048
51170
|
attachmentsRoot,
|
|
51049
|
-
`.upload-${process.pid}-${Date.now()}-${
|
|
51171
|
+
`.upload-${process.pid}-${Date.now()}-${import_node_crypto7.default.randomBytes(4).toString("hex")}`
|
|
51050
51172
|
);
|
|
51051
51173
|
try {
|
|
51052
51174
|
await (0, import_promises2.pipeline)(
|
|
@@ -51124,6 +51246,7 @@ var import_promises3 = __toESM(require("fs/promises"), 1);
|
|
|
51124
51246
|
var import_node_path26 = __toESM(require("path"), 1);
|
|
51125
51247
|
var import_node_os12 = __toESM(require("os"), 1);
|
|
51126
51248
|
var import_jszip = __toESM(require_lib3(), 1);
|
|
51249
|
+
init_src();
|
|
51127
51250
|
var ImportError = class extends Error {
|
|
51128
51251
|
constructor(code, message) {
|
|
51129
51252
|
super(message);
|
|
@@ -51922,7 +52045,7 @@ function runAttachmentGc(args) {
|
|
|
51922
52045
|
// src/attachment/group.ts
|
|
51923
52046
|
var import_node_fs28 = __toESM(require("fs"), 1);
|
|
51924
52047
|
var import_node_path29 = __toESM(require("path"), 1);
|
|
51925
|
-
var
|
|
52048
|
+
var import_node_crypto8 = __toESM(require("crypto"), 1);
|
|
51926
52049
|
init_protocol();
|
|
51927
52050
|
var GroupFileStore = class {
|
|
51928
52051
|
dataDir;
|
|
@@ -52011,7 +52134,7 @@ var GroupFileStore = class {
|
|
|
52011
52134
|
entries[idx] = next;
|
|
52012
52135
|
} else {
|
|
52013
52136
|
next = {
|
|
52014
|
-
id: `gf-${
|
|
52137
|
+
id: `gf-${import_node_crypto8.default.randomBytes(6).toString("base64url")}`,
|
|
52015
52138
|
relPath: input.relPath,
|
|
52016
52139
|
from: input.from,
|
|
52017
52140
|
label: input.label,
|
|
@@ -52130,7 +52253,7 @@ function readDaemonSourceFromEnv(env = process.env) {
|
|
|
52130
52253
|
// src/tunnel/tunnel-manager.ts
|
|
52131
52254
|
var import_node_fs33 = __toESM(require("fs"), 1);
|
|
52132
52255
|
var import_node_path34 = __toESM(require("path"), 1);
|
|
52133
|
-
var
|
|
52256
|
+
var import_node_crypto9 = __toESM(require("crypto"), 1);
|
|
52134
52257
|
var import_node_child_process9 = require("child_process");
|
|
52135
52258
|
|
|
52136
52259
|
// src/tunnel/tunnel-store.ts
|
|
@@ -52629,7 +52752,7 @@ var TunnelManager = class {
|
|
|
52629
52752
|
override: this.deps.frpcBinaryOverride ?? void 0
|
|
52630
52753
|
});
|
|
52631
52754
|
const tomlPath = import_node_path34.default.join(this.deps.dataDir, "frpc.toml");
|
|
52632
|
-
const proxyName = `clawd-${t.subdomain}-${localPort}-${
|
|
52755
|
+
const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto9.default.randomBytes(3).toString("hex")}`;
|
|
52633
52756
|
const toml = buildFrpcToml({
|
|
52634
52757
|
serverAddr: t.frpsHost,
|
|
52635
52758
|
serverPort: t.frpsPort,
|
|
@@ -52884,14 +53007,27 @@ var CLAWD_SSH_JAIL_SCRIPT = String.raw`#!/usr/bin/env bash
|
|
|
52884
53007
|
|
|
52885
53008
|
set -euo pipefail
|
|
52886
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
|
+
|
|
52887
53019
|
DEVICE_ID="\${1:-}"
|
|
52888
53020
|
if [ -z "$DEVICE_ID" ]; then
|
|
53021
|
+
log_line ERROR jail.entered "clawd-ssh-jail 缺 deviceId 参数"
|
|
52889
53022
|
echo "clawd-ssh-jail: missing deviceId" >&2
|
|
52890
53023
|
exit 1
|
|
52891
53024
|
fi
|
|
52892
53025
|
|
|
53026
|
+
log_line INFO jail.entered "clawd-ssh-jail 起来 device=\${DEVICE_ID} cmd=\${SSH_ORIGINAL_COMMAND:-<interactive-shell>}"
|
|
53027
|
+
|
|
52893
53028
|
CONTACTS="\${HOME}/.clawd/contacts.json"
|
|
52894
53029
|
if [ ! -f "$CONTACTS" ]; then
|
|
53030
|
+
log_line ERROR jail.entered "contacts.json 不存在,无法查 exposedDirs"
|
|
52895
53031
|
echo "clawd-ssh-jail: contacts.json missing" >&2
|
|
52896
53032
|
exit 1
|
|
52897
53033
|
fi
|
|
@@ -52912,9 +53048,20 @@ sys.exit(3)
|
|
|
52912
53048
|
")
|
|
52913
53049
|
|
|
52914
53050
|
if [ -z "$EXPOSED_JSON" ]; then
|
|
53051
|
+
log_line ERROR jail.entered "contact \${DEVICE_ID} 不在 store 或 exposedDirs 为空"
|
|
52915
53052
|
echo "clawd-ssh-jail: contact not found or no exposed dirs" >&2
|
|
52916
53053
|
exit 1
|
|
52917
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
|
|
52918
53065
|
|
|
52919
53066
|
# 校验路径安全(bash 侧二次防御)
|
|
52920
53067
|
while IFS= read -r line; do
|
|
@@ -53206,29 +53353,290 @@ function readIssuedPubkey(sshdDir, deviceId) {
|
|
|
53206
53353
|
}
|
|
53207
53354
|
}
|
|
53208
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
|
+
|
|
53209
53617
|
// src/tunnel/device-key.ts
|
|
53210
53618
|
var import_node_os14 = __toESM(require("os"), 1);
|
|
53211
|
-
var
|
|
53212
|
-
var
|
|
53619
|
+
var import_node_path41 = __toESM(require("path"), 1);
|
|
53620
|
+
var import_node_crypto10 = __toESM(require("crypto"), 1);
|
|
53213
53621
|
var DERIVE_SALT = "clawd-tunnel-device-v1";
|
|
53214
53622
|
function deriveStableDeviceKey(opts = {}) {
|
|
53215
53623
|
const hostname = opts.hostname ?? import_node_os14.default.hostname();
|
|
53216
53624
|
const uid = opts.uid ?? (typeof import_node_os14.default.userInfo === "function" ? import_node_os14.default.userInfo().uid : 0);
|
|
53217
53625
|
const home = opts.home ?? import_node_os14.default.homedir();
|
|
53218
|
-
const defaultDataDir =
|
|
53219
|
-
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;
|
|
53220
53628
|
const isDefaultDir = normalizedDataDir == null || normalizedDataDir === defaultDataDir;
|
|
53221
53629
|
const input = isDefaultDir ? `${hostname}::${uid}` : `${hostname}::${uid}::${normalizedDataDir}`;
|
|
53222
|
-
return
|
|
53630
|
+
return import_node_crypto10.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
|
|
53223
53631
|
}
|
|
53224
53632
|
|
|
53225
53633
|
// src/auth-store.ts
|
|
53226
|
-
var
|
|
53227
|
-
var
|
|
53228
|
-
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);
|
|
53229
53637
|
var AUTH_FILE_NAME = "auth.json";
|
|
53230
53638
|
function authFilePath(dataDir) {
|
|
53231
|
-
return
|
|
53639
|
+
return import_node_path42.default.join(dataDir, AUTH_FILE_NAME);
|
|
53232
53640
|
}
|
|
53233
53641
|
function loadOrCreateAuthFile(opts) {
|
|
53234
53642
|
const file = authFilePath(opts.dataDir);
|
|
@@ -53257,14 +53665,14 @@ function loadOrCreateAuthFile(opts) {
|
|
|
53257
53665
|
return next;
|
|
53258
53666
|
}
|
|
53259
53667
|
function defaultGenerateToken() {
|
|
53260
|
-
return
|
|
53668
|
+
return import_node_crypto11.default.randomBytes(32).toString("base64url");
|
|
53261
53669
|
}
|
|
53262
53670
|
function defaultGenerateOwnerPrincipalId() {
|
|
53263
|
-
return `owner-${
|
|
53671
|
+
return `owner-${import_node_crypto11.default.randomUUID()}`;
|
|
53264
53672
|
}
|
|
53265
53673
|
function readAuthFile(file) {
|
|
53266
53674
|
try {
|
|
53267
|
-
const raw =
|
|
53675
|
+
const raw = import_node_fs40.default.readFileSync(file, "utf8");
|
|
53268
53676
|
const parsed = JSON.parse(raw);
|
|
53269
53677
|
if (typeof parsed?.token !== "string" || parsed.token.length === 0) {
|
|
53270
53678
|
return null;
|
|
@@ -53284,25 +53692,25 @@ function readAuthFile(file) {
|
|
|
53284
53692
|
}
|
|
53285
53693
|
}
|
|
53286
53694
|
function writeAuthFile(file, content) {
|
|
53287
|
-
|
|
53288
|
-
|
|
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 });
|
|
53289
53697
|
try {
|
|
53290
|
-
|
|
53698
|
+
import_node_fs40.default.chmodSync(file, 384);
|
|
53291
53699
|
} catch {
|
|
53292
53700
|
}
|
|
53293
53701
|
}
|
|
53294
53702
|
|
|
53295
53703
|
// src/owner-profile.ts
|
|
53296
|
-
var
|
|
53704
|
+
var import_node_fs41 = __toESM(require("fs"), 1);
|
|
53297
53705
|
var import_node_os15 = __toESM(require("os"), 1);
|
|
53298
|
-
var
|
|
53706
|
+
var import_node_path43 = __toESM(require("path"), 1);
|
|
53299
53707
|
var PROFILE_FILENAME = "profile.json";
|
|
53300
53708
|
function loadOwnerDisplayName(dataDir) {
|
|
53301
53709
|
const fallback = import_node_os15.default.userInfo().username;
|
|
53302
|
-
const profilePath =
|
|
53710
|
+
const profilePath = import_node_path43.default.join(dataDir, PROFILE_FILENAME);
|
|
53303
53711
|
let raw;
|
|
53304
53712
|
try {
|
|
53305
|
-
raw =
|
|
53713
|
+
raw = import_node_fs41.default.readFileSync(profilePath, "utf8");
|
|
53306
53714
|
} catch {
|
|
53307
53715
|
return fallback;
|
|
53308
53716
|
}
|
|
@@ -53325,18 +53733,18 @@ function loadOwnerDisplayName(dataDir) {
|
|
|
53325
53733
|
}
|
|
53326
53734
|
|
|
53327
53735
|
// src/feishu-auth/owner-identity-store.ts
|
|
53328
|
-
var
|
|
53329
|
-
var
|
|
53736
|
+
var import_node_fs42 = __toESM(require("fs"), 1);
|
|
53737
|
+
var import_node_path44 = __toESM(require("path"), 1);
|
|
53330
53738
|
var OWNER_IDENTITY_FILE_NAME = "owner-identity.json";
|
|
53331
53739
|
var OwnerIdentityStore = class {
|
|
53332
53740
|
file;
|
|
53333
53741
|
constructor(dataDir) {
|
|
53334
|
-
this.file =
|
|
53742
|
+
this.file = import_node_path44.default.join(dataDir, OWNER_IDENTITY_FILE_NAME);
|
|
53335
53743
|
}
|
|
53336
53744
|
read() {
|
|
53337
53745
|
let raw;
|
|
53338
53746
|
try {
|
|
53339
|
-
raw =
|
|
53747
|
+
raw = import_node_fs42.default.readFileSync(this.file, "utf8");
|
|
53340
53748
|
} catch {
|
|
53341
53749
|
return null;
|
|
53342
53750
|
}
|
|
@@ -53364,16 +53772,16 @@ var OwnerIdentityStore = class {
|
|
|
53364
53772
|
};
|
|
53365
53773
|
}
|
|
53366
53774
|
write(record) {
|
|
53367
|
-
|
|
53368
|
-
|
|
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 });
|
|
53369
53777
|
try {
|
|
53370
|
-
|
|
53778
|
+
import_node_fs42.default.chmodSync(this.file, 384);
|
|
53371
53779
|
} catch {
|
|
53372
53780
|
}
|
|
53373
53781
|
}
|
|
53374
53782
|
clear() {
|
|
53375
53783
|
try {
|
|
53376
|
-
|
|
53784
|
+
import_node_fs42.default.unlinkSync(this.file);
|
|
53377
53785
|
} catch (err) {
|
|
53378
53786
|
const code = err?.code;
|
|
53379
53787
|
if (code !== "ENOENT") throw err;
|
|
@@ -53382,7 +53790,7 @@ var OwnerIdentityStore = class {
|
|
|
53382
53790
|
};
|
|
53383
53791
|
|
|
53384
53792
|
// src/feishu-auth/login-flow.ts
|
|
53385
|
-
var
|
|
53793
|
+
var import_node_crypto12 = __toESM(require("crypto"), 1);
|
|
53386
53794
|
var STATE_TTL_MS = 5 * 60 * 1e3;
|
|
53387
53795
|
var LoginFlow = class {
|
|
53388
53796
|
constructor(deps) {
|
|
@@ -53391,7 +53799,7 @@ var LoginFlow = class {
|
|
|
53391
53799
|
deps;
|
|
53392
53800
|
pendingStates = /* @__PURE__ */ new Map();
|
|
53393
53801
|
start() {
|
|
53394
|
-
const state =
|
|
53802
|
+
const state = import_node_crypto12.default.randomBytes(16).toString("base64url");
|
|
53395
53803
|
const now = (this.deps.now ?? Date.now)();
|
|
53396
53804
|
this.pendingStates.set(state, now);
|
|
53397
53805
|
this.gcExpired(now);
|
|
@@ -53494,9 +53902,9 @@ var CentralClientError = class extends Error {
|
|
|
53494
53902
|
code;
|
|
53495
53903
|
cause;
|
|
53496
53904
|
};
|
|
53497
|
-
async function centralRequest(opts,
|
|
53905
|
+
async function centralRequest(opts, path76, init) {
|
|
53498
53906
|
const f = opts.fetchImpl ?? globalThis.fetch;
|
|
53499
|
-
const url = `${opts.api.replace(/\/+$/, "")}${
|
|
53907
|
+
const url = `${opts.api.replace(/\/+$/, "")}${path76}`;
|
|
53500
53908
|
const ctrl = new AbortController();
|
|
53501
53909
|
const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 15e3);
|
|
53502
53910
|
let res;
|
|
@@ -53638,8 +54046,8 @@ function verifyConnectToken(args) {
|
|
|
53638
54046
|
}
|
|
53639
54047
|
|
|
53640
54048
|
// src/feishu-auth/server-key.ts
|
|
53641
|
-
var
|
|
53642
|
-
var
|
|
54049
|
+
var fs52 = __toESM(require("fs"), 1);
|
|
54050
|
+
var path53 = __toESM(require("path"), 1);
|
|
53643
54051
|
var FILE_NAME2 = "server-signing-key.json";
|
|
53644
54052
|
var ServerKeyStore = class {
|
|
53645
54053
|
constructor(dataDir) {
|
|
@@ -53647,12 +54055,12 @@ var ServerKeyStore = class {
|
|
|
53647
54055
|
}
|
|
53648
54056
|
dataDir;
|
|
53649
54057
|
filePath() {
|
|
53650
|
-
return
|
|
54058
|
+
return path53.join(this.dataDir, FILE_NAME2);
|
|
53651
54059
|
}
|
|
53652
54060
|
/** 读缓存的公钥;无缓存 / 损坏 → null(调用方决定是否触发拉取) */
|
|
53653
54061
|
read() {
|
|
53654
54062
|
try {
|
|
53655
|
-
const raw =
|
|
54063
|
+
const raw = fs52.readFileSync(this.filePath(), "utf8");
|
|
53656
54064
|
const parsed = JSON.parse(raw);
|
|
53657
54065
|
if (typeof parsed.publicKeyPem === "string" && parsed.publicKeyPem.includes("PUBLIC KEY")) {
|
|
53658
54066
|
return parsed.publicKeyPem;
|
|
@@ -53667,12 +54075,12 @@ var ServerKeyStore = class {
|
|
|
53667
54075
|
publicKeyPem,
|
|
53668
54076
|
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
53669
54077
|
};
|
|
53670
|
-
|
|
53671
|
-
|
|
54078
|
+
fs52.mkdirSync(this.dataDir, { recursive: true });
|
|
54079
|
+
fs52.writeFileSync(this.filePath(), JSON.stringify(content, null, 2), { mode: 384 });
|
|
53672
54080
|
}
|
|
53673
54081
|
clear() {
|
|
53674
54082
|
try {
|
|
53675
|
-
|
|
54083
|
+
fs52.unlinkSync(this.filePath());
|
|
53676
54084
|
} catch {
|
|
53677
54085
|
}
|
|
53678
54086
|
}
|
|
@@ -53685,12 +54093,12 @@ init_protocol();
|
|
|
53685
54093
|
init_protocol();
|
|
53686
54094
|
|
|
53687
54095
|
// src/session/fork.ts
|
|
53688
|
-
var
|
|
54096
|
+
var import_node_fs43 = __toESM(require("fs"), 1);
|
|
53689
54097
|
var import_node_os16 = __toESM(require("os"), 1);
|
|
53690
|
-
var
|
|
54098
|
+
var import_node_path45 = __toESM(require("path"), 1);
|
|
53691
54099
|
init_claude_history();
|
|
53692
54100
|
function readJsonlEntries(file) {
|
|
53693
|
-
const raw =
|
|
54101
|
+
const raw = import_node_fs43.default.readFileSync(file, "utf8");
|
|
53694
54102
|
const out = [];
|
|
53695
54103
|
for (const line of raw.split("\n")) {
|
|
53696
54104
|
const t = line.trim();
|
|
@@ -53703,10 +54111,10 @@ function readJsonlEntries(file) {
|
|
|
53703
54111
|
return out;
|
|
53704
54112
|
}
|
|
53705
54113
|
function forkSession(input) {
|
|
53706
|
-
const baseDir = input.baseDir ??
|
|
53707
|
-
const projectDir =
|
|
53708
|
-
const sourceFile =
|
|
53709
|
-
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)) {
|
|
53710
54118
|
throw new Error(`fork: source transcript not found: ${sourceFile}`);
|
|
53711
54119
|
}
|
|
53712
54120
|
const entries = readJsonlEntries(sourceFile);
|
|
@@ -53736,9 +54144,9 @@ function forkSession(input) {
|
|
|
53736
54144
|
}
|
|
53737
54145
|
forkedLines.push(JSON.stringify(forked));
|
|
53738
54146
|
}
|
|
53739
|
-
const forkedFilePath =
|
|
53740
|
-
|
|
53741
|
-
|
|
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 });
|
|
53742
54150
|
return { forkedToolSessionId, forkedFilePath };
|
|
53743
54151
|
}
|
|
53744
54152
|
|
|
@@ -54090,7 +54498,7 @@ function buildPermissionHandlers(deps) {
|
|
|
54090
54498
|
}
|
|
54091
54499
|
|
|
54092
54500
|
// src/handlers/history.ts
|
|
54093
|
-
var
|
|
54501
|
+
var path56 = __toESM(require("path"), 1);
|
|
54094
54502
|
init_protocol();
|
|
54095
54503
|
|
|
54096
54504
|
// src/session/recent-dirs.ts
|
|
@@ -54108,7 +54516,7 @@ function listRecentDirs(store, limit = 50) {
|
|
|
54108
54516
|
}
|
|
54109
54517
|
|
|
54110
54518
|
// src/permission/persona-paths.ts
|
|
54111
|
-
var
|
|
54519
|
+
var path55 = __toESM(require("path"), 1);
|
|
54112
54520
|
function getAllowedPersonaIds(grants, action) {
|
|
54113
54521
|
const ids = /* @__PURE__ */ new Set();
|
|
54114
54522
|
for (const g2 of grants) {
|
|
@@ -54121,42 +54529,42 @@ function getAllowedPersonaIds(grants, action) {
|
|
|
54121
54529
|
return ids;
|
|
54122
54530
|
}
|
|
54123
54531
|
function isGuestPathAllowed(grants, absPath, personaRoot, action = "read", userWorkDir) {
|
|
54124
|
-
const target =
|
|
54532
|
+
const target = path55.resolve(absPath);
|
|
54125
54533
|
if (userWorkDir) {
|
|
54126
|
-
const u =
|
|
54127
|
-
const usep = u.endsWith(
|
|
54534
|
+
const u = path55.resolve(userWorkDir);
|
|
54535
|
+
const usep = u.endsWith(path55.sep) ? "" : path55.sep;
|
|
54128
54536
|
if (target === u || target.startsWith(u + usep)) return true;
|
|
54129
54537
|
}
|
|
54130
|
-
const root =
|
|
54131
|
-
const sep3 = root.endsWith(
|
|
54538
|
+
const root = path55.resolve(personaRoot);
|
|
54539
|
+
const sep3 = root.endsWith(path55.sep) ? "" : path55.sep;
|
|
54132
54540
|
if (!target.startsWith(root + sep3)) return false;
|
|
54133
|
-
const rel =
|
|
54541
|
+
const rel = path55.relative(root, target);
|
|
54134
54542
|
if (!rel || rel.startsWith("..")) return false;
|
|
54135
|
-
const personaId = rel.split(
|
|
54543
|
+
const personaId = rel.split(path55.sep)[0];
|
|
54136
54544
|
if (!personaId) return false;
|
|
54137
54545
|
const allowed = getAllowedPersonaIds(grants, action);
|
|
54138
54546
|
if (allowed === "*") return true;
|
|
54139
54547
|
return allowed.has(personaId);
|
|
54140
54548
|
}
|
|
54141
54549
|
function personaIdFromPath(absPath, personaRoot) {
|
|
54142
|
-
const root =
|
|
54143
|
-
const target =
|
|
54144
|
-
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;
|
|
54145
54553
|
if (!target.startsWith(root + sep3)) return null;
|
|
54146
|
-
const rel =
|
|
54554
|
+
const rel = path55.relative(root, target);
|
|
54147
54555
|
if (!rel || rel.startsWith("..")) return null;
|
|
54148
|
-
const id = rel.split(
|
|
54556
|
+
const id = rel.split(path55.sep)[0];
|
|
54149
54557
|
return id || null;
|
|
54150
54558
|
}
|
|
54151
54559
|
function isPathWithin(dir, absPath) {
|
|
54152
|
-
const d =
|
|
54153
|
-
const t =
|
|
54154
|
-
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;
|
|
54155
54563
|
return t === d || t.startsWith(d + sep3);
|
|
54156
54564
|
}
|
|
54157
54565
|
function isPathInGuestBoundary(personaRoot, personaId, userWorkDir, absPath) {
|
|
54158
54566
|
if (userWorkDir && isPathWithin(userWorkDir, absPath)) return true;
|
|
54159
|
-
return personaIdFromPath(
|
|
54567
|
+
return personaIdFromPath(path55.resolve(absPath), personaRoot) === personaId;
|
|
54160
54568
|
}
|
|
54161
54569
|
|
|
54162
54570
|
// src/handlers/history.ts
|
|
@@ -54182,7 +54590,7 @@ function buildHistoryHandlers(deps) {
|
|
|
54182
54590
|
if (!pid) return false;
|
|
54183
54591
|
return isGuestPathAllowed(
|
|
54184
54592
|
ctx.grants,
|
|
54185
|
-
|
|
54593
|
+
path56.join(personaRoot, pid),
|
|
54186
54594
|
personaRoot,
|
|
54187
54595
|
"read",
|
|
54188
54596
|
userWorkDir
|
|
@@ -54194,7 +54602,7 @@ function buildHistoryHandlers(deps) {
|
|
|
54194
54602
|
};
|
|
54195
54603
|
const list = async (frame, _client, ctx) => {
|
|
54196
54604
|
const args = HistoryListArgs.parse(frame);
|
|
54197
|
-
assertGuestPath(ctx,
|
|
54605
|
+
assertGuestPath(ctx, path56.resolve(args.projectPath), personaRoot, "history:list");
|
|
54198
54606
|
const sessions = await history.listSessions(args);
|
|
54199
54607
|
return { response: { type: "history:list", sessions } };
|
|
54200
54608
|
};
|
|
@@ -54226,13 +54634,13 @@ function buildHistoryHandlers(deps) {
|
|
|
54226
54634
|
};
|
|
54227
54635
|
const subagents = async (frame, _client, ctx) => {
|
|
54228
54636
|
const args = HistorySubagentsArgs.parse(frame);
|
|
54229
|
-
assertGuestPath(ctx,
|
|
54637
|
+
assertGuestPath(ctx, path56.resolve(args.cwd), personaRoot, "history:subagents", usersRoot);
|
|
54230
54638
|
const subs = await history.listSubagents(args);
|
|
54231
54639
|
return { response: { type: "history:subagents", subagents: subs } };
|
|
54232
54640
|
};
|
|
54233
54641
|
const subagentRead = async (frame, _client, ctx) => {
|
|
54234
54642
|
const args = HistorySubagentReadArgs.parse(frame);
|
|
54235
|
-
assertGuestPath(ctx,
|
|
54643
|
+
assertGuestPath(ctx, path56.resolve(args.cwd), personaRoot, "history:subagent-read", usersRoot);
|
|
54236
54644
|
const res = await history.readSubagent(args);
|
|
54237
54645
|
return { response: { type: "history:subagent-read", ...res } };
|
|
54238
54646
|
};
|
|
@@ -54241,7 +54649,7 @@ function buildHistoryHandlers(deps) {
|
|
|
54241
54649
|
if (ctx?.principal.kind === "guest" && personaRoot) {
|
|
54242
54650
|
const userWorkDir = usersRoot ? deriveUserWorkDir(ctx.principal.id, usersRoot) : void 0;
|
|
54243
54651
|
const filtered = dirs.filter(
|
|
54244
|
-
(d) => isGuestPathAllowed(ctx.grants,
|
|
54652
|
+
(d) => isGuestPathAllowed(ctx.grants, path56.resolve(d.cwd), personaRoot, "read", userWorkDir)
|
|
54245
54653
|
);
|
|
54246
54654
|
return { response: { type: "history:recentDirs", dirs: filtered } };
|
|
54247
54655
|
}
|
|
@@ -54258,7 +54666,7 @@ function buildHistoryHandlers(deps) {
|
|
|
54258
54666
|
}
|
|
54259
54667
|
|
|
54260
54668
|
// src/handlers/workspace.ts
|
|
54261
|
-
var
|
|
54669
|
+
var path57 = __toESM(require("path"), 1);
|
|
54262
54670
|
var os16 = __toESM(require("os"), 1);
|
|
54263
54671
|
init_protocol();
|
|
54264
54672
|
init_protocol();
|
|
@@ -54300,22 +54708,22 @@ function buildWorkspaceHandlers(deps) {
|
|
|
54300
54708
|
const args = WorkspaceListArgs.parse(frame);
|
|
54301
54709
|
const isGuest = ctx?.principal.kind === "guest";
|
|
54302
54710
|
const fallbackCwd = isGuest && personaRoot ? personaRoot : os16.homedir();
|
|
54303
|
-
const resolvedCwd =
|
|
54304
|
-
const target = args.path ?
|
|
54711
|
+
const resolvedCwd = path57.resolve(args.cwd ?? fallbackCwd);
|
|
54712
|
+
const target = args.path ? path57.resolve(resolvedCwd, args.path) : resolvedCwd;
|
|
54305
54713
|
assertGuestPath2(ctx, target, personaRoot, "workspace:list", usersRoot);
|
|
54306
54714
|
const res = workspace.list({ ...args, cwd: resolvedCwd });
|
|
54307
54715
|
return { response: { type: "workspace:list", ...res } };
|
|
54308
54716
|
};
|
|
54309
54717
|
const read = async (frame, _client, ctx) => {
|
|
54310
54718
|
const args = WorkspaceReadArgs.parse(frame);
|
|
54311
|
-
const target =
|
|
54719
|
+
const target = path57.isAbsolute(args.path) ? path57.resolve(args.path) : path57.resolve(args.cwd, args.path);
|
|
54312
54720
|
assertGuestPath2(ctx, target, personaRoot, "workspace:read", usersRoot);
|
|
54313
54721
|
const res = workspace.read(args);
|
|
54314
54722
|
return { response: { type: "workspace:read", ...res } };
|
|
54315
54723
|
};
|
|
54316
54724
|
const skillsList = async (frame, _client, ctx) => {
|
|
54317
54725
|
const args = SkillsListArgs.parse(frame);
|
|
54318
|
-
const cwdAbs =
|
|
54726
|
+
const cwdAbs = path57.resolve(args.cwd);
|
|
54319
54727
|
assertGuestPath2(ctx, cwdAbs, personaRoot, "skills:list", usersRoot);
|
|
54320
54728
|
const list2 = await getSkillsForTool(args.tool ?? "claude", cwdAbs);
|
|
54321
54729
|
if (ctx?.principal.kind === "guest" && personaRoot) {
|
|
@@ -54327,7 +54735,7 @@ function buildWorkspaceHandlers(deps) {
|
|
|
54327
54735
|
};
|
|
54328
54736
|
const agentsList = async (frame, _client, ctx) => {
|
|
54329
54737
|
const args = AgentsListArgs.parse(frame);
|
|
54330
|
-
const cwdAbs =
|
|
54738
|
+
const cwdAbs = path57.resolve(args.cwd);
|
|
54331
54739
|
assertGuestPath2(ctx, cwdAbs, personaRoot, "agents:list", usersRoot);
|
|
54332
54740
|
if (args.tool === "codex") {
|
|
54333
54741
|
return { response: { type: "agents:list", agents: [] } };
|
|
@@ -54349,20 +54757,20 @@ function buildWorkspaceHandlers(deps) {
|
|
|
54349
54757
|
}
|
|
54350
54758
|
|
|
54351
54759
|
// src/handlers/git.ts
|
|
54352
|
-
var
|
|
54760
|
+
var path59 = __toESM(require("path"), 1);
|
|
54353
54761
|
init_protocol();
|
|
54354
54762
|
init_protocol();
|
|
54355
54763
|
|
|
54356
54764
|
// src/workspace/git.ts
|
|
54357
54765
|
var import_node_child_process12 = require("child_process");
|
|
54358
|
-
var
|
|
54359
|
-
var
|
|
54766
|
+
var import_node_fs44 = __toESM(require("fs"), 1);
|
|
54767
|
+
var import_node_path46 = __toESM(require("path"), 1);
|
|
54360
54768
|
var import_node_util = require("util");
|
|
54361
54769
|
var pexec = (0, import_node_util.promisify)(import_node_child_process12.execFile);
|
|
54362
54770
|
function normalizePath(p2) {
|
|
54363
|
-
const resolved =
|
|
54771
|
+
const resolved = import_node_path46.default.resolve(p2);
|
|
54364
54772
|
try {
|
|
54365
|
-
return
|
|
54773
|
+
return import_node_fs44.default.realpathSync(resolved);
|
|
54366
54774
|
} catch {
|
|
54367
54775
|
return resolved;
|
|
54368
54776
|
}
|
|
@@ -54436,7 +54844,7 @@ async function listGitBranches(cwd) {
|
|
|
54436
54844
|
function assertGuestCwd(ctx, cwd, personaRoot, method, usersRoot) {
|
|
54437
54845
|
if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
|
|
54438
54846
|
const userWorkDir = usersRoot ? deriveUserWorkDir(ctx.principal.id, usersRoot) : void 0;
|
|
54439
|
-
if (!isGuestPathAllowed(ctx.grants,
|
|
54847
|
+
if (!isGuestPathAllowed(ctx.grants, path59.resolve(cwd), personaRoot, "read", userWorkDir)) {
|
|
54440
54848
|
throw new ClawdError(
|
|
54441
54849
|
ERROR_CODES.UNAUTHORIZED,
|
|
54442
54850
|
`guest ${ctx.principal.id} cannot ${method} cwd ${cwd}`
|
|
@@ -54486,6 +54894,7 @@ function buildCapabilitiesHandlers(deps) {
|
|
|
54486
54894
|
|
|
54487
54895
|
// src/handlers/capability.ts
|
|
54488
54896
|
init_zod();
|
|
54897
|
+
init_src();
|
|
54489
54898
|
init_protocol();
|
|
54490
54899
|
var DeleteArgsSchema = external_exports.object({
|
|
54491
54900
|
capabilityId: external_exports.string().min(1)
|
|
@@ -54524,6 +54933,7 @@ function buildCapabilityHandlers(deps) {
|
|
|
54524
54933
|
}
|
|
54525
54934
|
|
|
54526
54935
|
// src/handlers/inbox.ts
|
|
54936
|
+
init_src();
|
|
54527
54937
|
init_protocol();
|
|
54528
54938
|
function resolvePeerDeviceId(ctx, argsPeerDeviceId) {
|
|
54529
54939
|
if (ctx.principal.kind === "owner") {
|
|
@@ -54702,6 +55112,7 @@ function buildInboxHandlers(deps) {
|
|
|
54702
55112
|
}
|
|
54703
55113
|
|
|
54704
55114
|
// src/handlers/contact.ts
|
|
55115
|
+
init_src();
|
|
54705
55116
|
init_protocol();
|
|
54706
55117
|
function ensureOwner(ctx) {
|
|
54707
55118
|
if (!ctx || ctx.principal.kind !== "owner") {
|
|
@@ -54770,25 +55181,26 @@ function buildContactHandlers(deps) {
|
|
|
54770
55181
|
}
|
|
54771
55182
|
|
|
54772
55183
|
// src/handlers/contact-ssh.ts
|
|
55184
|
+
init_src();
|
|
54773
55185
|
init_protocol();
|
|
54774
55186
|
|
|
54775
55187
|
// src/sshd/key-issue.ts
|
|
54776
|
-
var
|
|
54777
|
-
var
|
|
55188
|
+
var import_node_fs45 = __toESM(require("fs"), 1);
|
|
55189
|
+
var import_node_path47 = __toESM(require("path"), 1);
|
|
54778
55190
|
var import_node_child_process13 = require("child_process");
|
|
54779
55191
|
function safeDeviceId(deviceId) {
|
|
54780
55192
|
return deviceId.replace(/[\/\\]/g, "_");
|
|
54781
55193
|
}
|
|
54782
55194
|
async function issueContactSshKey(deviceId, sshdDir, opts = {}) {
|
|
54783
55195
|
const safeId = safeDeviceId(deviceId);
|
|
54784
|
-
const keysDir =
|
|
54785
|
-
|
|
54786
|
-
const privPath =
|
|
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`);
|
|
54787
55199
|
const pubPath = `${privPath}.pub`;
|
|
54788
|
-
if (
|
|
55200
|
+
if (import_node_fs45.default.existsSync(privPath) && import_node_fs45.default.existsSync(pubPath)) {
|
|
54789
55201
|
return {
|
|
54790
|
-
privateKeyPem:
|
|
54791
|
-
publicKeyLine:
|
|
55202
|
+
privateKeyPem: import_node_fs45.default.readFileSync(privPath, "utf8"),
|
|
55203
|
+
publicKeyLine: import_node_fs45.default.readFileSync(pubPath, "utf8").trim()
|
|
54792
55204
|
};
|
|
54793
55205
|
}
|
|
54794
55206
|
const bin = opts.keygenBin ?? "/usr/bin/ssh-keygen";
|
|
@@ -54802,16 +55214,16 @@ async function issueContactSshKey(deviceId, sshdDir, opts = {}) {
|
|
|
54802
55214
|
p2.on("error", reject);
|
|
54803
55215
|
});
|
|
54804
55216
|
try {
|
|
54805
|
-
|
|
55217
|
+
import_node_fs45.default.chmodSync(privPath, 384);
|
|
54806
55218
|
} catch {
|
|
54807
55219
|
}
|
|
54808
55220
|
try {
|
|
54809
|
-
|
|
55221
|
+
import_node_fs45.default.chmodSync(pubPath, 420);
|
|
54810
55222
|
} catch {
|
|
54811
55223
|
}
|
|
54812
55224
|
return {
|
|
54813
|
-
privateKeyPem:
|
|
54814
|
-
publicKeyLine:
|
|
55225
|
+
privateKeyPem: import_node_fs45.default.readFileSync(privPath, "utf8"),
|
|
55226
|
+
publicKeyLine: import_node_fs45.default.readFileSync(pubPath, "utf8").trim()
|
|
54815
55227
|
};
|
|
54816
55228
|
}
|
|
54817
55229
|
|
|
@@ -54834,6 +55246,7 @@ function ensureGuest(ctx) {
|
|
|
54834
55246
|
return ctx.principal.id;
|
|
54835
55247
|
}
|
|
54836
55248
|
function buildContactSshHandlers(deps) {
|
|
55249
|
+
const sshLog = deps.sshLog ?? nullContactSshLog;
|
|
54837
55250
|
const setSshAccess = async (frame, _client, ctx) => {
|
|
54838
55251
|
ensureOwner2(ctx);
|
|
54839
55252
|
const { type: _t, requestId: _r, ...rest } = frame;
|
|
@@ -54843,12 +55256,20 @@ function buildContactSshHandlers(deps) {
|
|
|
54843
55256
|
exposedDirs: args.exposedDirs
|
|
54844
55257
|
});
|
|
54845
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
|
+
});
|
|
54846
55262
|
throw new ClawdError(
|
|
54847
55263
|
ERROR_CODES.CONTACT_NOT_FOUND,
|
|
54848
55264
|
`CONTACT_NOT_FOUND: contact ${args.deviceId} not in store`
|
|
54849
55265
|
);
|
|
54850
55266
|
}
|
|
54851
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
|
+
});
|
|
54852
55273
|
deps.broadcast({
|
|
54853
55274
|
type: "contact:ssh-access-updated",
|
|
54854
55275
|
deviceId: args.deviceId,
|
|
@@ -54870,6 +55291,14 @@ function buildContactSshHandlers(deps) {
|
|
|
54870
55291
|
ContactSshKeyIssueArgsSchema.parse(rest);
|
|
54871
55292
|
const contact = deps.store.get(callerDeviceId);
|
|
54872
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
|
+
);
|
|
54873
55302
|
throw new ClawdError(
|
|
54874
55303
|
ERROR_CODES.UNAUTHORIZED,
|
|
54875
55304
|
`UNAUTHORIZED: contact ${callerDeviceId} not authorized for SSH`
|
|
@@ -54877,6 +55306,15 @@ function buildContactSshHandlers(deps) {
|
|
|
54877
55306
|
}
|
|
54878
55307
|
const { privateKeyPem, publicKeyLine } = await issueContactSshKey(callerDeviceId, deps.sshdDir);
|
|
54879
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
|
+
);
|
|
54880
55318
|
return {
|
|
54881
55319
|
response: {
|
|
54882
55320
|
type: "contact:sshKey:issue:ok",
|
|
@@ -54892,6 +55330,7 @@ function buildContactSshHandlers(deps) {
|
|
|
54892
55330
|
}
|
|
54893
55331
|
|
|
54894
55332
|
// src/handlers/whoami.ts
|
|
55333
|
+
init_src();
|
|
54895
55334
|
init_protocol();
|
|
54896
55335
|
function buildWhoamiHandler(deps) {
|
|
54897
55336
|
return async (_frame, _client, ctx) => {
|
|
@@ -54985,6 +55424,7 @@ function buildFeishuAuthHandlers(deps) {
|
|
|
54985
55424
|
}
|
|
54986
55425
|
|
|
54987
55426
|
// src/handlers/device.ts
|
|
55427
|
+
init_src();
|
|
54988
55428
|
init_protocol();
|
|
54989
55429
|
function ensureOwner3(ctx) {
|
|
54990
55430
|
if (!ctx || ctx.principal.kind !== "owner") {
|
|
@@ -55202,7 +55642,7 @@ function buildPersonaHandlers(deps) {
|
|
|
55202
55642
|
}
|
|
55203
55643
|
|
|
55204
55644
|
// src/handlers/attachment.ts
|
|
55205
|
-
var
|
|
55645
|
+
var import_node_path48 = __toESM(require("path"), 1);
|
|
55206
55646
|
init_protocol();
|
|
55207
55647
|
init_protocol();
|
|
55208
55648
|
var DEFAULT_TTL_SECONDS = 24 * 3600;
|
|
@@ -55282,12 +55722,12 @@ function buildAttachmentHandlers(deps) {
|
|
|
55282
55722
|
`session ${args.sessionId} scope unresolved`
|
|
55283
55723
|
);
|
|
55284
55724
|
}
|
|
55285
|
-
const cwdAbs =
|
|
55286
|
-
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);
|
|
55287
55727
|
guardAttachmentPath(ctx, args.sessionId, candidateAbs, "attachment.signUrl", "group-acl");
|
|
55288
55728
|
const entries = deps.groupFileStore.list(scope, args.sessionId);
|
|
55289
55729
|
const entry = entries.find((e) => {
|
|
55290
|
-
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);
|
|
55291
55731
|
return storedAbs === candidateAbs && !e.stale;
|
|
55292
55732
|
});
|
|
55293
55733
|
if (!entry) {
|
|
@@ -55312,7 +55752,7 @@ function buildAttachmentHandlers(deps) {
|
|
|
55312
55752
|
if (!ctx || ctx.principal.kind !== "guest" || !deps.personaRoot || !deps.sessionStore) return;
|
|
55313
55753
|
const f = deps.sessionStore.read(sessionId);
|
|
55314
55754
|
if (!f) return;
|
|
55315
|
-
assertGuestAttachmentPath(ctx,
|
|
55755
|
+
assertGuestAttachmentPath(ctx, import_node_path48.default.resolve(f.cwd), deps.personaRoot, method, deps.usersRoot);
|
|
55316
55756
|
}
|
|
55317
55757
|
const groupAdd = async (frame, _client, ctx) => {
|
|
55318
55758
|
if (!deps.groupFileStore || !deps.getSessionScope) {
|
|
@@ -55327,8 +55767,8 @@ function buildAttachmentHandlers(deps) {
|
|
|
55327
55767
|
if (!scope) {
|
|
55328
55768
|
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, `session ${args.sessionId} not found`);
|
|
55329
55769
|
}
|
|
55330
|
-
const cwdAbs =
|
|
55331
|
-
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);
|
|
55332
55772
|
guardAttachmentPath(ctx, args.sessionId, candidateAbs, "attachment.groupAdd", "cwd-subtree");
|
|
55333
55773
|
const from = ctx?.principal.kind === "owner" ? "owner" : "agent";
|
|
55334
55774
|
const size = 0;
|
|
@@ -55387,19 +55827,20 @@ function buildAttachmentHandlers(deps) {
|
|
|
55387
55827
|
|
|
55388
55828
|
// src/handlers/extension.ts
|
|
55389
55829
|
var import_promises8 = __toESM(require("fs/promises"), 1);
|
|
55390
|
-
var
|
|
55830
|
+
var import_node_path53 = __toESM(require("path"), 1);
|
|
55391
55831
|
init_protocol();
|
|
55832
|
+
init_src();
|
|
55392
55833
|
|
|
55393
55834
|
// src/extension/bundle-zip.ts
|
|
55394
55835
|
var import_promises5 = __toESM(require("fs/promises"), 1);
|
|
55395
|
-
var
|
|
55396
|
-
var
|
|
55836
|
+
var import_node_path49 = __toESM(require("path"), 1);
|
|
55837
|
+
var import_node_crypto13 = __toESM(require("crypto"), 1);
|
|
55397
55838
|
var import_jszip2 = __toESM(require_lib3(), 1);
|
|
55398
55839
|
async function bundleExtensionDir(dir) {
|
|
55399
55840
|
const entries = await listFilesSorted(dir);
|
|
55400
55841
|
const zip = new import_jszip2.default();
|
|
55401
55842
|
for (const rel of entries) {
|
|
55402
|
-
const abs =
|
|
55843
|
+
const abs = import_node_path49.default.join(dir, rel);
|
|
55403
55844
|
const content = await import_promises5.default.readFile(abs);
|
|
55404
55845
|
zip.file(rel, content, { date: FIXED_DATE });
|
|
55405
55846
|
}
|
|
@@ -55408,7 +55849,7 @@ async function bundleExtensionDir(dir) {
|
|
|
55408
55849
|
compression: "DEFLATE",
|
|
55409
55850
|
compressionOptions: { level: 6 }
|
|
55410
55851
|
});
|
|
55411
|
-
const sha256 =
|
|
55852
|
+
const sha256 = import_node_crypto13.default.createHash("sha256").update(buffer).digest("hex");
|
|
55412
55853
|
return { buffer, sha256 };
|
|
55413
55854
|
}
|
|
55414
55855
|
var FIXED_DATE = /* @__PURE__ */ new Date("2020-01-01T00:00:00.000Z");
|
|
@@ -55420,7 +55861,7 @@ async function listFilesSorted(rootDir) {
|
|
|
55420
55861
|
return out;
|
|
55421
55862
|
}
|
|
55422
55863
|
async function walk(absRoot, relPrefix, out) {
|
|
55423
|
-
const dirAbs =
|
|
55864
|
+
const dirAbs = import_node_path49.default.join(absRoot, relPrefix);
|
|
55424
55865
|
const entries = await import_promises5.default.readdir(dirAbs, { withFileTypes: true });
|
|
55425
55866
|
for (const e of entries) {
|
|
55426
55867
|
if (IGNORE_BASENAMES.has(e.name)) continue;
|
|
@@ -55434,6 +55875,8 @@ async function walk(absRoot, relPrefix, out) {
|
|
|
55434
55875
|
}
|
|
55435
55876
|
|
|
55436
55877
|
// src/extension/publish-check.ts
|
|
55878
|
+
init_src();
|
|
55879
|
+
init_src();
|
|
55437
55880
|
function computePublishCheck(args) {
|
|
55438
55881
|
const { localHash, localVersion, head } = args;
|
|
55439
55882
|
if (head === null) {
|
|
@@ -55474,25 +55917,27 @@ function computePublishCheck(args) {
|
|
|
55474
55917
|
|
|
55475
55918
|
// src/extension/install-flow.ts
|
|
55476
55919
|
var import_promises6 = __toESM(require("fs/promises"), 1);
|
|
55477
|
-
var
|
|
55920
|
+
var import_node_path51 = __toESM(require("path"), 1);
|
|
55478
55921
|
var import_node_os19 = __toESM(require("os"), 1);
|
|
55479
|
-
var
|
|
55922
|
+
var import_node_crypto14 = __toESM(require("crypto"), 1);
|
|
55480
55923
|
var import_jszip3 = __toESM(require_lib3(), 1);
|
|
55924
|
+
init_src();
|
|
55481
55925
|
|
|
55482
55926
|
// src/extension/paths.ts
|
|
55483
55927
|
var import_node_os18 = __toESM(require("os"), 1);
|
|
55484
|
-
var
|
|
55928
|
+
var import_node_path50 = __toESM(require("path"), 1);
|
|
55929
|
+
init_src();
|
|
55485
55930
|
function clawdHomeRoot(override) {
|
|
55486
|
-
return override ?? process.env.CLAWD_HOME ??
|
|
55931
|
+
return override ?? process.env.CLAWD_HOME ?? import_node_path50.default.join(import_node_os18.default.homedir(), ".clawd");
|
|
55487
55932
|
}
|
|
55488
55933
|
function extensionsRoot(override) {
|
|
55489
|
-
return
|
|
55934
|
+
return import_node_path50.default.join(clawdHomeRoot(override), "extensions");
|
|
55490
55935
|
}
|
|
55491
55936
|
function publishedChannelsFile(override) {
|
|
55492
|
-
return
|
|
55937
|
+
return import_node_path50.default.join(clawdHomeRoot(override), "extensions-published.json");
|
|
55493
55938
|
}
|
|
55494
55939
|
function bundleCacheRoot(override) {
|
|
55495
|
-
return
|
|
55940
|
+
return import_node_path50.default.join(clawdHomeRoot(override), "extension-bundles");
|
|
55496
55941
|
}
|
|
55497
55942
|
|
|
55498
55943
|
// src/extension/install-flow.ts
|
|
@@ -55505,7 +55950,7 @@ var InstallError = class extends Error {
|
|
|
55505
55950
|
};
|
|
55506
55951
|
async function installFromChannel(args, deps) {
|
|
55507
55952
|
const { channelRef, snapshotHash, bundleZip } = args;
|
|
55508
|
-
const computed =
|
|
55953
|
+
const computed = import_node_crypto14.default.createHash("sha256").update(bundleZip).digest("hex");
|
|
55509
55954
|
if (computed !== snapshotHash) {
|
|
55510
55955
|
throw new InstallError(
|
|
55511
55956
|
"HASH_MISMATCH",
|
|
@@ -55519,7 +55964,7 @@ async function installFromChannel(args, deps) {
|
|
|
55519
55964
|
throw new InstallError("ZIP_INVALID", `failed to load zip: ${e.message}`);
|
|
55520
55965
|
}
|
|
55521
55966
|
for (const name of Object.keys(zip.files)) {
|
|
55522
|
-
if (name.includes("..") || name.startsWith("/") ||
|
|
55967
|
+
if (name.includes("..") || name.startsWith("/") || import_node_path51.default.isAbsolute(name)) {
|
|
55523
55968
|
throw new InstallError("ZIP_INVALID", `unsafe zip entry: ${name}`);
|
|
55524
55969
|
}
|
|
55525
55970
|
}
|
|
@@ -55551,7 +55996,7 @@ async function installFromChannel(args, deps) {
|
|
|
55551
55996
|
);
|
|
55552
55997
|
}
|
|
55553
55998
|
const localExtId = namespacedExtId(ownerSlug, channelRef.ownerPrincipalId);
|
|
55554
|
-
const destDir =
|
|
55999
|
+
const destDir = import_node_path51.default.join(deps.extensionsRoot, localExtId);
|
|
55555
56000
|
let destExists = false;
|
|
55556
56001
|
try {
|
|
55557
56002
|
await import_promises6.default.access(destDir);
|
|
@@ -55565,16 +56010,16 @@ async function installFromChannel(args, deps) {
|
|
|
55565
56010
|
);
|
|
55566
56011
|
}
|
|
55567
56012
|
const stage = await import_promises6.default.mkdtemp(
|
|
55568
|
-
|
|
56013
|
+
import_node_path51.default.join(import_node_os19.default.tmpdir(), `clawd-ext-install-${localExtId}-`)
|
|
55569
56014
|
);
|
|
55570
56015
|
try {
|
|
55571
56016
|
for (const [name, entry] of Object.entries(zip.files)) {
|
|
55572
|
-
const dest =
|
|
56017
|
+
const dest = import_node_path51.default.join(stage, name);
|
|
55573
56018
|
if (entry.dir) {
|
|
55574
56019
|
await import_promises6.default.mkdir(dest, { recursive: true });
|
|
55575
56020
|
continue;
|
|
55576
56021
|
}
|
|
55577
|
-
await import_promises6.default.mkdir(
|
|
56022
|
+
await import_promises6.default.mkdir(import_node_path51.default.dirname(dest), { recursive: true });
|
|
55578
56023
|
if (name === "manifest.json") {
|
|
55579
56024
|
const rewritten = { ...parsed.data, id: localExtId };
|
|
55580
56025
|
await import_promises6.default.writeFile(dest, JSON.stringify(rewritten, null, 2));
|
|
@@ -55595,10 +56040,11 @@ async function installFromChannel(args, deps) {
|
|
|
55595
56040
|
|
|
55596
56041
|
// src/extension/update-flow.ts
|
|
55597
56042
|
var import_promises7 = __toESM(require("fs/promises"), 1);
|
|
55598
|
-
var
|
|
56043
|
+
var import_node_path52 = __toESM(require("path"), 1);
|
|
55599
56044
|
var import_node_os20 = __toESM(require("os"), 1);
|
|
55600
|
-
var
|
|
56045
|
+
var import_node_crypto15 = __toESM(require("crypto"), 1);
|
|
55601
56046
|
var import_jszip4 = __toESM(require_lib3(), 1);
|
|
56047
|
+
init_src();
|
|
55602
56048
|
var UpdateError = class extends Error {
|
|
55603
56049
|
constructor(code, message) {
|
|
55604
56050
|
super(message);
|
|
@@ -55612,11 +56058,11 @@ async function updateFromChannel(args, deps) {
|
|
|
55612
56058
|
channelRef.extId,
|
|
55613
56059
|
channelRef.ownerPrincipalId
|
|
55614
56060
|
);
|
|
55615
|
-
const liveDir =
|
|
56061
|
+
const liveDir = import_node_path52.default.join(deps.extensionsRoot, localExtId);
|
|
55616
56062
|
const prevDir = `${liveDir}.prev`;
|
|
55617
56063
|
let existingVersion;
|
|
55618
56064
|
try {
|
|
55619
|
-
const raw = await import_promises7.default.readFile(
|
|
56065
|
+
const raw = await import_promises7.default.readFile(import_node_path52.default.join(liveDir, "manifest.json"), "utf8");
|
|
55620
56066
|
const parsed2 = ExtensionManifestSchema.safeParse(JSON.parse(raw));
|
|
55621
56067
|
if (!parsed2.success) {
|
|
55622
56068
|
throw new UpdateError(
|
|
@@ -55635,7 +56081,7 @@ async function updateFromChannel(args, deps) {
|
|
|
55635
56081
|
if (e instanceof UpdateError) throw e;
|
|
55636
56082
|
throw e;
|
|
55637
56083
|
}
|
|
55638
|
-
const computed =
|
|
56084
|
+
const computed = import_node_crypto15.default.createHash("sha256").update(bundleZip).digest("hex");
|
|
55639
56085
|
if (computed !== snapshotHash) {
|
|
55640
56086
|
throw new UpdateError(
|
|
55641
56087
|
"HASH_MISMATCH",
|
|
@@ -55649,7 +56095,7 @@ async function updateFromChannel(args, deps) {
|
|
|
55649
56095
|
throw new UpdateError("ZIP_INVALID", `failed to load zip: ${e.message}`);
|
|
55650
56096
|
}
|
|
55651
56097
|
for (const name of Object.keys(zip.files)) {
|
|
55652
|
-
if (name.includes("..") || name.startsWith("/") ||
|
|
56098
|
+
if (name.includes("..") || name.startsWith("/") || import_node_path52.default.isAbsolute(name)) {
|
|
55653
56099
|
throw new UpdateError("ZIP_INVALID", `unsafe zip entry: ${name}`);
|
|
55654
56100
|
}
|
|
55655
56101
|
}
|
|
@@ -55684,16 +56130,16 @@ async function updateFromChannel(args, deps) {
|
|
|
55684
56130
|
await import_promises7.default.rm(prevDir, { recursive: true, force: true });
|
|
55685
56131
|
await import_promises7.default.rename(liveDir, prevDir);
|
|
55686
56132
|
const stage = await import_promises7.default.mkdtemp(
|
|
55687
|
-
|
|
56133
|
+
import_node_path52.default.join(import_node_os20.default.tmpdir(), `clawd-ext-update-${localExtId}-`)
|
|
55688
56134
|
);
|
|
55689
56135
|
try {
|
|
55690
56136
|
for (const [name, entry] of Object.entries(zip.files)) {
|
|
55691
|
-
const dest =
|
|
56137
|
+
const dest = import_node_path52.default.join(stage, name);
|
|
55692
56138
|
if (entry.dir) {
|
|
55693
56139
|
await import_promises7.default.mkdir(dest, { recursive: true });
|
|
55694
56140
|
continue;
|
|
55695
56141
|
}
|
|
55696
|
-
await import_promises7.default.mkdir(
|
|
56142
|
+
await import_promises7.default.mkdir(import_node_path52.default.dirname(dest), { recursive: true });
|
|
55697
56143
|
if (name === "manifest.json") {
|
|
55698
56144
|
const rewritten = { ...parsed.data, id: localExtId };
|
|
55699
56145
|
await import_promises7.default.writeFile(dest, JSON.stringify(rewritten, null, 2));
|
|
@@ -55744,6 +56190,7 @@ async function rollback(deps, localExtId, liveDir, prevDir) {
|
|
|
55744
56190
|
}
|
|
55745
56191
|
|
|
55746
56192
|
// src/handlers/extension.ts
|
|
56193
|
+
init_src();
|
|
55747
56194
|
function pickChannelRef(frame) {
|
|
55748
56195
|
const ref = frame.channelRef;
|
|
55749
56196
|
const parsed = ChannelRefSchema.safeParse(ref);
|
|
@@ -55786,7 +56233,7 @@ async function rewriteManifestVersion(root, extId, newVersion, previousPublished
|
|
|
55786
56233
|
);
|
|
55787
56234
|
}
|
|
55788
56235
|
}
|
|
55789
|
-
const manifestPath =
|
|
56236
|
+
const manifestPath = import_node_path53.default.join(root, extId, "manifest.json");
|
|
55790
56237
|
const manifest = await readManifest(root, extId);
|
|
55791
56238
|
const next = { ...manifest, version: newVersion };
|
|
55792
56239
|
const tmp = `${manifestPath}.tmp`;
|
|
@@ -55794,7 +56241,7 @@ async function rewriteManifestVersion(root, extId, newVersion, previousPublished
|
|
|
55794
56241
|
await import_promises8.default.rename(tmp, manifestPath);
|
|
55795
56242
|
}
|
|
55796
56243
|
async function readManifest(root, extId) {
|
|
55797
|
-
const file =
|
|
56244
|
+
const file = import_node_path53.default.join(root, extId, "manifest.json");
|
|
55798
56245
|
let raw;
|
|
55799
56246
|
try {
|
|
55800
56247
|
raw = await import_promises8.default.readFile(file, "utf8");
|
|
@@ -55885,7 +56332,7 @@ function buildExtensionHandlers(deps) {
|
|
|
55885
56332
|
};
|
|
55886
56333
|
async function buildSnapshotMeta(extId) {
|
|
55887
56334
|
const manifest = await readManifest(deps.root, extId);
|
|
55888
|
-
const { sha256, buffer } = await bundleExtensionDir(
|
|
56335
|
+
const { sha256, buffer } = await bundleExtensionDir(import_node_path53.default.join(deps.root, extId));
|
|
55889
56336
|
return { manifest, contentHash: sha256, buffer };
|
|
55890
56337
|
}
|
|
55891
56338
|
const publish = async (frame, _client, ctx) => {
|
|
@@ -56066,9 +56513,9 @@ function buildExtensionHandlers(deps) {
|
|
|
56066
56513
|
}
|
|
56067
56514
|
|
|
56068
56515
|
// src/app-builder/project-store.ts
|
|
56069
|
-
var
|
|
56516
|
+
var import_node_fs46 = require("fs");
|
|
56070
56517
|
var import_node_child_process14 = require("child_process");
|
|
56071
|
-
var
|
|
56518
|
+
var import_node_path54 = require("path");
|
|
56072
56519
|
init_protocol();
|
|
56073
56520
|
var PROJECTS_DIR = "projects";
|
|
56074
56521
|
var META_FILE = ".clawd-project.json";
|
|
@@ -56082,19 +56529,19 @@ var ProjectStore = class {
|
|
|
56082
56529
|
root;
|
|
56083
56530
|
/** projects/<name>/.clawd-project.json 路径 */
|
|
56084
56531
|
metaPath(name) {
|
|
56085
|
-
return (0,
|
|
56532
|
+
return (0, import_node_path54.join)(this.projectsRoot(), name, META_FILE);
|
|
56086
56533
|
}
|
|
56087
56534
|
/** projects/<name>/ 目录路径(cwd 用) */
|
|
56088
56535
|
projectDir(name) {
|
|
56089
|
-
return (0,
|
|
56536
|
+
return (0, import_node_path54.join)(this.projectsRoot(), name);
|
|
56090
56537
|
}
|
|
56091
56538
|
projectsRoot() {
|
|
56092
|
-
return (0,
|
|
56539
|
+
return (0, import_node_path54.join)(this.root, PROJECTS_DIR);
|
|
56093
56540
|
}
|
|
56094
56541
|
async list() {
|
|
56095
56542
|
let entries;
|
|
56096
56543
|
try {
|
|
56097
|
-
entries = await
|
|
56544
|
+
entries = await import_node_fs46.promises.readdir(this.projectsRoot());
|
|
56098
56545
|
} catch (err) {
|
|
56099
56546
|
if (err.code === "ENOENT") return [];
|
|
56100
56547
|
throw err;
|
|
@@ -56102,7 +56549,7 @@ var ProjectStore = class {
|
|
|
56102
56549
|
const out = [];
|
|
56103
56550
|
for (const name of entries) {
|
|
56104
56551
|
try {
|
|
56105
|
-
const raw = await
|
|
56552
|
+
const raw = await import_node_fs46.promises.readFile(this.metaPath(name), "utf8");
|
|
56106
56553
|
const json = JSON.parse(raw);
|
|
56107
56554
|
let migrated = false;
|
|
56108
56555
|
if (typeof json.devCommand !== "string" || json.devCommand.length === 0) {
|
|
@@ -56113,7 +56560,7 @@ var ProjectStore = class {
|
|
|
56113
56560
|
if (parsed.success) {
|
|
56114
56561
|
out.push(parsed.data);
|
|
56115
56562
|
if (migrated) {
|
|
56116
|
-
void
|
|
56563
|
+
void import_node_fs46.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
|
|
56117
56564
|
});
|
|
56118
56565
|
}
|
|
56119
56566
|
}
|
|
@@ -56157,8 +56604,8 @@ var ProjectStore = class {
|
|
|
56157
56604
|
throw new Error(`invalid name "${name}": ${validated.error.message}`);
|
|
56158
56605
|
}
|
|
56159
56606
|
const dir = this.projectDir(name);
|
|
56160
|
-
await
|
|
56161
|
-
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");
|
|
56162
56609
|
return meta;
|
|
56163
56610
|
}
|
|
56164
56611
|
/**
|
|
@@ -56201,7 +56648,7 @@ var ProjectStore = class {
|
|
|
56201
56648
|
}
|
|
56202
56649
|
async delete(name) {
|
|
56203
56650
|
const dir = this.projectDir(name);
|
|
56204
|
-
await
|
|
56651
|
+
await import_node_fs46.promises.rm(dir, { recursive: true, force: true });
|
|
56205
56652
|
}
|
|
56206
56653
|
async updatePort(name, newPort) {
|
|
56207
56654
|
if (newPort < PROJECT_PORT_MIN || newPort > PROJECT_PORT_MAX) {
|
|
@@ -56217,7 +56664,7 @@ var ProjectStore = class {
|
|
|
56217
56664
|
throw new Error(`port ${newPort} already used / \u5DF2\u88AB project "${conflict.name}" \u5360\u7528`);
|
|
56218
56665
|
}
|
|
56219
56666
|
const updated = { ...target, port: newPort };
|
|
56220
|
-
await
|
|
56667
|
+
await import_node_fs46.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
|
|
56221
56668
|
return updated;
|
|
56222
56669
|
}
|
|
56223
56670
|
/**
|
|
@@ -56234,7 +56681,7 @@ var ProjectStore = class {
|
|
|
56234
56681
|
if (!validated.success) {
|
|
56235
56682
|
throw new Error(`invalid prodUrl "${url}": ${validated.error.message}`);
|
|
56236
56683
|
}
|
|
56237
|
-
await
|
|
56684
|
+
await import_node_fs46.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
|
|
56238
56685
|
return validated.data;
|
|
56239
56686
|
}
|
|
56240
56687
|
/**
|
|
@@ -56255,7 +56702,7 @@ var ProjectStore = class {
|
|
|
56255
56702
|
if (!validated.success) {
|
|
56256
56703
|
throw new Error(`invalid publishJob: ${validated.error.message}`);
|
|
56257
56704
|
}
|
|
56258
|
-
await
|
|
56705
|
+
await import_node_fs46.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
|
|
56259
56706
|
return validated.data;
|
|
56260
56707
|
}
|
|
56261
56708
|
/** 清掉 .clawd-project.json.publishJob 字段。其他字段保持原样。 */
|
|
@@ -56270,7 +56717,7 @@ var ProjectStore = class {
|
|
|
56270
56717
|
if (!validated.success) {
|
|
56271
56718
|
throw new Error(`failed to clear publishJob: ${validated.error.message}`);
|
|
56272
56719
|
}
|
|
56273
|
-
await
|
|
56720
|
+
await import_node_fs46.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
|
|
56274
56721
|
return validated.data;
|
|
56275
56722
|
}
|
|
56276
56723
|
};
|
|
@@ -56340,7 +56787,7 @@ function listPidsOnPort(port) {
|
|
|
56340
56787
|
}
|
|
56341
56788
|
|
|
56342
56789
|
// src/app-builder/publish-registry.ts
|
|
56343
|
-
var
|
|
56790
|
+
var import_node_crypto16 = require("crypto");
|
|
56344
56791
|
var PublishJobRegistry = class {
|
|
56345
56792
|
jobs = /* @__PURE__ */ new Map();
|
|
56346
56793
|
has(name) {
|
|
@@ -56357,7 +56804,7 @@ var PublishJobRegistry = class {
|
|
|
56357
56804
|
if (this.jobs.has(args.name)) {
|
|
56358
56805
|
throw new Error(`already publishing: ${args.name}`);
|
|
56359
56806
|
}
|
|
56360
|
-
const jobId = args.jobId ?? `job-${(0,
|
|
56807
|
+
const jobId = args.jobId ?? `job-${(0, import_node_crypto16.randomUUID)()}`;
|
|
56361
56808
|
this.jobs.set(args.name, {
|
|
56362
56809
|
jobId,
|
|
56363
56810
|
name: args.name,
|
|
@@ -56391,8 +56838,8 @@ var PublishJobRegistry = class {
|
|
|
56391
56838
|
|
|
56392
56839
|
// src/app-builder/publish-job-runner.ts
|
|
56393
56840
|
var import_node_child_process16 = require("child_process");
|
|
56394
|
-
var
|
|
56395
|
-
var
|
|
56841
|
+
var import_node_fs47 = require("fs");
|
|
56842
|
+
var import_node_path55 = require("path");
|
|
56396
56843
|
|
|
56397
56844
|
// src/app-builder/publish-stage-parser.ts
|
|
56398
56845
|
var STAGE_RE = /^\s*::stage::(build|deploy|verify)\s*$/;
|
|
@@ -56424,10 +56871,10 @@ async function startPublishJob(deps, args) {
|
|
|
56424
56871
|
return { jobId: registry2.get(args.name).jobId, status: "already-publishing" };
|
|
56425
56872
|
}
|
|
56426
56873
|
const projDir = projectDir(args.name);
|
|
56427
|
-
const logPath = (0,
|
|
56874
|
+
const logPath = (0, import_node_path55.join)(projDir, ".publish.log");
|
|
56428
56875
|
let logStream = null;
|
|
56429
56876
|
try {
|
|
56430
|
-
logStream = (0,
|
|
56877
|
+
logStream = (0, import_node_fs47.createWriteStream)(logPath, { flags: "w" });
|
|
56431
56878
|
} catch {
|
|
56432
56879
|
logStream = null;
|
|
56433
56880
|
}
|
|
@@ -56684,8 +57131,8 @@ async function recoverInterruptedJobs(deps) {
|
|
|
56684
57131
|
|
|
56685
57132
|
// src/handlers/app-builder.ts
|
|
56686
57133
|
init_protocol();
|
|
56687
|
-
var
|
|
56688
|
-
var
|
|
57134
|
+
var import_node_path56 = require("path");
|
|
57135
|
+
var import_node_fs48 = require("fs");
|
|
56689
57136
|
var APP_BUILDER_PERSONAS = ["persona-app-builder", "persona-dataclaw-builder"];
|
|
56690
57137
|
var DEV_SERVER_READY_TIMEOUT_MS = 3e4;
|
|
56691
57138
|
async function recoverInterruptedPublishJobs(store, logger) {
|
|
@@ -56766,7 +57213,7 @@ function buildAppBuilderHandlers(deps) {
|
|
|
56766
57213
|
async function listAllUsersProjects() {
|
|
56767
57214
|
if (!deps.usersRoot || !deps.getStore) return [];
|
|
56768
57215
|
const getStore = deps.getStore;
|
|
56769
|
-
const userIds = await
|
|
57216
|
+
const userIds = await import_node_fs48.promises.readdir(deps.usersRoot).catch(() => []);
|
|
56770
57217
|
const perUser = await Promise.all(
|
|
56771
57218
|
userIds.map((uid) => getStore(uid).list().catch(() => []))
|
|
56772
57219
|
);
|
|
@@ -56842,8 +57289,8 @@ function buildAppBuilderHandlers(deps) {
|
|
|
56842
57289
|
const project = await userStore.create(f.name, reservedPorts);
|
|
56843
57290
|
try {
|
|
56844
57291
|
const personaRoot = deps.resolvePersonaRoot ? deps.resolvePersonaRoot(session.ownerPersonaId ?? "") : deps.personaRoot;
|
|
56845
|
-
const templateSrcDir = (0,
|
|
56846
|
-
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");
|
|
56847
57294
|
const scaffoldResult = await userStore.scaffold(project.name, templateSrcDir, scaffoldScript);
|
|
56848
57295
|
deps.logger?.info("app-builder.scaffold.done", {
|
|
56849
57296
|
name: project.name,
|
|
@@ -57064,7 +57511,7 @@ function buildAppBuilderHandlers(deps) {
|
|
|
57064
57511
|
await userStore.clearPublishJob(args.name);
|
|
57065
57512
|
}
|
|
57066
57513
|
const personaRoot = deps.resolvePersonaRoot ? deps.resolvePersonaRoot(boundSession.ownerPersonaId ?? "") : deps.personaRoot;
|
|
57067
|
-
const scriptPath = (0,
|
|
57514
|
+
const scriptPath = (0, import_node_path56.join)(deps.deployKitRoot, "scripts", "publish.sh");
|
|
57068
57515
|
deps.logger?.info("app-builder.publish.start", {
|
|
57069
57516
|
name: args.name,
|
|
57070
57517
|
sessionId: boundSession.sessionId,
|
|
@@ -57233,7 +57680,8 @@ function buildVisitorHandlers(deps) {
|
|
|
57233
57680
|
|
|
57234
57681
|
// src/extension/registry.ts
|
|
57235
57682
|
var import_promises9 = __toESM(require("fs/promises"), 1);
|
|
57236
|
-
var
|
|
57683
|
+
var import_node_path57 = __toESM(require("path"), 1);
|
|
57684
|
+
init_src();
|
|
57237
57685
|
async function loadAll(root) {
|
|
57238
57686
|
let entries;
|
|
57239
57687
|
try {
|
|
@@ -57246,13 +57694,13 @@ async function loadAll(root) {
|
|
|
57246
57694
|
for (const ent of entries) {
|
|
57247
57695
|
if (!ent.isDirectory()) continue;
|
|
57248
57696
|
if (ent.name.startsWith(".")) continue;
|
|
57249
|
-
records.push(await loadOne(
|
|
57697
|
+
records.push(await loadOne(import_node_path57.default.join(root, ent.name), ent.name));
|
|
57250
57698
|
}
|
|
57251
57699
|
records.sort((a, b2) => a.extId < b2.extId ? -1 : a.extId > b2.extId ? 1 : 0);
|
|
57252
57700
|
return records;
|
|
57253
57701
|
}
|
|
57254
57702
|
async function loadOne(dir, dirName) {
|
|
57255
|
-
const manifestPath =
|
|
57703
|
+
const manifestPath = import_node_path57.default.join(dir, "manifest.json");
|
|
57256
57704
|
let raw;
|
|
57257
57705
|
try {
|
|
57258
57706
|
raw = await import_promises9.default.readFile(manifestPath, "utf8");
|
|
@@ -57297,7 +57745,7 @@ async function loadOne(dir, dirName) {
|
|
|
57297
57745
|
|
|
57298
57746
|
// src/extension/uninstall.ts
|
|
57299
57747
|
var import_promises10 = __toESM(require("fs/promises"), 1);
|
|
57300
|
-
var
|
|
57748
|
+
var import_node_path58 = __toESM(require("path"), 1);
|
|
57301
57749
|
var UninstallError = class extends Error {
|
|
57302
57750
|
constructor(code, message) {
|
|
57303
57751
|
super(message);
|
|
@@ -57306,7 +57754,7 @@ var UninstallError = class extends Error {
|
|
|
57306
57754
|
code;
|
|
57307
57755
|
};
|
|
57308
57756
|
async function uninstall(deps) {
|
|
57309
|
-
const dir =
|
|
57757
|
+
const dir = import_node_path58.default.join(deps.root, deps.extId);
|
|
57310
57758
|
try {
|
|
57311
57759
|
await import_promises10.default.access(dir);
|
|
57312
57760
|
} catch {
|
|
@@ -57317,7 +57765,8 @@ async function uninstall(deps) {
|
|
|
57317
57765
|
}
|
|
57318
57766
|
|
|
57319
57767
|
// src/handlers/index.ts
|
|
57320
|
-
var
|
|
57768
|
+
var import_node_crypto17 = require("crypto");
|
|
57769
|
+
init_peer_forward();
|
|
57321
57770
|
function buildMethodHandlers(deps) {
|
|
57322
57771
|
return {
|
|
57323
57772
|
...buildSessionHandlers({
|
|
@@ -57350,7 +57799,7 @@ function buildMethodHandlers(deps) {
|
|
|
57350
57799
|
const c = deps.contactStore.get(deviceId);
|
|
57351
57800
|
return c ? { deviceId: c.deviceId, remoteUrl: c.remoteUrl, connectToken: c.connectToken } : null;
|
|
57352
57801
|
},
|
|
57353
|
-
genId: () => (0,
|
|
57802
|
+
genId: () => (0, import_node_crypto17.randomUUID)(),
|
|
57354
57803
|
now: () => Date.now(),
|
|
57355
57804
|
forwardInboxPostToPeer,
|
|
57356
57805
|
logger: deps.logger
|
|
@@ -57364,7 +57813,8 @@ function buildMethodHandlers(deps) {
|
|
|
57364
57813
|
...buildContactSshHandlers({
|
|
57365
57814
|
store: deps.contactStore,
|
|
57366
57815
|
broadcast: deps.broadcastToOwners,
|
|
57367
|
-
sshdDir: deps.sshdDir
|
|
57816
|
+
sshdDir: deps.sshdDir,
|
|
57817
|
+
sshLog: deps.contactSshLog
|
|
57368
57818
|
}),
|
|
57369
57819
|
whoami: buildWhoamiHandler({
|
|
57370
57820
|
ownerDisplayName: deps.ownerDisplayName,
|
|
@@ -57888,11 +58338,12 @@ async function dispatchRpc(method, frame, client, ctx, deps) {
|
|
|
57888
58338
|
|
|
57889
58339
|
// src/extension/runtime.ts
|
|
57890
58340
|
var import_node_child_process18 = require("child_process");
|
|
57891
|
-
var
|
|
58341
|
+
var import_node_path59 = __toESM(require("path"), 1);
|
|
57892
58342
|
var import_promises11 = require("timers/promises");
|
|
58343
|
+
init_src();
|
|
57893
58344
|
|
|
57894
58345
|
// src/extension/port-allocator.ts
|
|
57895
|
-
var
|
|
58346
|
+
var import_node_net3 = __toESM(require("net"), 1);
|
|
57896
58347
|
var PortExhaustedError = class extends Error {
|
|
57897
58348
|
constructor(min, max) {
|
|
57898
58349
|
super(`no free port in [${min},${max}]`);
|
|
@@ -57905,7 +58356,7 @@ var PortExhaustedError = class extends Error {
|
|
|
57905
58356
|
};
|
|
57906
58357
|
function probe(port) {
|
|
57907
58358
|
return new Promise((resolve6) => {
|
|
57908
|
-
const srv =
|
|
58359
|
+
const srv = import_node_net3.default.createServer();
|
|
57909
58360
|
srv.once("error", () => resolve6(false));
|
|
57910
58361
|
srv.once("listening", () => {
|
|
57911
58362
|
srv.close(() => resolve6(true));
|
|
@@ -57989,7 +58440,7 @@ var Runtime = class {
|
|
|
57989
58440
|
/\$CLAWOS_EXT_PORT/g,
|
|
57990
58441
|
String(port)
|
|
57991
58442
|
);
|
|
57992
|
-
const dir =
|
|
58443
|
+
const dir = import_node_path59.default.join(this.root, extId);
|
|
57993
58444
|
const env = {
|
|
57994
58445
|
...process.env,
|
|
57995
58446
|
CLAWOS_EXT_PORT: String(port),
|
|
@@ -58101,7 +58552,8 @@ ${handle.stderrTail}`
|
|
|
58101
58552
|
|
|
58102
58553
|
// src/extension/published-channels.ts
|
|
58103
58554
|
var import_promises12 = __toESM(require("fs/promises"), 1);
|
|
58104
|
-
var
|
|
58555
|
+
var import_node_path60 = __toESM(require("path"), 1);
|
|
58556
|
+
init_src();
|
|
58105
58557
|
init_zod();
|
|
58106
58558
|
var PublishedChannelsError = class extends Error {
|
|
58107
58559
|
constructor(code, message) {
|
|
@@ -58200,7 +58652,7 @@ var PublishedChannelStore = class {
|
|
|
58200
58652
|
)
|
|
58201
58653
|
};
|
|
58202
58654
|
const tmp = `${this.filePath}.tmp`;
|
|
58203
|
-
await import_promises12.default.mkdir(
|
|
58655
|
+
await import_promises12.default.mkdir(import_node_path60.default.dirname(this.filePath), { recursive: true });
|
|
58204
58656
|
await import_promises12.default.writeFile(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
58205
58657
|
await import_promises12.default.rename(tmp, this.filePath);
|
|
58206
58658
|
}
|
|
@@ -58208,7 +58660,7 @@ var PublishedChannelStore = class {
|
|
|
58208
58660
|
|
|
58209
58661
|
// src/extension/bundle-cache.ts
|
|
58210
58662
|
var import_promises13 = __toESM(require("fs/promises"), 1);
|
|
58211
|
-
var
|
|
58663
|
+
var import_node_path61 = __toESM(require("path"), 1);
|
|
58212
58664
|
var BundleCache = class {
|
|
58213
58665
|
constructor(rootDir) {
|
|
58214
58666
|
this.rootDir = rootDir;
|
|
@@ -58217,14 +58669,14 @@ var BundleCache = class {
|
|
|
58217
58669
|
/** Atomic write: stage tmp → rename. Caller passes the hex sha256. */
|
|
58218
58670
|
async write(snapshotHash, buffer) {
|
|
58219
58671
|
await import_promises13.default.mkdir(this.rootDir, { recursive: true });
|
|
58220
|
-
const file =
|
|
58672
|
+
const file = import_node_path61.default.join(this.rootDir, `${snapshotHash}.zip`);
|
|
58221
58673
|
const tmp = `${file}.tmp`;
|
|
58222
58674
|
await import_promises13.default.writeFile(tmp, buffer, { mode: 384 });
|
|
58223
58675
|
await import_promises13.default.rename(tmp, file);
|
|
58224
58676
|
}
|
|
58225
58677
|
/** Returns the bundle bytes, or null when the file doesn't exist. */
|
|
58226
58678
|
async read(snapshotHash) {
|
|
58227
|
-
const file =
|
|
58679
|
+
const file = import_node_path61.default.join(this.rootDir, `${snapshotHash}.zip`);
|
|
58228
58680
|
try {
|
|
58229
58681
|
return await import_promises13.default.readFile(file);
|
|
58230
58682
|
} catch (e) {
|
|
@@ -58234,7 +58686,7 @@ var BundleCache = class {
|
|
|
58234
58686
|
}
|
|
58235
58687
|
/** Idempotent — missing file is not an error. */
|
|
58236
58688
|
async delete(snapshotHash) {
|
|
58237
|
-
const file =
|
|
58689
|
+
const file = import_node_path61.default.join(this.rootDir, `${snapshotHash}.zip`);
|
|
58238
58690
|
await import_promises13.default.rm(file, { force: true });
|
|
58239
58691
|
}
|
|
58240
58692
|
};
|
|
@@ -58259,17 +58711,10 @@ async function startDaemon(config) {
|
|
|
58259
58711
|
});
|
|
58260
58712
|
const logger = createLogger({
|
|
58261
58713
|
level: config.logLevel,
|
|
58262
|
-
file:
|
|
58714
|
+
file: import_node_path62.default.join(config.dataDir, "clawd.log"),
|
|
58263
58715
|
logClient
|
|
58264
58716
|
});
|
|
58265
58717
|
logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
|
|
58266
|
-
const screenIdleProbeLogger = createFileOnlyLogger({
|
|
58267
|
-
file: import_node_path60.default.join(config.dataDir, "screen-idle-probe.log"),
|
|
58268
|
-
level: "debug"
|
|
58269
|
-
});
|
|
58270
|
-
logger.info("screen-idle probe logger enabled", {
|
|
58271
|
-
file: import_node_path60.default.join(config.dataDir, "screen-idle-probe.log")
|
|
58272
|
-
});
|
|
58273
58718
|
const stateMgr = new StateFileManager({ dataDir: config.dataDir });
|
|
58274
58719
|
const pre = stateMgr.preflight();
|
|
58275
58720
|
if (pre.status === "active") {
|
|
@@ -58406,8 +58851,8 @@ async function startDaemon(config) {
|
|
|
58406
58851
|
const agents = new AgentsScanner();
|
|
58407
58852
|
const history = new ClaudeHistoryReader();
|
|
58408
58853
|
let transport = null;
|
|
58409
|
-
const personaStore = new PersonaStore(
|
|
58410
|
-
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");
|
|
58411
58856
|
const defaultsRoot = findDefaultsRoot(logger);
|
|
58412
58857
|
if (defaultsRoot) {
|
|
58413
58858
|
seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
|
|
@@ -58427,17 +58872,17 @@ async function startDaemon(config) {
|
|
|
58427
58872
|
migrateCodexSandbox({ store: personaStore, logger });
|
|
58428
58873
|
const groupFileStore = new GroupFileStore({ dataDir: config.dataDir, logger });
|
|
58429
58874
|
const personaDispatchManager = new PersonaDispatchManager({ genId: () => v4_default() });
|
|
58430
|
-
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));
|
|
58431
58876
|
const dispatchServerCandidates = [
|
|
58432
|
-
|
|
58877
|
+
import_node_path62.default.join(here, "dispatch", "mcp-server.cjs"),
|
|
58433
58878
|
// 生产 dist/index → dist/dispatch/mcp-server.cjs
|
|
58434
|
-
|
|
58879
|
+
import_node_path62.default.join(here, "..", "dist", "dispatch", "mcp-server.cjs")
|
|
58435
58880
|
// dev tsx src/index → ../dist/dispatch/mcp-server.cjs
|
|
58436
58881
|
];
|
|
58437
|
-
const dispatchServerScriptPath = dispatchServerCandidates.find((p2) =>
|
|
58882
|
+
const dispatchServerScriptPath = dispatchServerCandidates.find((p2) => import_node_fs49.default.existsSync(p2));
|
|
58438
58883
|
let dispatchMcpConfigPath2;
|
|
58439
58884
|
if (dispatchServerScriptPath) {
|
|
58440
|
-
const dispatchLogPath =
|
|
58885
|
+
const dispatchLogPath = import_node_path62.default.join(config.dataDir, "dispatch-mcp-server.log");
|
|
58441
58886
|
dispatchMcpConfigPath2 = writeDispatchMcpConfig({
|
|
58442
58887
|
dataDir: config.dataDir,
|
|
58443
58888
|
serverScriptPath: dispatchServerScriptPath,
|
|
@@ -58454,15 +58899,15 @@ async function startDaemon(config) {
|
|
|
58454
58899
|
});
|
|
58455
58900
|
}
|
|
58456
58901
|
const ticketServerCandidates = [
|
|
58457
|
-
|
|
58458
|
-
|
|
58902
|
+
import_node_path62.default.join(here, "ticket", "mcp-server.cjs"),
|
|
58903
|
+
import_node_path62.default.join(here, "..", "dist", "ticket", "mcp-server.cjs")
|
|
58459
58904
|
];
|
|
58460
|
-
const ticketServerScriptPath = ticketServerCandidates.find((p2) =>
|
|
58905
|
+
const ticketServerScriptPath = ticketServerCandidates.find((p2) => import_node_fs49.default.existsSync(p2));
|
|
58461
58906
|
const ticketOwnerUnionId = feishuIdentity?.identity.unionId ?? "";
|
|
58462
58907
|
const ticketOwnerName = feishuIdentity?.identity.displayName ?? "";
|
|
58463
58908
|
let ticketMcpConfigPath2;
|
|
58464
58909
|
if (ticketServerScriptPath && ticketOwnerUnionId) {
|
|
58465
|
-
const ticketLogPath =
|
|
58910
|
+
const ticketLogPath = import_node_path62.default.join(config.dataDir, "ticket-mcp-server.log");
|
|
58466
58911
|
ticketMcpConfigPath2 = writeTicketMcpConfig({
|
|
58467
58912
|
dataDir: config.dataDir,
|
|
58468
58913
|
serverScriptPath: ticketServerScriptPath,
|
|
@@ -58483,13 +58928,13 @@ async function startDaemon(config) {
|
|
|
58483
58928
|
});
|
|
58484
58929
|
}
|
|
58485
58930
|
const shiftServerCandidates = [
|
|
58486
|
-
|
|
58487
|
-
|
|
58931
|
+
import_node_path62.default.join(here, "shift", "mcp-server.cjs"),
|
|
58932
|
+
import_node_path62.default.join(here, "..", "dist", "shift", "mcp-server.cjs")
|
|
58488
58933
|
];
|
|
58489
|
-
const shiftServerScriptPath = shiftServerCandidates.find((p2) =>
|
|
58934
|
+
const shiftServerScriptPath = shiftServerCandidates.find((p2) => import_node_fs49.default.existsSync(p2));
|
|
58490
58935
|
let shiftMcpConfigPath2;
|
|
58491
58936
|
if (shiftServerScriptPath) {
|
|
58492
|
-
const shiftLogPath =
|
|
58937
|
+
const shiftLogPath = import_node_path62.default.join(config.dataDir, "shift-mcp-server.log");
|
|
58493
58938
|
shiftMcpConfigPath2 = await writeShiftMcpConfig({
|
|
58494
58939
|
dataDir: config.dataDir,
|
|
58495
58940
|
serverScriptPath: shiftServerScriptPath,
|
|
@@ -58507,13 +58952,13 @@ async function startDaemon(config) {
|
|
|
58507
58952
|
);
|
|
58508
58953
|
}
|
|
58509
58954
|
const inboxServerCandidates = [
|
|
58510
|
-
|
|
58511
|
-
|
|
58955
|
+
import_node_path62.default.join(here, "inbox", "mcp-server.cjs"),
|
|
58956
|
+
import_node_path62.default.join(here, "..", "dist", "inbox", "mcp-server.cjs")
|
|
58512
58957
|
];
|
|
58513
|
-
const inboxServerScriptPath = inboxServerCandidates.find((p2) =>
|
|
58958
|
+
const inboxServerScriptPath = inboxServerCandidates.find((p2) => import_node_fs49.default.existsSync(p2));
|
|
58514
58959
|
let inboxMcpConfigPath2;
|
|
58515
58960
|
if (inboxServerScriptPath) {
|
|
58516
|
-
const inboxLogPath =
|
|
58961
|
+
const inboxLogPath = import_node_path62.default.join(config.dataDir, "inbox-mcp-server.log");
|
|
58517
58962
|
inboxMcpConfigPath2 = await writeInboxMcpConfig({
|
|
58518
58963
|
dataDir: config.dataDir,
|
|
58519
58964
|
serverScriptPath: inboxServerScriptPath,
|
|
@@ -58531,7 +58976,7 @@ async function startDaemon(config) {
|
|
|
58531
58976
|
);
|
|
58532
58977
|
}
|
|
58533
58978
|
const shiftStore = createShiftStore({
|
|
58534
|
-
filePath:
|
|
58979
|
+
filePath: import_node_path62.default.join(config.dataDir, "shift.json"),
|
|
58535
58980
|
ownerIdProvider: () => ownerPrincipalId,
|
|
58536
58981
|
now: () => Date.now()
|
|
58537
58982
|
});
|
|
@@ -58546,14 +58991,10 @@ async function startDaemon(config) {
|
|
|
58546
58991
|
// 新布局派生 (sessions/* + personas/<pid>/.clawd/sessions/owner/*)
|
|
58547
58992
|
storeFactory: sessionStoreFactory,
|
|
58548
58993
|
logger,
|
|
58549
|
-
// 取证 probe(可选,CLAWD_SCREEN_IDLE_PROBE=1 时启用):manager turn_end 判定链
|
|
58550
|
-
// 的所有决策点打到独立文件,跟 adapter 的 observeScreenIdle probe 共用同一份 file logger,
|
|
58551
|
-
// 便于 grep sessionId 时 tui 层 + manager 层交叉时序都在同一文件里
|
|
58552
|
-
...screenIdleProbeLogger ? { screenIdleProbeLogger } : {},
|
|
58553
58994
|
getAdapter,
|
|
58554
58995
|
historyReader: history,
|
|
58555
58996
|
dataDir: config.dataDir,
|
|
58556
|
-
personaRoot:
|
|
58997
|
+
personaRoot: import_node_path62.default.join(config.dataDir, "personas"),
|
|
58557
58998
|
usersRoot,
|
|
58558
58999
|
personaStore,
|
|
58559
59000
|
ownerDisplayName,
|
|
@@ -58596,10 +59037,10 @@ async function startDaemon(config) {
|
|
|
58596
59037
|
// 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
|
|
58597
59038
|
attachmentGroup: {
|
|
58598
59039
|
onFileEdit: (input) => {
|
|
58599
|
-
const absPath =
|
|
59040
|
+
const absPath = import_node_path62.default.isAbsolute(input.relPath) ? input.relPath : import_node_path62.default.join(input.cwd, input.relPath);
|
|
58600
59041
|
let size = 0;
|
|
58601
59042
|
try {
|
|
58602
|
-
size =
|
|
59043
|
+
size = import_node_fs49.default.statSync(absPath).size;
|
|
58603
59044
|
} catch (err) {
|
|
58604
59045
|
logger.warn("attachment.onFileEdit stat failed", {
|
|
58605
59046
|
sessionId: input.sessionId,
|
|
@@ -58667,10 +59108,10 @@ async function startDaemon(config) {
|
|
|
58667
59108
|
onSurfaceUnregister: (tsid) => manager.unregisterSurface(tsid),
|
|
58668
59109
|
// ReadyGate v2:ReadyDetector emit ready 时投递 reducer 'ready-detected' input
|
|
58669
59110
|
onReady: (tsid) => manager.dispatchReadyDetected(tsid),
|
|
58670
|
-
//
|
|
58671
|
-
|
|
58672
|
-
//
|
|
58673
|
-
|
|
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)
|
|
58674
59115
|
}) : new ClaudeAdapter({ logger, historyReader: new ClaudeHistoryReader() });
|
|
58675
59116
|
registerAdapter("claude", claudeAdapter);
|
|
58676
59117
|
registerAdapter("codex", new CodexAdapter({ logger, historyReader: new CodexHistoryReader() }));
|
|
@@ -58754,6 +59195,7 @@ async function startDaemon(config) {
|
|
|
58754
59195
|
const effectivePreviewPorts = Array.from(
|
|
58755
59196
|
/* @__PURE__ */ new Set([...config.previewPorts ?? [], ...appBuilderPortRange])
|
|
58756
59197
|
);
|
|
59198
|
+
const sshLog = createContactSshLog(config.dataDir);
|
|
58757
59199
|
const makeHandlers = () => buildMethodHandlers({
|
|
58758
59200
|
manager,
|
|
58759
59201
|
workspace,
|
|
@@ -58797,11 +59239,11 @@ async function startDaemon(config) {
|
|
|
58797
59239
|
// 'persona/<pid>/owner',default 走 'default'。
|
|
58798
59240
|
getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
|
|
58799
59241
|
// guest path guard:candidate 必须在 personaRoot 子树或调用者自己的 user-dir 下
|
|
58800
|
-
personaRoot:
|
|
59242
|
+
personaRoot: import_node_path62.default.join(config.dataDir, "personas"),
|
|
58801
59243
|
usersRoot
|
|
58802
59244
|
},
|
|
58803
59245
|
// workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
|
|
58804
|
-
personaRoot:
|
|
59246
|
+
personaRoot: import_node_path62.default.join(config.dataDir, "personas"),
|
|
58805
59247
|
// v2 多人 persona 隔离:handler 派生 guest user-dir 放行
|
|
58806
59248
|
usersRoot,
|
|
58807
59249
|
// capability:list / delete handler 依赖
|
|
@@ -58823,7 +59265,8 @@ async function startDaemon(config) {
|
|
|
58823
59265
|
contactStore,
|
|
58824
59266
|
// <dataDir>/sshd 绝对路径 —— contact-ssh handlers 用它拼 authorized_keys / keys/ 子路径
|
|
58825
59267
|
// Task 10 会加 SshdManager 起 sshd;handlers wire 提前挂 sshdDir 让 typecheck 过
|
|
58826
|
-
sshdDir:
|
|
59268
|
+
sshdDir: import_node_path62.default.join(config.dataDir, "sshd"),
|
|
59269
|
+
contactSshLog: sshLog,
|
|
58827
59270
|
// inbox:sendDm 用:sessionId → session 出身(复用 attachment 同款 findOwnedSessionScope)
|
|
58828
59271
|
getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
|
|
58829
59272
|
// contact:removed broadcast;复用 capability:tokenIssued 同款通路
|
|
@@ -58913,11 +59356,11 @@ async function startDaemon(config) {
|
|
|
58913
59356
|
// 发布上线脚手架化 (spec 2026-06-03 §5.2):
|
|
58914
59357
|
// appBuilderPersonaRoot 用于拼 publish.sh 绝对路径(persona-app-builder 安装在
|
|
58915
59358
|
// dataDir/personas/persona-app-builder 之下,extension-kit/scripts/publish.sh 是相对路径)。
|
|
58916
|
-
appBuilderPersonaRoot:
|
|
59359
|
+
appBuilderPersonaRoot: import_node_path62.default.join(config.dataDir, "personas", "persona-app-builder"),
|
|
58917
59360
|
// 共享 deploy-kit 根:scaffold/publish 脚本骨架 + 阿里云凭证单一真源。
|
|
58918
|
-
deployKitRoot:
|
|
59361
|
+
deployKitRoot: import_node_path62.default.join(config.dataDir, "deploy-kit"),
|
|
58919
59362
|
// scaffold/publish 按当前 session 的 persona 解析其安装根,让每个 persona 用自己的模板/注入配置。
|
|
58920
|
-
resolvePersonaRoot: (personaId) =>
|
|
59363
|
+
resolvePersonaRoot: (personaId) => import_node_path62.default.join(config.dataDir, "personas", personaId),
|
|
58921
59364
|
// 发布上线脚手架化 (spec 2026-06-03 §5.2.2):
|
|
58922
59365
|
// 复用 SessionManagerDeps.broadcastFrame 同款 dispatch 逻辑 —— runner 调 manager.send
|
|
58923
59366
|
// 取回 broadcast 帧后逐帧 push 到 transport,跟 manager 自身的 deps 一致。
|
|
@@ -58960,7 +59403,7 @@ async function startDaemon(config) {
|
|
|
58960
59403
|
}
|
|
58961
59404
|
let sourceJsonlPath = "(no transcript yet \u2014 operate from the task description alone)";
|
|
58962
59405
|
if (sourceFile && sourceFile.toolSessionId) {
|
|
58963
|
-
sourceJsonlPath =
|
|
59406
|
+
sourceJsonlPath = import_node_path62.default.join(
|
|
58964
59407
|
import_node_os21.default.homedir(),
|
|
58965
59408
|
".claude",
|
|
58966
59409
|
"projects",
|
|
@@ -59149,6 +59592,36 @@ async function startDaemon(config) {
|
|
|
59149
59592
|
httpRequestHandler: httpRouter,
|
|
59150
59593
|
// app-builder build 模式:/preview/<port>/ HMR websocket upgrade 转发的端口白名单(同 http-router 配置)
|
|
59151
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
|
+
},
|
|
59152
59625
|
// 订阅成功后给该 client 重放 in-flight pendingQuestions(plan: clawd-question-server-truth)。
|
|
59153
59626
|
// daemon 是 pendingQuestions 的唯一 source of truth;新 client 接入 / 刷新页面时
|
|
59154
59627
|
// 把当前所有未决 question 以 session:question 帧定向回放,让 UI 不再误显示 Ended。
|
|
@@ -59260,8 +59733,8 @@ async function startDaemon(config) {
|
|
|
59260
59733
|
const lines = [
|
|
59261
59734
|
`Tunnel: ${r.url}`,
|
|
59262
59735
|
...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
|
|
59263
|
-
`Frpc config: ${
|
|
59264
|
-
`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")}`
|
|
59265
59738
|
];
|
|
59266
59739
|
const width = Math.max(...lines.map((l) => l.length));
|
|
59267
59740
|
const bar = "\u2550".repeat(width + 4);
|
|
@@ -59274,8 +59747,8 @@ ${bar}
|
|
|
59274
59747
|
|
|
59275
59748
|
`);
|
|
59276
59749
|
try {
|
|
59277
|
-
const connectPath =
|
|
59278
|
-
|
|
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 });
|
|
59279
59752
|
} catch {
|
|
59280
59753
|
}
|
|
59281
59754
|
} catch (err) {
|
|
@@ -59309,13 +59782,21 @@ ${bar}
|
|
|
59309
59782
|
});
|
|
59310
59783
|
try {
|
|
59311
59784
|
await sshdMgr.start();
|
|
59312
|
-
rebuildAuthorizedKeys(contactStore,
|
|
59785
|
+
rebuildAuthorizedKeys(contactStore, import_node_path62.default.join(config.dataDir, "sshd"));
|
|
59313
59786
|
logger.info("sshd: contact-ssh sandbox ready", { port: config.sshdPort });
|
|
59314
59787
|
} catch (err) {
|
|
59315
59788
|
logger.warn("sshd start failed; contact SSH grant will not work until fixed", {
|
|
59316
59789
|
err: err.message
|
|
59317
59790
|
});
|
|
59318
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();
|
|
59319
59800
|
void reportDevice();
|
|
59320
59801
|
void fetchServerKey();
|
|
59321
59802
|
const tickAttachmentGc = () => {
|
|
@@ -59350,6 +59831,7 @@ ${bar}
|
|
|
59350
59831
|
if (tunnelMgr) {
|
|
59351
59832
|
await tunnelMgr.stop();
|
|
59352
59833
|
}
|
|
59834
|
+
contactKeyPuller.stop();
|
|
59353
59835
|
await sshdMgr.stop().catch((err) => {
|
|
59354
59836
|
logger.warn("shutdown.sshd-stop-failed", {
|
|
59355
59837
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -59368,9 +59850,9 @@ ${bar}
|
|
|
59368
59850
|
};
|
|
59369
59851
|
}
|
|
59370
59852
|
function migrateDropPersonsDir(dataDir) {
|
|
59371
|
-
const dir =
|
|
59853
|
+
const dir = import_node_path62.default.join(dataDir, "persons");
|
|
59372
59854
|
try {
|
|
59373
|
-
|
|
59855
|
+
import_node_fs49.default.rmSync(dir, { recursive: true, force: true });
|
|
59374
59856
|
} catch {
|
|
59375
59857
|
}
|
|
59376
59858
|
}
|
|
@@ -59383,6 +59865,11 @@ async function main() {
|
|
|
59383
59865
|
const code = await runCase2(argv.slice(1));
|
|
59384
59866
|
process.exit(code);
|
|
59385
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
|
+
}
|
|
59386
59873
|
const parsed = parseArgs(argv);
|
|
59387
59874
|
if (parsed.help) {
|
|
59388
59875
|
process.stdout.write(HELP_TEXT);
|