@clawos-dev/clawd 0.2.27 → 0.2.28-beta.38.7eb315d
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 +1317 -386
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -296,8 +296,8 @@ var require_req = __commonJS({
|
|
|
296
296
|
if (req.originalUrl) {
|
|
297
297
|
_req.url = req.originalUrl;
|
|
298
298
|
} else {
|
|
299
|
-
const
|
|
300
|
-
_req.url = typeof
|
|
299
|
+
const path23 = req.path;
|
|
300
|
+
_req.url = typeof path23 === "string" ? path23 : req.url ? req.url.path || req.url : void 0;
|
|
301
301
|
}
|
|
302
302
|
if (req.query) {
|
|
303
303
|
_req.query = req.query;
|
|
@@ -462,14 +462,14 @@ var require_redact = __commonJS({
|
|
|
462
462
|
}
|
|
463
463
|
return obj;
|
|
464
464
|
}
|
|
465
|
-
function parsePath(
|
|
465
|
+
function parsePath(path23) {
|
|
466
466
|
const parts = [];
|
|
467
467
|
let current = "";
|
|
468
468
|
let inBrackets = false;
|
|
469
469
|
let inQuotes = false;
|
|
470
470
|
let quoteChar = "";
|
|
471
|
-
for (let i = 0; i <
|
|
472
|
-
const char =
|
|
471
|
+
for (let i = 0; i < path23.length; i++) {
|
|
472
|
+
const char = path23[i];
|
|
473
473
|
if (!inBrackets && char === ".") {
|
|
474
474
|
if (current) {
|
|
475
475
|
parts.push(current);
|
|
@@ -600,10 +600,10 @@ var require_redact = __commonJS({
|
|
|
600
600
|
return current;
|
|
601
601
|
}
|
|
602
602
|
function redactPaths(obj, paths, censor, remove = false) {
|
|
603
|
-
for (const
|
|
604
|
-
const parts = parsePath(
|
|
603
|
+
for (const path23 of paths) {
|
|
604
|
+
const parts = parsePath(path23);
|
|
605
605
|
if (parts.includes("*")) {
|
|
606
|
-
redactWildcardPath(obj, parts, censor,
|
|
606
|
+
redactWildcardPath(obj, parts, censor, path23, remove);
|
|
607
607
|
} else {
|
|
608
608
|
if (remove) {
|
|
609
609
|
removeKey(obj, parts);
|
|
@@ -688,8 +688,8 @@ var require_redact = __commonJS({
|
|
|
688
688
|
}
|
|
689
689
|
} else {
|
|
690
690
|
if (afterWildcard.includes("*")) {
|
|
691
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
692
|
-
const fullPath = [...pathArray.slice(0, pathLength), ...
|
|
691
|
+
const wrappedCensor = typeof censor === "function" ? (value, path23) => {
|
|
692
|
+
const fullPath = [...pathArray.slice(0, pathLength), ...path23];
|
|
693
693
|
return censor(value, fullPath);
|
|
694
694
|
} : censor;
|
|
695
695
|
redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
|
|
@@ -724,8 +724,8 @@ var require_redact = __commonJS({
|
|
|
724
724
|
return null;
|
|
725
725
|
}
|
|
726
726
|
const pathStructure = /* @__PURE__ */ new Map();
|
|
727
|
-
for (const
|
|
728
|
-
const parts = parsePath(
|
|
727
|
+
for (const path23 of pathsToClone) {
|
|
728
|
+
const parts = parsePath(path23);
|
|
729
729
|
let current = pathStructure;
|
|
730
730
|
for (let i = 0; i < parts.length; i++) {
|
|
731
731
|
const part = parts[i];
|
|
@@ -777,24 +777,24 @@ var require_redact = __commonJS({
|
|
|
777
777
|
}
|
|
778
778
|
return cloneSelectively(obj, pathStructure);
|
|
779
779
|
}
|
|
780
|
-
function validatePath(
|
|
781
|
-
if (typeof
|
|
780
|
+
function validatePath(path23) {
|
|
781
|
+
if (typeof path23 !== "string") {
|
|
782
782
|
throw new Error("Paths must be (non-empty) strings");
|
|
783
783
|
}
|
|
784
|
-
if (
|
|
784
|
+
if (path23 === "") {
|
|
785
785
|
throw new Error("Invalid redaction path ()");
|
|
786
786
|
}
|
|
787
|
-
if (
|
|
788
|
-
throw new Error(`Invalid redaction path (${
|
|
787
|
+
if (path23.includes("..")) {
|
|
788
|
+
throw new Error(`Invalid redaction path (${path23})`);
|
|
789
789
|
}
|
|
790
|
-
if (
|
|
791
|
-
throw new Error(`Invalid redaction path (${
|
|
790
|
+
if (path23.includes(",")) {
|
|
791
|
+
throw new Error(`Invalid redaction path (${path23})`);
|
|
792
792
|
}
|
|
793
793
|
let bracketCount = 0;
|
|
794
794
|
let inQuotes = false;
|
|
795
795
|
let quoteChar = "";
|
|
796
|
-
for (let i = 0; i <
|
|
797
|
-
const char =
|
|
796
|
+
for (let i = 0; i < path23.length; i++) {
|
|
797
|
+
const char = path23[i];
|
|
798
798
|
if ((char === '"' || char === "'") && bracketCount > 0) {
|
|
799
799
|
if (!inQuotes) {
|
|
800
800
|
inQuotes = true;
|
|
@@ -808,20 +808,20 @@ var require_redact = __commonJS({
|
|
|
808
808
|
} else if (char === "]" && !inQuotes) {
|
|
809
809
|
bracketCount--;
|
|
810
810
|
if (bracketCount < 0) {
|
|
811
|
-
throw new Error(`Invalid redaction path (${
|
|
811
|
+
throw new Error(`Invalid redaction path (${path23})`);
|
|
812
812
|
}
|
|
813
813
|
}
|
|
814
814
|
}
|
|
815
815
|
if (bracketCount !== 0) {
|
|
816
|
-
throw new Error(`Invalid redaction path (${
|
|
816
|
+
throw new Error(`Invalid redaction path (${path23})`);
|
|
817
817
|
}
|
|
818
818
|
}
|
|
819
819
|
function validatePaths(paths) {
|
|
820
820
|
if (!Array.isArray(paths)) {
|
|
821
821
|
throw new TypeError("paths must be an array");
|
|
822
822
|
}
|
|
823
|
-
for (const
|
|
824
|
-
validatePath(
|
|
823
|
+
for (const path23 of paths) {
|
|
824
|
+
validatePath(path23);
|
|
825
825
|
}
|
|
826
826
|
}
|
|
827
827
|
function slowRedact(options = {}) {
|
|
@@ -989,8 +989,8 @@ var require_redaction = __commonJS({
|
|
|
989
989
|
if (shape[k] === null) {
|
|
990
990
|
o[k] = (value) => topCensor(value, [k]);
|
|
991
991
|
} else {
|
|
992
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
993
|
-
return censor(value, [k, ...
|
|
992
|
+
const wrappedCensor = typeof censor === "function" ? (value, path23) => {
|
|
993
|
+
return censor(value, [k, ...path23]);
|
|
994
994
|
} : censor;
|
|
995
995
|
o[k] = Redact({
|
|
996
996
|
paths: shape[k],
|
|
@@ -1208,10 +1208,10 @@ var require_atomic_sleep = __commonJS({
|
|
|
1208
1208
|
var require_sonic_boom = __commonJS({
|
|
1209
1209
|
"../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
|
|
1210
1210
|
"use strict";
|
|
1211
|
-
var
|
|
1211
|
+
var fs22 = require("fs");
|
|
1212
1212
|
var EventEmitter = require("events");
|
|
1213
1213
|
var inherits = require("util").inherits;
|
|
1214
|
-
var
|
|
1214
|
+
var path23 = require("path");
|
|
1215
1215
|
var sleep = require_atomic_sleep();
|
|
1216
1216
|
var assert = require("assert");
|
|
1217
1217
|
var BUSY_WRITE_TIMEOUT = 100;
|
|
@@ -1265,20 +1265,20 @@ var require_sonic_boom = __commonJS({
|
|
|
1265
1265
|
const mode = sonic.mode;
|
|
1266
1266
|
if (sonic.sync) {
|
|
1267
1267
|
try {
|
|
1268
|
-
if (sonic.mkdir)
|
|
1269
|
-
const fd =
|
|
1268
|
+
if (sonic.mkdir) fs22.mkdirSync(path23.dirname(file), { recursive: true });
|
|
1269
|
+
const fd = fs22.openSync(file, flags, mode);
|
|
1270
1270
|
fileOpened(null, fd);
|
|
1271
1271
|
} catch (err) {
|
|
1272
1272
|
fileOpened(err);
|
|
1273
1273
|
throw err;
|
|
1274
1274
|
}
|
|
1275
1275
|
} else if (sonic.mkdir) {
|
|
1276
|
-
|
|
1276
|
+
fs22.mkdir(path23.dirname(file), { recursive: true }, (err) => {
|
|
1277
1277
|
if (err) return fileOpened(err);
|
|
1278
|
-
|
|
1278
|
+
fs22.open(file, flags, mode, fileOpened);
|
|
1279
1279
|
});
|
|
1280
1280
|
} else {
|
|
1281
|
-
|
|
1281
|
+
fs22.open(file, flags, mode, fileOpened);
|
|
1282
1282
|
}
|
|
1283
1283
|
}
|
|
1284
1284
|
function SonicBoom(opts) {
|
|
@@ -1319,8 +1319,8 @@ var require_sonic_boom = __commonJS({
|
|
|
1319
1319
|
this.flush = flushBuffer;
|
|
1320
1320
|
this.flushSync = flushBufferSync;
|
|
1321
1321
|
this._actualWrite = actualWriteBuffer;
|
|
1322
|
-
fsWriteSync = () =>
|
|
1323
|
-
fsWrite = () =>
|
|
1322
|
+
fsWriteSync = () => fs22.writeSync(this.fd, this._writingBuf);
|
|
1323
|
+
fsWrite = () => fs22.write(this.fd, this._writingBuf, this.release);
|
|
1324
1324
|
} else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
|
|
1325
1325
|
this._writingBuf = "";
|
|
1326
1326
|
this.write = write;
|
|
@@ -1329,15 +1329,15 @@ var require_sonic_boom = __commonJS({
|
|
|
1329
1329
|
this._actualWrite = actualWrite;
|
|
1330
1330
|
fsWriteSync = () => {
|
|
1331
1331
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
1332
|
-
return
|
|
1332
|
+
return fs22.writeSync(this.fd, this._writingBuf);
|
|
1333
1333
|
}
|
|
1334
|
-
return
|
|
1334
|
+
return fs22.writeSync(this.fd, this._writingBuf, "utf8");
|
|
1335
1335
|
};
|
|
1336
1336
|
fsWrite = () => {
|
|
1337
1337
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
1338
|
-
return
|
|
1338
|
+
return fs22.write(this.fd, this._writingBuf, this.release);
|
|
1339
1339
|
}
|
|
1340
|
-
return
|
|
1340
|
+
return fs22.write(this.fd, this._writingBuf, "utf8", this.release);
|
|
1341
1341
|
};
|
|
1342
1342
|
} else {
|
|
1343
1343
|
throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
|
|
@@ -1394,7 +1394,7 @@ var require_sonic_boom = __commonJS({
|
|
|
1394
1394
|
}
|
|
1395
1395
|
}
|
|
1396
1396
|
if (this._fsync) {
|
|
1397
|
-
|
|
1397
|
+
fs22.fsyncSync(this.fd);
|
|
1398
1398
|
}
|
|
1399
1399
|
const len = this._len;
|
|
1400
1400
|
if (this._reopening) {
|
|
@@ -1508,7 +1508,7 @@ var require_sonic_boom = __commonJS({
|
|
|
1508
1508
|
const onDrain = () => {
|
|
1509
1509
|
if (!this._fsync) {
|
|
1510
1510
|
try {
|
|
1511
|
-
|
|
1511
|
+
fs22.fsync(this.fd, (err) => {
|
|
1512
1512
|
this._flushPending = false;
|
|
1513
1513
|
cb(err);
|
|
1514
1514
|
});
|
|
@@ -1610,7 +1610,7 @@ var require_sonic_boom = __commonJS({
|
|
|
1610
1610
|
const fd = this.fd;
|
|
1611
1611
|
this.once("ready", () => {
|
|
1612
1612
|
if (fd !== this.fd) {
|
|
1613
|
-
|
|
1613
|
+
fs22.close(fd, (err) => {
|
|
1614
1614
|
if (err) {
|
|
1615
1615
|
return this.emit("error", err);
|
|
1616
1616
|
}
|
|
@@ -1659,7 +1659,7 @@ var require_sonic_boom = __commonJS({
|
|
|
1659
1659
|
buf = this._bufs[0];
|
|
1660
1660
|
}
|
|
1661
1661
|
try {
|
|
1662
|
-
const n = Buffer.isBuffer(buf) ?
|
|
1662
|
+
const n = Buffer.isBuffer(buf) ? fs22.writeSync(this.fd, buf) : fs22.writeSync(this.fd, buf, "utf8");
|
|
1663
1663
|
const releasedBufObj = releaseWritingBuf(buf, this._len, n);
|
|
1664
1664
|
buf = releasedBufObj.writingBuf;
|
|
1665
1665
|
this._len = releasedBufObj.len;
|
|
@@ -1675,7 +1675,7 @@ var require_sonic_boom = __commonJS({
|
|
|
1675
1675
|
}
|
|
1676
1676
|
}
|
|
1677
1677
|
try {
|
|
1678
|
-
|
|
1678
|
+
fs22.fsyncSync(this.fd);
|
|
1679
1679
|
} catch {
|
|
1680
1680
|
}
|
|
1681
1681
|
}
|
|
@@ -1696,7 +1696,7 @@ var require_sonic_boom = __commonJS({
|
|
|
1696
1696
|
buf = mergeBuf(this._bufs[0], this._lens[0]);
|
|
1697
1697
|
}
|
|
1698
1698
|
try {
|
|
1699
|
-
const n =
|
|
1699
|
+
const n = fs22.writeSync(this.fd, buf);
|
|
1700
1700
|
buf = buf.subarray(n);
|
|
1701
1701
|
this._len = Math.max(this._len - n, 0);
|
|
1702
1702
|
if (buf.length <= 0) {
|
|
@@ -1724,13 +1724,13 @@ var require_sonic_boom = __commonJS({
|
|
|
1724
1724
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
|
|
1725
1725
|
if (this.sync) {
|
|
1726
1726
|
try {
|
|
1727
|
-
const written = Buffer.isBuffer(this._writingBuf) ?
|
|
1727
|
+
const written = Buffer.isBuffer(this._writingBuf) ? fs22.writeSync(this.fd, this._writingBuf) : fs22.writeSync(this.fd, this._writingBuf, "utf8");
|
|
1728
1728
|
release(null, written);
|
|
1729
1729
|
} catch (err) {
|
|
1730
1730
|
release(err);
|
|
1731
1731
|
}
|
|
1732
1732
|
} else {
|
|
1733
|
-
|
|
1733
|
+
fs22.write(this.fd, this._writingBuf, release);
|
|
1734
1734
|
}
|
|
1735
1735
|
}
|
|
1736
1736
|
function actualWriteBuffer() {
|
|
@@ -1739,7 +1739,7 @@ var require_sonic_boom = __commonJS({
|
|
|
1739
1739
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
|
|
1740
1740
|
if (this.sync) {
|
|
1741
1741
|
try {
|
|
1742
|
-
const written =
|
|
1742
|
+
const written = fs22.writeSync(this.fd, this._writingBuf);
|
|
1743
1743
|
release(null, written);
|
|
1744
1744
|
} catch (err) {
|
|
1745
1745
|
release(err);
|
|
@@ -1748,7 +1748,7 @@ var require_sonic_boom = __commonJS({
|
|
|
1748
1748
|
if (kCopyBuffer) {
|
|
1749
1749
|
this._writingBuf = Buffer.from(this._writingBuf);
|
|
1750
1750
|
}
|
|
1751
|
-
|
|
1751
|
+
fs22.write(this.fd, this._writingBuf, release);
|
|
1752
1752
|
}
|
|
1753
1753
|
}
|
|
1754
1754
|
function actualClose(sonic) {
|
|
@@ -1764,12 +1764,12 @@ var require_sonic_boom = __commonJS({
|
|
|
1764
1764
|
sonic._lens = [];
|
|
1765
1765
|
assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
|
|
1766
1766
|
try {
|
|
1767
|
-
|
|
1767
|
+
fs22.fsync(sonic.fd, closeWrapped);
|
|
1768
1768
|
} catch {
|
|
1769
1769
|
}
|
|
1770
1770
|
function closeWrapped() {
|
|
1771
1771
|
if (sonic.fd !== 1 && sonic.fd !== 2) {
|
|
1772
|
-
|
|
1772
|
+
fs22.close(sonic.fd, done);
|
|
1773
1773
|
} else {
|
|
1774
1774
|
done();
|
|
1775
1775
|
}
|
|
@@ -4391,6 +4391,16 @@ var init_methods = __esm({
|
|
|
4391
4391
|
"git:worktree:create",
|
|
4392
4392
|
"git:worktree:remove",
|
|
4393
4393
|
"capabilities:get",
|
|
4394
|
+
// ---- persona:* (老板侧管理 + 插话;alice 走 persona-bound WS 简化协议,不在此白名单) ----
|
|
4395
|
+
"persona:create",
|
|
4396
|
+
"persona:list",
|
|
4397
|
+
"persona:get",
|
|
4398
|
+
"persona:update",
|
|
4399
|
+
"persona:delete",
|
|
4400
|
+
"persona:issueToken",
|
|
4401
|
+
"persona:revokeToken",
|
|
4402
|
+
"persona:listSubSessions",
|
|
4403
|
+
"persona:appendOwnerMessage",
|
|
4394
4404
|
"info",
|
|
4395
4405
|
"ping"
|
|
4396
4406
|
];
|
|
@@ -4398,17 +4408,10 @@ var init_methods = __esm({
|
|
|
4398
4408
|
});
|
|
4399
4409
|
|
|
4400
4410
|
// ../protocol/src/events.ts
|
|
4401
|
-
var
|
|
4411
|
+
var HISTORY_USER_META_VALUES, ASK_USER_QUESTION_TOOL_NAME;
|
|
4402
4412
|
var init_events = __esm({
|
|
4403
4413
|
"../protocol/src/events.ts"() {
|
|
4404
4414
|
"use strict";
|
|
4405
|
-
SESSION_STATUS_VALUES = [
|
|
4406
|
-
"idle",
|
|
4407
|
-
"running",
|
|
4408
|
-
"stopped",
|
|
4409
|
-
"error",
|
|
4410
|
-
"observing"
|
|
4411
|
-
];
|
|
4412
4415
|
HISTORY_USER_META_VALUES = [
|
|
4413
4416
|
"task-notification",
|
|
4414
4417
|
"slash-command",
|
|
@@ -4873,8 +4876,8 @@ var init_parseUtil = __esm({
|
|
|
4873
4876
|
init_errors2();
|
|
4874
4877
|
init_en();
|
|
4875
4878
|
makeIssue = (params) => {
|
|
4876
|
-
const { data, path:
|
|
4877
|
-
const fullPath = [...
|
|
4879
|
+
const { data, path: path23, errorMaps, issueData } = params;
|
|
4880
|
+
const fullPath = [...path23, ...issueData.path || []];
|
|
4878
4881
|
const fullIssue = {
|
|
4879
4882
|
...issueData,
|
|
4880
4883
|
path: fullPath
|
|
@@ -5185,11 +5188,11 @@ var init_types = __esm({
|
|
|
5185
5188
|
init_parseUtil();
|
|
5186
5189
|
init_util();
|
|
5187
5190
|
ParseInputLazyPath = class {
|
|
5188
|
-
constructor(parent, value,
|
|
5191
|
+
constructor(parent, value, path23, key) {
|
|
5189
5192
|
this._cachedPath = [];
|
|
5190
5193
|
this.parent = parent;
|
|
5191
5194
|
this.data = value;
|
|
5192
|
-
this._path =
|
|
5195
|
+
this._path = path23;
|
|
5193
5196
|
this._key = key;
|
|
5194
5197
|
}
|
|
5195
5198
|
get path() {
|
|
@@ -8572,14 +8575,136 @@ var init_zod = __esm({
|
|
|
8572
8575
|
}
|
|
8573
8576
|
});
|
|
8574
8577
|
|
|
8578
|
+
// ../protocol/src/persona-schemas.ts
|
|
8579
|
+
var ALLOWED_PERSONA_TOOLS, PersonaTokenEntrySchema, PersonaFileSchema, PersonaHelloFrameSchema, PersonaSendFrameSchema, PersonaResetFrameSchema, PersonaPingFrameSchema, PersonaReadyFrameSchema, PersonaHistoryEventFrameSchema, PersonaHistoryDoneFrameSchema, PersonaEventFrameSchema, PersonaErrorFrameSchema, PersonaPongFrameSchema, PersonaClientFrameSchema, PersonaServerFrameSchema, PersonaCreateArgs, PersonaIdArgs, PersonaUpdateArgs, PersonaIssueTokenArgs, PersonaRevokeTokenArgs, PersonaAppendOwnerMessageArgs;
|
|
8580
|
+
var init_persona_schemas = __esm({
|
|
8581
|
+
"../protocol/src/persona-schemas.ts"() {
|
|
8582
|
+
"use strict";
|
|
8583
|
+
init_zod();
|
|
8584
|
+
ALLOWED_PERSONA_TOOLS = ["Read", "Grep", "Glob"];
|
|
8585
|
+
PersonaTokenEntrySchema = external_exports.object({
|
|
8586
|
+
label: external_exports.string().min(1),
|
|
8587
|
+
issuedAt: external_exports.string(),
|
|
8588
|
+
revoked: external_exports.boolean().optional()
|
|
8589
|
+
});
|
|
8590
|
+
PersonaFileSchema = external_exports.object({
|
|
8591
|
+
personaId: external_exports.string().min(1),
|
|
8592
|
+
label: external_exports.string().min(1),
|
|
8593
|
+
// cwd 必须是绝对路径;老板侧创建时 daemon 校验
|
|
8594
|
+
cwd: external_exports.string().refine((p) => p.startsWith("/"), { message: "cwd must be absolute" }),
|
|
8595
|
+
model: external_exports.string().optional(),
|
|
8596
|
+
systemPromptAppend: external_exports.string().optional(),
|
|
8597
|
+
// L1 锁死:permissionMode 只能是 'plan',toolAllowlist 只能是 ALLOWED_PERSONA_TOOLS 子集
|
|
8598
|
+
permissionMode: external_exports.literal("plan"),
|
|
8599
|
+
toolAllowlist: external_exports.array(external_exports.enum(ALLOWED_PERSONA_TOOLS)),
|
|
8600
|
+
public: external_exports.boolean(),
|
|
8601
|
+
tokenMap: external_exports.record(external_exports.string(), PersonaTokenEntrySchema),
|
|
8602
|
+
createdAt: external_exports.string(),
|
|
8603
|
+
updatedAt: external_exports.string()
|
|
8604
|
+
});
|
|
8605
|
+
PersonaHelloFrameSchema = external_exports.object({
|
|
8606
|
+
kind: external_exports.literal("persona-hello"),
|
|
8607
|
+
token: external_exports.string().min(1)
|
|
8608
|
+
});
|
|
8609
|
+
PersonaSendFrameSchema = external_exports.object({
|
|
8610
|
+
kind: external_exports.literal("send"),
|
|
8611
|
+
text: external_exports.string()
|
|
8612
|
+
});
|
|
8613
|
+
PersonaResetFrameSchema = external_exports.object({
|
|
8614
|
+
kind: external_exports.literal("reset")
|
|
8615
|
+
});
|
|
8616
|
+
PersonaPingFrameSchema = external_exports.object({
|
|
8617
|
+
kind: external_exports.literal("ping")
|
|
8618
|
+
});
|
|
8619
|
+
PersonaReadyFrameSchema = external_exports.object({
|
|
8620
|
+
kind: external_exports.literal("persona-ready"),
|
|
8621
|
+
subSessionId: external_exports.string(),
|
|
8622
|
+
personaLabel: external_exports.string(),
|
|
8623
|
+
cwd: external_exports.string(),
|
|
8624
|
+
model: external_exports.string().optional()
|
|
8625
|
+
});
|
|
8626
|
+
PersonaHistoryEventFrameSchema = external_exports.object({
|
|
8627
|
+
kind: external_exports.literal("history-event"),
|
|
8628
|
+
event: external_exports.unknown()
|
|
8629
|
+
});
|
|
8630
|
+
PersonaHistoryDoneFrameSchema = external_exports.object({
|
|
8631
|
+
kind: external_exports.literal("history-done")
|
|
8632
|
+
});
|
|
8633
|
+
PersonaEventFrameSchema = external_exports.object({
|
|
8634
|
+
kind: external_exports.literal("event"),
|
|
8635
|
+
event: external_exports.unknown()
|
|
8636
|
+
});
|
|
8637
|
+
PersonaErrorFrameSchema = external_exports.object({
|
|
8638
|
+
kind: external_exports.literal("error"),
|
|
8639
|
+
code: external_exports.enum([
|
|
8640
|
+
"TOKEN_INVALID",
|
|
8641
|
+
"TOKEN_REVOKED",
|
|
8642
|
+
"PERSONA_DELETED",
|
|
8643
|
+
"PERSONA_NOT_PUBLIC",
|
|
8644
|
+
"INTERNAL"
|
|
8645
|
+
]),
|
|
8646
|
+
message: external_exports.string()
|
|
8647
|
+
});
|
|
8648
|
+
PersonaPongFrameSchema = external_exports.object({
|
|
8649
|
+
kind: external_exports.literal("pong")
|
|
8650
|
+
});
|
|
8651
|
+
PersonaClientFrameSchema = external_exports.discriminatedUnion("kind", [
|
|
8652
|
+
PersonaHelloFrameSchema,
|
|
8653
|
+
PersonaSendFrameSchema,
|
|
8654
|
+
PersonaResetFrameSchema,
|
|
8655
|
+
PersonaPingFrameSchema
|
|
8656
|
+
]);
|
|
8657
|
+
PersonaServerFrameSchema = external_exports.discriminatedUnion("kind", [
|
|
8658
|
+
PersonaReadyFrameSchema,
|
|
8659
|
+
PersonaHistoryEventFrameSchema,
|
|
8660
|
+
PersonaHistoryDoneFrameSchema,
|
|
8661
|
+
PersonaEventFrameSchema,
|
|
8662
|
+
PersonaErrorFrameSchema,
|
|
8663
|
+
PersonaPongFrameSchema
|
|
8664
|
+
]);
|
|
8665
|
+
PersonaCreateArgs = external_exports.object({
|
|
8666
|
+
label: external_exports.string().min(1),
|
|
8667
|
+
cwd: external_exports.string().min(1),
|
|
8668
|
+
model: external_exports.string().optional(),
|
|
8669
|
+
systemPromptAppend: external_exports.string().optional()
|
|
8670
|
+
});
|
|
8671
|
+
PersonaIdArgs = external_exports.object({
|
|
8672
|
+
personaId: external_exports.string().min(1)
|
|
8673
|
+
});
|
|
8674
|
+
PersonaUpdateArgs = external_exports.object({
|
|
8675
|
+
personaId: external_exports.string().min(1),
|
|
8676
|
+
patch: external_exports.object({
|
|
8677
|
+
label: external_exports.string().min(1).optional(),
|
|
8678
|
+
cwd: external_exports.string().min(1).optional(),
|
|
8679
|
+
model: external_exports.string().optional(),
|
|
8680
|
+
systemPromptAppend: external_exports.string().optional(),
|
|
8681
|
+
public: external_exports.boolean().optional()
|
|
8682
|
+
}).strict()
|
|
8683
|
+
});
|
|
8684
|
+
PersonaIssueTokenArgs = external_exports.object({
|
|
8685
|
+
personaId: external_exports.string().min(1),
|
|
8686
|
+
label: external_exports.string().min(1)
|
|
8687
|
+
});
|
|
8688
|
+
PersonaRevokeTokenArgs = external_exports.object({
|
|
8689
|
+
personaId: external_exports.string().min(1),
|
|
8690
|
+
token: external_exports.string().min(1)
|
|
8691
|
+
});
|
|
8692
|
+
PersonaAppendOwnerMessageArgs = external_exports.object({
|
|
8693
|
+
personaId: external_exports.string().min(1),
|
|
8694
|
+
subSessionId: external_exports.string().min(1),
|
|
8695
|
+
text: external_exports.string().min(1)
|
|
8696
|
+
});
|
|
8697
|
+
}
|
|
8698
|
+
});
|
|
8699
|
+
|
|
8575
8700
|
// ../protocol/src/schemas.ts
|
|
8576
|
-
var
|
|
8701
|
+
var UsageSchema, ContextUsageSchema, sessionMetaShape, SessionMetaSchema, ModelInfoSchema, ModeInfoSchema, ConfigFieldSchemaSchema, CapabilitiesGetArgs, CapabilitiesResponseSchema, AllowRuleSchema, SessionFileSchema, ParsedEventBase, HistoryUserMetaSchema, SubagentToolStatsSchema, StructuredPatchHunkSchema, ToolResultExtraSchema, MemoryEntrySchema, AskQuestionOptionSchema, AskQuestionItemSchema, ParsedEventSchema, SessionCreateArgs, SessionIdArgs, SessionUpdateArgs, SessionSendArgs, SessionRewindArgs, SessionRewindResponseSchema, SessionRewindDiffArgs, RewindDiffHunkSchema, RewindDiffFileSchema, SessionRewindDiffResponseSchema, SessionRewindableMessageIdsArgs, SessionRewindableMessageIdsResponseSchema, SessionResumeArgs, SessionForkArgs, SessionForkResponseSchema, SessionObserveArgs, SessionEventsArgs, PermissionRespondArgs, HistoryListArgs, HistoryReadArgs, HistorySubagentsArgs, HistorySubagentReadArgs, WorkspaceListArgs, WorkspaceReadArgs, SkillsListArgs, SessionSubscribeArgs, SessionPinArgs, SessionReorderPinsArgs, GitRootArgs, GitRootResponseSchema, GitBranchArgs, GitBranchResponseSchema, GitBranchesArgs, GitBranchesResponseSchema, GitWorktreePrefixArgs, GitWorktreePrefixResponseSchema, GitWorktreeCreateArgs, GitWorktreeCreateResponseSchema, GitWorktreeRemoveArgs, GitWorktreeRemoveResponseSchema, HistoryRecentDirsArgs, RecentDirEntrySchema, HistoryRecentDirsResponseSchema, SessionQuestionFrameSchema, AnswerQuestionArgs, AnswerQuestionResponseSchema, AuthRequestFrameSchema, AuthOkFrameSchema, TunnelExitedEventSchema;
|
|
8577
8702
|
var init_schemas = __esm({
|
|
8578
8703
|
"../protocol/src/schemas.ts"() {
|
|
8579
8704
|
"use strict";
|
|
8580
8705
|
init_zod();
|
|
8581
8706
|
init_events();
|
|
8582
|
-
|
|
8707
|
+
init_persona_schemas();
|
|
8583
8708
|
UsageSchema = external_exports.object({
|
|
8584
8709
|
input_tokens: external_exports.number().int().nonnegative().optional(),
|
|
8585
8710
|
cache_read_input_tokens: external_exports.number().int().nonnegative().optional(),
|
|
@@ -8853,6 +8978,16 @@ var init_schemas = __esm({
|
|
|
8853
8978
|
...ParsedEventBase,
|
|
8854
8979
|
kind: external_exports.literal("meta_update"),
|
|
8855
8980
|
patch: SessionMetaSchema
|
|
8981
|
+
}),
|
|
8982
|
+
// 系统消息(CC 自家系统提示 / 老板插话补充);persona 场景下区分两类:
|
|
8983
|
+
// metaSource='cc' → CC 注入的系统消息(默认低视觉权重渲染)
|
|
8984
|
+
// metaSource='owner' → 老板通过 persona:appendOwnerMessage 插入的话(蓝色气泡)
|
|
8985
|
+
// 缺省视为 'cc';现有非 persona 场景保持兼容(不需要 owner 时不下发该字段)
|
|
8986
|
+
external_exports.object({
|
|
8987
|
+
...ParsedEventBase,
|
|
8988
|
+
kind: external_exports.literal("meta-text"),
|
|
8989
|
+
text: external_exports.string(),
|
|
8990
|
+
metaSource: external_exports.enum(["cc", "owner"]).optional()
|
|
8856
8991
|
})
|
|
8857
8992
|
]);
|
|
8858
8993
|
SessionCreateArgs = external_exports.object({
|
|
@@ -9042,13 +9177,6 @@ var init_schemas = __esm({
|
|
|
9042
9177
|
// UI 单卡承载多 question;空数组语义无效,schema 必拒
|
|
9043
9178
|
questions: external_exports.array(AskQuestionItemSchema).min(1)
|
|
9044
9179
|
});
|
|
9045
|
-
SessionQuestionClearedFrameSchema = external_exports.object({
|
|
9046
|
-
type: external_exports.literal("session:question:cleared"),
|
|
9047
|
-
sessionId: external_exports.string().min(1),
|
|
9048
|
-
toolUseId: external_exports.string().min(1),
|
|
9049
|
-
// record<string,string>:与 AnswerQuestionArgs.answers 同形状(question text → label)
|
|
9050
|
-
answers: external_exports.record(external_exports.string(), external_exports.string()).optional()
|
|
9051
|
-
});
|
|
9052
9180
|
AnswerQuestionArgs = external_exports.object({
|
|
9053
9181
|
sessionId: external_exports.string().min(1),
|
|
9054
9182
|
toolUseId: external_exports.string().min(1),
|
|
@@ -9071,22 +9199,6 @@ var init_schemas = __esm({
|
|
|
9071
9199
|
subdomain: external_exports.string().nullable(),
|
|
9072
9200
|
url: external_exports.string().nullable()
|
|
9073
9201
|
});
|
|
9074
|
-
InfoRunningSessionSchema = external_exports.object({
|
|
9075
|
-
sessionId: external_exports.string().min(1),
|
|
9076
|
-
status: SessionStatusSchema,
|
|
9077
|
-
freshSpawn: external_exports.boolean(),
|
|
9078
|
-
pendingPermissionsCount: external_exports.number().int().nonnegative(),
|
|
9079
|
-
pendingQuestionsCount: external_exports.number().int().nonnegative()
|
|
9080
|
-
});
|
|
9081
|
-
InfoResponseSchema = external_exports.object({
|
|
9082
|
-
type: external_exports.literal("info"),
|
|
9083
|
-
version: external_exports.string(),
|
|
9084
|
-
protocolVersion: external_exports.number(),
|
|
9085
|
-
hostname: external_exports.string(),
|
|
9086
|
-
os: external_exports.string(),
|
|
9087
|
-
tools: external_exports.array(external_exports.object({ id: external_exports.string(), available: external_exports.boolean() })),
|
|
9088
|
-
runningSessions: external_exports.array(InfoRunningSessionSchema)
|
|
9089
|
-
});
|
|
9090
9202
|
}
|
|
9091
9203
|
});
|
|
9092
9204
|
|
|
@@ -9108,6 +9220,7 @@ var init_runtime = __esm({
|
|
|
9108
9220
|
init_errors();
|
|
9109
9221
|
init_schemas();
|
|
9110
9222
|
init_frames();
|
|
9223
|
+
init_persona_schemas();
|
|
9111
9224
|
}
|
|
9112
9225
|
});
|
|
9113
9226
|
|
|
@@ -9684,11 +9797,11 @@ var init_lib = __esm({
|
|
|
9684
9797
|
}
|
|
9685
9798
|
}
|
|
9686
9799
|
},
|
|
9687
|
-
addToPath: function addToPath(
|
|
9688
|
-
var last =
|
|
9800
|
+
addToPath: function addToPath(path23, added, removed, oldPosInc, options) {
|
|
9801
|
+
var last = path23.lastComponent;
|
|
9689
9802
|
if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
|
|
9690
9803
|
return {
|
|
9691
|
-
oldPos:
|
|
9804
|
+
oldPos: path23.oldPos + oldPosInc,
|
|
9692
9805
|
lastComponent: {
|
|
9693
9806
|
count: last.count + 1,
|
|
9694
9807
|
added,
|
|
@@ -9698,7 +9811,7 @@ var init_lib = __esm({
|
|
|
9698
9811
|
};
|
|
9699
9812
|
} else {
|
|
9700
9813
|
return {
|
|
9701
|
-
oldPos:
|
|
9814
|
+
oldPos: path23.oldPos + oldPosInc,
|
|
9702
9815
|
lastComponent: {
|
|
9703
9816
|
count: 1,
|
|
9704
9817
|
added,
|
|
@@ -9944,7 +10057,7 @@ function hashDirToCwd(hash) {
|
|
|
9944
10057
|
}
|
|
9945
10058
|
function safeStatMtime(p) {
|
|
9946
10059
|
try {
|
|
9947
|
-
return
|
|
10060
|
+
return import_node_fs8.default.statSync(p).mtimeMs;
|
|
9948
10061
|
} catch {
|
|
9949
10062
|
return 0;
|
|
9950
10063
|
}
|
|
@@ -9952,7 +10065,7 @@ function safeStatMtime(p) {
|
|
|
9952
10065
|
function readJsonlLines(file) {
|
|
9953
10066
|
let raw;
|
|
9954
10067
|
try {
|
|
9955
|
-
raw =
|
|
10068
|
+
raw = import_node_fs8.default.readFileSync(file, "utf8");
|
|
9956
10069
|
} catch (err) {
|
|
9957
10070
|
if (err.code === "ENOENT") return [];
|
|
9958
10071
|
throw err;
|
|
@@ -10129,10 +10242,10 @@ function attachmentToHistoryMessage(o, ts) {
|
|
|
10129
10242
|
const memories = raw.map((m) => {
|
|
10130
10243
|
if (!m || typeof m !== "object") return null;
|
|
10131
10244
|
const rec = m;
|
|
10132
|
-
const
|
|
10245
|
+
const path23 = typeof rec.path === "string" ? rec.path : null;
|
|
10133
10246
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
10134
|
-
if (!
|
|
10135
|
-
const entry = { path:
|
|
10247
|
+
if (!path23 || content == null) return null;
|
|
10248
|
+
const entry = { path: path23, content };
|
|
10136
10249
|
if (typeof rec.mtimeMs === "number") entry.mtimeMs = rec.mtimeMs;
|
|
10137
10250
|
return entry;
|
|
10138
10251
|
}).filter((m) => m !== null);
|
|
@@ -10168,8 +10281,8 @@ function attachmentDeferredToolsText(a) {
|
|
|
10168
10281
|
function readBackupContent(fileHistoryRoot, toolSessionId, backupFileName) {
|
|
10169
10282
|
if (backupFileName === null) return null;
|
|
10170
10283
|
try {
|
|
10171
|
-
return
|
|
10172
|
-
|
|
10284
|
+
return import_node_fs8.default.readFileSync(
|
|
10285
|
+
import_node_path8.default.join(fileHistoryRoot, toolSessionId, backupFileName),
|
|
10173
10286
|
"utf8"
|
|
10174
10287
|
);
|
|
10175
10288
|
} catch {
|
|
@@ -10178,19 +10291,19 @@ function readBackupContent(fileHistoryRoot, toolSessionId, backupFileName) {
|
|
|
10178
10291
|
}
|
|
10179
10292
|
function readCurrentContent(filePath) {
|
|
10180
10293
|
try {
|
|
10181
|
-
return
|
|
10294
|
+
return import_node_fs8.default.readFileSync(filePath, "utf8");
|
|
10182
10295
|
} catch (err) {
|
|
10183
10296
|
if (err.code === "ENOENT") return null;
|
|
10184
10297
|
return null;
|
|
10185
10298
|
}
|
|
10186
10299
|
}
|
|
10187
|
-
var
|
|
10300
|
+
var import_node_fs8, import_node_os2, import_node_path8, TASK_NOTIFICATION_RE, TASK_ID_RE, TOOL_USE_ID_RE, SLASH_COMMAND_RE, LOCAL_COMMAND_RE, SYSTEM_REMINDER_RE, OPENSPEC_BLOCK_RE, SKILL_HINT_RE, ATTACHMENT_SILENT_SUBTYPES, ClaudeHistoryReader;
|
|
10188
10301
|
var init_claude_history = __esm({
|
|
10189
10302
|
"src/tools/claude-history.ts"() {
|
|
10190
10303
|
"use strict";
|
|
10191
|
-
|
|
10304
|
+
import_node_fs8 = __toESM(require("fs"), 1);
|
|
10192
10305
|
import_node_os2 = __toESM(require("os"), 1);
|
|
10193
|
-
|
|
10306
|
+
import_node_path8 = __toESM(require("path"), 1);
|
|
10194
10307
|
init_lib();
|
|
10195
10308
|
init_tool_result_extra();
|
|
10196
10309
|
TASK_NOTIFICATION_RE = /<task-notification\b[\s\S]*?<\/task-notification>/i;
|
|
@@ -10214,14 +10327,14 @@ var init_claude_history = __esm({
|
|
|
10214
10327
|
// 每次 user 提交前 trackEdit 拷一份,作为 rewind 回退目标
|
|
10215
10328
|
fileHistoryRoot;
|
|
10216
10329
|
constructor(opts = {}) {
|
|
10217
|
-
const base = opts.baseDir ??
|
|
10218
|
-
this.projectsRoot =
|
|
10219
|
-
this.fileHistoryRoot =
|
|
10330
|
+
const base = opts.baseDir ?? import_node_path8.default.join(import_node_os2.default.homedir(), ".claude");
|
|
10331
|
+
this.projectsRoot = import_node_path8.default.join(base, "projects");
|
|
10332
|
+
this.fileHistoryRoot = import_node_path8.default.join(base, "file-history");
|
|
10220
10333
|
}
|
|
10221
10334
|
async listProjects() {
|
|
10222
10335
|
let entries;
|
|
10223
10336
|
try {
|
|
10224
|
-
entries =
|
|
10337
|
+
entries = import_node_fs8.default.readdirSync(this.projectsRoot, { withFileTypes: true });
|
|
10225
10338
|
} catch (err) {
|
|
10226
10339
|
if (err.code === "ENOENT") return [];
|
|
10227
10340
|
throw err;
|
|
@@ -10229,9 +10342,9 @@ var init_claude_history = __esm({
|
|
|
10229
10342
|
const out = [];
|
|
10230
10343
|
for (const ent of entries) {
|
|
10231
10344
|
if (!ent.isDirectory()) continue;
|
|
10232
|
-
const dir =
|
|
10233
|
-
const files =
|
|
10234
|
-
const updatedAtMs = files.reduce((m, f) => Math.max(m, safeStatMtime(
|
|
10345
|
+
const dir = import_node_path8.default.join(this.projectsRoot, ent.name);
|
|
10346
|
+
const files = import_node_fs8.default.readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
|
|
10347
|
+
const updatedAtMs = files.reduce((m, f) => Math.max(m, safeStatMtime(import_node_path8.default.join(dir, f))), 0);
|
|
10235
10348
|
out.push({
|
|
10236
10349
|
projectPath: hashDirToCwd(ent.name),
|
|
10237
10350
|
hashDir: ent.name,
|
|
@@ -10243,17 +10356,17 @@ var init_claude_history = __esm({
|
|
|
10243
10356
|
return out;
|
|
10244
10357
|
}
|
|
10245
10358
|
async listSessions(args) {
|
|
10246
|
-
const dir =
|
|
10359
|
+
const dir = import_node_path8.default.join(this.projectsRoot, cwdToHashDir(args.projectPath));
|
|
10247
10360
|
let files;
|
|
10248
10361
|
try {
|
|
10249
|
-
files =
|
|
10362
|
+
files = import_node_fs8.default.readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
|
|
10250
10363
|
} catch (err) {
|
|
10251
10364
|
if (err.code === "ENOENT") return [];
|
|
10252
10365
|
throw err;
|
|
10253
10366
|
}
|
|
10254
10367
|
const out = [];
|
|
10255
10368
|
for (const f of files) {
|
|
10256
|
-
const full =
|
|
10369
|
+
const full = import_node_path8.default.join(dir, f);
|
|
10257
10370
|
const toolSessionId = f.slice(0, -".jsonl".length);
|
|
10258
10371
|
const lines = readJsonlLines(full);
|
|
10259
10372
|
let summary = "";
|
|
@@ -10308,7 +10421,7 @@ var init_claude_history = __esm({
|
|
|
10308
10421
|
return out;
|
|
10309
10422
|
}
|
|
10310
10423
|
async read(args) {
|
|
10311
|
-
const file =
|
|
10424
|
+
const file = import_node_path8.default.join(
|
|
10312
10425
|
this.projectsRoot,
|
|
10313
10426
|
cwdToHashDir(args.cwd),
|
|
10314
10427
|
`${args.toolSessionId}.jsonl`
|
|
@@ -10341,7 +10454,7 @@ var init_claude_history = __esm({
|
|
|
10341
10454
|
// 独立目录路径:<projectsRoot>/<cwdHash>/<toolSessionId>/subagents/*.jsonl
|
|
10342
10455
|
// 返回 null 表示目录不存在(调用方回退旧实现);返回空数组表示目录存在但无 jsonl
|
|
10343
10456
|
listSubagentsFromDirectory(cwd, toolSessionId) {
|
|
10344
|
-
const dir =
|
|
10457
|
+
const dir = import_node_path8.default.join(
|
|
10345
10458
|
this.projectsRoot,
|
|
10346
10459
|
cwdToHashDir(cwd),
|
|
10347
10460
|
toolSessionId,
|
|
@@ -10349,7 +10462,7 @@ var init_claude_history = __esm({
|
|
|
10349
10462
|
);
|
|
10350
10463
|
let entries;
|
|
10351
10464
|
try {
|
|
10352
|
-
entries =
|
|
10465
|
+
entries = import_node_fs8.default.readdirSync(dir, { withFileTypes: true });
|
|
10353
10466
|
} catch (err) {
|
|
10354
10467
|
if (err.code === "ENOENT") return null;
|
|
10355
10468
|
return null;
|
|
@@ -10359,7 +10472,7 @@ var init_claude_history = __esm({
|
|
|
10359
10472
|
if (!e.isFile()) continue;
|
|
10360
10473
|
if (!e.name.startsWith("agent-") || !e.name.endsWith(".jsonl")) continue;
|
|
10361
10474
|
const subagentId = e.name.slice("agent-".length, -".jsonl".length);
|
|
10362
|
-
const filePath =
|
|
10475
|
+
const filePath = import_node_path8.default.join(dir, e.name);
|
|
10363
10476
|
const lines = readJsonlLines(filePath);
|
|
10364
10477
|
let firstText = "";
|
|
10365
10478
|
let messageCount = 0;
|
|
@@ -10376,7 +10489,7 @@ var init_claude_history = __esm({
|
|
|
10376
10489
|
return out;
|
|
10377
10490
|
}
|
|
10378
10491
|
listSubagentsFromMainJsonl(cwd, toolSessionId) {
|
|
10379
|
-
const file =
|
|
10492
|
+
const file = import_node_path8.default.join(
|
|
10380
10493
|
this.projectsRoot,
|
|
10381
10494
|
cwdToHashDir(cwd),
|
|
10382
10495
|
`${toolSessionId}.jsonl`
|
|
@@ -10411,7 +10524,7 @@ var init_claude_history = __esm({
|
|
|
10411
10524
|
}
|
|
10412
10525
|
// 独立文件路径:agent-<subagentId>.jsonl;文件不存在返回 null 让调用方回退旧实现
|
|
10413
10526
|
readSubagentFromFile(cwd, toolSessionId, subagentId) {
|
|
10414
|
-
const file =
|
|
10527
|
+
const file = import_node_path8.default.join(
|
|
10415
10528
|
this.projectsRoot,
|
|
10416
10529
|
cwdToHashDir(cwd),
|
|
10417
10530
|
toolSessionId,
|
|
@@ -10420,7 +10533,7 @@ var init_claude_history = __esm({
|
|
|
10420
10533
|
);
|
|
10421
10534
|
let exists = false;
|
|
10422
10535
|
try {
|
|
10423
|
-
exists =
|
|
10536
|
+
exists = import_node_fs8.default.statSync(file).isFile();
|
|
10424
10537
|
} catch {
|
|
10425
10538
|
return null;
|
|
10426
10539
|
}
|
|
@@ -10439,7 +10552,7 @@ var init_claude_history = __esm({
|
|
|
10439
10552
|
* "那一刻每个 tracked 文件对应的 backup 文件名"
|
|
10440
10553
|
*/
|
|
10441
10554
|
readFileHistorySnapshots(args) {
|
|
10442
|
-
const file =
|
|
10555
|
+
const file = import_node_path8.default.join(
|
|
10443
10556
|
this.projectsRoot,
|
|
10444
10557
|
cwdToHashDir(args.cwd),
|
|
10445
10558
|
`${args.toolSessionId}.jsonl`
|
|
@@ -10484,7 +10597,7 @@ var init_claude_history = __esm({
|
|
|
10484
10597
|
for (const [anchorId, target] of snapshots) {
|
|
10485
10598
|
let hasAny = false;
|
|
10486
10599
|
for (const [rawPath, backup] of Object.entries(target)) {
|
|
10487
|
-
const absPath =
|
|
10600
|
+
const absPath = import_node_path8.default.isAbsolute(rawPath) ? rawPath : import_node_path8.default.join(args.cwd, rawPath);
|
|
10488
10601
|
const backupContent = readBackupContent(
|
|
10489
10602
|
this.fileHistoryRoot,
|
|
10490
10603
|
args.toolSessionId,
|
|
@@ -10524,7 +10637,7 @@ var init_claude_history = __esm({
|
|
|
10524
10637
|
let totalInsertions = 0;
|
|
10525
10638
|
let totalDeletions = 0;
|
|
10526
10639
|
for (const [rawPath, backup] of Object.entries(target)) {
|
|
10527
|
-
const absPath =
|
|
10640
|
+
const absPath = import_node_path8.default.isAbsolute(rawPath) ? rawPath : import_node_path8.default.join(args.cwd, rawPath);
|
|
10528
10641
|
const backupContent = readBackupContent(
|
|
10529
10642
|
this.fileHistoryRoot,
|
|
10530
10643
|
args.toolSessionId,
|
|
@@ -10571,7 +10684,7 @@ var init_claude_history = __esm({
|
|
|
10571
10684
|
};
|
|
10572
10685
|
}
|
|
10573
10686
|
readSubagentFromMainJsonl(cwd, toolSessionId, subagentId) {
|
|
10574
|
-
const file =
|
|
10687
|
+
const file = import_node_path8.default.join(
|
|
10575
10688
|
this.projectsRoot,
|
|
10576
10689
|
cwdToHashDir(cwd),
|
|
10577
10690
|
`${toolSessionId}.jsonl`
|
|
@@ -10595,27 +10708,27 @@ var init_claude_history = __esm({
|
|
|
10595
10708
|
// src/tools/claude.ts
|
|
10596
10709
|
function macOSDesktopCandidates(home) {
|
|
10597
10710
|
return [
|
|
10598
|
-
|
|
10711
|
+
import_node_path9.default.join(home, "Applications", "Claude.app", "Contents", "Resources", "app.asar.unpacked", "node_modules", "@anthropic-ai", "claude-code", "cli.js"),
|
|
10599
10712
|
"/Applications/Claude.app/Contents/Resources/app.asar.unpacked/node_modules/@anthropic-ai/claude-code/cli.js"
|
|
10600
10713
|
];
|
|
10601
10714
|
}
|
|
10602
10715
|
function probeViaWhich() {
|
|
10603
10716
|
try {
|
|
10604
10717
|
const out = (0, import_node_child_process2.execFileSync)("which", ["claude"], { encoding: "utf8" }).trim();
|
|
10605
|
-
if (out &&
|
|
10718
|
+
if (out && import_node_fs9.default.existsSync(out)) return out;
|
|
10606
10719
|
} catch {
|
|
10607
10720
|
}
|
|
10608
10721
|
return null;
|
|
10609
10722
|
}
|
|
10610
10723
|
async function probeClaude(env = process.env, home = import_node_os3.default.homedir()) {
|
|
10611
|
-
if (env.CLAUDE_BIN &&
|
|
10724
|
+
if (env.CLAUDE_BIN && import_node_fs9.default.existsSync(env.CLAUDE_BIN)) {
|
|
10612
10725
|
return { available: true, path: env.CLAUDE_BIN };
|
|
10613
10726
|
}
|
|
10614
10727
|
const w = probeViaWhich();
|
|
10615
10728
|
if (w) return { available: true, path: w };
|
|
10616
10729
|
if (process.platform === "darwin") {
|
|
10617
10730
|
for (const candidate of macOSDesktopCandidates(home)) {
|
|
10618
|
-
if (
|
|
10731
|
+
if (import_node_fs9.default.existsSync(candidate)) {
|
|
10619
10732
|
return { available: true, path: candidate };
|
|
10620
10733
|
}
|
|
10621
10734
|
}
|
|
@@ -10640,7 +10753,14 @@ function buildSpawnArgs(ctx) {
|
|
|
10640
10753
|
"--verbose"
|
|
10641
10754
|
];
|
|
10642
10755
|
if (ctx.model) args.push("--model", ctx.model);
|
|
10643
|
-
|
|
10756
|
+
const hardcoded = ctx.hardcodedToolAllowlist && ctx.hardcodedToolAllowlist.length > 0;
|
|
10757
|
+
if (hardcoded) {
|
|
10758
|
+
args.push("--permission-mode", ctx.hardcodedPermissionMode ?? "plan");
|
|
10759
|
+
args.push("--add-dir", ctx.cwd);
|
|
10760
|
+
args.push("--disallowed-tools", HARDCODED_DISALLOWED_TOOLS.join(" "));
|
|
10761
|
+
} else if (ctx.permissionMode) {
|
|
10762
|
+
args.push("--permission-mode", ctx.permissionMode);
|
|
10763
|
+
}
|
|
10644
10764
|
if (ctx.effort) args.push("--effort", ctx.effort);
|
|
10645
10765
|
if (ctx.toolSessionId) args.push("--resume", ctx.toolSessionId);
|
|
10646
10766
|
return args;
|
|
@@ -10919,10 +11039,10 @@ function parseAttachment(obj) {
|
|
|
10919
11039
|
const memories = raw.map((m) => {
|
|
10920
11040
|
if (!m || typeof m !== "object") return null;
|
|
10921
11041
|
const rec = m;
|
|
10922
|
-
const
|
|
11042
|
+
const path23 = typeof rec.path === "string" ? rec.path : null;
|
|
10923
11043
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
10924
|
-
if (!
|
|
10925
|
-
const out = { path:
|
|
11044
|
+
if (!path23 || content == null) return null;
|
|
11045
|
+
const out = { path: path23, content };
|
|
10926
11046
|
if (typeof rec.mtimeMs === "number") out.mtimeMs = rec.mtimeMs;
|
|
10927
11047
|
return out;
|
|
10928
11048
|
}).filter((m) => m !== null);
|
|
@@ -11026,18 +11146,19 @@ function encodeClaudeStdin(text) {
|
|
|
11026
11146
|
};
|
|
11027
11147
|
return JSON.stringify(frame) + "\n";
|
|
11028
11148
|
}
|
|
11029
|
-
var import_node_child_process, import_node_child_process2,
|
|
11149
|
+
var import_node_child_process, import_node_child_process2, import_node_fs9, import_node_os3, import_node_path9, HARDCODED_DISALLOWED_TOOLS, ATTACHMENT_SILENT_SUBTYPES2, unknownTypeHandler, ATTACHMENT_RE, IMAGE_EXT_MIME, CLAUDE_MODELS, CLAUDE_PERMISSION_MODES, CLAUDE_CAPABILITIES, ClaudeAdapter;
|
|
11030
11150
|
var init_claude = __esm({
|
|
11031
11151
|
"src/tools/claude.ts"() {
|
|
11032
11152
|
"use strict";
|
|
11033
11153
|
import_node_child_process = require("child_process");
|
|
11034
11154
|
import_node_child_process2 = require("child_process");
|
|
11035
|
-
|
|
11155
|
+
import_node_fs9 = __toESM(require("fs"), 1);
|
|
11036
11156
|
import_node_os3 = __toESM(require("os"), 1);
|
|
11037
|
-
|
|
11157
|
+
import_node_path9 = __toESM(require("path"), 1);
|
|
11038
11158
|
init_protocol();
|
|
11039
11159
|
init_claude_history();
|
|
11040
11160
|
init_tool_result_extra();
|
|
11161
|
+
HARDCODED_DISALLOWED_TOOLS = ["Bash", "Edit", "Write", "WebFetch", "WebSearch", "Task"];
|
|
11041
11162
|
ATTACHMENT_SILENT_SUBTYPES2 = /* @__PURE__ */ new Set([
|
|
11042
11163
|
"hook_additional_context",
|
|
11043
11164
|
"hook_success",
|
|
@@ -14789,7 +14910,7 @@ var require_websocket_server = __commonJS({
|
|
|
14789
14910
|
// src/run-case/recorder.ts
|
|
14790
14911
|
function startRunCaseRecorder(opts) {
|
|
14791
14912
|
const now = opts.now ?? Date.now;
|
|
14792
|
-
const dir =
|
|
14913
|
+
const dir = import_node_path21.default.dirname(opts.recordPath);
|
|
14793
14914
|
let stream = null;
|
|
14794
14915
|
let closing = false;
|
|
14795
14916
|
let closedSettled = false;
|
|
@@ -14803,8 +14924,8 @@ function startRunCaseRecorder(opts) {
|
|
|
14803
14924
|
});
|
|
14804
14925
|
const ensureStream = () => {
|
|
14805
14926
|
if (stream) return stream;
|
|
14806
|
-
|
|
14807
|
-
stream =
|
|
14927
|
+
import_node_fs21.default.mkdirSync(dir, { recursive: true });
|
|
14928
|
+
stream = import_node_fs21.default.createWriteStream(opts.recordPath, { flags: "a" });
|
|
14808
14929
|
stream.on("close", () => closedResolve());
|
|
14809
14930
|
return stream;
|
|
14810
14931
|
};
|
|
@@ -14829,12 +14950,12 @@ function startRunCaseRecorder(opts) {
|
|
|
14829
14950
|
};
|
|
14830
14951
|
return { tap, close, closed };
|
|
14831
14952
|
}
|
|
14832
|
-
var
|
|
14953
|
+
var import_node_fs21, import_node_path21;
|
|
14833
14954
|
var init_recorder = __esm({
|
|
14834
14955
|
"src/run-case/recorder.ts"() {
|
|
14835
14956
|
"use strict";
|
|
14836
|
-
|
|
14837
|
-
|
|
14957
|
+
import_node_fs21 = __toESM(require("fs"), 1);
|
|
14958
|
+
import_node_path21 = __toESM(require("path"), 1);
|
|
14838
14959
|
}
|
|
14839
14960
|
});
|
|
14840
14961
|
|
|
@@ -14877,7 +14998,7 @@ var init_wire = __esm({
|
|
|
14877
14998
|
// src/run-case/controller.ts
|
|
14878
14999
|
async function runController(opts) {
|
|
14879
15000
|
const now = opts.now ?? Date.now;
|
|
14880
|
-
const cwd = opts.cwd ?? (0,
|
|
15001
|
+
const cwd = opts.cwd ?? (0, import_node_fs22.mkdtempSync)(import_node_path22.default.join(import_node_os12.default.tmpdir(), "clawd-runcase-"));
|
|
14881
15002
|
const ownsCwd = opts.cwd === void 0;
|
|
14882
15003
|
const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
|
|
14883
15004
|
const spawnCtx = { cwd };
|
|
@@ -15038,19 +15159,19 @@ async function runController(opts) {
|
|
|
15038
15159
|
if (sigintHandler) process.off("SIGINT", sigintHandler);
|
|
15039
15160
|
if (ownsCwd) {
|
|
15040
15161
|
try {
|
|
15041
|
-
(0,
|
|
15162
|
+
(0, import_node_fs22.rmSync)(cwd, { recursive: true, force: true });
|
|
15042
15163
|
} catch {
|
|
15043
15164
|
}
|
|
15044
15165
|
}
|
|
15045
15166
|
return exitCode ?? 0;
|
|
15046
15167
|
}
|
|
15047
|
-
var
|
|
15168
|
+
var import_node_fs22, import_node_os12, import_node_path22;
|
|
15048
15169
|
var init_controller = __esm({
|
|
15049
15170
|
"src/run-case/controller.ts"() {
|
|
15050
15171
|
"use strict";
|
|
15051
|
-
|
|
15172
|
+
import_node_fs22 = require("fs");
|
|
15052
15173
|
import_node_os12 = __toESM(require("os"), 1);
|
|
15053
|
-
|
|
15174
|
+
import_node_path22 = __toESM(require("path"), 1);
|
|
15054
15175
|
init_claude();
|
|
15055
15176
|
init_stdout_splitter();
|
|
15056
15177
|
init_permission_stdio();
|
|
@@ -15275,8 +15396,8 @@ Env (advanced):
|
|
|
15275
15396
|
`;
|
|
15276
15397
|
|
|
15277
15398
|
// src/index.ts
|
|
15278
|
-
var
|
|
15279
|
-
var
|
|
15399
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
15400
|
+
var import_node_fs20 = __toESM(require("fs"), 1);
|
|
15280
15401
|
|
|
15281
15402
|
// src/logger.ts
|
|
15282
15403
|
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
@@ -15495,9 +15616,11 @@ function cloneState(s) {
|
|
|
15495
15616
|
pendingQuestions: s.pendingQuestions,
|
|
15496
15617
|
freshSpawn: s.freshSpawn,
|
|
15497
15618
|
procAlive: s.procAlive,
|
|
15498
|
-
seenUuids: s.seenUuids
|
|
15619
|
+
seenUuids: s.seenUuids,
|
|
15620
|
+
subSessionMeta: s.subSessionMeta
|
|
15499
15621
|
};
|
|
15500
15622
|
}
|
|
15623
|
+
var IDLE_KILL_DELAY_MS = 5e3;
|
|
15501
15624
|
var SEEN_UUID_CAP = 2e3;
|
|
15502
15625
|
function applyEventBatch(state, events, deps) {
|
|
15503
15626
|
if (events.length === 0) return { state, effects: [] };
|
|
@@ -15530,14 +15653,21 @@ function emitSessionEvent(sessionId, event, target) {
|
|
|
15530
15653
|
};
|
|
15531
15654
|
return target ? { kind: "emit-frame", frame, target } : { kind: "emit-frame", frame };
|
|
15532
15655
|
}
|
|
15533
|
-
function buildSpawnContext(
|
|
15534
|
-
|
|
15656
|
+
function buildSpawnContext(state) {
|
|
15657
|
+
const file = state.file;
|
|
15658
|
+
const ctx = {
|
|
15535
15659
|
cwd: file.cwd,
|
|
15536
15660
|
toolSessionId: file.toolSessionId,
|
|
15537
15661
|
model: file.model,
|
|
15538
15662
|
permissionMode: file.permissionMode,
|
|
15539
15663
|
effort: file.effort
|
|
15540
15664
|
};
|
|
15665
|
+
const meta = state.subSessionMeta;
|
|
15666
|
+
if (meta?.toolAllowlist && meta.toolAllowlist.length > 0) {
|
|
15667
|
+
ctx.hardcodedToolAllowlist = meta.toolAllowlist;
|
|
15668
|
+
ctx.hardcodedPermissionMode = meta.permissionMode ?? "plan";
|
|
15669
|
+
}
|
|
15670
|
+
return ctx;
|
|
15541
15671
|
}
|
|
15542
15672
|
function sessionInfoFrame(file) {
|
|
15543
15673
|
const frame = { type: "session:info", ...file };
|
|
@@ -15672,10 +15802,16 @@ function pushEventToBuffer(state, event, deps) {
|
|
|
15672
15802
|
next.bufferStartSeq = trimmed[0]?.seq ?? seq;
|
|
15673
15803
|
}
|
|
15674
15804
|
if (next.freshSpawn) next.freshSpawn = false;
|
|
15675
|
-
|
|
15676
|
-
|
|
15677
|
-
|
|
15678
|
-
|
|
15805
|
+
const effects = [emitSessionEvent(next.file.sessionId, withSeq)];
|
|
15806
|
+
if (isTurnEnd && next.subSessionMeta?.idleKillEnabled && next.procAlive && (next.status === "running" || next.status === "spawning")) {
|
|
15807
|
+
next.status = "running-idle";
|
|
15808
|
+
effects.push({
|
|
15809
|
+
kind: "schedule-idle-kill",
|
|
15810
|
+
sessionId: next.file.sessionId,
|
|
15811
|
+
ms: IDLE_KILL_DELAY_MS
|
|
15812
|
+
});
|
|
15813
|
+
}
|
|
15814
|
+
return { state: next, effects };
|
|
15679
15815
|
}
|
|
15680
15816
|
var RUNTIME_PATCH_KEYS = [
|
|
15681
15817
|
"model",
|
|
@@ -15689,6 +15825,10 @@ function applyCommand(state, command, deps) {
|
|
|
15689
15825
|
const sessionId = next.file.sessionId;
|
|
15690
15826
|
switch (command.kind) {
|
|
15691
15827
|
case "send": {
|
|
15828
|
+
if (next.status === "running-idle") {
|
|
15829
|
+
effects.push({ kind: "cancel-idle-kill", sessionId });
|
|
15830
|
+
next.status = next.procAlive ? "running" : "idle";
|
|
15831
|
+
}
|
|
15692
15832
|
if (!next.procAlive) {
|
|
15693
15833
|
next.procAlive = true;
|
|
15694
15834
|
next.freshSpawn = true;
|
|
@@ -15697,7 +15837,7 @@ function applyCommand(state, command, deps) {
|
|
|
15697
15837
|
next.nextSeq = 0;
|
|
15698
15838
|
next.turnOpen = false;
|
|
15699
15839
|
next.status = "running";
|
|
15700
|
-
effects.push({ kind: "spawn", ctx: buildSpawnContext(next
|
|
15840
|
+
effects.push({ kind: "spawn", ctx: buildSpawnContext(next) });
|
|
15701
15841
|
effects.push({
|
|
15702
15842
|
kind: "emit-frame",
|
|
15703
15843
|
frame: {
|
|
@@ -15720,16 +15860,6 @@ function applyCommand(state, command, deps) {
|
|
|
15720
15860
|
return { state: nextState, effects };
|
|
15721
15861
|
}
|
|
15722
15862
|
case "stop": {
|
|
15723
|
-
for (const toolUseId of Object.keys(state.pendingQuestions ?? {})) {
|
|
15724
|
-
effects.push({
|
|
15725
|
-
kind: "emit-frame",
|
|
15726
|
-
frame: {
|
|
15727
|
-
type: "session:question:cleared",
|
|
15728
|
-
sessionId,
|
|
15729
|
-
toolUseId
|
|
15730
|
-
}
|
|
15731
|
-
});
|
|
15732
|
-
}
|
|
15733
15863
|
next.pendingPermissions = {};
|
|
15734
15864
|
next.pendingQuestions = {};
|
|
15735
15865
|
if (next.procAlive) {
|
|
@@ -15749,7 +15879,7 @@ function applyCommand(state, command, deps) {
|
|
|
15749
15879
|
next.nextSeq = 0;
|
|
15750
15880
|
next.turnOpen = false;
|
|
15751
15881
|
next.status = "running";
|
|
15752
|
-
effects.push({ kind: "spawn", ctx: buildSpawnContext(next
|
|
15882
|
+
effects.push({ kind: "spawn", ctx: buildSpawnContext(next) });
|
|
15753
15883
|
effects.push({
|
|
15754
15884
|
kind: "emit-frame",
|
|
15755
15885
|
frame: { type: "session:status", sessionId, status: "running" }
|
|
@@ -15757,16 +15887,6 @@ function applyCommand(state, command, deps) {
|
|
|
15757
15887
|
return { state: next, effects };
|
|
15758
15888
|
}
|
|
15759
15889
|
case "delete": {
|
|
15760
|
-
for (const toolUseId of Object.keys(state.pendingQuestions ?? {})) {
|
|
15761
|
-
effects.push({
|
|
15762
|
-
kind: "emit-frame",
|
|
15763
|
-
frame: {
|
|
15764
|
-
type: "session:question:cleared",
|
|
15765
|
-
sessionId,
|
|
15766
|
-
toolUseId
|
|
15767
|
-
}
|
|
15768
|
-
});
|
|
15769
|
-
}
|
|
15770
15890
|
next.pendingPermissions = {};
|
|
15771
15891
|
next.pendingQuestions = {};
|
|
15772
15892
|
if (next.procAlive) {
|
|
@@ -15878,17 +15998,6 @@ function reduceSession(state, input, deps) {
|
|
|
15878
15998
|
const next = cloneState(state);
|
|
15879
15999
|
next.procAlive = false;
|
|
15880
16000
|
next.status = "stopped";
|
|
15881
|
-
const sessionId = next.file.sessionId;
|
|
15882
|
-
const clearedEffects = Object.keys(state.pendingQuestions ?? {}).map(
|
|
15883
|
-
(toolUseId) => ({
|
|
15884
|
-
kind: "emit-frame",
|
|
15885
|
-
frame: {
|
|
15886
|
-
type: "session:question:cleared",
|
|
15887
|
-
sessionId,
|
|
15888
|
-
toolUseId
|
|
15889
|
-
}
|
|
15890
|
-
})
|
|
15891
|
-
);
|
|
15892
16001
|
next.pendingQuestions = {};
|
|
15893
16002
|
return {
|
|
15894
16003
|
state: next,
|
|
@@ -15897,12 +16006,11 @@ function reduceSession(state, input, deps) {
|
|
|
15897
16006
|
kind: "emit-frame",
|
|
15898
16007
|
frame: {
|
|
15899
16008
|
type: "session:status",
|
|
15900
|
-
sessionId,
|
|
16009
|
+
sessionId: next.file.sessionId,
|
|
15901
16010
|
status: next.status,
|
|
15902
16011
|
exitCode: input.code
|
|
15903
16012
|
}
|
|
15904
|
-
}
|
|
15905
|
-
...clearedEffects
|
|
16013
|
+
}
|
|
15906
16014
|
]
|
|
15907
16015
|
};
|
|
15908
16016
|
}
|
|
@@ -15976,7 +16084,6 @@ function reduceSession(state, input, deps) {
|
|
|
15976
16084
|
...baseInput,
|
|
15977
16085
|
answers: input.answers
|
|
15978
16086
|
};
|
|
15979
|
-
const sessionId = next.file.sessionId;
|
|
15980
16087
|
return {
|
|
15981
16088
|
state: next,
|
|
15982
16089
|
effects: [
|
|
@@ -15984,21 +16091,29 @@ function reduceSession(state, input, deps) {
|
|
|
15984
16091
|
kind: "send-control-response-allow-with-input",
|
|
15985
16092
|
requestId: pending.requestId,
|
|
15986
16093
|
updatedInput
|
|
15987
|
-
},
|
|
15988
|
-
// cleared(with answers) broadcast:UI dispatch session:question:submitted,
|
|
15989
|
-
// pendingQuestions[toolUseId] 转 submitted 只读态。target 缺省=broadcast。
|
|
15990
|
-
{
|
|
15991
|
-
kind: "emit-frame",
|
|
15992
|
-
frame: {
|
|
15993
|
-
type: "session:question:cleared",
|
|
15994
|
-
sessionId,
|
|
15995
|
-
toolUseId: input.toolUseId,
|
|
15996
|
-
answers: input.answers
|
|
15997
|
-
}
|
|
15998
16094
|
}
|
|
15999
16095
|
]
|
|
16000
16096
|
};
|
|
16001
16097
|
}
|
|
16098
|
+
case "idle-kill-fired": {
|
|
16099
|
+
if (state.status !== "running-idle") {
|
|
16100
|
+
return { state, effects: [] };
|
|
16101
|
+
}
|
|
16102
|
+
const next = cloneState(state);
|
|
16103
|
+
next.status = "stopping";
|
|
16104
|
+
return {
|
|
16105
|
+
state: next,
|
|
16106
|
+
effects: [{ kind: "kill", signal: "SIGKILL" }]
|
|
16107
|
+
};
|
|
16108
|
+
}
|
|
16109
|
+
case "inject-owner-text": {
|
|
16110
|
+
const ownerEvent = {
|
|
16111
|
+
kind: "meta-text",
|
|
16112
|
+
text: input.text,
|
|
16113
|
+
metaSource: "owner"
|
|
16114
|
+
};
|
|
16115
|
+
return pushEventToBuffer(state, ownerEvent, deps);
|
|
16116
|
+
}
|
|
16002
16117
|
case "tick": {
|
|
16003
16118
|
const staleMs = 10 * 60 * 1e3;
|
|
16004
16119
|
const now = input.nowMs;
|
|
@@ -16076,6 +16191,19 @@ function startRecorder(opts) {
|
|
|
16076
16191
|
};
|
|
16077
16192
|
}
|
|
16078
16193
|
|
|
16194
|
+
// src/tools/cwd-boundary.ts
|
|
16195
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
16196
|
+
function isPathInsideCwd(cwd, target) {
|
|
16197
|
+
if (!import_node_path5.default.isAbsolute(target)) return false;
|
|
16198
|
+
const cwdNorm = import_node_path5.default.resolve(cwd);
|
|
16199
|
+
const targetNorm = import_node_path5.default.resolve(target);
|
|
16200
|
+
if (cwdNorm === targetNorm) return true;
|
|
16201
|
+
const rel = import_node_path5.default.relative(cwdNorm, targetNorm);
|
|
16202
|
+
if (rel.startsWith("..")) return false;
|
|
16203
|
+
if (import_node_path5.default.isAbsolute(rel)) return false;
|
|
16204
|
+
return true;
|
|
16205
|
+
}
|
|
16206
|
+
|
|
16079
16207
|
// src/session/runner.ts
|
|
16080
16208
|
var DEFAULT_CONTROL_REQUEST_TIMEOUT_MS = 1e4;
|
|
16081
16209
|
function encodeAllowWithInputControlResponse(requestId, updatedInput) {
|
|
@@ -16093,6 +16221,15 @@ function encodeAllowWithInputControlResponse(requestId, updatedInput) {
|
|
|
16093
16221
|
return JSON.stringify(payload) + "\n";
|
|
16094
16222
|
}
|
|
16095
16223
|
var DEFAULT_WAIT_STOP_TIMEOUT_MS = 3e3;
|
|
16224
|
+
function extractToolPath(ev) {
|
|
16225
|
+
const input = ev.input;
|
|
16226
|
+
if (!input || typeof input !== "object") return void 0;
|
|
16227
|
+
const inp = input;
|
|
16228
|
+
if (ev.tool === "Read" && typeof inp.file_path === "string") return inp.file_path;
|
|
16229
|
+
if (ev.tool === "Grep" && typeof inp.path === "string") return inp.path;
|
|
16230
|
+
if (ev.tool === "Glob" && typeof inp.path === "string") return inp.path;
|
|
16231
|
+
return void 0;
|
|
16232
|
+
}
|
|
16096
16233
|
var SessionRunner = class {
|
|
16097
16234
|
constructor(initial, hooks) {
|
|
16098
16235
|
this.hooks = hooks;
|
|
@@ -16108,6 +16245,9 @@ var SessionRunner = class {
|
|
|
16108
16245
|
stopWaiters = [];
|
|
16109
16246
|
// IPC recorder(CLAWD_RECORD_IPC=1 时启用);null 表示当前 spawn 未启用 / 已退出
|
|
16110
16247
|
recorder = null;
|
|
16248
|
+
// sub-session idle-kill timer ref;key = sessionId(实际同一 runner 只对应一个 session,
|
|
16249
|
+
// 但 reducer Effect schema 带 sessionId 作为唯一键,对齐 schedule/cancel 配对)
|
|
16250
|
+
idleKillTimers = /* @__PURE__ */ new Map();
|
|
16111
16251
|
getState() {
|
|
16112
16252
|
return this.state;
|
|
16113
16253
|
}
|
|
@@ -16205,6 +16345,62 @@ var SessionRunner = class {
|
|
|
16205
16345
|
}
|
|
16206
16346
|
});
|
|
16207
16347
|
}
|
|
16348
|
+
// L1 锁定(task 14)拦截:仅 sub-session(state.subSessionMeta.toolAllowlist 非空)启用。
|
|
16349
|
+
// 1. 解析 line 为 ParsedEvent[],扫 'tool_call' kind
|
|
16350
|
+
// 2. tool 名不在白名单 → emit session:event(kind='error') + SIGKILL,丢弃本行
|
|
16351
|
+
// 3. tool input 里带 path 字段(Read.file_path / Grep.path / Glob.path)越出 cwd 子树 → 同上处理
|
|
16352
|
+
// 命中返回 true,调用方不再喂 reducer;命中即 SIGKILL,CC 进程会触发 proc.on('exit')
|
|
16353
|
+
// 走正常的 stop / proc-exit 流程。本拦截层与 buildSpawnArgs 的 --disallowed-tools / --add-dir
|
|
16354
|
+
// 共同构成 L1 第二 / 第三层防御(schema 层 + manager 层 + claude CLI 层 + runner 拦截层)
|
|
16355
|
+
tryInterceptL1Violation(line) {
|
|
16356
|
+
const meta = this.state.subSessionMeta;
|
|
16357
|
+
if (!meta?.toolAllowlist || meta.toolAllowlist.length === 0) return false;
|
|
16358
|
+
const allowed = new Set(meta.toolAllowlist);
|
|
16359
|
+
let events;
|
|
16360
|
+
try {
|
|
16361
|
+
events = this.hooks.adapter.parseLine(line);
|
|
16362
|
+
} catch {
|
|
16363
|
+
return false;
|
|
16364
|
+
}
|
|
16365
|
+
if (events.length === 0) return false;
|
|
16366
|
+
const cwd = this.state.file.cwd;
|
|
16367
|
+
for (const ev of events) {
|
|
16368
|
+
if (ev.kind !== "tool_call") continue;
|
|
16369
|
+
if (typeof ev.tool === "string" && ev.tool && !allowed.has(ev.tool)) {
|
|
16370
|
+
this.emitL1Error(`tool ${ev.tool} blocked by L1 lockdown`, "TOOL_NOT_ALLOWED");
|
|
16371
|
+
this.doKill("SIGKILL");
|
|
16372
|
+
return true;
|
|
16373
|
+
}
|
|
16374
|
+
const argPath = extractToolPath(ev);
|
|
16375
|
+
if (argPath && !isPathInsideCwd(cwd, argPath)) {
|
|
16376
|
+
this.emitL1Error(
|
|
16377
|
+
`path ${argPath} outside cwd ${cwd}`,
|
|
16378
|
+
"PATH_OUT_OF_BOUND"
|
|
16379
|
+
);
|
|
16380
|
+
this.doKill("SIGKILL");
|
|
16381
|
+
return true;
|
|
16382
|
+
}
|
|
16383
|
+
}
|
|
16384
|
+
return false;
|
|
16385
|
+
}
|
|
16386
|
+
// L1 违规错误帧:走 session:event 通道,code 编码进 ParsedEvent.error message。
|
|
16387
|
+
// 不引入新的 wire 帧 type(避免 protocol 改动),现有 error kind 已经走 broadcast 路径
|
|
16388
|
+
emitL1Error(message, code) {
|
|
16389
|
+
const errEvent = {
|
|
16390
|
+
kind: "error",
|
|
16391
|
+
message: `[${code}] ${message}`
|
|
16392
|
+
};
|
|
16393
|
+
const frame = {
|
|
16394
|
+
type: "session:event",
|
|
16395
|
+
sessionId: this.state.file.sessionId,
|
|
16396
|
+
event: errEvent
|
|
16397
|
+
};
|
|
16398
|
+
try {
|
|
16399
|
+
this.hooks.broadcastFrame(frame, "broadcast");
|
|
16400
|
+
} catch {
|
|
16401
|
+
}
|
|
16402
|
+
this.hooks.logger?.warn("clawd-l1-violation", { code, message, sessionId: this.state.file.sessionId });
|
|
16403
|
+
}
|
|
16208
16404
|
// 尝试把一行 stdout 解析成 `type:"control_response"` 帧并 resolve 匹配的 pending。
|
|
16209
16405
|
// 命中返回 true —— 调用方不再把该行喂给 reducer(control_response 不是会话事件)。
|
|
16210
16406
|
tryHandleControlResponse(line) {
|
|
@@ -16283,8 +16479,33 @@ var SessionRunner = class {
|
|
|
16283
16479
|
setTimeout(() => this.input({ kind: "tick", nowMs: now() }), effect.delayMs).unref();
|
|
16284
16480
|
break;
|
|
16285
16481
|
}
|
|
16482
|
+
case "schedule-idle-kill": {
|
|
16483
|
+
const existing = this.idleKillTimers.get(effect.sessionId);
|
|
16484
|
+
if (existing) clearTimeout(existing);
|
|
16485
|
+
const timer = setTimeout(() => {
|
|
16486
|
+
this.idleKillTimers.delete(effect.sessionId);
|
|
16487
|
+
this.input({ kind: "idle-kill-fired" });
|
|
16488
|
+
}, effect.ms);
|
|
16489
|
+
timer.unref?.();
|
|
16490
|
+
this.idleKillTimers.set(effect.sessionId, timer);
|
|
16491
|
+
break;
|
|
16492
|
+
}
|
|
16493
|
+
case "cancel-idle-kill": {
|
|
16494
|
+
const timer = this.idleKillTimers.get(effect.sessionId);
|
|
16495
|
+
if (timer) {
|
|
16496
|
+
clearTimeout(timer);
|
|
16497
|
+
this.idleKillTimers.delete(effect.sessionId);
|
|
16498
|
+
}
|
|
16499
|
+
break;
|
|
16500
|
+
}
|
|
16286
16501
|
}
|
|
16287
16502
|
}
|
|
16503
|
+
// 清空所有 idle-kill timer(runner dispose / proc 永久退出时调用)。
|
|
16504
|
+
// 不喂 idle-kill-fired —— dispose 路径不再翻 reducer 状态
|
|
16505
|
+
clearIdleKillTimers() {
|
|
16506
|
+
for (const t of this.idleKillTimers.values()) clearTimeout(t);
|
|
16507
|
+
this.idleKillTimers.clear();
|
|
16508
|
+
}
|
|
16288
16509
|
// 启动子进程,绑定 stdout line buffer → 回灌 reducer
|
|
16289
16510
|
doSpawn(ctx) {
|
|
16290
16511
|
const proc = this.hooks.spawnOverride ? this.hooks.spawnOverride(ctx) : this.hooks.adapter.spawn(ctx);
|
|
@@ -16302,6 +16523,7 @@ var SessionRunner = class {
|
|
|
16302
16523
|
this.stdoutBuf = newBuf;
|
|
16303
16524
|
for (const line of lines) {
|
|
16304
16525
|
if (this.tryHandleControlResponse(line)) continue;
|
|
16526
|
+
if (this.tryInterceptL1Violation(line)) continue;
|
|
16305
16527
|
this.input({ kind: "stdout-line", line });
|
|
16306
16528
|
}
|
|
16307
16529
|
});
|
|
@@ -16313,6 +16535,7 @@ var SessionRunner = class {
|
|
|
16313
16535
|
this.proc = null;
|
|
16314
16536
|
this.recorder = null;
|
|
16315
16537
|
this.rejectAllPending(new Error("session gone"));
|
|
16538
|
+
this.clearIdleKillTimers();
|
|
16316
16539
|
this.input({ kind: "proc-exit", code });
|
|
16317
16540
|
});
|
|
16318
16541
|
proc.on("error", (err) => {
|
|
@@ -16341,6 +16564,8 @@ function compressFrameForWire(frame) {
|
|
|
16341
16564
|
switch (status) {
|
|
16342
16565
|
case "spawning":
|
|
16343
16566
|
case "running":
|
|
16567
|
+
// sub-session(persona)专属内部状态:进程仍活着等 idle-kill;wire 协议看到的是 'running'
|
|
16568
|
+
case "running-idle":
|
|
16344
16569
|
compressed = "running";
|
|
16345
16570
|
break;
|
|
16346
16571
|
case "stopping":
|
|
@@ -16367,6 +16592,7 @@ function compressStatus(status) {
|
|
|
16367
16592
|
switch (status) {
|
|
16368
16593
|
case "spawning":
|
|
16369
16594
|
case "running":
|
|
16595
|
+
case "running-idle":
|
|
16370
16596
|
return "running";
|
|
16371
16597
|
case "stopping":
|
|
16372
16598
|
case "stopped":
|
|
@@ -16384,7 +16610,7 @@ function nowIso2(deps) {
|
|
|
16384
16610
|
function newSessionId() {
|
|
16385
16611
|
return v4_default().replace(/-/g, "").slice(0, 16);
|
|
16386
16612
|
}
|
|
16387
|
-
function makeInitialState(file) {
|
|
16613
|
+
function makeInitialState(file, subSessionMeta) {
|
|
16388
16614
|
return {
|
|
16389
16615
|
file,
|
|
16390
16616
|
status: "idle",
|
|
@@ -16396,12 +16622,14 @@ function makeInitialState(file) {
|
|
|
16396
16622
|
pendingQuestions: {},
|
|
16397
16623
|
freshSpawn: false,
|
|
16398
16624
|
procAlive: false,
|
|
16399
|
-
seenUuids: []
|
|
16625
|
+
seenUuids: [],
|
|
16626
|
+
subSessionMeta
|
|
16400
16627
|
};
|
|
16401
16628
|
}
|
|
16402
16629
|
var SessionManager = class {
|
|
16403
16630
|
constructor(deps) {
|
|
16404
16631
|
this.deps = deps;
|
|
16632
|
+
this.storesByAgent.set(DEFAULT_AGENT_ID, deps.store);
|
|
16405
16633
|
}
|
|
16406
16634
|
deps;
|
|
16407
16635
|
// sessionId → SessionRunner;在 send / ensureSession 时按需创建
|
|
@@ -16417,6 +16645,28 @@ var SessionManager = class {
|
|
|
16417
16645
|
// 由 observer 监听 jsonl user 行后调 recordRealUserUuid 建立映射;rewind 系列 RPC 在
|
|
16418
16646
|
// 入参 / 出参做转译,保证 UI 看到的 uuid 始终是 events 流里的 synth uuid
|
|
16419
16647
|
realUuidBySynth = /* @__PURE__ */ new Map();
|
|
16648
|
+
// persona sub-session 路径:按 agentId 派生 SessionStore(root = dataDir/sessions/<agentId>/)。
|
|
16649
|
+
// 'default' 直接复用 deps.store;其它 agentId 第一次访问时按需创建并缓存
|
|
16650
|
+
storesByAgent = /* @__PURE__ */ new Map();
|
|
16651
|
+
// sub-session 创建时记录的 subSessionMeta;ensureSession / runner 创建时塞入 reducer state。
|
|
16652
|
+
// 不进 SessionFile schema(避免破坏现有 zod parse),仅运行时缓存
|
|
16653
|
+
subSessionMetaBySid = /* @__PURE__ */ new Map();
|
|
16654
|
+
// persona-bound transport 订阅器:sessionId → Set<listener>。
|
|
16655
|
+
// 仅推 reducer 产出的 'session:event' 帧里的 ParsedEvent,其他帧(status / info / cleared)
|
|
16656
|
+
// 由调用方按需自行处理(persona-bound 简化协议只暴露 event 流,不需要这些)
|
|
16657
|
+
eventSubscribers = /* @__PURE__ */ new Map();
|
|
16658
|
+
// 按 agentId 拿对应的 SessionStore;persona sub-session 走这条路径写到
|
|
16659
|
+
// sessions/<personaId>/<sessionId>.json。需要 deps.dataDir 同源,否则脑裂
|
|
16660
|
+
storeFor(agentId) {
|
|
16661
|
+
const cached = this.storesByAgent.get(agentId);
|
|
16662
|
+
if (cached) return cached;
|
|
16663
|
+
if (!this.deps.dataDir) {
|
|
16664
|
+
throw new Error(`SessionManager: dataDir required to route agentId='${agentId}'`);
|
|
16665
|
+
}
|
|
16666
|
+
const st = new SessionStore({ dataDir: this.deps.dataDir, agentId });
|
|
16667
|
+
this.storesByAgent.set(agentId, st);
|
|
16668
|
+
return st;
|
|
16669
|
+
}
|
|
16420
16670
|
async getCapabilities(tool) {
|
|
16421
16671
|
const cached = this.capabilitiesCache.get(tool);
|
|
16422
16672
|
if (cached) return cached;
|
|
@@ -16427,11 +16677,14 @@ var SessionManager = class {
|
|
|
16427
16677
|
}
|
|
16428
16678
|
// 创建 runner 时包一层 broadcast hook:所有外出 frame 统一走 routeFromRunner,
|
|
16429
16679
|
// 经过 compressFrameForWire 后决定是 push collector 还是走 deps.broadcastFrame
|
|
16430
|
-
|
|
16680
|
+
// store:默认 deps.store;persona sub-session 路径下传该 personaId 对应的 SessionStore
|
|
16681
|
+
// subSessionMeta:persona 路径传 { idleKillEnabled: true },其它路径不传
|
|
16682
|
+
newRunner(file, opts = {}) {
|
|
16431
16683
|
const adapter = this.deps.getAdapter(file.tool ?? "claude");
|
|
16432
|
-
const
|
|
16684
|
+
const store = opts.store ?? this.deps.store;
|
|
16685
|
+
const runner = new SessionRunner(makeInitialState(file, opts.subSessionMeta), {
|
|
16433
16686
|
broadcastFrame: (frame, target) => this.routeFromRunner(frame, target),
|
|
16434
|
-
store
|
|
16687
|
+
store,
|
|
16435
16688
|
adapter,
|
|
16436
16689
|
logger: this.deps.logger,
|
|
16437
16690
|
spawnOverride: this.deps.spawnOverride,
|
|
@@ -16449,6 +16702,21 @@ var SessionManager = class {
|
|
|
16449
16702
|
routeFromRunner(frame, target) {
|
|
16450
16703
|
const compressed = compressFrameForWire(frame);
|
|
16451
16704
|
if (!compressed) return;
|
|
16705
|
+
if (compressed.type === "session:event") {
|
|
16706
|
+
const sid = compressed.sessionId;
|
|
16707
|
+
const ev = compressed.event;
|
|
16708
|
+
if (sid && ev) {
|
|
16709
|
+
const subs = this.eventSubscribers.get(sid);
|
|
16710
|
+
if (subs && subs.size > 0) {
|
|
16711
|
+
for (const fn of subs) {
|
|
16712
|
+
try {
|
|
16713
|
+
fn(ev);
|
|
16714
|
+
} catch {
|
|
16715
|
+
}
|
|
16716
|
+
}
|
|
16717
|
+
}
|
|
16718
|
+
}
|
|
16719
|
+
}
|
|
16452
16720
|
if (this.currentCollector) {
|
|
16453
16721
|
this.currentCollector.push({ frame: compressed, target });
|
|
16454
16722
|
return;
|
|
@@ -16870,10 +17138,7 @@ var SessionManager = class {
|
|
|
16870
17138
|
running.push({
|
|
16871
17139
|
sessionId: sid,
|
|
16872
17140
|
status: compressStatus(st.status),
|
|
16873
|
-
freshSpawn: st.freshSpawn
|
|
16874
|
-
// sidebar awaiting-user baseline:UI 端据此填 anonymous slot 直到 push 帧带真实 toolUseId
|
|
16875
|
-
pendingPermissionsCount: Object.keys(st.pendingPermissions).length,
|
|
16876
|
-
pendingQuestionsCount: Object.keys(st.pendingQuestions).length
|
|
17141
|
+
freshSpawn: st.freshSpawn
|
|
16877
17142
|
});
|
|
16878
17143
|
}
|
|
16879
17144
|
return { runningSessions: running };
|
|
@@ -16891,10 +17156,189 @@ var SessionManager = class {
|
|
|
16891
17156
|
ensureSession(file) {
|
|
16892
17157
|
let r = this.runners.get(file.sessionId);
|
|
16893
17158
|
if (r) return r;
|
|
16894
|
-
|
|
17159
|
+
const subSessionMeta = this.subSessionMetaBySid.get(file.sessionId);
|
|
17160
|
+
r = this.newRunner(file, { subSessionMeta });
|
|
16895
17161
|
this.runners.set(file.sessionId, r);
|
|
16896
17162
|
return r;
|
|
16897
17163
|
}
|
|
17164
|
+
// ---------------- persona / sub-session 专用 API ----------------
|
|
17165
|
+
//
|
|
17166
|
+
// PersonaManager 是 SessionManager 之上的薄编排层,sub-session 的持久化(SessionFile)
|
|
17167
|
+
// 仍由 SessionManager 持有。区别于普通 session:
|
|
17168
|
+
// - SessionFile 落到 sessions/<personaId>/ 而非 sessions/default/
|
|
17169
|
+
// - reducer state.subSessionMeta.idleKillEnabled = true(turn_end 自动 idle-kill)
|
|
17170
|
+
// - sessionId 由 PersonaManager 派生(personaId-<tokenHash>),不走自动 newSessionId
|
|
17171
|
+
/** 按 agentId 读取 SessionFile;不存在返回 null */
|
|
17172
|
+
readForAgent(sessionId, agentId) {
|
|
17173
|
+
return this.storeFor(agentId).read(sessionId);
|
|
17174
|
+
}
|
|
17175
|
+
/**
|
|
17176
|
+
* 按 agentId 列出该命名空间下所有 SessionFile(persona:listSubSessions 入口)。
|
|
17177
|
+
* agentId 还未访问过 → storeFor 第一次创建对应 SessionStore(root 不存在则 list 返回 [])
|
|
17178
|
+
*/
|
|
17179
|
+
listForAgent(agentId) {
|
|
17180
|
+
return this.storeFor(agentId).list();
|
|
17181
|
+
}
|
|
17182
|
+
/**
|
|
17183
|
+
* 创建 sub-session 的 SessionFile + 准备 runner(不 spawn)。
|
|
17184
|
+
* subSessionMeta 不进 SessionFile schema,仅缓存进 runner state。
|
|
17185
|
+
* 同一 sessionId 重复调用:抛错(PersonaManager 应先调 readForAgent 命中复用)
|
|
17186
|
+
*/
|
|
17187
|
+
createForAgent(args) {
|
|
17188
|
+
try {
|
|
17189
|
+
const stat = import_node_fs5.default.statSync(args.cwd);
|
|
17190
|
+
if (!stat.isDirectory()) throw new Error("not dir");
|
|
17191
|
+
} catch {
|
|
17192
|
+
throw new ClawdError(ERROR_CODES.INVALID_CWD, `cwd not a directory: ${args.cwd}`);
|
|
17193
|
+
}
|
|
17194
|
+
const tool = args.tool ?? "claude";
|
|
17195
|
+
this.deps.getAdapter(tool);
|
|
17196
|
+
const store = this.storeFor(args.agentId);
|
|
17197
|
+
if (store.read(args.sessionId)) {
|
|
17198
|
+
throw new Error(`session already exists for agent ${args.agentId}: ${args.sessionId}`);
|
|
17199
|
+
}
|
|
17200
|
+
const iso = nowIso2(this.deps);
|
|
17201
|
+
const file = {
|
|
17202
|
+
sessionId: args.sessionId,
|
|
17203
|
+
cwd: args.cwd,
|
|
17204
|
+
tool,
|
|
17205
|
+
label: args.label,
|
|
17206
|
+
model: args.model,
|
|
17207
|
+
permissionMode: args.permissionMode,
|
|
17208
|
+
createdAt: iso,
|
|
17209
|
+
updatedAt: iso
|
|
17210
|
+
};
|
|
17211
|
+
const written = store.write(file);
|
|
17212
|
+
if (args.subSessionMeta || args.hardcodedToolAllowlist) {
|
|
17213
|
+
const meta = {
|
|
17214
|
+
idleKillEnabled: args.subSessionMeta?.idleKillEnabled ?? false,
|
|
17215
|
+
...args.hardcodedToolAllowlist && args.hardcodedToolAllowlist.length > 0 ? {
|
|
17216
|
+
toolAllowlist: args.hardcodedToolAllowlist,
|
|
17217
|
+
permissionMode: args.permissionMode
|
|
17218
|
+
} : {}
|
|
17219
|
+
};
|
|
17220
|
+
this.subSessionMetaBySid.set(args.sessionId, meta);
|
|
17221
|
+
}
|
|
17222
|
+
return written;
|
|
17223
|
+
}
|
|
17224
|
+
/**
|
|
17225
|
+
* persona-bound transport 用:读取 sub-session 的历史 ParsedEvent[]。
|
|
17226
|
+
* 实现策略:保证 runner 存在(buffer 是 reducer 唯一权威源),返回 buffer 里所有事件。
|
|
17227
|
+
* - 第一次访问:lazy ensureSession,buffer 为空 → 返回 []。
|
|
17228
|
+
* - 之前 send 过 / observer 喂过:buffer 已经有累积事件,直接返回。
|
|
17229
|
+
*
|
|
17230
|
+
* 不读 jsonl:
|
|
17231
|
+
* 1. observer 路径在 spawn 后会自动接管 jsonl 回灌,进 reducer buffer。
|
|
17232
|
+
* 2. 第一次握手时 sub-session 还没 spawn → toolSessionId 为空 → jsonl 不存在。
|
|
17233
|
+
* sessionFile 不存在抛 SESSION_NOT_FOUND;上层应先调 createForAgent。
|
|
17234
|
+
*/
|
|
17235
|
+
readHistoryEvents(sessionId, agentId) {
|
|
17236
|
+
const store = this.storeFor(agentId);
|
|
17237
|
+
const file = store.read(sessionId);
|
|
17238
|
+
if (!file) {
|
|
17239
|
+
throw new ClawdError(
|
|
17240
|
+
ERROR_CODES.SESSION_NOT_FOUND,
|
|
17241
|
+
`sub-session not found: ${agentId}/${sessionId}`
|
|
17242
|
+
);
|
|
17243
|
+
}
|
|
17244
|
+
const runner = this.ensureRunnerFor(file, agentId);
|
|
17245
|
+
return runner.getState().buffer.map((e) => e.event);
|
|
17246
|
+
}
|
|
17247
|
+
/**
|
|
17248
|
+
* persona-bound transport 用:订阅 sub-session 实时 ParsedEvent。
|
|
17249
|
+
* 返回 unsubscribe;不破坏现有 wire 广播路径——routeFromRunner 同时 fan-out 到
|
|
17250
|
+
* eventSubscribers 和 deps.broadcastFrame
|
|
17251
|
+
*/
|
|
17252
|
+
subscribe(_sessionId, _agentId, listener) {
|
|
17253
|
+
const sid = _sessionId;
|
|
17254
|
+
let subs = this.eventSubscribers.get(sid);
|
|
17255
|
+
if (!subs) {
|
|
17256
|
+
subs = /* @__PURE__ */ new Set();
|
|
17257
|
+
this.eventSubscribers.set(sid, subs);
|
|
17258
|
+
}
|
|
17259
|
+
subs.add(listener);
|
|
17260
|
+
return () => {
|
|
17261
|
+
const cur = this.eventSubscribers.get(sid);
|
|
17262
|
+
if (!cur) return;
|
|
17263
|
+
cur.delete(listener);
|
|
17264
|
+
if (cur.size === 0) this.eventSubscribers.delete(sid);
|
|
17265
|
+
};
|
|
17266
|
+
}
|
|
17267
|
+
/**
|
|
17268
|
+
* persona-bound transport 用:sub-session 路径的 send(按 agentId 路由 SessionStore)。
|
|
17269
|
+
* 现状 send(args.sessionId, args.text) 默认 'default' agent;这里按 agentId 拿对应 store
|
|
17270
|
+
* + ensureRunnerFor 保证 runner 用同一个 store 写盘
|
|
17271
|
+
*/
|
|
17272
|
+
sendForAgent(args) {
|
|
17273
|
+
const store = this.storeFor(args.agentId);
|
|
17274
|
+
const file = store.read(args.sessionId);
|
|
17275
|
+
if (!file) {
|
|
17276
|
+
throw new ClawdError(
|
|
17277
|
+
ERROR_CODES.SESSION_NOT_FOUND,
|
|
17278
|
+
`sub-session not found: ${args.agentId}/${args.sessionId}`
|
|
17279
|
+
);
|
|
17280
|
+
}
|
|
17281
|
+
const runner = this.ensureRunnerFor(file, args.agentId);
|
|
17282
|
+
const { broadcast } = this.withCollector(() => {
|
|
17283
|
+
runner.input({ kind: "command", command: { kind: "send", text: args.text } });
|
|
17284
|
+
});
|
|
17285
|
+
return { response: { ok: true }, broadcast };
|
|
17286
|
+
}
|
|
17287
|
+
/**
|
|
17288
|
+
* persona-bound transport 用:sub-session reset。
|
|
17289
|
+
* 复用 reducer 'new' 命令:清 toolSessionId / buffer / nextSeq / pending* + kill proc + emit
|
|
17290
|
+
* session:cleared。语义上等价 alice 端"清空当前会话上下文"。
|
|
17291
|
+
* 归档已写盘的 jsonl 不在本路径处理(CC 后续 spawn 会写新的 toolSessionId.jsonl,旧的留盘);
|
|
17292
|
+
* 物理归档可在后续单独 task 加(spec § 6 待 plan 决定项)。
|
|
17293
|
+
*/
|
|
17294
|
+
resetForAgent(args) {
|
|
17295
|
+
const store = this.storeFor(args.agentId);
|
|
17296
|
+
const file = store.read(args.sessionId);
|
|
17297
|
+
if (!file) {
|
|
17298
|
+
throw new ClawdError(
|
|
17299
|
+
ERROR_CODES.SESSION_NOT_FOUND,
|
|
17300
|
+
`sub-session not found: ${args.agentId}/${args.sessionId}`
|
|
17301
|
+
);
|
|
17302
|
+
}
|
|
17303
|
+
const runner = this.ensureRunnerFor(file, args.agentId);
|
|
17304
|
+
const { broadcast } = this.withCollector(() => {
|
|
17305
|
+
runner.input({ kind: "command", command: { kind: "new" } });
|
|
17306
|
+
});
|
|
17307
|
+
return { response: { ok: true }, broadcast };
|
|
17308
|
+
}
|
|
17309
|
+
/** ensureSession 的 agentId-aware 版本:复用现有 runner 或按 agentId 派生 store 创建 */
|
|
17310
|
+
ensureRunnerFor(file, agentId) {
|
|
17311
|
+
const existing = this.runners.get(file.sessionId);
|
|
17312
|
+
if (existing) return existing;
|
|
17313
|
+
const store = this.storeFor(agentId);
|
|
17314
|
+
const subSessionMeta = this.subSessionMetaBySid.get(file.sessionId);
|
|
17315
|
+
const runner = this.newRunner(file, { store, subSessionMeta });
|
|
17316
|
+
this.runners.set(file.sessionId, runner);
|
|
17317
|
+
return runner;
|
|
17318
|
+
}
|
|
17319
|
+
/**
|
|
17320
|
+
* 老板插话:把 'inject-owner-text' input 喂给对应 runner,
|
|
17321
|
+
* runner 不存在时按 ensureSession 路径懒创建(以 subSessionMeta 缓存为准)。
|
|
17322
|
+
* frames 走异步路径(broadcastFrame):调用方一般在 PersonaManager.appendOwnerMessage
|
|
17323
|
+
* 内部走 RPC 处理流,没有同步 collector 上下文
|
|
17324
|
+
*/
|
|
17325
|
+
injectOwnerMessage(args) {
|
|
17326
|
+
const store = this.storeFor(args.agentId);
|
|
17327
|
+
const file = store.read(args.sessionId);
|
|
17328
|
+
if (!file) {
|
|
17329
|
+
throw new ClawdError(
|
|
17330
|
+
ERROR_CODES.SESSION_NOT_FOUND,
|
|
17331
|
+
`sub-session not found: ${args.agentId}/${args.sessionId}`
|
|
17332
|
+
);
|
|
17333
|
+
}
|
|
17334
|
+
let runner = this.runners.get(args.sessionId);
|
|
17335
|
+
if (!runner) {
|
|
17336
|
+
const subSessionMeta = this.subSessionMetaBySid.get(args.sessionId);
|
|
17337
|
+
runner = this.newRunner(file, { store, subSessionMeta });
|
|
17338
|
+
this.runners.set(args.sessionId, runner);
|
|
17339
|
+
}
|
|
17340
|
+
runner.input({ kind: "inject-owner-text", text: args.text });
|
|
17341
|
+
}
|
|
16898
17342
|
// observer 把 stdout line 转发到指定 runner;单一 reducer 入口保证 seq 分配统一
|
|
16899
17343
|
feedObserverLine(sessionId, line) {
|
|
16900
17344
|
const runner = this.runners.get(sessionId);
|
|
@@ -16959,28 +17403,268 @@ var SessionManager = class {
|
|
|
16959
17403
|
}
|
|
16960
17404
|
};
|
|
16961
17405
|
|
|
17406
|
+
// src/persona/store.ts
|
|
17407
|
+
var import_node_fs6 = __toESM(require("fs"), 1);
|
|
17408
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
17409
|
+
init_protocol();
|
|
17410
|
+
var PersonaStore = class {
|
|
17411
|
+
root;
|
|
17412
|
+
constructor(opts) {
|
|
17413
|
+
this.root = import_node_path6.default.join(opts.dataDir, "personas");
|
|
17414
|
+
}
|
|
17415
|
+
filePath(personaId) {
|
|
17416
|
+
return import_node_path6.default.join(this.root, `${safeFileName(personaId)}.json`);
|
|
17417
|
+
}
|
|
17418
|
+
ensureDir() {
|
|
17419
|
+
import_node_fs6.default.mkdirSync(this.root, { recursive: true });
|
|
17420
|
+
}
|
|
17421
|
+
read(personaId) {
|
|
17422
|
+
try {
|
|
17423
|
+
const raw = import_node_fs6.default.readFileSync(this.filePath(personaId), "utf8");
|
|
17424
|
+
return PersonaFileSchema.parse(JSON.parse(raw));
|
|
17425
|
+
} catch (err) {
|
|
17426
|
+
const code = err?.code;
|
|
17427
|
+
if (code === "ENOENT") return null;
|
|
17428
|
+
return null;
|
|
17429
|
+
}
|
|
17430
|
+
}
|
|
17431
|
+
write(file) {
|
|
17432
|
+
const validated = PersonaFileSchema.parse(file);
|
|
17433
|
+
this.ensureDir();
|
|
17434
|
+
const target = this.filePath(validated.personaId);
|
|
17435
|
+
const tmp = `${target}.tmp-${process.pid}-${Date.now()}`;
|
|
17436
|
+
import_node_fs6.default.writeFileSync(tmp, JSON.stringify(validated, null, 2), { encoding: "utf8", mode: 384 });
|
|
17437
|
+
import_node_fs6.default.renameSync(tmp, target);
|
|
17438
|
+
return validated;
|
|
17439
|
+
}
|
|
17440
|
+
delete(personaId) {
|
|
17441
|
+
try {
|
|
17442
|
+
import_node_fs6.default.unlinkSync(this.filePath(personaId));
|
|
17443
|
+
} catch (err) {
|
|
17444
|
+
const code = err?.code;
|
|
17445
|
+
if (code !== "ENOENT") throw err;
|
|
17446
|
+
}
|
|
17447
|
+
}
|
|
17448
|
+
list() {
|
|
17449
|
+
if (!import_node_fs6.default.existsSync(this.root)) return [];
|
|
17450
|
+
const files = import_node_fs6.default.readdirSync(this.root).filter((f) => f.endsWith(".json") && !f.includes(".tmp"));
|
|
17451
|
+
const items = [];
|
|
17452
|
+
for (const f of files) {
|
|
17453
|
+
try {
|
|
17454
|
+
const raw = import_node_fs6.default.readFileSync(import_node_path6.default.join(this.root, f), "utf8");
|
|
17455
|
+
items.push(PersonaFileSchema.parse(JSON.parse(raw)));
|
|
17456
|
+
} catch {
|
|
17457
|
+
}
|
|
17458
|
+
}
|
|
17459
|
+
return items.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
17460
|
+
}
|
|
17461
|
+
};
|
|
17462
|
+
|
|
17463
|
+
// src/persona/registry.ts
|
|
17464
|
+
var PersonaRegistry = class {
|
|
17465
|
+
constructor(store) {
|
|
17466
|
+
this.store = store;
|
|
17467
|
+
this.reload();
|
|
17468
|
+
}
|
|
17469
|
+
store;
|
|
17470
|
+
cache = /* @__PURE__ */ new Map();
|
|
17471
|
+
/** 从 store 全量重建缓存(boot 时 + 测试 setup 时) */
|
|
17472
|
+
reload() {
|
|
17473
|
+
this.cache.clear();
|
|
17474
|
+
for (const p of this.store.list()) this.cache.set(p.personaId, p);
|
|
17475
|
+
}
|
|
17476
|
+
get(personaId) {
|
|
17477
|
+
return this.cache.get(personaId);
|
|
17478
|
+
}
|
|
17479
|
+
/** PersonaManager 写盘后同步 cache;不主动写盘 */
|
|
17480
|
+
set(file) {
|
|
17481
|
+
this.cache.set(file.personaId, file);
|
|
17482
|
+
}
|
|
17483
|
+
remove(personaId) {
|
|
17484
|
+
this.cache.delete(personaId);
|
|
17485
|
+
}
|
|
17486
|
+
list() {
|
|
17487
|
+
return Array.from(this.cache.values());
|
|
17488
|
+
}
|
|
17489
|
+
verifyToken(personaId, token) {
|
|
17490
|
+
const persona = this.cache.get(personaId);
|
|
17491
|
+
if (!persona) return { ok: false, code: "PERSONA_DELETED" };
|
|
17492
|
+
if (!persona.public) return { ok: false, code: "PERSONA_NOT_PUBLIC" };
|
|
17493
|
+
const entry = persona.tokenMap[token];
|
|
17494
|
+
if (!entry) return { ok: false, code: "TOKEN_INVALID" };
|
|
17495
|
+
if (entry.revoked) return { ok: false, code: "TOKEN_REVOKED" };
|
|
17496
|
+
return { ok: true, label: entry.label };
|
|
17497
|
+
}
|
|
17498
|
+
};
|
|
17499
|
+
|
|
17500
|
+
// src/persona/manager.ts
|
|
17501
|
+
var import_node_crypto3 = __toESM(require("crypto"), 1);
|
|
17502
|
+
var import_node_fs7 = __toESM(require("fs"), 1);
|
|
17503
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
17504
|
+
var PersonaManager = class {
|
|
17505
|
+
constructor(deps) {
|
|
17506
|
+
this.deps = deps;
|
|
17507
|
+
}
|
|
17508
|
+
deps;
|
|
17509
|
+
create(args) {
|
|
17510
|
+
const personaId = this.generatePersonaId(args.label);
|
|
17511
|
+
const iso = this.deps.now().toISOString();
|
|
17512
|
+
const file = {
|
|
17513
|
+
personaId,
|
|
17514
|
+
label: args.label,
|
|
17515
|
+
cwd: args.cwd,
|
|
17516
|
+
model: args.model,
|
|
17517
|
+
systemPromptAppend: args.systemPromptAppend,
|
|
17518
|
+
// L1 锁死:plan 模式 + 只读三件套
|
|
17519
|
+
permissionMode: "plan",
|
|
17520
|
+
toolAllowlist: ["Read", "Grep", "Glob"],
|
|
17521
|
+
public: true,
|
|
17522
|
+
tokenMap: {},
|
|
17523
|
+
createdAt: iso,
|
|
17524
|
+
updatedAt: iso
|
|
17525
|
+
};
|
|
17526
|
+
const written = this.deps.store.write(file);
|
|
17527
|
+
this.deps.registry.set(written);
|
|
17528
|
+
return written;
|
|
17529
|
+
}
|
|
17530
|
+
update(personaId, patch) {
|
|
17531
|
+
const existing = this.deps.registry.get(personaId);
|
|
17532
|
+
if (!existing) throw new Error(`persona not found: ${personaId}`);
|
|
17533
|
+
const updated = {
|
|
17534
|
+
...existing,
|
|
17535
|
+
...patch,
|
|
17536
|
+
updatedAt: this.deps.now().toISOString()
|
|
17537
|
+
};
|
|
17538
|
+
const written = this.deps.store.write(updated);
|
|
17539
|
+
this.deps.registry.set(written);
|
|
17540
|
+
return written;
|
|
17541
|
+
}
|
|
17542
|
+
/** 删除 persona + 级联清掉 sessions/<personaId>/ 目录 */
|
|
17543
|
+
delete(personaId) {
|
|
17544
|
+
this.deps.store.delete(personaId);
|
|
17545
|
+
this.deps.registry.remove(personaId);
|
|
17546
|
+
const dir = import_node_path7.default.join(this.deps.dataDir, "sessions", personaId);
|
|
17547
|
+
try {
|
|
17548
|
+
import_node_fs7.default.rmSync(dir, { recursive: true, force: true });
|
|
17549
|
+
} catch (err) {
|
|
17550
|
+
this.deps.logger.warn(`PersonaManager.delete: cleanup ${dir} failed`, {
|
|
17551
|
+
err: err.message
|
|
17552
|
+
});
|
|
17553
|
+
}
|
|
17554
|
+
}
|
|
17555
|
+
/** 生成 32-char base64url token(24 bytes 随机),label 必填 */
|
|
17556
|
+
issueToken(personaId, label) {
|
|
17557
|
+
const existing = this.deps.registry.get(personaId);
|
|
17558
|
+
if (!existing) throw new Error(`persona not found: ${personaId}`);
|
|
17559
|
+
const token = import_node_crypto3.default.randomBytes(24).toString("base64url");
|
|
17560
|
+
const entry = {
|
|
17561
|
+
label,
|
|
17562
|
+
issuedAt: this.deps.now().toISOString()
|
|
17563
|
+
};
|
|
17564
|
+
const updated = {
|
|
17565
|
+
...existing,
|
|
17566
|
+
tokenMap: { ...existing.tokenMap, [token]: entry },
|
|
17567
|
+
updatedAt: this.deps.now().toISOString()
|
|
17568
|
+
};
|
|
17569
|
+
const written = this.deps.store.write(updated);
|
|
17570
|
+
this.deps.registry.set(written);
|
|
17571
|
+
return { token, persona: written };
|
|
17572
|
+
}
|
|
17573
|
+
revokeToken(personaId, token) {
|
|
17574
|
+
const existing = this.deps.registry.get(personaId);
|
|
17575
|
+
if (!existing) throw new Error(`persona not found: ${personaId}`);
|
|
17576
|
+
const tokenEntry = existing.tokenMap[token];
|
|
17577
|
+
if (!tokenEntry) throw new Error(`token not found in persona ${personaId}`);
|
|
17578
|
+
const updated = {
|
|
17579
|
+
...existing,
|
|
17580
|
+
tokenMap: {
|
|
17581
|
+
...existing.tokenMap,
|
|
17582
|
+
[token]: { ...tokenEntry, revoked: true }
|
|
17583
|
+
},
|
|
17584
|
+
updatedAt: this.deps.now().toISOString()
|
|
17585
|
+
};
|
|
17586
|
+
const written = this.deps.store.write(updated);
|
|
17587
|
+
this.deps.registry.set(written);
|
|
17588
|
+
return written;
|
|
17589
|
+
}
|
|
17590
|
+
/**
|
|
17591
|
+
* 拿到(或按需创建)该 token 对应的 sub-session。subSessionId 由 personaId + token 哈希派生
|
|
17592
|
+
* 保证 idempotent:同一 token 永远复用同一个 sub-session。
|
|
17593
|
+
* 创建路径:开启 idleKillEnabled,cwd / model / tool 等字段从 PersonaFile 模板复制
|
|
17594
|
+
*/
|
|
17595
|
+
getOrCreateSubSession(personaId, token) {
|
|
17596
|
+
const persona = this.deps.registry.get(personaId);
|
|
17597
|
+
if (!persona) throw new Error(`persona not found: ${personaId}`);
|
|
17598
|
+
const subSessionId = this.deriveSubSessionId(personaId, token);
|
|
17599
|
+
const existing = this.deps.sessionManager.readForAgent(subSessionId, personaId);
|
|
17600
|
+
if (existing) {
|
|
17601
|
+
return { sessionFile: existing, isNew: false };
|
|
17602
|
+
}
|
|
17603
|
+
const tokenEntry = persona.tokenMap[token];
|
|
17604
|
+
const subLabel = tokenEntry?.label ?? "unknown";
|
|
17605
|
+
const sessionFile = this.deps.sessionManager.createForAgent({
|
|
17606
|
+
sessionId: subSessionId,
|
|
17607
|
+
agentId: personaId,
|
|
17608
|
+
cwd: persona.cwd,
|
|
17609
|
+
tool: "claude",
|
|
17610
|
+
label: subLabel,
|
|
17611
|
+
model: persona.model,
|
|
17612
|
+
permissionMode: "plan",
|
|
17613
|
+
// L1 锁定(task 14):把 PersonaFile.toolAllowlist 透传到 SessionManager,
|
|
17614
|
+
// 最终在 buildSpawnArgs 拼成 --add-dir + --disallowed-tools,runner stdout 拦截层
|
|
17615
|
+
// 也读 SubSessionMeta.toolAllowlist 做工具名 / cwd 越界拦截
|
|
17616
|
+
hardcodedToolAllowlist: persona.toolAllowlist,
|
|
17617
|
+
subSessionMeta: { idleKillEnabled: true }
|
|
17618
|
+
});
|
|
17619
|
+
return { sessionFile, isNew: true };
|
|
17620
|
+
}
|
|
17621
|
+
/**
|
|
17622
|
+
* 老板插话:把"老板的话"作为 meta-text + metaSource='owner' 推到 sub-session 的事件流。
|
|
17623
|
+
* 委托 SessionManager.injectOwnerMessage 路由到 reducer 'inject-owner-text' input
|
|
17624
|
+
*/
|
|
17625
|
+
appendOwnerMessage(personaId, subSessionId, text) {
|
|
17626
|
+
this.deps.sessionManager.injectOwnerMessage({
|
|
17627
|
+
sessionId: subSessionId,
|
|
17628
|
+
agentId: personaId,
|
|
17629
|
+
text
|
|
17630
|
+
});
|
|
17631
|
+
}
|
|
17632
|
+
// ---------------- 内部 ----------------
|
|
17633
|
+
/** label 转 4-16 char slug + 4 char base64url 后缀,避免冲突且文件名安全 */
|
|
17634
|
+
generatePersonaId(label) {
|
|
17635
|
+
const slug = label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 16) || "persona";
|
|
17636
|
+
const rand = import_node_crypto3.default.randomBytes(3).toString("base64url").slice(0, 4);
|
|
17637
|
+
return `persona-${slug}-${rand}`;
|
|
17638
|
+
}
|
|
17639
|
+
/** subSessionId = persona-<short>-<tokenHash12>;同 token 始终复用同一 sub-session */
|
|
17640
|
+
deriveSubSessionId(personaId, token) {
|
|
17641
|
+
const tokenHash = import_node_crypto3.default.createHash("sha256").update(token).digest("hex").slice(0, 12);
|
|
17642
|
+
return `${personaId}-${tokenHash}`;
|
|
17643
|
+
}
|
|
17644
|
+
};
|
|
17645
|
+
|
|
16962
17646
|
// src/index.ts
|
|
16963
17647
|
init_claude();
|
|
16964
17648
|
init_claude_history();
|
|
16965
17649
|
|
|
16966
17650
|
// src/workspace/browser.ts
|
|
16967
|
-
var
|
|
17651
|
+
var import_node_fs10 = __toESM(require("fs"), 1);
|
|
16968
17652
|
var import_node_os4 = __toESM(require("os"), 1);
|
|
16969
|
-
var
|
|
17653
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
16970
17654
|
init_protocol();
|
|
16971
17655
|
var MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
16972
17656
|
function resolveInsideCwd(cwd, subpath) {
|
|
16973
|
-
const absCwd =
|
|
16974
|
-
const joined =
|
|
16975
|
-
const rel =
|
|
16976
|
-
if (rel.startsWith("..") ||
|
|
17657
|
+
const absCwd = import_node_path10.default.resolve(cwd);
|
|
17658
|
+
const joined = import_node_path10.default.resolve(absCwd, subpath ?? ".");
|
|
17659
|
+
const rel = import_node_path10.default.relative(absCwd, joined);
|
|
17660
|
+
if (rel.startsWith("..") || import_node_path10.default.isAbsolute(rel)) {
|
|
16977
17661
|
throw new ClawdError(ERROR_CODES.INVALID_PATH, `path escapes cwd: ${subpath}`);
|
|
16978
17662
|
}
|
|
16979
17663
|
return joined;
|
|
16980
17664
|
}
|
|
16981
17665
|
function ensureCwd(cwd) {
|
|
16982
17666
|
try {
|
|
16983
|
-
const stat =
|
|
17667
|
+
const stat = import_node_fs10.default.statSync(cwd);
|
|
16984
17668
|
if (!stat.isDirectory()) {
|
|
16985
17669
|
throw new ClawdError(ERROR_CODES.INVALID_CWD, `not a directory: ${cwd}`);
|
|
16986
17670
|
}
|
|
@@ -16994,7 +17678,7 @@ var WorkspaceBrowser = class {
|
|
|
16994
17678
|
const cwd = args.cwd && args.cwd.length > 0 ? args.cwd : import_node_os4.default.homedir();
|
|
16995
17679
|
ensureCwd(cwd);
|
|
16996
17680
|
const full = resolveInsideCwd(cwd, args.path);
|
|
16997
|
-
const dirents =
|
|
17681
|
+
const dirents = import_node_fs10.default.readdirSync(full, { withFileTypes: true });
|
|
16998
17682
|
const entries = [];
|
|
16999
17683
|
for (const d of dirents) {
|
|
17000
17684
|
if (!args.showHidden && d.name.startsWith(".")) continue;
|
|
@@ -17004,7 +17688,7 @@ var WorkspaceBrowser = class {
|
|
|
17004
17688
|
mtime: ""
|
|
17005
17689
|
};
|
|
17006
17690
|
try {
|
|
17007
|
-
const st =
|
|
17691
|
+
const st = import_node_fs10.default.statSync(import_node_path10.default.join(full, d.name));
|
|
17008
17692
|
entry.mtime = new Date(st.mtimeMs).toISOString();
|
|
17009
17693
|
if (d.isFile()) entry.size = st.size;
|
|
17010
17694
|
} catch {
|
|
@@ -17020,14 +17704,14 @@ var WorkspaceBrowser = class {
|
|
|
17020
17704
|
read(args) {
|
|
17021
17705
|
ensureCwd(args.cwd);
|
|
17022
17706
|
const full = resolveInsideCwd(args.cwd, args.path);
|
|
17023
|
-
const st =
|
|
17707
|
+
const st = import_node_fs10.default.statSync(full);
|
|
17024
17708
|
if (!st.isFile()) {
|
|
17025
17709
|
throw new ClawdError(ERROR_CODES.INVALID_PATH, `not a file: ${args.path}`);
|
|
17026
17710
|
}
|
|
17027
17711
|
if (st.size > MAX_FILE_BYTES) {
|
|
17028
17712
|
throw new ClawdError(ERROR_CODES.FILE_TOO_LARGE, `file > ${MAX_FILE_BYTES} bytes`);
|
|
17029
17713
|
}
|
|
17030
|
-
const buf =
|
|
17714
|
+
const buf = import_node_fs10.default.readFileSync(full);
|
|
17031
17715
|
const isBinary = buf.includes(0);
|
|
17032
17716
|
if (isBinary) {
|
|
17033
17717
|
return {
|
|
@@ -17049,9 +17733,9 @@ var WorkspaceBrowser = class {
|
|
|
17049
17733
|
};
|
|
17050
17734
|
|
|
17051
17735
|
// src/skills/scanner.ts
|
|
17052
|
-
var
|
|
17736
|
+
var import_node_fs11 = __toESM(require("fs"), 1);
|
|
17053
17737
|
var import_node_os5 = __toESM(require("os"), 1);
|
|
17054
|
-
var
|
|
17738
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
17055
17739
|
function parseFrontmatter(content) {
|
|
17056
17740
|
if (!content.startsWith("---")) return { name: "", description: "" };
|
|
17057
17741
|
const end = content.indexOf("---", 3);
|
|
@@ -17087,7 +17771,7 @@ function parseFrontmatter(content) {
|
|
|
17087
17771
|
}
|
|
17088
17772
|
function isDirLikeSync(p) {
|
|
17089
17773
|
try {
|
|
17090
|
-
return
|
|
17774
|
+
return import_node_fs11.default.statSync(p).isDirectory();
|
|
17091
17775
|
} catch {
|
|
17092
17776
|
return false;
|
|
17093
17777
|
}
|
|
@@ -17095,19 +17779,19 @@ function isDirLikeSync(p) {
|
|
|
17095
17779
|
function scanSkillDir(dir, source, seen, out, pluginName) {
|
|
17096
17780
|
let entries;
|
|
17097
17781
|
try {
|
|
17098
|
-
entries =
|
|
17782
|
+
entries = import_node_fs11.default.readdirSync(dir, { withFileTypes: true });
|
|
17099
17783
|
} catch {
|
|
17100
17784
|
return;
|
|
17101
17785
|
}
|
|
17102
17786
|
for (const ent of entries) {
|
|
17103
|
-
const entryPath =
|
|
17787
|
+
const entryPath = import_node_path11.default.join(dir, ent.name);
|
|
17104
17788
|
if (!ent.isDirectory() && !(ent.isSymbolicLink() && isDirLikeSync(entryPath))) continue;
|
|
17105
17789
|
let content;
|
|
17106
17790
|
try {
|
|
17107
|
-
content =
|
|
17791
|
+
content = import_node_fs11.default.readFileSync(import_node_path11.default.join(entryPath, "SKILL.md"), "utf8");
|
|
17108
17792
|
} catch {
|
|
17109
17793
|
try {
|
|
17110
|
-
content =
|
|
17794
|
+
content = import_node_fs11.default.readFileSync(import_node_path11.default.join(entryPath, "skill.md"), "utf8");
|
|
17111
17795
|
} catch {
|
|
17112
17796
|
continue;
|
|
17113
17797
|
}
|
|
@@ -17125,26 +17809,26 @@ function scanSkillDir(dir, source, seen, out, pluginName) {
|
|
|
17125
17809
|
function scanCommandDir(dir, source, seen, out, pluginName) {
|
|
17126
17810
|
let entries;
|
|
17127
17811
|
try {
|
|
17128
|
-
entries =
|
|
17812
|
+
entries = import_node_fs11.default.readdirSync(dir, { withFileTypes: true });
|
|
17129
17813
|
} catch {
|
|
17130
17814
|
return;
|
|
17131
17815
|
}
|
|
17132
17816
|
for (const ent of entries) {
|
|
17133
|
-
const entryPath =
|
|
17817
|
+
const entryPath = import_node_path11.default.join(dir, ent.name);
|
|
17134
17818
|
if (ent.isDirectory() || ent.isSymbolicLink() && isDirLikeSync(entryPath)) {
|
|
17135
17819
|
const ns = ent.name;
|
|
17136
17820
|
let subEntries;
|
|
17137
17821
|
try {
|
|
17138
|
-
subEntries =
|
|
17822
|
+
subEntries = import_node_fs11.default.readdirSync(entryPath, { withFileTypes: true });
|
|
17139
17823
|
} catch {
|
|
17140
17824
|
continue;
|
|
17141
17825
|
}
|
|
17142
17826
|
for (const se of subEntries) {
|
|
17143
17827
|
if (!se.name.endsWith(".md")) continue;
|
|
17144
|
-
const sePath =
|
|
17828
|
+
const sePath = import_node_path11.default.join(entryPath, se.name);
|
|
17145
17829
|
let content;
|
|
17146
17830
|
try {
|
|
17147
|
-
content =
|
|
17831
|
+
content = import_node_fs11.default.readFileSync(sePath, "utf8");
|
|
17148
17832
|
} catch {
|
|
17149
17833
|
continue;
|
|
17150
17834
|
}
|
|
@@ -17161,7 +17845,7 @@ function scanCommandDir(dir, source, seen, out, pluginName) {
|
|
|
17161
17845
|
} else if (ent.name.endsWith(".md")) {
|
|
17162
17846
|
let content;
|
|
17163
17847
|
try {
|
|
17164
|
-
content =
|
|
17848
|
+
content = import_node_fs11.default.readFileSync(entryPath, "utf8");
|
|
17165
17849
|
} catch {
|
|
17166
17850
|
continue;
|
|
17167
17851
|
}
|
|
@@ -17177,10 +17861,10 @@ function scanCommandDir(dir, source, seen, out, pluginName) {
|
|
|
17177
17861
|
}
|
|
17178
17862
|
}
|
|
17179
17863
|
function readInstalledPlugins(home) {
|
|
17180
|
-
const file =
|
|
17864
|
+
const file = import_node_path11.default.join(home, ".claude", "plugins", "installed_plugins.json");
|
|
17181
17865
|
let raw;
|
|
17182
17866
|
try {
|
|
17183
|
-
raw =
|
|
17867
|
+
raw = import_node_fs11.default.readFileSync(file, "utf8");
|
|
17184
17868
|
} catch {
|
|
17185
17869
|
return [];
|
|
17186
17870
|
}
|
|
@@ -17218,14 +17902,14 @@ var SkillsScanner = class {
|
|
|
17218
17902
|
list(args) {
|
|
17219
17903
|
const seen = /* @__PURE__ */ new Set();
|
|
17220
17904
|
const out = [];
|
|
17221
|
-
scanSkillDir(
|
|
17222
|
-
scanCommandDir(
|
|
17223
|
-
scanSkillDir(
|
|
17224
|
-
scanCommandDir(
|
|
17905
|
+
scanSkillDir(import_node_path11.default.join(this.home, ".claude", "skills"), "global", seen, out);
|
|
17906
|
+
scanCommandDir(import_node_path11.default.join(this.home, ".claude", "commands"), "global", seen, out);
|
|
17907
|
+
scanSkillDir(import_node_path11.default.join(args.cwd, ".claude", "skills"), "project", seen, out);
|
|
17908
|
+
scanCommandDir(import_node_path11.default.join(args.cwd, ".claude", "commands"), "project", seen, out);
|
|
17225
17909
|
const plugins = [...readInstalledPlugins(this.home), ...this.extraPluginRoots];
|
|
17226
17910
|
for (const { name, root } of plugins) {
|
|
17227
|
-
scanSkillDir(
|
|
17228
|
-
scanCommandDir(
|
|
17911
|
+
scanSkillDir(import_node_path11.default.join(root, "skills"), "plugin", seen, out, name);
|
|
17912
|
+
scanCommandDir(import_node_path11.default.join(root, "commands"), "plugin", seen, out, name);
|
|
17229
17913
|
}
|
|
17230
17914
|
out.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
17231
17915
|
return out;
|
|
@@ -17233,9 +17917,9 @@ var SkillsScanner = class {
|
|
|
17233
17917
|
};
|
|
17234
17918
|
|
|
17235
17919
|
// src/observer/session-observer.ts
|
|
17236
|
-
var
|
|
17920
|
+
var import_node_fs12 = __toESM(require("fs"), 1);
|
|
17237
17921
|
var import_node_os6 = __toESM(require("os"), 1);
|
|
17238
|
-
var
|
|
17922
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
17239
17923
|
init_claude_history();
|
|
17240
17924
|
var SessionObserver = class {
|
|
17241
17925
|
constructor(opts) {
|
|
@@ -17247,14 +17931,14 @@ var SessionObserver = class {
|
|
|
17247
17931
|
watches = /* @__PURE__ */ new Map();
|
|
17248
17932
|
resolveJsonlPath(cwd, toolSessionId, override) {
|
|
17249
17933
|
if (override) return override;
|
|
17250
|
-
return
|
|
17934
|
+
return import_node_path12.default.join(this.home, ".claude", "projects", cwdToHashDir(cwd), `${toolSessionId}.jsonl`);
|
|
17251
17935
|
}
|
|
17252
17936
|
start(args) {
|
|
17253
17937
|
this.stop(args.sessionId);
|
|
17254
17938
|
const filePath = this.resolveJsonlPath(args.cwd, args.toolSessionId, args.jsonlPath);
|
|
17255
17939
|
let size = 0;
|
|
17256
17940
|
try {
|
|
17257
|
-
size =
|
|
17941
|
+
size = import_node_fs12.default.statSync(filePath).size;
|
|
17258
17942
|
} catch {
|
|
17259
17943
|
}
|
|
17260
17944
|
const w = {
|
|
@@ -17267,10 +17951,10 @@ var SessionObserver = class {
|
|
|
17267
17951
|
adapter: args.adapter
|
|
17268
17952
|
};
|
|
17269
17953
|
try {
|
|
17270
|
-
|
|
17954
|
+
import_node_fs12.default.mkdirSync(import_node_path12.default.dirname(filePath), { recursive: true });
|
|
17271
17955
|
} catch {
|
|
17272
17956
|
}
|
|
17273
|
-
w.watcher =
|
|
17957
|
+
w.watcher = import_node_fs12.default.watch(import_node_path12.default.dirname(filePath), { persistent: false }, (_event, changedName) => {
|
|
17274
17958
|
if (!changedName || !filePath.endsWith(changedName)) return;
|
|
17275
17959
|
this.poll(w);
|
|
17276
17960
|
});
|
|
@@ -17285,7 +17969,7 @@ var SessionObserver = class {
|
|
|
17285
17969
|
// reducer.shallowEqMeta diff 让重复 patch 静默吞掉;异常静默吞,不阻塞 watcher 启动
|
|
17286
17970
|
hydrateMetaTail(w, maxLines = 200) {
|
|
17287
17971
|
try {
|
|
17288
|
-
const raw =
|
|
17972
|
+
const raw = import_node_fs12.default.readFileSync(w.filePath, "utf8");
|
|
17289
17973
|
if (!raw) return;
|
|
17290
17974
|
const allLines = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
17291
17975
|
if (allLines.length === 0) return;
|
|
@@ -17306,7 +17990,7 @@ var SessionObserver = class {
|
|
|
17306
17990
|
poll(w) {
|
|
17307
17991
|
let size = 0;
|
|
17308
17992
|
try {
|
|
17309
|
-
size =
|
|
17993
|
+
size = import_node_fs12.default.statSync(w.filePath).size;
|
|
17310
17994
|
} catch {
|
|
17311
17995
|
return;
|
|
17312
17996
|
}
|
|
@@ -17315,11 +17999,11 @@ var SessionObserver = class {
|
|
|
17315
17999
|
w.buf = "";
|
|
17316
18000
|
}
|
|
17317
18001
|
if (size === w.lastSize) return;
|
|
17318
|
-
const fd =
|
|
18002
|
+
const fd = import_node_fs12.default.openSync(w.filePath, "r");
|
|
17319
18003
|
try {
|
|
17320
18004
|
const len = size - w.lastSize;
|
|
17321
18005
|
const buf = Buffer.alloc(len);
|
|
17322
|
-
|
|
18006
|
+
import_node_fs12.default.readSync(fd, buf, 0, len, w.lastSize);
|
|
17323
18007
|
w.lastSize = size;
|
|
17324
18008
|
w.buf += buf.toString("utf8");
|
|
17325
18009
|
let newlineIndex;
|
|
@@ -17333,7 +18017,7 @@ var SessionObserver = class {
|
|
|
17333
18017
|
this.maybeReportUserMessage(w.sessionId, line);
|
|
17334
18018
|
}
|
|
17335
18019
|
} finally {
|
|
17336
|
-
|
|
18020
|
+
import_node_fs12.default.closeSync(fd);
|
|
17337
18021
|
}
|
|
17338
18022
|
}
|
|
17339
18023
|
// 解析 JSONL 单行:仅当是主链 user 文本行(非 sidechain / 非 sub-agent / message.role='user'
|
|
@@ -17431,6 +18115,7 @@ var import_websocket = __toESM(require_websocket(), 1);
|
|
|
17431
18115
|
var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
17432
18116
|
|
|
17433
18117
|
// src/transport/local-ws-server.ts
|
|
18118
|
+
var PERSONA_PATH_RE = /^\/personas\/([a-zA-Z0-9._-]+)$/;
|
|
17434
18119
|
var LocalWsServer = class {
|
|
17435
18120
|
constructor(opts) {
|
|
17436
18121
|
this.opts = opts;
|
|
@@ -17459,7 +18144,7 @@ var LocalWsServer = class {
|
|
|
17459
18144
|
this.logger?.error("ws server error", { err: err.message });
|
|
17460
18145
|
reject(err);
|
|
17461
18146
|
});
|
|
17462
|
-
wss.on("connection", (socket, req) => this.
|
|
18147
|
+
wss.on("connection", (socket, req) => this.routeConnection(socket, req));
|
|
17463
18148
|
this.wss = wss;
|
|
17464
18149
|
});
|
|
17465
18150
|
}
|
|
@@ -17524,6 +18209,35 @@ var LocalWsServer = class {
|
|
|
17524
18209
|
if (!c) return;
|
|
17525
18210
|
this.safeSend(c.ws, frame);
|
|
17526
18211
|
}
|
|
18212
|
+
// URL path 路由:'/' → owner mode(first-message authToken gate 走 onConnection),
|
|
18213
|
+
// '/personas/<id>' → personaBoundHandler,其它 → close 4404
|
|
18214
|
+
routeConnection(socket, req) {
|
|
18215
|
+
const remoteAddress = req?.socket?.remoteAddress;
|
|
18216
|
+
const urlPath = (() => {
|
|
18217
|
+
try {
|
|
18218
|
+
return new URL(req.url ?? "/", "http://localhost").pathname;
|
|
18219
|
+
} catch {
|
|
18220
|
+
return "/";
|
|
18221
|
+
}
|
|
18222
|
+
})();
|
|
18223
|
+
if (urlPath === "/") {
|
|
18224
|
+
this.onConnection(socket, remoteAddress);
|
|
18225
|
+
return;
|
|
18226
|
+
}
|
|
18227
|
+
const personaMatch = urlPath.match(PERSONA_PATH_RE);
|
|
18228
|
+
if (personaMatch && this.opts.personaBoundHandler) {
|
|
18229
|
+
this.logger?.info("persona ws connected", {
|
|
18230
|
+
personaId: personaMatch[1],
|
|
18231
|
+
remoteAddress
|
|
18232
|
+
});
|
|
18233
|
+
this.opts.personaBoundHandler.handle(socket, personaMatch[1]);
|
|
18234
|
+
return;
|
|
18235
|
+
}
|
|
18236
|
+
try {
|
|
18237
|
+
socket.close(4404, "unknown path");
|
|
18238
|
+
} catch {
|
|
18239
|
+
}
|
|
18240
|
+
}
|
|
17527
18241
|
onConnection(socket, remoteAddress) {
|
|
17528
18242
|
const id = v4_default();
|
|
17529
18243
|
const subscribedSessions = /* @__PURE__ */ new Map();
|
|
@@ -17603,15 +18317,9 @@ var LocalWsServer = class {
|
|
|
17603
18317
|
return;
|
|
17604
18318
|
}
|
|
17605
18319
|
if (frame.type === "session:subscribe" && typeof frame.sessionId === "string") {
|
|
17606
|
-
|
|
17607
|
-
addSubscription(client, sessionId);
|
|
18320
|
+
addSubscription(client, frame.sessionId);
|
|
17608
18321
|
if (typeof frame.requestId === "string") {
|
|
17609
|
-
this.safeSend(this.clients.get(client.id).ws, { type: "subscribed", requestId: frame.requestId, sessionId });
|
|
17610
|
-
}
|
|
17611
|
-
try {
|
|
17612
|
-
this.opts.onSubscribe?.(client, sessionId);
|
|
17613
|
-
} catch (err) {
|
|
17614
|
-
this.logger?.warn("onSubscribe hook threw", { err: err.message });
|
|
18322
|
+
this.safeSend(this.clients.get(client.id).ws, { type: "subscribed", requestId: frame.requestId, sessionId: frame.sessionId });
|
|
17615
18323
|
}
|
|
17616
18324
|
return;
|
|
17617
18325
|
}
|
|
@@ -17661,6 +18369,141 @@ function removeSubscription(client, sessionId) {
|
|
|
17661
18369
|
else client.subscribedSessions.set(sessionId, next);
|
|
17662
18370
|
}
|
|
17663
18371
|
|
|
18372
|
+
// src/transport/persona-bound-handler.ts
|
|
18373
|
+
init_protocol();
|
|
18374
|
+
var PersonaBoundHandler = class {
|
|
18375
|
+
constructor(deps) {
|
|
18376
|
+
this.deps = deps;
|
|
18377
|
+
}
|
|
18378
|
+
deps;
|
|
18379
|
+
handle(ws, personaId) {
|
|
18380
|
+
let scope = null;
|
|
18381
|
+
let unsubscribe = null;
|
|
18382
|
+
const send = (frame) => {
|
|
18383
|
+
try {
|
|
18384
|
+
ws.send(JSON.stringify(frame));
|
|
18385
|
+
} catch (err) {
|
|
18386
|
+
this.deps.logger.warn(`persona ws send failed: ${err.message}`);
|
|
18387
|
+
}
|
|
18388
|
+
};
|
|
18389
|
+
ws.on("message", (raw) => {
|
|
18390
|
+
let parsedRaw;
|
|
18391
|
+
try {
|
|
18392
|
+
parsedRaw = JSON.parse(raw.toString());
|
|
18393
|
+
} catch {
|
|
18394
|
+
ws.close(4400, "PROTOCOL_VIOLATION");
|
|
18395
|
+
return;
|
|
18396
|
+
}
|
|
18397
|
+
const parseResult = PersonaClientFrameSchema.safeParse(parsedRaw);
|
|
18398
|
+
if (!parseResult.success) {
|
|
18399
|
+
ws.close(4400, "PROTOCOL_VIOLATION");
|
|
18400
|
+
return;
|
|
18401
|
+
}
|
|
18402
|
+
const frame = parseResult.data;
|
|
18403
|
+
if (!scope) {
|
|
18404
|
+
if (frame.kind !== "persona-hello") {
|
|
18405
|
+
ws.close(4400, "PROTOCOL_VIOLATION");
|
|
18406
|
+
return;
|
|
18407
|
+
}
|
|
18408
|
+
const verify = this.deps.registry.verifyToken(personaId, frame.token);
|
|
18409
|
+
if (!verify.ok) {
|
|
18410
|
+
const code = verify.code === "PERSONA_DELETED" ? 4404 : verify.code === "PERSONA_NOT_PUBLIC" ? 4403 : 4401;
|
|
18411
|
+
ws.close(code, verify.code);
|
|
18412
|
+
return;
|
|
18413
|
+
}
|
|
18414
|
+
const persona = this.deps.registry.get(personaId);
|
|
18415
|
+
if (!persona) {
|
|
18416
|
+
ws.close(4404, "PERSONA_DELETED");
|
|
18417
|
+
return;
|
|
18418
|
+
}
|
|
18419
|
+
let subSessionFile;
|
|
18420
|
+
try {
|
|
18421
|
+
const { sessionFile } = this.deps.personaManager.getOrCreateSubSession(
|
|
18422
|
+
personaId,
|
|
18423
|
+
frame.token
|
|
18424
|
+
);
|
|
18425
|
+
subSessionFile = sessionFile;
|
|
18426
|
+
} catch (err) {
|
|
18427
|
+
this.deps.logger.warn(
|
|
18428
|
+
`persona getOrCreateSubSession failed: ${err.message}`
|
|
18429
|
+
);
|
|
18430
|
+
ws.close(1011, "INTERNAL");
|
|
18431
|
+
return;
|
|
18432
|
+
}
|
|
18433
|
+
scope = { personaId, subSessionId: subSessionFile.sessionId };
|
|
18434
|
+
send({
|
|
18435
|
+
kind: "persona-ready",
|
|
18436
|
+
subSessionId: subSessionFile.sessionId,
|
|
18437
|
+
personaLabel: persona.label,
|
|
18438
|
+
cwd: persona.cwd,
|
|
18439
|
+
model: persona.model
|
|
18440
|
+
});
|
|
18441
|
+
unsubscribe = this.deps.sessionManager.subscribe(
|
|
18442
|
+
subSessionFile.sessionId,
|
|
18443
|
+
personaId,
|
|
18444
|
+
(event) => {
|
|
18445
|
+
send({ kind: "event", event });
|
|
18446
|
+
}
|
|
18447
|
+
);
|
|
18448
|
+
try {
|
|
18449
|
+
const history = this.deps.sessionManager.readHistoryEvents(
|
|
18450
|
+
subSessionFile.sessionId,
|
|
18451
|
+
personaId
|
|
18452
|
+
);
|
|
18453
|
+
for (const event of history) {
|
|
18454
|
+
send({ kind: "history-event", event });
|
|
18455
|
+
}
|
|
18456
|
+
} catch (err) {
|
|
18457
|
+
this.deps.logger.warn(
|
|
18458
|
+
`persona history read failed: ${err.message}`
|
|
18459
|
+
);
|
|
18460
|
+
}
|
|
18461
|
+
send({ kind: "history-done" });
|
|
18462
|
+
return;
|
|
18463
|
+
}
|
|
18464
|
+
switch (frame.kind) {
|
|
18465
|
+
case "persona-hello":
|
|
18466
|
+
ws.close(4400, "PROTOCOL_VIOLATION");
|
|
18467
|
+
return;
|
|
18468
|
+
case "send": {
|
|
18469
|
+
try {
|
|
18470
|
+
this.deps.sessionManager.sendForAgent({
|
|
18471
|
+
sessionId: scope.subSessionId,
|
|
18472
|
+
agentId: scope.personaId,
|
|
18473
|
+
text: frame.text
|
|
18474
|
+
});
|
|
18475
|
+
} catch (err) {
|
|
18476
|
+
this.deps.logger.warn(
|
|
18477
|
+
`persona sendForAgent failed: ${err.message}`
|
|
18478
|
+
);
|
|
18479
|
+
}
|
|
18480
|
+
return;
|
|
18481
|
+
}
|
|
18482
|
+
case "reset": {
|
|
18483
|
+
try {
|
|
18484
|
+
this.deps.sessionManager.resetForAgent({
|
|
18485
|
+
sessionId: scope.subSessionId,
|
|
18486
|
+
agentId: scope.personaId
|
|
18487
|
+
});
|
|
18488
|
+
} catch (err) {
|
|
18489
|
+
this.deps.logger.warn(
|
|
18490
|
+
`persona resetForAgent failed: ${err.message}`
|
|
18491
|
+
);
|
|
18492
|
+
}
|
|
18493
|
+
return;
|
|
18494
|
+
}
|
|
18495
|
+
case "ping":
|
|
18496
|
+
send({ kind: "pong" });
|
|
18497
|
+
return;
|
|
18498
|
+
}
|
|
18499
|
+
});
|
|
18500
|
+
ws.on("close", () => {
|
|
18501
|
+
unsubscribe?.();
|
|
18502
|
+
unsubscribe = null;
|
|
18503
|
+
});
|
|
18504
|
+
}
|
|
18505
|
+
};
|
|
18506
|
+
|
|
17664
18507
|
// src/transport/auth.ts
|
|
17665
18508
|
init_protocol();
|
|
17666
18509
|
var AuthGate = class {
|
|
@@ -17756,10 +18599,10 @@ function isLocalhost(addr) {
|
|
|
17756
18599
|
}
|
|
17757
18600
|
|
|
17758
18601
|
// src/discovery/state-file.ts
|
|
17759
|
-
var
|
|
17760
|
-
var
|
|
18602
|
+
var import_node_fs13 = __toESM(require("fs"), 1);
|
|
18603
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
17761
18604
|
function defaultStateFilePath(dataDir) {
|
|
17762
|
-
return
|
|
18605
|
+
return import_node_path13.default.join(dataDir, "state.json");
|
|
17763
18606
|
}
|
|
17764
18607
|
function isPidAlive(pid) {
|
|
17765
18608
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
@@ -17781,7 +18624,7 @@ var StateFileManager = class {
|
|
|
17781
18624
|
}
|
|
17782
18625
|
read() {
|
|
17783
18626
|
try {
|
|
17784
|
-
const raw =
|
|
18627
|
+
const raw = import_node_fs13.default.readFileSync(this.file, "utf8");
|
|
17785
18628
|
const parsed = JSON.parse(raw);
|
|
17786
18629
|
return parsed;
|
|
17787
18630
|
} catch {
|
|
@@ -17795,34 +18638,34 @@ var StateFileManager = class {
|
|
|
17795
18638
|
return { status: "stale", existing };
|
|
17796
18639
|
}
|
|
17797
18640
|
write(state) {
|
|
17798
|
-
|
|
18641
|
+
import_node_fs13.default.mkdirSync(import_node_path13.default.dirname(this.file), { recursive: true });
|
|
17799
18642
|
const tmp = `${this.file}.tmp.${process.pid}.${Date.now()}`;
|
|
17800
|
-
|
|
17801
|
-
|
|
18643
|
+
import_node_fs13.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
|
|
18644
|
+
import_node_fs13.default.renameSync(tmp, this.file);
|
|
17802
18645
|
if (process.platform !== "win32") {
|
|
17803
18646
|
try {
|
|
17804
|
-
|
|
18647
|
+
import_node_fs13.default.chmodSync(this.file, 384);
|
|
17805
18648
|
} catch {
|
|
17806
18649
|
}
|
|
17807
18650
|
}
|
|
17808
18651
|
}
|
|
17809
18652
|
delete() {
|
|
17810
18653
|
try {
|
|
17811
|
-
|
|
18654
|
+
import_node_fs13.default.unlinkSync(this.file);
|
|
17812
18655
|
} catch {
|
|
17813
18656
|
}
|
|
17814
18657
|
}
|
|
17815
18658
|
};
|
|
17816
18659
|
|
|
17817
18660
|
// src/tunnel/tunnel-manager.ts
|
|
17818
|
-
var
|
|
17819
|
-
var
|
|
17820
|
-
var
|
|
18661
|
+
var import_node_fs16 = __toESM(require("fs"), 1);
|
|
18662
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
18663
|
+
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
17821
18664
|
var import_node_child_process4 = require("child_process");
|
|
17822
18665
|
|
|
17823
18666
|
// src/tunnel/tunnel-store.ts
|
|
17824
|
-
var
|
|
17825
|
-
var
|
|
18667
|
+
var import_node_fs14 = __toESM(require("fs"), 1);
|
|
18668
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
17826
18669
|
var TunnelStore = class {
|
|
17827
18670
|
constructor(filePath) {
|
|
17828
18671
|
this.filePath = filePath;
|
|
@@ -17830,7 +18673,7 @@ var TunnelStore = class {
|
|
|
17830
18673
|
filePath;
|
|
17831
18674
|
async get() {
|
|
17832
18675
|
try {
|
|
17833
|
-
const raw = await
|
|
18676
|
+
const raw = await import_node_fs14.default.promises.readFile(this.filePath, "utf8");
|
|
17834
18677
|
const obj = JSON.parse(raw);
|
|
17835
18678
|
if (!isPersistedTunnel(obj)) return null;
|
|
17836
18679
|
return obj;
|
|
@@ -17841,22 +18684,22 @@ var TunnelStore = class {
|
|
|
17841
18684
|
}
|
|
17842
18685
|
}
|
|
17843
18686
|
async set(v) {
|
|
17844
|
-
const dir =
|
|
17845
|
-
await
|
|
18687
|
+
const dir = import_node_path14.default.dirname(this.filePath);
|
|
18688
|
+
await import_node_fs14.default.promises.mkdir(dir, { recursive: true });
|
|
17846
18689
|
const data = JSON.stringify(v, null, 2);
|
|
17847
18690
|
const tmp = `${this.filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
17848
|
-
await
|
|
18691
|
+
await import_node_fs14.default.promises.writeFile(tmp, data, { mode: 384 });
|
|
17849
18692
|
if (process.platform !== "win32") {
|
|
17850
18693
|
try {
|
|
17851
|
-
await
|
|
18694
|
+
await import_node_fs14.default.promises.chmod(tmp, 384);
|
|
17852
18695
|
} catch {
|
|
17853
18696
|
}
|
|
17854
18697
|
}
|
|
17855
|
-
await
|
|
18698
|
+
await import_node_fs14.default.promises.rename(tmp, this.filePath);
|
|
17856
18699
|
}
|
|
17857
18700
|
async clear() {
|
|
17858
18701
|
try {
|
|
17859
|
-
await
|
|
18702
|
+
await import_node_fs14.default.promises.unlink(this.filePath);
|
|
17860
18703
|
} catch (err) {
|
|
17861
18704
|
const code = err?.code;
|
|
17862
18705
|
if (code !== "ENOENT") throw err;
|
|
@@ -17951,9 +18794,9 @@ function escape(v) {
|
|
|
17951
18794
|
}
|
|
17952
18795
|
|
|
17953
18796
|
// src/tunnel/frpc-binary.ts
|
|
17954
|
-
var
|
|
18797
|
+
var import_node_fs15 = __toESM(require("fs"), 1);
|
|
17955
18798
|
var import_node_os7 = __toESM(require("os"), 1);
|
|
17956
|
-
var
|
|
18799
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
17957
18800
|
var import_node_child_process3 = require("child_process");
|
|
17958
18801
|
var import_node_stream = require("stream");
|
|
17959
18802
|
var import_promises = require("stream/promises");
|
|
@@ -17985,20 +18828,20 @@ function frpcDownloadUrl(version2, p) {
|
|
|
17985
18828
|
}
|
|
17986
18829
|
async function ensureFrpcBinary(opts) {
|
|
17987
18830
|
if (opts.override) {
|
|
17988
|
-
if (!
|
|
18831
|
+
if (!import_node_fs15.default.existsSync(opts.override)) {
|
|
17989
18832
|
throw new Error(`frpc binary not found at override path: ${opts.override}`);
|
|
17990
18833
|
}
|
|
17991
18834
|
return opts.override;
|
|
17992
18835
|
}
|
|
17993
18836
|
const version2 = opts.version ?? FRPC_VERSION;
|
|
17994
18837
|
const platform = opts.platform ?? detectPlatform();
|
|
17995
|
-
const binDir =
|
|
17996
|
-
|
|
18838
|
+
const binDir = import_node_path15.default.join(opts.dataDir, "bin");
|
|
18839
|
+
import_node_fs15.default.mkdirSync(binDir, { recursive: true });
|
|
17997
18840
|
cleanupStaleArtifacts(binDir);
|
|
17998
|
-
const stableBin =
|
|
17999
|
-
if (
|
|
18841
|
+
const stableBin = import_node_path15.default.join(binDir, "frpc");
|
|
18842
|
+
if (import_node_fs15.default.existsSync(stableBin)) return stableBin;
|
|
18000
18843
|
const partialBin = `${stableBin}.partial`;
|
|
18001
|
-
const tarballPath =
|
|
18844
|
+
const tarballPath = import_node_path15.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
|
|
18002
18845
|
try {
|
|
18003
18846
|
const url = frpcDownloadUrl(version2, platform);
|
|
18004
18847
|
await downloadToFile(url, tarballPath, opts.fetchImpl);
|
|
@@ -18007,8 +18850,8 @@ async function ensureFrpcBinary(opts) {
|
|
|
18007
18850
|
} else {
|
|
18008
18851
|
await extractFrpcFromTarball(tarballPath, binDir, version2, platform, partialBin);
|
|
18009
18852
|
}
|
|
18010
|
-
|
|
18011
|
-
|
|
18853
|
+
import_node_fs15.default.chmodSync(partialBin, 493);
|
|
18854
|
+
import_node_fs15.default.renameSync(partialBin, stableBin);
|
|
18012
18855
|
} finally {
|
|
18013
18856
|
safeUnlink(tarballPath);
|
|
18014
18857
|
safeUnlink(partialBin);
|
|
@@ -18018,15 +18861,15 @@ async function ensureFrpcBinary(opts) {
|
|
|
18018
18861
|
function cleanupStaleArtifacts(binDir) {
|
|
18019
18862
|
let entries;
|
|
18020
18863
|
try {
|
|
18021
|
-
entries =
|
|
18864
|
+
entries = import_node_fs15.default.readdirSync(binDir);
|
|
18022
18865
|
} catch {
|
|
18023
18866
|
return;
|
|
18024
18867
|
}
|
|
18025
18868
|
for (const name of entries) {
|
|
18026
18869
|
if (name.endsWith(".partial") || name.startsWith("extract-")) {
|
|
18027
|
-
const full =
|
|
18870
|
+
const full = import_node_path15.default.join(binDir, name);
|
|
18028
18871
|
try {
|
|
18029
|
-
|
|
18872
|
+
import_node_fs15.default.rmSync(full, { recursive: true, force: true });
|
|
18030
18873
|
} catch {
|
|
18031
18874
|
}
|
|
18032
18875
|
}
|
|
@@ -18034,7 +18877,7 @@ function cleanupStaleArtifacts(binDir) {
|
|
|
18034
18877
|
}
|
|
18035
18878
|
function safeUnlink(p) {
|
|
18036
18879
|
try {
|
|
18037
|
-
|
|
18880
|
+
import_node_fs15.default.unlinkSync(p);
|
|
18038
18881
|
} catch {
|
|
18039
18882
|
}
|
|
18040
18883
|
}
|
|
@@ -18045,13 +18888,13 @@ async function downloadToFile(url, dest, fetchImpl) {
|
|
|
18045
18888
|
if (!res.ok || !res.body) {
|
|
18046
18889
|
throw new Error(`download failed: ${res.status} ${res.statusText}`);
|
|
18047
18890
|
}
|
|
18048
|
-
const out =
|
|
18891
|
+
const out = import_node_fs15.default.createWriteStream(dest);
|
|
18049
18892
|
const nodeStream = import_node_stream.Readable.fromWeb(res.body);
|
|
18050
18893
|
await (0, import_promises.pipeline)(nodeStream, out);
|
|
18051
18894
|
}
|
|
18052
18895
|
async function extractFrpcFromTarball(tarball, binDir, version2, platform, destBin) {
|
|
18053
|
-
const work =
|
|
18054
|
-
|
|
18896
|
+
const work = import_node_path15.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
|
|
18897
|
+
import_node_fs15.default.mkdirSync(work, { recursive: true });
|
|
18055
18898
|
try {
|
|
18056
18899
|
await new Promise((resolve, reject) => {
|
|
18057
18900
|
const proc = (0, import_node_child_process3.spawn)("tar", ["xzf", tarball, "-C", work], { stdio: "pipe" });
|
|
@@ -18059,13 +18902,13 @@ async function extractFrpcFromTarball(tarball, binDir, version2, platform, destB
|
|
|
18059
18902
|
proc.on("exit", (code) => code === 0 ? resolve() : reject(new Error(`tar exited ${code}`)));
|
|
18060
18903
|
});
|
|
18061
18904
|
const dirName = `frp_${version2}_${platform.os}_${platform.arch}`;
|
|
18062
|
-
const src =
|
|
18063
|
-
if (!
|
|
18905
|
+
const src = import_node_path15.default.join(work, dirName, "frpc");
|
|
18906
|
+
if (!import_node_fs15.default.existsSync(src)) {
|
|
18064
18907
|
throw new Error(`frpc not found inside tarball at ${src}`);
|
|
18065
18908
|
}
|
|
18066
|
-
|
|
18909
|
+
import_node_fs15.default.copyFileSync(src, destBin);
|
|
18067
18910
|
} finally {
|
|
18068
|
-
|
|
18911
|
+
import_node_fs15.default.rmSync(work, { recursive: true, force: true });
|
|
18069
18912
|
}
|
|
18070
18913
|
}
|
|
18071
18914
|
|
|
@@ -18074,7 +18917,7 @@ var DEFAULT_TUNNEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
|
18074
18917
|
var TunnelManager = class {
|
|
18075
18918
|
constructor(deps) {
|
|
18076
18919
|
this.deps = deps;
|
|
18077
|
-
this.store = deps.store ?? new TunnelStore(
|
|
18920
|
+
this.store = deps.store ?? new TunnelStore(import_node_path16.default.join(deps.dataDir, "tunnel.json"));
|
|
18078
18921
|
this.ttlMs = deps.ttlMs ?? DEFAULT_TUNNEL_TTL_MS;
|
|
18079
18922
|
this.startupTimeoutMs = deps.startupTimeoutMs ?? 15e3;
|
|
18080
18923
|
}
|
|
@@ -18191,8 +19034,8 @@ var TunnelManager = class {
|
|
|
18191
19034
|
dataDir: this.deps.dataDir,
|
|
18192
19035
|
override: this.deps.frpcBinaryOverride ?? void 0
|
|
18193
19036
|
});
|
|
18194
|
-
const tomlPath =
|
|
18195
|
-
const proxyName = `clawd-${t.subdomain}-${localPort}-${
|
|
19037
|
+
const tomlPath = import_node_path16.default.join(this.deps.dataDir, "frpc.toml");
|
|
19038
|
+
const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto4.default.randomBytes(3).toString("hex")}`;
|
|
18196
19039
|
const toml = buildFrpcToml({
|
|
18197
19040
|
serverAddr: t.frpsHost,
|
|
18198
19041
|
serverPort: t.frpsPort,
|
|
@@ -18202,12 +19045,12 @@ var TunnelManager = class {
|
|
|
18202
19045
|
localPort,
|
|
18203
19046
|
logLevel: "info"
|
|
18204
19047
|
});
|
|
18205
|
-
await
|
|
19048
|
+
await import_node_fs16.default.promises.writeFile(tomlPath, toml, { mode: 384 });
|
|
18206
19049
|
const proc = (this.deps.spawnImpl ?? import_node_child_process4.spawn)(frpcBin, ["-c", tomlPath], {
|
|
18207
19050
|
stdio: ["ignore", "pipe", "pipe"]
|
|
18208
19051
|
});
|
|
18209
|
-
const logFilePath =
|
|
18210
|
-
const logStream =
|
|
19052
|
+
const logFilePath = import_node_path16.default.join(this.deps.dataDir, "frpc.log");
|
|
19053
|
+
const logStream = import_node_fs16.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
|
|
18211
19054
|
logStream.on("error", () => {
|
|
18212
19055
|
});
|
|
18213
19056
|
const tee = (chunk) => {
|
|
@@ -18289,22 +19132,22 @@ async function waitForFrpcReady(proc, timeoutMs) {
|
|
|
18289
19132
|
|
|
18290
19133
|
// src/tunnel/device-key.ts
|
|
18291
19134
|
var import_node_os8 = __toESM(require("os"), 1);
|
|
18292
|
-
var
|
|
19135
|
+
var import_node_crypto5 = __toESM(require("crypto"), 1);
|
|
18293
19136
|
var DERIVE_SALT = "clawd-tunnel-device-v1";
|
|
18294
19137
|
function deriveStableDeviceKey(opts = {}) {
|
|
18295
19138
|
const hostname = opts.hostname ?? import_node_os8.default.hostname();
|
|
18296
19139
|
const uid = opts.uid ?? (typeof import_node_os8.default.userInfo === "function" ? import_node_os8.default.userInfo().uid : 0);
|
|
18297
19140
|
const input = `${hostname}::${uid}`;
|
|
18298
|
-
return
|
|
19141
|
+
return import_node_crypto5.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
|
|
18299
19142
|
}
|
|
18300
19143
|
|
|
18301
19144
|
// src/auth-store.ts
|
|
18302
|
-
var
|
|
18303
|
-
var
|
|
18304
|
-
var
|
|
19145
|
+
var import_node_fs17 = __toESM(require("fs"), 1);
|
|
19146
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
19147
|
+
var import_node_crypto6 = __toESM(require("crypto"), 1);
|
|
18305
19148
|
var AUTH_FILE_NAME = "auth.json";
|
|
18306
19149
|
function authFilePath(dataDir) {
|
|
18307
|
-
return
|
|
19150
|
+
return import_node_path17.default.join(dataDir, AUTH_FILE_NAME);
|
|
18308
19151
|
}
|
|
18309
19152
|
function loadOrCreateAuthToken(opts) {
|
|
18310
19153
|
const file = authFilePath(opts.dataDir);
|
|
@@ -18316,11 +19159,11 @@ function loadOrCreateAuthToken(opts) {
|
|
|
18316
19159
|
return token;
|
|
18317
19160
|
}
|
|
18318
19161
|
function defaultGenerate() {
|
|
18319
|
-
return
|
|
19162
|
+
return import_node_crypto6.default.randomBytes(32).toString("base64url");
|
|
18320
19163
|
}
|
|
18321
19164
|
function readAuthFile(file) {
|
|
18322
19165
|
try {
|
|
18323
|
-
const raw =
|
|
19166
|
+
const raw = import_node_fs17.default.readFileSync(file, "utf8");
|
|
18324
19167
|
const parsed = JSON.parse(raw);
|
|
18325
19168
|
if (typeof parsed?.token === "string" && parsed.token.length > 0) {
|
|
18326
19169
|
return {
|
|
@@ -18336,10 +19179,10 @@ function readAuthFile(file) {
|
|
|
18336
19179
|
}
|
|
18337
19180
|
}
|
|
18338
19181
|
function writeAuthFile(file, content) {
|
|
18339
|
-
|
|
18340
|
-
|
|
19182
|
+
import_node_fs17.default.mkdirSync(import_node_path17.default.dirname(file), { recursive: true });
|
|
19183
|
+
import_node_fs17.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
|
|
18341
19184
|
try {
|
|
18342
|
-
|
|
19185
|
+
import_node_fs17.default.chmodSync(file, 384);
|
|
18343
19186
|
} catch {
|
|
18344
19187
|
}
|
|
18345
19188
|
}
|
|
@@ -18351,12 +19194,12 @@ init_protocol();
|
|
|
18351
19194
|
init_protocol();
|
|
18352
19195
|
|
|
18353
19196
|
// src/session/fork.ts
|
|
18354
|
-
var
|
|
19197
|
+
var import_node_fs18 = __toESM(require("fs"), 1);
|
|
18355
19198
|
var import_node_os9 = __toESM(require("os"), 1);
|
|
18356
|
-
var
|
|
19199
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
18357
19200
|
init_claude_history();
|
|
18358
19201
|
function readJsonlEntries(file) {
|
|
18359
|
-
const raw =
|
|
19202
|
+
const raw = import_node_fs18.default.readFileSync(file, "utf8");
|
|
18360
19203
|
const out = [];
|
|
18361
19204
|
for (const line of raw.split("\n")) {
|
|
18362
19205
|
const t = line.trim();
|
|
@@ -18369,10 +19212,10 @@ function readJsonlEntries(file) {
|
|
|
18369
19212
|
return out;
|
|
18370
19213
|
}
|
|
18371
19214
|
function forkSession(input) {
|
|
18372
|
-
const baseDir = input.baseDir ??
|
|
18373
|
-
const projectDir =
|
|
18374
|
-
const sourceFile =
|
|
18375
|
-
if (!
|
|
19215
|
+
const baseDir = input.baseDir ?? import_node_path18.default.join(import_node_os9.default.homedir(), ".claude");
|
|
19216
|
+
const projectDir = import_node_path18.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
|
|
19217
|
+
const sourceFile = import_node_path18.default.join(projectDir, `${input.toolSessionId}.jsonl`);
|
|
19218
|
+
if (!import_node_fs18.default.existsSync(sourceFile)) {
|
|
18376
19219
|
throw new Error(`fork: source transcript not found: ${sourceFile}`);
|
|
18377
19220
|
}
|
|
18378
19221
|
const entries = readJsonlEntries(sourceFile);
|
|
@@ -18402,9 +19245,9 @@ function forkSession(input) {
|
|
|
18402
19245
|
}
|
|
18403
19246
|
forkedLines.push(JSON.stringify(forked));
|
|
18404
19247
|
}
|
|
18405
|
-
const forkedFilePath =
|
|
18406
|
-
|
|
18407
|
-
|
|
19248
|
+
const forkedFilePath = import_node_path18.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
|
|
19249
|
+
import_node_fs18.default.mkdirSync(projectDir, { recursive: true });
|
|
19250
|
+
import_node_fs18.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
|
|
18408
19251
|
return { forkedToolSessionId, forkedFilePath };
|
|
18409
19252
|
}
|
|
18410
19253
|
|
|
@@ -18674,9 +19517,9 @@ init_protocol();
|
|
|
18674
19517
|
|
|
18675
19518
|
// src/workspace/git.ts
|
|
18676
19519
|
var import_node_child_process5 = require("child_process");
|
|
18677
|
-
var
|
|
19520
|
+
var import_node_fs19 = __toESM(require("fs"), 1);
|
|
18678
19521
|
var import_node_os10 = __toESM(require("os"), 1);
|
|
18679
|
-
var
|
|
19522
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
18680
19523
|
var import_node_util = require("util");
|
|
18681
19524
|
var pexec = (0, import_node_util.promisify)(import_node_child_process5.execFile);
|
|
18682
19525
|
function formatChildProcessError(err) {
|
|
@@ -18691,9 +19534,9 @@ function formatChildProcessError(err) {
|
|
|
18691
19534
|
return e.message ?? "unknown error";
|
|
18692
19535
|
}
|
|
18693
19536
|
function normalizePath(p) {
|
|
18694
|
-
const resolved =
|
|
19537
|
+
const resolved = import_node_path19.default.resolve(p);
|
|
18695
19538
|
try {
|
|
18696
|
-
return
|
|
19539
|
+
return import_node_fs19.default.realpathSync(resolved);
|
|
18697
19540
|
} catch {
|
|
18698
19541
|
return resolved;
|
|
18699
19542
|
}
|
|
@@ -18794,13 +19637,13 @@ function flattenToDirName(branch) {
|
|
|
18794
19637
|
}
|
|
18795
19638
|
function encodeClaudeProjectDir(absPath) {
|
|
18796
19639
|
if (!absPath || typeof absPath !== "string") return "";
|
|
18797
|
-
let canonical =
|
|
19640
|
+
let canonical = import_node_path19.default.resolve(absPath);
|
|
18798
19641
|
try {
|
|
18799
|
-
canonical =
|
|
19642
|
+
canonical = import_node_fs19.default.realpathSync(canonical);
|
|
18800
19643
|
} catch {
|
|
18801
19644
|
try {
|
|
18802
|
-
const parent =
|
|
18803
|
-
canonical =
|
|
19645
|
+
const parent = import_node_fs19.default.realpathSync(import_node_path19.default.dirname(canonical));
|
|
19646
|
+
canonical = import_node_path19.default.join(parent, import_node_path19.default.basename(canonical));
|
|
18804
19647
|
} catch {
|
|
18805
19648
|
}
|
|
18806
19649
|
}
|
|
@@ -18824,11 +19667,11 @@ async function createWorktree(input) {
|
|
|
18824
19667
|
if (!isGitRoot) {
|
|
18825
19668
|
throw new Error(`\u76EE\u5F55 ${cwd} \u4E0D\u662F git repo \u6839`);
|
|
18826
19669
|
}
|
|
18827
|
-
const parent =
|
|
18828
|
-
if (parent === "/" || parent ===
|
|
19670
|
+
const parent = import_node_path19.default.dirname(import_node_path19.default.resolve(cwd));
|
|
19671
|
+
if (parent === "/" || parent === import_node_path19.default.resolve(cwd)) {
|
|
18829
19672
|
throw new Error("repo \u5728\u78C1\u76D8\u6839\u76EE\u5F55\uFF0C\u65E0\u6CD5\u5728\u540C\u7EA7\u521B\u5EFA worktree");
|
|
18830
19673
|
}
|
|
18831
|
-
const worktreeRoot =
|
|
19674
|
+
const worktreeRoot = import_node_path19.default.join(parent, dirName);
|
|
18832
19675
|
try {
|
|
18833
19676
|
await pexec("git", ["-C", cwd, "rev-parse", "--verify", `refs/heads/${baseBranch}`], {
|
|
18834
19677
|
timeout: 3e3
|
|
@@ -18845,7 +19688,7 @@ async function createWorktree(input) {
|
|
|
18845
19688
|
const msg = err.message;
|
|
18846
19689
|
if (msg.startsWith("\u5206\u652F ")) throw err;
|
|
18847
19690
|
}
|
|
18848
|
-
if (
|
|
19691
|
+
if (import_node_fs19.default.existsSync(worktreeRoot)) {
|
|
18849
19692
|
throw new Error(`\u76EE\u5F55 ${worktreeRoot} \u5DF2\u5B58\u5728\uFF0C\u8BF7\u6362\u4E00\u4E2A label \u6216\u6E05\u7406\u540E\u91CD\u8BD5`);
|
|
18850
19693
|
}
|
|
18851
19694
|
try {
|
|
@@ -18873,8 +19716,8 @@ async function removeWorktree(input) {
|
|
|
18873
19716
|
);
|
|
18874
19717
|
const gitCommonDir = stdout.trim();
|
|
18875
19718
|
if (!gitCommonDir) throw new Error("empty git-common-dir");
|
|
18876
|
-
const absGitCommon =
|
|
18877
|
-
repoRoot =
|
|
19719
|
+
const absGitCommon = import_node_path19.default.isAbsolute(gitCommonDir) ? gitCommonDir : import_node_path19.default.resolve(worktreeRoot, gitCommonDir);
|
|
19720
|
+
repoRoot = import_node_path19.default.dirname(absGitCommon);
|
|
18878
19721
|
} catch {
|
|
18879
19722
|
repoRoot = null;
|
|
18880
19723
|
}
|
|
@@ -18886,7 +19729,7 @@ async function removeWorktree(input) {
|
|
|
18886
19729
|
} catch (err) {
|
|
18887
19730
|
const stderr = err.stderr ?? "";
|
|
18888
19731
|
const lower = stderr.toLowerCase();
|
|
18889
|
-
const vanished = lower.includes("not a working tree") || lower.includes("is not a working tree") || !
|
|
19732
|
+
const vanished = lower.includes("not a working tree") || lower.includes("is not a working tree") || !import_node_fs19.default.existsSync(worktreeRoot);
|
|
18890
19733
|
if (!vanished) {
|
|
18891
19734
|
throw new Error(`\u6E05\u7406 worktree \u5931\u8D25\uFF1A${formatChildProcessError(err)}`);
|
|
18892
19735
|
}
|
|
@@ -18905,10 +19748,10 @@ async function removeWorktree(input) {
|
|
|
18905
19748
|
try {
|
|
18906
19749
|
const encoded = encodeClaudeProjectDir(worktreeRoot);
|
|
18907
19750
|
if (encoded) {
|
|
18908
|
-
const projectsRoot =
|
|
18909
|
-
const target =
|
|
18910
|
-
if (target.startsWith(projectsRoot +
|
|
18911
|
-
|
|
19751
|
+
const projectsRoot = import_node_path19.default.join(import_node_os10.default.homedir(), ".claude", "projects");
|
|
19752
|
+
const target = import_node_path19.default.resolve(projectsRoot, encoded);
|
|
19753
|
+
if (target.startsWith(projectsRoot + import_node_path19.default.sep) && target !== projectsRoot) {
|
|
19754
|
+
import_node_fs19.default.rmSync(target, { recursive: true, force: true });
|
|
18912
19755
|
}
|
|
18913
19756
|
}
|
|
18914
19757
|
} catch {
|
|
@@ -18998,13 +19841,15 @@ function buildReadyFrame(deps) {
|
|
|
18998
19841
|
tools.push({ id, available: false });
|
|
18999
19842
|
}
|
|
19000
19843
|
}
|
|
19844
|
+
const tunnelUrl = deps.getTunnelUrl ? deps.getTunnelUrl() : null;
|
|
19001
19845
|
return {
|
|
19002
19846
|
version,
|
|
19003
19847
|
protocolVersion: PROTOCOL_VERSION,
|
|
19004
19848
|
hostname: import_node_os11.default.hostname(),
|
|
19005
19849
|
os: process.platform,
|
|
19006
19850
|
tools,
|
|
19007
|
-
runningSessions: info.runningSessions
|
|
19851
|
+
runningSessions: info.runningSessions,
|
|
19852
|
+
tunnelUrl
|
|
19008
19853
|
};
|
|
19009
19854
|
}
|
|
19010
19855
|
function buildMetaHandlers(deps) {
|
|
@@ -19020,6 +19865,79 @@ function buildMetaHandlers(deps) {
|
|
|
19020
19865
|
};
|
|
19021
19866
|
}
|
|
19022
19867
|
|
|
19868
|
+
// src/handlers/persona.ts
|
|
19869
|
+
init_protocol();
|
|
19870
|
+
function buildPersonaHandlers(deps) {
|
|
19871
|
+
const { personaManager, personaRegistry, sessionManager } = deps;
|
|
19872
|
+
const create = async (frame) => {
|
|
19873
|
+
const args = PersonaCreateArgs.parse(frame);
|
|
19874
|
+
const persona = personaManager.create(args);
|
|
19875
|
+
return { response: { type: "persona:info", ...persona } };
|
|
19876
|
+
};
|
|
19877
|
+
const list = async () => {
|
|
19878
|
+
return {
|
|
19879
|
+
response: { type: "persona:list", personas: personaRegistry.list() }
|
|
19880
|
+
};
|
|
19881
|
+
};
|
|
19882
|
+
const get = async (frame) => {
|
|
19883
|
+
const args = PersonaIdArgs.parse(frame);
|
|
19884
|
+
const persona = personaRegistry.get(args.personaId) ?? null;
|
|
19885
|
+
if (!persona) {
|
|
19886
|
+
return { response: { type: "persona:info", persona: null } };
|
|
19887
|
+
}
|
|
19888
|
+
return { response: { type: "persona:info", ...persona } };
|
|
19889
|
+
};
|
|
19890
|
+
const update = async (frame) => {
|
|
19891
|
+
const args = PersonaUpdateArgs.parse(frame);
|
|
19892
|
+
const persona = personaManager.update(args.personaId, args.patch);
|
|
19893
|
+
return { response: { type: "persona:info", ...persona } };
|
|
19894
|
+
};
|
|
19895
|
+
const del = async (frame) => {
|
|
19896
|
+
const args = PersonaIdArgs.parse(frame);
|
|
19897
|
+
personaManager.delete(args.personaId);
|
|
19898
|
+
return {
|
|
19899
|
+
response: { type: "persona:deleted", personaId: args.personaId }
|
|
19900
|
+
};
|
|
19901
|
+
};
|
|
19902
|
+
const issueToken = async (frame) => {
|
|
19903
|
+
const args = PersonaIssueTokenArgs.parse(frame);
|
|
19904
|
+
const { token, persona } = personaManager.issueToken(args.personaId, args.label);
|
|
19905
|
+
return {
|
|
19906
|
+
response: { type: "persona:tokenIssued", token, persona }
|
|
19907
|
+
};
|
|
19908
|
+
};
|
|
19909
|
+
const revokeToken = async (frame) => {
|
|
19910
|
+
const args = PersonaRevokeTokenArgs.parse(frame);
|
|
19911
|
+
const persona = personaManager.revokeToken(args.personaId, args.token);
|
|
19912
|
+
return { response: { type: "persona:info", ...persona } };
|
|
19913
|
+
};
|
|
19914
|
+
const listSubSessions = async (frame) => {
|
|
19915
|
+
const args = PersonaIdArgs.parse(frame);
|
|
19916
|
+
const sessions = sessionManager.listForAgent(args.personaId);
|
|
19917
|
+
return {
|
|
19918
|
+
response: { type: "persona:subSessions", sessions }
|
|
19919
|
+
};
|
|
19920
|
+
};
|
|
19921
|
+
const appendOwnerMessage = async (frame) => {
|
|
19922
|
+
const args = PersonaAppendOwnerMessageArgs.parse(frame);
|
|
19923
|
+
personaManager.appendOwnerMessage(args.personaId, args.subSessionId, args.text);
|
|
19924
|
+
return {
|
|
19925
|
+
response: { type: "persona:appendOwnerMessage", ok: true }
|
|
19926
|
+
};
|
|
19927
|
+
};
|
|
19928
|
+
return {
|
|
19929
|
+
"persona:create": create,
|
|
19930
|
+
"persona:list": list,
|
|
19931
|
+
"persona:get": get,
|
|
19932
|
+
"persona:update": update,
|
|
19933
|
+
"persona:delete": del,
|
|
19934
|
+
"persona:issueToken": issueToken,
|
|
19935
|
+
"persona:revokeToken": revokeToken,
|
|
19936
|
+
"persona:listSubSessions": listSubSessions,
|
|
19937
|
+
"persona:appendOwnerMessage": appendOwnerMessage
|
|
19938
|
+
};
|
|
19939
|
+
}
|
|
19940
|
+
|
|
19023
19941
|
// src/handlers/index.ts
|
|
19024
19942
|
function buildMethodHandlers(deps) {
|
|
19025
19943
|
return {
|
|
@@ -19029,7 +19947,12 @@ function buildMethodHandlers(deps) {
|
|
|
19029
19947
|
...buildWorkspaceHandlers(deps),
|
|
19030
19948
|
...buildGitHandlers(),
|
|
19031
19949
|
...buildCapabilitiesHandlers(deps),
|
|
19032
|
-
...buildMetaHandlers(deps)
|
|
19950
|
+
...buildMetaHandlers(deps),
|
|
19951
|
+
...buildPersonaHandlers({
|
|
19952
|
+
personaManager: deps.personaManager,
|
|
19953
|
+
personaRegistry: deps.personaRegistry,
|
|
19954
|
+
sessionManager: deps.manager
|
|
19955
|
+
})
|
|
19033
19956
|
};
|
|
19034
19957
|
}
|
|
19035
19958
|
|
|
@@ -19037,7 +19960,7 @@ function buildMethodHandlers(deps) {
|
|
|
19037
19960
|
async function startDaemon(config) {
|
|
19038
19961
|
const logger = createLogger({
|
|
19039
19962
|
level: config.logLevel,
|
|
19040
|
-
file:
|
|
19963
|
+
file: import_node_path20.default.join(config.dataDir, "clawd.log")
|
|
19041
19964
|
});
|
|
19042
19965
|
logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
|
|
19043
19966
|
const stateMgr = new StateFileManager({ dataDir: config.dataDir });
|
|
@@ -19117,35 +20040,43 @@ async function startDaemon(config) {
|
|
|
19117
20040
|
manager.recordRealUserUuid({ sessionId, realUuid, text });
|
|
19118
20041
|
}
|
|
19119
20042
|
});
|
|
19120
|
-
const
|
|
20043
|
+
const personaStore = new PersonaStore({ dataDir: config.dataDir });
|
|
20044
|
+
const personaRegistry = new PersonaRegistry(personaStore);
|
|
20045
|
+
const personaManager = new PersonaManager({
|
|
20046
|
+
store: personaStore,
|
|
20047
|
+
registry: personaRegistry,
|
|
20048
|
+
sessionManager: manager,
|
|
20049
|
+
dataDir: config.dataDir,
|
|
20050
|
+
now: () => /* @__PURE__ */ new Date(),
|
|
20051
|
+
logger
|
|
20052
|
+
});
|
|
20053
|
+
let currentTunnelUrl = null;
|
|
20054
|
+
const handlers = buildMethodHandlers({
|
|
20055
|
+
manager,
|
|
20056
|
+
workspace,
|
|
20057
|
+
skills,
|
|
20058
|
+
history,
|
|
20059
|
+
observer,
|
|
20060
|
+
getAdapter,
|
|
20061
|
+
store,
|
|
20062
|
+
personaManager,
|
|
20063
|
+
personaRegistry,
|
|
20064
|
+
getTunnelUrl: () => currentTunnelUrl
|
|
20065
|
+
});
|
|
20066
|
+
const personaBoundHandler = new PersonaBoundHandler({
|
|
20067
|
+
registry: personaRegistry,
|
|
20068
|
+
personaManager,
|
|
20069
|
+
sessionManager: manager,
|
|
20070
|
+
logger
|
|
20071
|
+
});
|
|
19121
20072
|
wsServer = new LocalWsServer({
|
|
19122
20073
|
host: config.host,
|
|
19123
20074
|
port: config.port,
|
|
19124
20075
|
logger,
|
|
19125
|
-
readyFrameBuilder: () => buildReadyFrame({ manager, getAdapter }),
|
|
20076
|
+
readyFrameBuilder: () => buildReadyFrame({ manager, getAdapter, getTunnelUrl: () => currentTunnelUrl }),
|
|
19126
20077
|
protocolVersion: PROTOCOL_VERSION,
|
|
19127
20078
|
authGate: authGate ?? void 0,
|
|
19128
|
-
|
|
19129
|
-
// daemon 是 pendingQuestions 的唯一 source of truth;新 client 接入 / 刷新页面时
|
|
19130
|
-
// 把当前所有未决 question 以 session:question 帧定向回放,让 UI 不再误显示 Ended。
|
|
19131
|
-
// 形状与 reducer permission_request 分支构造的 session:question 帧一致;UI 不区分
|
|
19132
|
-
// 首播 vs 重放(last-write-wins,详见 protocol/events.ts JSDoc)。
|
|
19133
|
-
onSubscribe: (client, sessionId) => {
|
|
19134
|
-
const runner = manager.getActive(sessionId);
|
|
19135
|
-
if (!runner) return;
|
|
19136
|
-
const pendingQuestions = runner.getState().pendingQuestions ?? {};
|
|
19137
|
-
for (const [toolUseId, entry] of Object.entries(pendingQuestions)) {
|
|
19138
|
-
const rawInput = entry.input;
|
|
19139
|
-
const questions = rawInput && typeof rawInput === "object" && "questions" in rawInput ? rawInput.questions : void 0;
|
|
19140
|
-
client.send({
|
|
19141
|
-
type: "session:question",
|
|
19142
|
-
sessionId,
|
|
19143
|
-
toolUseId,
|
|
19144
|
-
requestId: entry.requestId,
|
|
19145
|
-
questions: Array.isArray(questions) ? questions : []
|
|
19146
|
-
});
|
|
19147
|
-
}
|
|
19148
|
-
}
|
|
20079
|
+
personaBoundHandler
|
|
19149
20080
|
});
|
|
19150
20081
|
transport = wsServer;
|
|
19151
20082
|
const wss = wsServer;
|
|
@@ -19217,12 +20148,13 @@ async function startDaemon(config) {
|
|
|
19217
20148
|
const r = await tunnelMgr.start({ localPort: config.port });
|
|
19218
20149
|
stateSnapshot = { ...stateSnapshot, tunnelUrl: r.url };
|
|
19219
20150
|
stateMgr.write(stateSnapshot);
|
|
20151
|
+
currentTunnelUrl = r.url;
|
|
19220
20152
|
const connectUrl = resolvedAuthToken ? `${r.url}#token=${resolvedAuthToken}` : r.url;
|
|
19221
20153
|
const lines = [
|
|
19222
20154
|
`Tunnel: ${r.url}`,
|
|
19223
20155
|
...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
|
|
19224
|
-
`Frpc config: ${
|
|
19225
|
-
`Frpc log: ${
|
|
20156
|
+
`Frpc config: ${import_node_path20.default.join(config.dataDir, "frpc.toml")}`,
|
|
20157
|
+
`Frpc log: ${import_node_path20.default.join(config.dataDir, "frpc.log")}`
|
|
19226
20158
|
];
|
|
19227
20159
|
const width = Math.max(...lines.map((l) => l.length));
|
|
19228
20160
|
const bar = "\u2550".repeat(width + 4);
|
|
@@ -19235,8 +20167,8 @@ ${bar}
|
|
|
19235
20167
|
|
|
19236
20168
|
`);
|
|
19237
20169
|
try {
|
|
19238
|
-
const connectPath =
|
|
19239
|
-
|
|
20170
|
+
const connectPath = import_node_path20.default.join(config.dataDir, "connect.txt");
|
|
20171
|
+
import_node_fs20.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
|
|
19240
20172
|
} catch {
|
|
19241
20173
|
}
|
|
19242
20174
|
} catch (err) {
|
|
@@ -19264,8 +20196,7 @@ ${bar}
|
|
|
19264
20196
|
stop: shutdown,
|
|
19265
20197
|
url,
|
|
19266
20198
|
tunnelUrl: stateSnapshot.tunnelUrl ?? null,
|
|
19267
|
-
authToken: resolvedAuthToken
|
|
19268
|
-
manager
|
|
20199
|
+
authToken: resolvedAuthToken
|
|
19269
20200
|
};
|
|
19270
20201
|
}
|
|
19271
20202
|
|