@clawos-dev/clawd 0.2.27 → 0.2.28-beta.39.d6768c8
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 +1309 -272
- 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
|
];
|
|
@@ -4873,8 +4883,8 @@ var init_parseUtil = __esm({
|
|
|
4873
4883
|
init_errors2();
|
|
4874
4884
|
init_en();
|
|
4875
4885
|
makeIssue = (params) => {
|
|
4876
|
-
const { data, path:
|
|
4877
|
-
const fullPath = [...
|
|
4886
|
+
const { data, path: path23, errorMaps, issueData } = params;
|
|
4887
|
+
const fullPath = [...path23, ...issueData.path || []];
|
|
4878
4888
|
const fullIssue = {
|
|
4879
4889
|
...issueData,
|
|
4880
4890
|
path: fullPath
|
|
@@ -5185,11 +5195,11 @@ var init_types = __esm({
|
|
|
5185
5195
|
init_parseUtil();
|
|
5186
5196
|
init_util();
|
|
5187
5197
|
ParseInputLazyPath = class {
|
|
5188
|
-
constructor(parent, value,
|
|
5198
|
+
constructor(parent, value, path23, key) {
|
|
5189
5199
|
this._cachedPath = [];
|
|
5190
5200
|
this.parent = parent;
|
|
5191
5201
|
this.data = value;
|
|
5192
|
-
this._path =
|
|
5202
|
+
this._path = path23;
|
|
5193
5203
|
this._key = key;
|
|
5194
5204
|
}
|
|
5195
5205
|
get path() {
|
|
@@ -8572,6 +8582,128 @@ var init_zod = __esm({
|
|
|
8572
8582
|
}
|
|
8573
8583
|
});
|
|
8574
8584
|
|
|
8585
|
+
// ../protocol/src/persona-schemas.ts
|
|
8586
|
+
var ALLOWED_PERSONA_TOOLS, PersonaTokenEntrySchema, PersonaFileSchema, PersonaHelloFrameSchema, PersonaSendFrameSchema, PersonaResetFrameSchema, PersonaPingFrameSchema, PersonaReadyFrameSchema, PersonaHistoryEventFrameSchema, PersonaHistoryDoneFrameSchema, PersonaEventFrameSchema, PersonaErrorFrameSchema, PersonaPongFrameSchema, PersonaClientFrameSchema, PersonaServerFrameSchema, PersonaCreateArgs, PersonaIdArgs, PersonaUpdateArgs, PersonaIssueTokenArgs, PersonaRevokeTokenArgs, PersonaAppendOwnerMessageArgs;
|
|
8587
|
+
var init_persona_schemas = __esm({
|
|
8588
|
+
"../protocol/src/persona-schemas.ts"() {
|
|
8589
|
+
"use strict";
|
|
8590
|
+
init_zod();
|
|
8591
|
+
ALLOWED_PERSONA_TOOLS = ["Read", "Grep", "Glob"];
|
|
8592
|
+
PersonaTokenEntrySchema = external_exports.object({
|
|
8593
|
+
label: external_exports.string().min(1),
|
|
8594
|
+
issuedAt: external_exports.string(),
|
|
8595
|
+
revoked: external_exports.boolean().optional()
|
|
8596
|
+
});
|
|
8597
|
+
PersonaFileSchema = external_exports.object({
|
|
8598
|
+
personaId: external_exports.string().min(1),
|
|
8599
|
+
label: external_exports.string().min(1),
|
|
8600
|
+
// cwd 必须是绝对路径;老板侧创建时 daemon 校验
|
|
8601
|
+
cwd: external_exports.string().refine((p) => p.startsWith("/"), { message: "cwd must be absolute" }),
|
|
8602
|
+
model: external_exports.string().optional(),
|
|
8603
|
+
systemPromptAppend: external_exports.string().optional(),
|
|
8604
|
+
// L1 锁死:permissionMode 只能是 'plan',toolAllowlist 只能是 ALLOWED_PERSONA_TOOLS 子集
|
|
8605
|
+
permissionMode: external_exports.literal("plan"),
|
|
8606
|
+
toolAllowlist: external_exports.array(external_exports.enum(ALLOWED_PERSONA_TOOLS)),
|
|
8607
|
+
public: external_exports.boolean(),
|
|
8608
|
+
tokenMap: external_exports.record(external_exports.string(), PersonaTokenEntrySchema),
|
|
8609
|
+
createdAt: external_exports.string(),
|
|
8610
|
+
updatedAt: external_exports.string()
|
|
8611
|
+
});
|
|
8612
|
+
PersonaHelloFrameSchema = external_exports.object({
|
|
8613
|
+
kind: external_exports.literal("persona-hello"),
|
|
8614
|
+
token: external_exports.string().min(1)
|
|
8615
|
+
});
|
|
8616
|
+
PersonaSendFrameSchema = external_exports.object({
|
|
8617
|
+
kind: external_exports.literal("send"),
|
|
8618
|
+
text: external_exports.string()
|
|
8619
|
+
});
|
|
8620
|
+
PersonaResetFrameSchema = external_exports.object({
|
|
8621
|
+
kind: external_exports.literal("reset")
|
|
8622
|
+
});
|
|
8623
|
+
PersonaPingFrameSchema = external_exports.object({
|
|
8624
|
+
kind: external_exports.literal("ping")
|
|
8625
|
+
});
|
|
8626
|
+
PersonaReadyFrameSchema = external_exports.object({
|
|
8627
|
+
kind: external_exports.literal("persona-ready"),
|
|
8628
|
+
subSessionId: external_exports.string(),
|
|
8629
|
+
personaLabel: external_exports.string(),
|
|
8630
|
+
cwd: external_exports.string(),
|
|
8631
|
+
model: external_exports.string().optional()
|
|
8632
|
+
});
|
|
8633
|
+
PersonaHistoryEventFrameSchema = external_exports.object({
|
|
8634
|
+
kind: external_exports.literal("history-event"),
|
|
8635
|
+
event: external_exports.unknown()
|
|
8636
|
+
});
|
|
8637
|
+
PersonaHistoryDoneFrameSchema = external_exports.object({
|
|
8638
|
+
kind: external_exports.literal("history-done")
|
|
8639
|
+
});
|
|
8640
|
+
PersonaEventFrameSchema = external_exports.object({
|
|
8641
|
+
kind: external_exports.literal("event"),
|
|
8642
|
+
event: external_exports.unknown()
|
|
8643
|
+
});
|
|
8644
|
+
PersonaErrorFrameSchema = external_exports.object({
|
|
8645
|
+
kind: external_exports.literal("error"),
|
|
8646
|
+
code: external_exports.enum([
|
|
8647
|
+
"TOKEN_INVALID",
|
|
8648
|
+
"TOKEN_REVOKED",
|
|
8649
|
+
"PERSONA_DELETED",
|
|
8650
|
+
"PERSONA_NOT_PUBLIC",
|
|
8651
|
+
"INTERNAL"
|
|
8652
|
+
]),
|
|
8653
|
+
message: external_exports.string()
|
|
8654
|
+
});
|
|
8655
|
+
PersonaPongFrameSchema = external_exports.object({
|
|
8656
|
+
kind: external_exports.literal("pong")
|
|
8657
|
+
});
|
|
8658
|
+
PersonaClientFrameSchema = external_exports.discriminatedUnion("kind", [
|
|
8659
|
+
PersonaHelloFrameSchema,
|
|
8660
|
+
PersonaSendFrameSchema,
|
|
8661
|
+
PersonaResetFrameSchema,
|
|
8662
|
+
PersonaPingFrameSchema
|
|
8663
|
+
]);
|
|
8664
|
+
PersonaServerFrameSchema = external_exports.discriminatedUnion("kind", [
|
|
8665
|
+
PersonaReadyFrameSchema,
|
|
8666
|
+
PersonaHistoryEventFrameSchema,
|
|
8667
|
+
PersonaHistoryDoneFrameSchema,
|
|
8668
|
+
PersonaEventFrameSchema,
|
|
8669
|
+
PersonaErrorFrameSchema,
|
|
8670
|
+
PersonaPongFrameSchema
|
|
8671
|
+
]);
|
|
8672
|
+
PersonaCreateArgs = external_exports.object({
|
|
8673
|
+
label: external_exports.string().min(1),
|
|
8674
|
+
cwd: external_exports.string().min(1),
|
|
8675
|
+
model: external_exports.string().optional(),
|
|
8676
|
+
systemPromptAppend: external_exports.string().optional()
|
|
8677
|
+
});
|
|
8678
|
+
PersonaIdArgs = external_exports.object({
|
|
8679
|
+
personaId: external_exports.string().min(1)
|
|
8680
|
+
});
|
|
8681
|
+
PersonaUpdateArgs = external_exports.object({
|
|
8682
|
+
personaId: external_exports.string().min(1),
|
|
8683
|
+
patch: external_exports.object({
|
|
8684
|
+
label: external_exports.string().min(1).optional(),
|
|
8685
|
+
cwd: external_exports.string().min(1).optional(),
|
|
8686
|
+
model: external_exports.string().optional(),
|
|
8687
|
+
systemPromptAppend: external_exports.string().optional(),
|
|
8688
|
+
public: external_exports.boolean().optional()
|
|
8689
|
+
}).strict()
|
|
8690
|
+
});
|
|
8691
|
+
PersonaIssueTokenArgs = external_exports.object({
|
|
8692
|
+
personaId: external_exports.string().min(1),
|
|
8693
|
+
label: external_exports.string().min(1)
|
|
8694
|
+
});
|
|
8695
|
+
PersonaRevokeTokenArgs = external_exports.object({
|
|
8696
|
+
personaId: external_exports.string().min(1),
|
|
8697
|
+
token: external_exports.string().min(1)
|
|
8698
|
+
});
|
|
8699
|
+
PersonaAppendOwnerMessageArgs = external_exports.object({
|
|
8700
|
+
personaId: external_exports.string().min(1),
|
|
8701
|
+
subSessionId: external_exports.string().min(1),
|
|
8702
|
+
text: external_exports.string().min(1)
|
|
8703
|
+
});
|
|
8704
|
+
}
|
|
8705
|
+
});
|
|
8706
|
+
|
|
8575
8707
|
// ../protocol/src/schemas.ts
|
|
8576
8708
|
var SessionStatusSchema, 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, SessionQuestionClearedFrameSchema, AnswerQuestionArgs, AnswerQuestionResponseSchema, AuthRequestFrameSchema, AuthOkFrameSchema, TunnelExitedEventSchema, InfoRunningSessionSchema, InfoResponseSchema;
|
|
8577
8709
|
var init_schemas = __esm({
|
|
@@ -8579,6 +8711,7 @@ var init_schemas = __esm({
|
|
|
8579
8711
|
"use strict";
|
|
8580
8712
|
init_zod();
|
|
8581
8713
|
init_events();
|
|
8714
|
+
init_persona_schemas();
|
|
8582
8715
|
SessionStatusSchema = external_exports.enum(SESSION_STATUS_VALUES);
|
|
8583
8716
|
UsageSchema = external_exports.object({
|
|
8584
8717
|
input_tokens: external_exports.number().int().nonnegative().optional(),
|
|
@@ -8853,6 +8986,16 @@ var init_schemas = __esm({
|
|
|
8853
8986
|
...ParsedEventBase,
|
|
8854
8987
|
kind: external_exports.literal("meta_update"),
|
|
8855
8988
|
patch: SessionMetaSchema
|
|
8989
|
+
}),
|
|
8990
|
+
// 系统消息(CC 自家系统提示 / 老板插话补充);persona 场景下区分两类:
|
|
8991
|
+
// metaSource='cc' → CC 注入的系统消息(默认低视觉权重渲染)
|
|
8992
|
+
// metaSource='owner' → 老板通过 persona:appendOwnerMessage 插入的话(蓝色气泡)
|
|
8993
|
+
// 缺省视为 'cc';现有非 persona 场景保持兼容(不需要 owner 时不下发该字段)
|
|
8994
|
+
external_exports.object({
|
|
8995
|
+
...ParsedEventBase,
|
|
8996
|
+
kind: external_exports.literal("meta-text"),
|
|
8997
|
+
text: external_exports.string(),
|
|
8998
|
+
metaSource: external_exports.enum(["cc", "owner"]).optional()
|
|
8856
8999
|
})
|
|
8857
9000
|
]);
|
|
8858
9001
|
SessionCreateArgs = external_exports.object({
|
|
@@ -9108,6 +9251,7 @@ var init_runtime = __esm({
|
|
|
9108
9251
|
init_errors();
|
|
9109
9252
|
init_schemas();
|
|
9110
9253
|
init_frames();
|
|
9254
|
+
init_persona_schemas();
|
|
9111
9255
|
}
|
|
9112
9256
|
});
|
|
9113
9257
|
|
|
@@ -9684,11 +9828,11 @@ var init_lib = __esm({
|
|
|
9684
9828
|
}
|
|
9685
9829
|
}
|
|
9686
9830
|
},
|
|
9687
|
-
addToPath: function addToPath(
|
|
9688
|
-
var last =
|
|
9831
|
+
addToPath: function addToPath(path23, added, removed, oldPosInc, options) {
|
|
9832
|
+
var last = path23.lastComponent;
|
|
9689
9833
|
if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
|
|
9690
9834
|
return {
|
|
9691
|
-
oldPos:
|
|
9835
|
+
oldPos: path23.oldPos + oldPosInc,
|
|
9692
9836
|
lastComponent: {
|
|
9693
9837
|
count: last.count + 1,
|
|
9694
9838
|
added,
|
|
@@ -9698,7 +9842,7 @@ var init_lib = __esm({
|
|
|
9698
9842
|
};
|
|
9699
9843
|
} else {
|
|
9700
9844
|
return {
|
|
9701
|
-
oldPos:
|
|
9845
|
+
oldPos: path23.oldPos + oldPosInc,
|
|
9702
9846
|
lastComponent: {
|
|
9703
9847
|
count: 1,
|
|
9704
9848
|
added,
|
|
@@ -9944,7 +10088,7 @@ function hashDirToCwd(hash) {
|
|
|
9944
10088
|
}
|
|
9945
10089
|
function safeStatMtime(p) {
|
|
9946
10090
|
try {
|
|
9947
|
-
return
|
|
10091
|
+
return import_node_fs8.default.statSync(p).mtimeMs;
|
|
9948
10092
|
} catch {
|
|
9949
10093
|
return 0;
|
|
9950
10094
|
}
|
|
@@ -9952,7 +10096,7 @@ function safeStatMtime(p) {
|
|
|
9952
10096
|
function readJsonlLines(file) {
|
|
9953
10097
|
let raw;
|
|
9954
10098
|
try {
|
|
9955
|
-
raw =
|
|
10099
|
+
raw = import_node_fs8.default.readFileSync(file, "utf8");
|
|
9956
10100
|
} catch (err) {
|
|
9957
10101
|
if (err.code === "ENOENT") return [];
|
|
9958
10102
|
throw err;
|
|
@@ -10129,10 +10273,10 @@ function attachmentToHistoryMessage(o, ts) {
|
|
|
10129
10273
|
const memories = raw.map((m) => {
|
|
10130
10274
|
if (!m || typeof m !== "object") return null;
|
|
10131
10275
|
const rec = m;
|
|
10132
|
-
const
|
|
10276
|
+
const path23 = typeof rec.path === "string" ? rec.path : null;
|
|
10133
10277
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
10134
|
-
if (!
|
|
10135
|
-
const entry = { path:
|
|
10278
|
+
if (!path23 || content == null) return null;
|
|
10279
|
+
const entry = { path: path23, content };
|
|
10136
10280
|
if (typeof rec.mtimeMs === "number") entry.mtimeMs = rec.mtimeMs;
|
|
10137
10281
|
return entry;
|
|
10138
10282
|
}).filter((m) => m !== null);
|
|
@@ -10168,8 +10312,8 @@ function attachmentDeferredToolsText(a) {
|
|
|
10168
10312
|
function readBackupContent(fileHistoryRoot, toolSessionId, backupFileName) {
|
|
10169
10313
|
if (backupFileName === null) return null;
|
|
10170
10314
|
try {
|
|
10171
|
-
return
|
|
10172
|
-
|
|
10315
|
+
return import_node_fs8.default.readFileSync(
|
|
10316
|
+
import_node_path8.default.join(fileHistoryRoot, toolSessionId, backupFileName),
|
|
10173
10317
|
"utf8"
|
|
10174
10318
|
);
|
|
10175
10319
|
} catch {
|
|
@@ -10178,19 +10322,19 @@ function readBackupContent(fileHistoryRoot, toolSessionId, backupFileName) {
|
|
|
10178
10322
|
}
|
|
10179
10323
|
function readCurrentContent(filePath) {
|
|
10180
10324
|
try {
|
|
10181
|
-
return
|
|
10325
|
+
return import_node_fs8.default.readFileSync(filePath, "utf8");
|
|
10182
10326
|
} catch (err) {
|
|
10183
10327
|
if (err.code === "ENOENT") return null;
|
|
10184
10328
|
return null;
|
|
10185
10329
|
}
|
|
10186
10330
|
}
|
|
10187
|
-
var
|
|
10331
|
+
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
10332
|
var init_claude_history = __esm({
|
|
10189
10333
|
"src/tools/claude-history.ts"() {
|
|
10190
10334
|
"use strict";
|
|
10191
|
-
|
|
10335
|
+
import_node_fs8 = __toESM(require("fs"), 1);
|
|
10192
10336
|
import_node_os2 = __toESM(require("os"), 1);
|
|
10193
|
-
|
|
10337
|
+
import_node_path8 = __toESM(require("path"), 1);
|
|
10194
10338
|
init_lib();
|
|
10195
10339
|
init_tool_result_extra();
|
|
10196
10340
|
TASK_NOTIFICATION_RE = /<task-notification\b[\s\S]*?<\/task-notification>/i;
|
|
@@ -10214,14 +10358,14 @@ var init_claude_history = __esm({
|
|
|
10214
10358
|
// 每次 user 提交前 trackEdit 拷一份,作为 rewind 回退目标
|
|
10215
10359
|
fileHistoryRoot;
|
|
10216
10360
|
constructor(opts = {}) {
|
|
10217
|
-
const base = opts.baseDir ??
|
|
10218
|
-
this.projectsRoot =
|
|
10219
|
-
this.fileHistoryRoot =
|
|
10361
|
+
const base = opts.baseDir ?? import_node_path8.default.join(import_node_os2.default.homedir(), ".claude");
|
|
10362
|
+
this.projectsRoot = import_node_path8.default.join(base, "projects");
|
|
10363
|
+
this.fileHistoryRoot = import_node_path8.default.join(base, "file-history");
|
|
10220
10364
|
}
|
|
10221
10365
|
async listProjects() {
|
|
10222
10366
|
let entries;
|
|
10223
10367
|
try {
|
|
10224
|
-
entries =
|
|
10368
|
+
entries = import_node_fs8.default.readdirSync(this.projectsRoot, { withFileTypes: true });
|
|
10225
10369
|
} catch (err) {
|
|
10226
10370
|
if (err.code === "ENOENT") return [];
|
|
10227
10371
|
throw err;
|
|
@@ -10229,9 +10373,9 @@ var init_claude_history = __esm({
|
|
|
10229
10373
|
const out = [];
|
|
10230
10374
|
for (const ent of entries) {
|
|
10231
10375
|
if (!ent.isDirectory()) continue;
|
|
10232
|
-
const dir =
|
|
10233
|
-
const files =
|
|
10234
|
-
const updatedAtMs = files.reduce((m, f) => Math.max(m, safeStatMtime(
|
|
10376
|
+
const dir = import_node_path8.default.join(this.projectsRoot, ent.name);
|
|
10377
|
+
const files = import_node_fs8.default.readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
|
|
10378
|
+
const updatedAtMs = files.reduce((m, f) => Math.max(m, safeStatMtime(import_node_path8.default.join(dir, f))), 0);
|
|
10235
10379
|
out.push({
|
|
10236
10380
|
projectPath: hashDirToCwd(ent.name),
|
|
10237
10381
|
hashDir: ent.name,
|
|
@@ -10243,17 +10387,17 @@ var init_claude_history = __esm({
|
|
|
10243
10387
|
return out;
|
|
10244
10388
|
}
|
|
10245
10389
|
async listSessions(args) {
|
|
10246
|
-
const dir =
|
|
10390
|
+
const dir = import_node_path8.default.join(this.projectsRoot, cwdToHashDir(args.projectPath));
|
|
10247
10391
|
let files;
|
|
10248
10392
|
try {
|
|
10249
|
-
files =
|
|
10393
|
+
files = import_node_fs8.default.readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
|
|
10250
10394
|
} catch (err) {
|
|
10251
10395
|
if (err.code === "ENOENT") return [];
|
|
10252
10396
|
throw err;
|
|
10253
10397
|
}
|
|
10254
10398
|
const out = [];
|
|
10255
10399
|
for (const f of files) {
|
|
10256
|
-
const full =
|
|
10400
|
+
const full = import_node_path8.default.join(dir, f);
|
|
10257
10401
|
const toolSessionId = f.slice(0, -".jsonl".length);
|
|
10258
10402
|
const lines = readJsonlLines(full);
|
|
10259
10403
|
let summary = "";
|
|
@@ -10308,7 +10452,7 @@ var init_claude_history = __esm({
|
|
|
10308
10452
|
return out;
|
|
10309
10453
|
}
|
|
10310
10454
|
async read(args) {
|
|
10311
|
-
const file =
|
|
10455
|
+
const file = import_node_path8.default.join(
|
|
10312
10456
|
this.projectsRoot,
|
|
10313
10457
|
cwdToHashDir(args.cwd),
|
|
10314
10458
|
`${args.toolSessionId}.jsonl`
|
|
@@ -10341,7 +10485,7 @@ var init_claude_history = __esm({
|
|
|
10341
10485
|
// 独立目录路径:<projectsRoot>/<cwdHash>/<toolSessionId>/subagents/*.jsonl
|
|
10342
10486
|
// 返回 null 表示目录不存在(调用方回退旧实现);返回空数组表示目录存在但无 jsonl
|
|
10343
10487
|
listSubagentsFromDirectory(cwd, toolSessionId) {
|
|
10344
|
-
const dir =
|
|
10488
|
+
const dir = import_node_path8.default.join(
|
|
10345
10489
|
this.projectsRoot,
|
|
10346
10490
|
cwdToHashDir(cwd),
|
|
10347
10491
|
toolSessionId,
|
|
@@ -10349,7 +10493,7 @@ var init_claude_history = __esm({
|
|
|
10349
10493
|
);
|
|
10350
10494
|
let entries;
|
|
10351
10495
|
try {
|
|
10352
|
-
entries =
|
|
10496
|
+
entries = import_node_fs8.default.readdirSync(dir, { withFileTypes: true });
|
|
10353
10497
|
} catch (err) {
|
|
10354
10498
|
if (err.code === "ENOENT") return null;
|
|
10355
10499
|
return null;
|
|
@@ -10359,7 +10503,7 @@ var init_claude_history = __esm({
|
|
|
10359
10503
|
if (!e.isFile()) continue;
|
|
10360
10504
|
if (!e.name.startsWith("agent-") || !e.name.endsWith(".jsonl")) continue;
|
|
10361
10505
|
const subagentId = e.name.slice("agent-".length, -".jsonl".length);
|
|
10362
|
-
const filePath =
|
|
10506
|
+
const filePath = import_node_path8.default.join(dir, e.name);
|
|
10363
10507
|
const lines = readJsonlLines(filePath);
|
|
10364
10508
|
let firstText = "";
|
|
10365
10509
|
let messageCount = 0;
|
|
@@ -10376,7 +10520,7 @@ var init_claude_history = __esm({
|
|
|
10376
10520
|
return out;
|
|
10377
10521
|
}
|
|
10378
10522
|
listSubagentsFromMainJsonl(cwd, toolSessionId) {
|
|
10379
|
-
const file =
|
|
10523
|
+
const file = import_node_path8.default.join(
|
|
10380
10524
|
this.projectsRoot,
|
|
10381
10525
|
cwdToHashDir(cwd),
|
|
10382
10526
|
`${toolSessionId}.jsonl`
|
|
@@ -10411,7 +10555,7 @@ var init_claude_history = __esm({
|
|
|
10411
10555
|
}
|
|
10412
10556
|
// 独立文件路径:agent-<subagentId>.jsonl;文件不存在返回 null 让调用方回退旧实现
|
|
10413
10557
|
readSubagentFromFile(cwd, toolSessionId, subagentId) {
|
|
10414
|
-
const file =
|
|
10558
|
+
const file = import_node_path8.default.join(
|
|
10415
10559
|
this.projectsRoot,
|
|
10416
10560
|
cwdToHashDir(cwd),
|
|
10417
10561
|
toolSessionId,
|
|
@@ -10420,7 +10564,7 @@ var init_claude_history = __esm({
|
|
|
10420
10564
|
);
|
|
10421
10565
|
let exists = false;
|
|
10422
10566
|
try {
|
|
10423
|
-
exists =
|
|
10567
|
+
exists = import_node_fs8.default.statSync(file).isFile();
|
|
10424
10568
|
} catch {
|
|
10425
10569
|
return null;
|
|
10426
10570
|
}
|
|
@@ -10439,7 +10583,7 @@ var init_claude_history = __esm({
|
|
|
10439
10583
|
* "那一刻每个 tracked 文件对应的 backup 文件名"
|
|
10440
10584
|
*/
|
|
10441
10585
|
readFileHistorySnapshots(args) {
|
|
10442
|
-
const file =
|
|
10586
|
+
const file = import_node_path8.default.join(
|
|
10443
10587
|
this.projectsRoot,
|
|
10444
10588
|
cwdToHashDir(args.cwd),
|
|
10445
10589
|
`${args.toolSessionId}.jsonl`
|
|
@@ -10484,7 +10628,7 @@ var init_claude_history = __esm({
|
|
|
10484
10628
|
for (const [anchorId, target] of snapshots) {
|
|
10485
10629
|
let hasAny = false;
|
|
10486
10630
|
for (const [rawPath, backup] of Object.entries(target)) {
|
|
10487
|
-
const absPath =
|
|
10631
|
+
const absPath = import_node_path8.default.isAbsolute(rawPath) ? rawPath : import_node_path8.default.join(args.cwd, rawPath);
|
|
10488
10632
|
const backupContent = readBackupContent(
|
|
10489
10633
|
this.fileHistoryRoot,
|
|
10490
10634
|
args.toolSessionId,
|
|
@@ -10524,7 +10668,7 @@ var init_claude_history = __esm({
|
|
|
10524
10668
|
let totalInsertions = 0;
|
|
10525
10669
|
let totalDeletions = 0;
|
|
10526
10670
|
for (const [rawPath, backup] of Object.entries(target)) {
|
|
10527
|
-
const absPath =
|
|
10671
|
+
const absPath = import_node_path8.default.isAbsolute(rawPath) ? rawPath : import_node_path8.default.join(args.cwd, rawPath);
|
|
10528
10672
|
const backupContent = readBackupContent(
|
|
10529
10673
|
this.fileHistoryRoot,
|
|
10530
10674
|
args.toolSessionId,
|
|
@@ -10571,7 +10715,7 @@ var init_claude_history = __esm({
|
|
|
10571
10715
|
};
|
|
10572
10716
|
}
|
|
10573
10717
|
readSubagentFromMainJsonl(cwd, toolSessionId, subagentId) {
|
|
10574
|
-
const file =
|
|
10718
|
+
const file = import_node_path8.default.join(
|
|
10575
10719
|
this.projectsRoot,
|
|
10576
10720
|
cwdToHashDir(cwd),
|
|
10577
10721
|
`${toolSessionId}.jsonl`
|
|
@@ -10595,27 +10739,27 @@ var init_claude_history = __esm({
|
|
|
10595
10739
|
// src/tools/claude.ts
|
|
10596
10740
|
function macOSDesktopCandidates(home) {
|
|
10597
10741
|
return [
|
|
10598
|
-
|
|
10742
|
+
import_node_path9.default.join(home, "Applications", "Claude.app", "Contents", "Resources", "app.asar.unpacked", "node_modules", "@anthropic-ai", "claude-code", "cli.js"),
|
|
10599
10743
|
"/Applications/Claude.app/Contents/Resources/app.asar.unpacked/node_modules/@anthropic-ai/claude-code/cli.js"
|
|
10600
10744
|
];
|
|
10601
10745
|
}
|
|
10602
10746
|
function probeViaWhich() {
|
|
10603
10747
|
try {
|
|
10604
10748
|
const out = (0, import_node_child_process2.execFileSync)("which", ["claude"], { encoding: "utf8" }).trim();
|
|
10605
|
-
if (out &&
|
|
10749
|
+
if (out && import_node_fs9.default.existsSync(out)) return out;
|
|
10606
10750
|
} catch {
|
|
10607
10751
|
}
|
|
10608
10752
|
return null;
|
|
10609
10753
|
}
|
|
10610
10754
|
async function probeClaude(env = process.env, home = import_node_os3.default.homedir()) {
|
|
10611
|
-
if (env.CLAUDE_BIN &&
|
|
10755
|
+
if (env.CLAUDE_BIN && import_node_fs9.default.existsSync(env.CLAUDE_BIN)) {
|
|
10612
10756
|
return { available: true, path: env.CLAUDE_BIN };
|
|
10613
10757
|
}
|
|
10614
10758
|
const w = probeViaWhich();
|
|
10615
10759
|
if (w) return { available: true, path: w };
|
|
10616
10760
|
if (process.platform === "darwin") {
|
|
10617
10761
|
for (const candidate of macOSDesktopCandidates(home)) {
|
|
10618
|
-
if (
|
|
10762
|
+
if (import_node_fs9.default.existsSync(candidate)) {
|
|
10619
10763
|
return { available: true, path: candidate };
|
|
10620
10764
|
}
|
|
10621
10765
|
}
|
|
@@ -10640,7 +10784,14 @@ function buildSpawnArgs(ctx) {
|
|
|
10640
10784
|
"--verbose"
|
|
10641
10785
|
];
|
|
10642
10786
|
if (ctx.model) args.push("--model", ctx.model);
|
|
10643
|
-
|
|
10787
|
+
const hardcoded = ctx.hardcodedToolAllowlist && ctx.hardcodedToolAllowlist.length > 0;
|
|
10788
|
+
if (hardcoded) {
|
|
10789
|
+
args.push("--permission-mode", ctx.hardcodedPermissionMode ?? "plan");
|
|
10790
|
+
args.push("--add-dir", ctx.cwd);
|
|
10791
|
+
args.push("--disallowed-tools", HARDCODED_DISALLOWED_TOOLS.join(" "));
|
|
10792
|
+
} else if (ctx.permissionMode) {
|
|
10793
|
+
args.push("--permission-mode", ctx.permissionMode);
|
|
10794
|
+
}
|
|
10644
10795
|
if (ctx.effort) args.push("--effort", ctx.effort);
|
|
10645
10796
|
if (ctx.toolSessionId) args.push("--resume", ctx.toolSessionId);
|
|
10646
10797
|
return args;
|
|
@@ -10919,10 +11070,10 @@ function parseAttachment(obj) {
|
|
|
10919
11070
|
const memories = raw.map((m) => {
|
|
10920
11071
|
if (!m || typeof m !== "object") return null;
|
|
10921
11072
|
const rec = m;
|
|
10922
|
-
const
|
|
11073
|
+
const path23 = typeof rec.path === "string" ? rec.path : null;
|
|
10923
11074
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
10924
|
-
if (!
|
|
10925
|
-
const out = { path:
|
|
11075
|
+
if (!path23 || content == null) return null;
|
|
11076
|
+
const out = { path: path23, content };
|
|
10926
11077
|
if (typeof rec.mtimeMs === "number") out.mtimeMs = rec.mtimeMs;
|
|
10927
11078
|
return out;
|
|
10928
11079
|
}).filter((m) => m !== null);
|
|
@@ -11026,18 +11177,19 @@ function encodeClaudeStdin(text) {
|
|
|
11026
11177
|
};
|
|
11027
11178
|
return JSON.stringify(frame) + "\n";
|
|
11028
11179
|
}
|
|
11029
|
-
var import_node_child_process, import_node_child_process2,
|
|
11180
|
+
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
11181
|
var init_claude = __esm({
|
|
11031
11182
|
"src/tools/claude.ts"() {
|
|
11032
11183
|
"use strict";
|
|
11033
11184
|
import_node_child_process = require("child_process");
|
|
11034
11185
|
import_node_child_process2 = require("child_process");
|
|
11035
|
-
|
|
11186
|
+
import_node_fs9 = __toESM(require("fs"), 1);
|
|
11036
11187
|
import_node_os3 = __toESM(require("os"), 1);
|
|
11037
|
-
|
|
11188
|
+
import_node_path9 = __toESM(require("path"), 1);
|
|
11038
11189
|
init_protocol();
|
|
11039
11190
|
init_claude_history();
|
|
11040
11191
|
init_tool_result_extra();
|
|
11192
|
+
HARDCODED_DISALLOWED_TOOLS = ["Bash", "Edit", "Write", "WebFetch", "WebSearch", "Task"];
|
|
11041
11193
|
ATTACHMENT_SILENT_SUBTYPES2 = /* @__PURE__ */ new Set([
|
|
11042
11194
|
"hook_additional_context",
|
|
11043
11195
|
"hook_success",
|
|
@@ -14789,7 +14941,7 @@ var require_websocket_server = __commonJS({
|
|
|
14789
14941
|
// src/run-case/recorder.ts
|
|
14790
14942
|
function startRunCaseRecorder(opts) {
|
|
14791
14943
|
const now = opts.now ?? Date.now;
|
|
14792
|
-
const dir =
|
|
14944
|
+
const dir = import_node_path21.default.dirname(opts.recordPath);
|
|
14793
14945
|
let stream = null;
|
|
14794
14946
|
let closing = false;
|
|
14795
14947
|
let closedSettled = false;
|
|
@@ -14803,8 +14955,8 @@ function startRunCaseRecorder(opts) {
|
|
|
14803
14955
|
});
|
|
14804
14956
|
const ensureStream = () => {
|
|
14805
14957
|
if (stream) return stream;
|
|
14806
|
-
|
|
14807
|
-
stream =
|
|
14958
|
+
import_node_fs21.default.mkdirSync(dir, { recursive: true });
|
|
14959
|
+
stream = import_node_fs21.default.createWriteStream(opts.recordPath, { flags: "a" });
|
|
14808
14960
|
stream.on("close", () => closedResolve());
|
|
14809
14961
|
return stream;
|
|
14810
14962
|
};
|
|
@@ -14829,12 +14981,12 @@ function startRunCaseRecorder(opts) {
|
|
|
14829
14981
|
};
|
|
14830
14982
|
return { tap, close, closed };
|
|
14831
14983
|
}
|
|
14832
|
-
var
|
|
14984
|
+
var import_node_fs21, import_node_path21;
|
|
14833
14985
|
var init_recorder = __esm({
|
|
14834
14986
|
"src/run-case/recorder.ts"() {
|
|
14835
14987
|
"use strict";
|
|
14836
|
-
|
|
14837
|
-
|
|
14988
|
+
import_node_fs21 = __toESM(require("fs"), 1);
|
|
14989
|
+
import_node_path21 = __toESM(require("path"), 1);
|
|
14838
14990
|
}
|
|
14839
14991
|
});
|
|
14840
14992
|
|
|
@@ -14877,7 +15029,7 @@ var init_wire = __esm({
|
|
|
14877
15029
|
// src/run-case/controller.ts
|
|
14878
15030
|
async function runController(opts) {
|
|
14879
15031
|
const now = opts.now ?? Date.now;
|
|
14880
|
-
const cwd = opts.cwd ?? (0,
|
|
15032
|
+
const cwd = opts.cwd ?? (0, import_node_fs22.mkdtempSync)(import_node_path22.default.join(import_node_os12.default.tmpdir(), "clawd-runcase-"));
|
|
14881
15033
|
const ownsCwd = opts.cwd === void 0;
|
|
14882
15034
|
const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
|
|
14883
15035
|
const spawnCtx = { cwd };
|
|
@@ -15038,19 +15190,19 @@ async function runController(opts) {
|
|
|
15038
15190
|
if (sigintHandler) process.off("SIGINT", sigintHandler);
|
|
15039
15191
|
if (ownsCwd) {
|
|
15040
15192
|
try {
|
|
15041
|
-
(0,
|
|
15193
|
+
(0, import_node_fs22.rmSync)(cwd, { recursive: true, force: true });
|
|
15042
15194
|
} catch {
|
|
15043
15195
|
}
|
|
15044
15196
|
}
|
|
15045
15197
|
return exitCode ?? 0;
|
|
15046
15198
|
}
|
|
15047
|
-
var
|
|
15199
|
+
var import_node_fs22, import_node_os12, import_node_path22;
|
|
15048
15200
|
var init_controller = __esm({
|
|
15049
15201
|
"src/run-case/controller.ts"() {
|
|
15050
15202
|
"use strict";
|
|
15051
|
-
|
|
15203
|
+
import_node_fs22 = require("fs");
|
|
15052
15204
|
import_node_os12 = __toESM(require("os"), 1);
|
|
15053
|
-
|
|
15205
|
+
import_node_path22 = __toESM(require("path"), 1);
|
|
15054
15206
|
init_claude();
|
|
15055
15207
|
init_stdout_splitter();
|
|
15056
15208
|
init_permission_stdio();
|
|
@@ -15275,8 +15427,8 @@ Env (advanced):
|
|
|
15275
15427
|
`;
|
|
15276
15428
|
|
|
15277
15429
|
// src/index.ts
|
|
15278
|
-
var
|
|
15279
|
-
var
|
|
15430
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
15431
|
+
var import_node_fs20 = __toESM(require("fs"), 1);
|
|
15280
15432
|
|
|
15281
15433
|
// src/logger.ts
|
|
15282
15434
|
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
@@ -15495,9 +15647,11 @@ function cloneState(s) {
|
|
|
15495
15647
|
pendingQuestions: s.pendingQuestions,
|
|
15496
15648
|
freshSpawn: s.freshSpawn,
|
|
15497
15649
|
procAlive: s.procAlive,
|
|
15498
|
-
seenUuids: s.seenUuids
|
|
15650
|
+
seenUuids: s.seenUuids,
|
|
15651
|
+
subSessionMeta: s.subSessionMeta
|
|
15499
15652
|
};
|
|
15500
15653
|
}
|
|
15654
|
+
var IDLE_KILL_DELAY_MS = 5e3;
|
|
15501
15655
|
var SEEN_UUID_CAP = 2e3;
|
|
15502
15656
|
function applyEventBatch(state, events, deps) {
|
|
15503
15657
|
if (events.length === 0) return { state, effects: [] };
|
|
@@ -15530,14 +15684,21 @@ function emitSessionEvent(sessionId, event, target) {
|
|
|
15530
15684
|
};
|
|
15531
15685
|
return target ? { kind: "emit-frame", frame, target } : { kind: "emit-frame", frame };
|
|
15532
15686
|
}
|
|
15533
|
-
function buildSpawnContext(
|
|
15534
|
-
|
|
15687
|
+
function buildSpawnContext(state) {
|
|
15688
|
+
const file = state.file;
|
|
15689
|
+
const ctx = {
|
|
15535
15690
|
cwd: file.cwd,
|
|
15536
15691
|
toolSessionId: file.toolSessionId,
|
|
15537
15692
|
model: file.model,
|
|
15538
15693
|
permissionMode: file.permissionMode,
|
|
15539
15694
|
effort: file.effort
|
|
15540
15695
|
};
|
|
15696
|
+
const meta = state.subSessionMeta;
|
|
15697
|
+
if (meta?.toolAllowlist && meta.toolAllowlist.length > 0) {
|
|
15698
|
+
ctx.hardcodedToolAllowlist = meta.toolAllowlist;
|
|
15699
|
+
ctx.hardcodedPermissionMode = meta.permissionMode ?? "plan";
|
|
15700
|
+
}
|
|
15701
|
+
return ctx;
|
|
15541
15702
|
}
|
|
15542
15703
|
function sessionInfoFrame(file) {
|
|
15543
15704
|
const frame = { type: "session:info", ...file };
|
|
@@ -15672,10 +15833,16 @@ function pushEventToBuffer(state, event, deps) {
|
|
|
15672
15833
|
next.bufferStartSeq = trimmed[0]?.seq ?? seq;
|
|
15673
15834
|
}
|
|
15674
15835
|
if (next.freshSpawn) next.freshSpawn = false;
|
|
15675
|
-
|
|
15676
|
-
|
|
15677
|
-
|
|
15678
|
-
|
|
15836
|
+
const effects = [emitSessionEvent(next.file.sessionId, withSeq)];
|
|
15837
|
+
if (isTurnEnd && next.subSessionMeta?.idleKillEnabled && next.procAlive && (next.status === "running" || next.status === "spawning")) {
|
|
15838
|
+
next.status = "running-idle";
|
|
15839
|
+
effects.push({
|
|
15840
|
+
kind: "schedule-idle-kill",
|
|
15841
|
+
sessionId: next.file.sessionId,
|
|
15842
|
+
ms: IDLE_KILL_DELAY_MS
|
|
15843
|
+
});
|
|
15844
|
+
}
|
|
15845
|
+
return { state: next, effects };
|
|
15679
15846
|
}
|
|
15680
15847
|
var RUNTIME_PATCH_KEYS = [
|
|
15681
15848
|
"model",
|
|
@@ -15689,6 +15856,10 @@ function applyCommand(state, command, deps) {
|
|
|
15689
15856
|
const sessionId = next.file.sessionId;
|
|
15690
15857
|
switch (command.kind) {
|
|
15691
15858
|
case "send": {
|
|
15859
|
+
if (next.status === "running-idle") {
|
|
15860
|
+
effects.push({ kind: "cancel-idle-kill", sessionId });
|
|
15861
|
+
next.status = next.procAlive ? "running" : "idle";
|
|
15862
|
+
}
|
|
15692
15863
|
if (!next.procAlive) {
|
|
15693
15864
|
next.procAlive = true;
|
|
15694
15865
|
next.freshSpawn = true;
|
|
@@ -15697,7 +15868,7 @@ function applyCommand(state, command, deps) {
|
|
|
15697
15868
|
next.nextSeq = 0;
|
|
15698
15869
|
next.turnOpen = false;
|
|
15699
15870
|
next.status = "running";
|
|
15700
|
-
effects.push({ kind: "spawn", ctx: buildSpawnContext(next
|
|
15871
|
+
effects.push({ kind: "spawn", ctx: buildSpawnContext(next) });
|
|
15701
15872
|
effects.push({
|
|
15702
15873
|
kind: "emit-frame",
|
|
15703
15874
|
frame: {
|
|
@@ -15749,7 +15920,7 @@ function applyCommand(state, command, deps) {
|
|
|
15749
15920
|
next.nextSeq = 0;
|
|
15750
15921
|
next.turnOpen = false;
|
|
15751
15922
|
next.status = "running";
|
|
15752
|
-
effects.push({ kind: "spawn", ctx: buildSpawnContext(next
|
|
15923
|
+
effects.push({ kind: "spawn", ctx: buildSpawnContext(next) });
|
|
15753
15924
|
effects.push({
|
|
15754
15925
|
kind: "emit-frame",
|
|
15755
15926
|
frame: { type: "session:status", sessionId, status: "running" }
|
|
@@ -15999,6 +16170,25 @@ function reduceSession(state, input, deps) {
|
|
|
15999
16170
|
]
|
|
16000
16171
|
};
|
|
16001
16172
|
}
|
|
16173
|
+
case "idle-kill-fired": {
|
|
16174
|
+
if (state.status !== "running-idle") {
|
|
16175
|
+
return { state, effects: [] };
|
|
16176
|
+
}
|
|
16177
|
+
const next = cloneState(state);
|
|
16178
|
+
next.status = "stopping";
|
|
16179
|
+
return {
|
|
16180
|
+
state: next,
|
|
16181
|
+
effects: [{ kind: "kill", signal: "SIGKILL" }]
|
|
16182
|
+
};
|
|
16183
|
+
}
|
|
16184
|
+
case "inject-owner-text": {
|
|
16185
|
+
const ownerEvent = {
|
|
16186
|
+
kind: "meta-text",
|
|
16187
|
+
text: input.text,
|
|
16188
|
+
metaSource: "owner"
|
|
16189
|
+
};
|
|
16190
|
+
return pushEventToBuffer(state, ownerEvent, deps);
|
|
16191
|
+
}
|
|
16002
16192
|
case "tick": {
|
|
16003
16193
|
const staleMs = 10 * 60 * 1e3;
|
|
16004
16194
|
const now = input.nowMs;
|
|
@@ -16076,6 +16266,19 @@ function startRecorder(opts) {
|
|
|
16076
16266
|
};
|
|
16077
16267
|
}
|
|
16078
16268
|
|
|
16269
|
+
// src/tools/cwd-boundary.ts
|
|
16270
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
16271
|
+
function isPathInsideCwd(cwd, target) {
|
|
16272
|
+
if (!import_node_path5.default.isAbsolute(target)) return false;
|
|
16273
|
+
const cwdNorm = import_node_path5.default.resolve(cwd);
|
|
16274
|
+
const targetNorm = import_node_path5.default.resolve(target);
|
|
16275
|
+
if (cwdNorm === targetNorm) return true;
|
|
16276
|
+
const rel = import_node_path5.default.relative(cwdNorm, targetNorm);
|
|
16277
|
+
if (rel.startsWith("..")) return false;
|
|
16278
|
+
if (import_node_path5.default.isAbsolute(rel)) return false;
|
|
16279
|
+
return true;
|
|
16280
|
+
}
|
|
16281
|
+
|
|
16079
16282
|
// src/session/runner.ts
|
|
16080
16283
|
var DEFAULT_CONTROL_REQUEST_TIMEOUT_MS = 1e4;
|
|
16081
16284
|
function encodeAllowWithInputControlResponse(requestId, updatedInput) {
|
|
@@ -16093,6 +16296,15 @@ function encodeAllowWithInputControlResponse(requestId, updatedInput) {
|
|
|
16093
16296
|
return JSON.stringify(payload) + "\n";
|
|
16094
16297
|
}
|
|
16095
16298
|
var DEFAULT_WAIT_STOP_TIMEOUT_MS = 3e3;
|
|
16299
|
+
function extractToolPath(ev) {
|
|
16300
|
+
const input = ev.input;
|
|
16301
|
+
if (!input || typeof input !== "object") return void 0;
|
|
16302
|
+
const inp = input;
|
|
16303
|
+
if (ev.tool === "Read" && typeof inp.file_path === "string") return inp.file_path;
|
|
16304
|
+
if (ev.tool === "Grep" && typeof inp.path === "string") return inp.path;
|
|
16305
|
+
if (ev.tool === "Glob" && typeof inp.path === "string") return inp.path;
|
|
16306
|
+
return void 0;
|
|
16307
|
+
}
|
|
16096
16308
|
var SessionRunner = class {
|
|
16097
16309
|
constructor(initial, hooks) {
|
|
16098
16310
|
this.hooks = hooks;
|
|
@@ -16108,6 +16320,9 @@ var SessionRunner = class {
|
|
|
16108
16320
|
stopWaiters = [];
|
|
16109
16321
|
// IPC recorder(CLAWD_RECORD_IPC=1 时启用);null 表示当前 spawn 未启用 / 已退出
|
|
16110
16322
|
recorder = null;
|
|
16323
|
+
// sub-session idle-kill timer ref;key = sessionId(实际同一 runner 只对应一个 session,
|
|
16324
|
+
// 但 reducer Effect schema 带 sessionId 作为唯一键,对齐 schedule/cancel 配对)
|
|
16325
|
+
idleKillTimers = /* @__PURE__ */ new Map();
|
|
16111
16326
|
getState() {
|
|
16112
16327
|
return this.state;
|
|
16113
16328
|
}
|
|
@@ -16205,6 +16420,62 @@ var SessionRunner = class {
|
|
|
16205
16420
|
}
|
|
16206
16421
|
});
|
|
16207
16422
|
}
|
|
16423
|
+
// L1 锁定(task 14)拦截:仅 sub-session(state.subSessionMeta.toolAllowlist 非空)启用。
|
|
16424
|
+
// 1. 解析 line 为 ParsedEvent[],扫 'tool_call' kind
|
|
16425
|
+
// 2. tool 名不在白名单 → emit session:event(kind='error') + SIGKILL,丢弃本行
|
|
16426
|
+
// 3. tool input 里带 path 字段(Read.file_path / Grep.path / Glob.path)越出 cwd 子树 → 同上处理
|
|
16427
|
+
// 命中返回 true,调用方不再喂 reducer;命中即 SIGKILL,CC 进程会触发 proc.on('exit')
|
|
16428
|
+
// 走正常的 stop / proc-exit 流程。本拦截层与 buildSpawnArgs 的 --disallowed-tools / --add-dir
|
|
16429
|
+
// 共同构成 L1 第二 / 第三层防御(schema 层 + manager 层 + claude CLI 层 + runner 拦截层)
|
|
16430
|
+
tryInterceptL1Violation(line) {
|
|
16431
|
+
const meta = this.state.subSessionMeta;
|
|
16432
|
+
if (!meta?.toolAllowlist || meta.toolAllowlist.length === 0) return false;
|
|
16433
|
+
const allowed = new Set(meta.toolAllowlist);
|
|
16434
|
+
let events;
|
|
16435
|
+
try {
|
|
16436
|
+
events = this.hooks.adapter.parseLine(line);
|
|
16437
|
+
} catch {
|
|
16438
|
+
return false;
|
|
16439
|
+
}
|
|
16440
|
+
if (events.length === 0) return false;
|
|
16441
|
+
const cwd = this.state.file.cwd;
|
|
16442
|
+
for (const ev of events) {
|
|
16443
|
+
if (ev.kind !== "tool_call") continue;
|
|
16444
|
+
if (typeof ev.tool === "string" && ev.tool && !allowed.has(ev.tool)) {
|
|
16445
|
+
this.emitL1Error(`tool ${ev.tool} blocked by L1 lockdown`, "TOOL_NOT_ALLOWED");
|
|
16446
|
+
this.doKill("SIGKILL");
|
|
16447
|
+
return true;
|
|
16448
|
+
}
|
|
16449
|
+
const argPath = extractToolPath(ev);
|
|
16450
|
+
if (argPath && !isPathInsideCwd(cwd, argPath)) {
|
|
16451
|
+
this.emitL1Error(
|
|
16452
|
+
`path ${argPath} outside cwd ${cwd}`,
|
|
16453
|
+
"PATH_OUT_OF_BOUND"
|
|
16454
|
+
);
|
|
16455
|
+
this.doKill("SIGKILL");
|
|
16456
|
+
return true;
|
|
16457
|
+
}
|
|
16458
|
+
}
|
|
16459
|
+
return false;
|
|
16460
|
+
}
|
|
16461
|
+
// L1 违规错误帧:走 session:event 通道,code 编码进 ParsedEvent.error message。
|
|
16462
|
+
// 不引入新的 wire 帧 type(避免 protocol 改动),现有 error kind 已经走 broadcast 路径
|
|
16463
|
+
emitL1Error(message, code) {
|
|
16464
|
+
const errEvent = {
|
|
16465
|
+
kind: "error",
|
|
16466
|
+
message: `[${code}] ${message}`
|
|
16467
|
+
};
|
|
16468
|
+
const frame = {
|
|
16469
|
+
type: "session:event",
|
|
16470
|
+
sessionId: this.state.file.sessionId,
|
|
16471
|
+
event: errEvent
|
|
16472
|
+
};
|
|
16473
|
+
try {
|
|
16474
|
+
this.hooks.broadcastFrame(frame, "broadcast");
|
|
16475
|
+
} catch {
|
|
16476
|
+
}
|
|
16477
|
+
this.hooks.logger?.warn("clawd-l1-violation", { code, message, sessionId: this.state.file.sessionId });
|
|
16478
|
+
}
|
|
16208
16479
|
// 尝试把一行 stdout 解析成 `type:"control_response"` 帧并 resolve 匹配的 pending。
|
|
16209
16480
|
// 命中返回 true —— 调用方不再把该行喂给 reducer(control_response 不是会话事件)。
|
|
16210
16481
|
tryHandleControlResponse(line) {
|
|
@@ -16283,8 +16554,33 @@ var SessionRunner = class {
|
|
|
16283
16554
|
setTimeout(() => this.input({ kind: "tick", nowMs: now() }), effect.delayMs).unref();
|
|
16284
16555
|
break;
|
|
16285
16556
|
}
|
|
16557
|
+
case "schedule-idle-kill": {
|
|
16558
|
+
const existing = this.idleKillTimers.get(effect.sessionId);
|
|
16559
|
+
if (existing) clearTimeout(existing);
|
|
16560
|
+
const timer = setTimeout(() => {
|
|
16561
|
+
this.idleKillTimers.delete(effect.sessionId);
|
|
16562
|
+
this.input({ kind: "idle-kill-fired" });
|
|
16563
|
+
}, effect.ms);
|
|
16564
|
+
timer.unref?.();
|
|
16565
|
+
this.idleKillTimers.set(effect.sessionId, timer);
|
|
16566
|
+
break;
|
|
16567
|
+
}
|
|
16568
|
+
case "cancel-idle-kill": {
|
|
16569
|
+
const timer = this.idleKillTimers.get(effect.sessionId);
|
|
16570
|
+
if (timer) {
|
|
16571
|
+
clearTimeout(timer);
|
|
16572
|
+
this.idleKillTimers.delete(effect.sessionId);
|
|
16573
|
+
}
|
|
16574
|
+
break;
|
|
16575
|
+
}
|
|
16286
16576
|
}
|
|
16287
16577
|
}
|
|
16578
|
+
// 清空所有 idle-kill timer(runner dispose / proc 永久退出时调用)。
|
|
16579
|
+
// 不喂 idle-kill-fired —— dispose 路径不再翻 reducer 状态
|
|
16580
|
+
clearIdleKillTimers() {
|
|
16581
|
+
for (const t of this.idleKillTimers.values()) clearTimeout(t);
|
|
16582
|
+
this.idleKillTimers.clear();
|
|
16583
|
+
}
|
|
16288
16584
|
// 启动子进程,绑定 stdout line buffer → 回灌 reducer
|
|
16289
16585
|
doSpawn(ctx) {
|
|
16290
16586
|
const proc = this.hooks.spawnOverride ? this.hooks.spawnOverride(ctx) : this.hooks.adapter.spawn(ctx);
|
|
@@ -16302,6 +16598,7 @@ var SessionRunner = class {
|
|
|
16302
16598
|
this.stdoutBuf = newBuf;
|
|
16303
16599
|
for (const line of lines) {
|
|
16304
16600
|
if (this.tryHandleControlResponse(line)) continue;
|
|
16601
|
+
if (this.tryInterceptL1Violation(line)) continue;
|
|
16305
16602
|
this.input({ kind: "stdout-line", line });
|
|
16306
16603
|
}
|
|
16307
16604
|
});
|
|
@@ -16313,6 +16610,7 @@ var SessionRunner = class {
|
|
|
16313
16610
|
this.proc = null;
|
|
16314
16611
|
this.recorder = null;
|
|
16315
16612
|
this.rejectAllPending(new Error("session gone"));
|
|
16613
|
+
this.clearIdleKillTimers();
|
|
16316
16614
|
this.input({ kind: "proc-exit", code });
|
|
16317
16615
|
});
|
|
16318
16616
|
proc.on("error", (err) => {
|
|
@@ -16341,6 +16639,8 @@ function compressFrameForWire(frame) {
|
|
|
16341
16639
|
switch (status) {
|
|
16342
16640
|
case "spawning":
|
|
16343
16641
|
case "running":
|
|
16642
|
+
// sub-session(persona)专属内部状态:进程仍活着等 idle-kill;wire 协议看到的是 'running'
|
|
16643
|
+
case "running-idle":
|
|
16344
16644
|
compressed = "running";
|
|
16345
16645
|
break;
|
|
16346
16646
|
case "stopping":
|
|
@@ -16367,6 +16667,7 @@ function compressStatus(status) {
|
|
|
16367
16667
|
switch (status) {
|
|
16368
16668
|
case "spawning":
|
|
16369
16669
|
case "running":
|
|
16670
|
+
case "running-idle":
|
|
16370
16671
|
return "running";
|
|
16371
16672
|
case "stopping":
|
|
16372
16673
|
case "stopped":
|
|
@@ -16384,7 +16685,7 @@ function nowIso2(deps) {
|
|
|
16384
16685
|
function newSessionId() {
|
|
16385
16686
|
return v4_default().replace(/-/g, "").slice(0, 16);
|
|
16386
16687
|
}
|
|
16387
|
-
function makeInitialState(file) {
|
|
16688
|
+
function makeInitialState(file, subSessionMeta) {
|
|
16388
16689
|
return {
|
|
16389
16690
|
file,
|
|
16390
16691
|
status: "idle",
|
|
@@ -16396,12 +16697,14 @@ function makeInitialState(file) {
|
|
|
16396
16697
|
pendingQuestions: {},
|
|
16397
16698
|
freshSpawn: false,
|
|
16398
16699
|
procAlive: false,
|
|
16399
|
-
seenUuids: []
|
|
16700
|
+
seenUuids: [],
|
|
16701
|
+
subSessionMeta
|
|
16400
16702
|
};
|
|
16401
16703
|
}
|
|
16402
16704
|
var SessionManager = class {
|
|
16403
16705
|
constructor(deps) {
|
|
16404
16706
|
this.deps = deps;
|
|
16707
|
+
this.storesByAgent.set(DEFAULT_AGENT_ID, deps.store);
|
|
16405
16708
|
}
|
|
16406
16709
|
deps;
|
|
16407
16710
|
// sessionId → SessionRunner;在 send / ensureSession 时按需创建
|
|
@@ -16417,6 +16720,28 @@ var SessionManager = class {
|
|
|
16417
16720
|
// 由 observer 监听 jsonl user 行后调 recordRealUserUuid 建立映射;rewind 系列 RPC 在
|
|
16418
16721
|
// 入参 / 出参做转译,保证 UI 看到的 uuid 始终是 events 流里的 synth uuid
|
|
16419
16722
|
realUuidBySynth = /* @__PURE__ */ new Map();
|
|
16723
|
+
// persona sub-session 路径:按 agentId 派生 SessionStore(root = dataDir/sessions/<agentId>/)。
|
|
16724
|
+
// 'default' 直接复用 deps.store;其它 agentId 第一次访问时按需创建并缓存
|
|
16725
|
+
storesByAgent = /* @__PURE__ */ new Map();
|
|
16726
|
+
// sub-session 创建时记录的 subSessionMeta;ensureSession / runner 创建时塞入 reducer state。
|
|
16727
|
+
// 不进 SessionFile schema(避免破坏现有 zod parse),仅运行时缓存
|
|
16728
|
+
subSessionMetaBySid = /* @__PURE__ */ new Map();
|
|
16729
|
+
// persona-bound transport 订阅器:sessionId → Set<listener>。
|
|
16730
|
+
// 仅推 reducer 产出的 'session:event' 帧里的 ParsedEvent,其他帧(status / info / cleared)
|
|
16731
|
+
// 由调用方按需自行处理(persona-bound 简化协议只暴露 event 流,不需要这些)
|
|
16732
|
+
eventSubscribers = /* @__PURE__ */ new Map();
|
|
16733
|
+
// 按 agentId 拿对应的 SessionStore;persona sub-session 走这条路径写到
|
|
16734
|
+
// sessions/<personaId>/<sessionId>.json。需要 deps.dataDir 同源,否则脑裂
|
|
16735
|
+
storeFor(agentId) {
|
|
16736
|
+
const cached = this.storesByAgent.get(agentId);
|
|
16737
|
+
if (cached) return cached;
|
|
16738
|
+
if (!this.deps.dataDir) {
|
|
16739
|
+
throw new Error(`SessionManager: dataDir required to route agentId='${agentId}'`);
|
|
16740
|
+
}
|
|
16741
|
+
const st = new SessionStore({ dataDir: this.deps.dataDir, agentId });
|
|
16742
|
+
this.storesByAgent.set(agentId, st);
|
|
16743
|
+
return st;
|
|
16744
|
+
}
|
|
16420
16745
|
async getCapabilities(tool) {
|
|
16421
16746
|
const cached = this.capabilitiesCache.get(tool);
|
|
16422
16747
|
if (cached) return cached;
|
|
@@ -16427,11 +16752,14 @@ var SessionManager = class {
|
|
|
16427
16752
|
}
|
|
16428
16753
|
// 创建 runner 时包一层 broadcast hook:所有外出 frame 统一走 routeFromRunner,
|
|
16429
16754
|
// 经过 compressFrameForWire 后决定是 push collector 还是走 deps.broadcastFrame
|
|
16430
|
-
|
|
16755
|
+
// store:默认 deps.store;persona sub-session 路径下传该 personaId 对应的 SessionStore
|
|
16756
|
+
// subSessionMeta:persona 路径传 { idleKillEnabled: true },其它路径不传
|
|
16757
|
+
newRunner(file, opts = {}) {
|
|
16431
16758
|
const adapter = this.deps.getAdapter(file.tool ?? "claude");
|
|
16432
|
-
const
|
|
16759
|
+
const store = opts.store ?? this.deps.store;
|
|
16760
|
+
const runner = new SessionRunner(makeInitialState(file, opts.subSessionMeta), {
|
|
16433
16761
|
broadcastFrame: (frame, target) => this.routeFromRunner(frame, target),
|
|
16434
|
-
store
|
|
16762
|
+
store,
|
|
16435
16763
|
adapter,
|
|
16436
16764
|
logger: this.deps.logger,
|
|
16437
16765
|
spawnOverride: this.deps.spawnOverride,
|
|
@@ -16449,6 +16777,21 @@ var SessionManager = class {
|
|
|
16449
16777
|
routeFromRunner(frame, target) {
|
|
16450
16778
|
const compressed = compressFrameForWire(frame);
|
|
16451
16779
|
if (!compressed) return;
|
|
16780
|
+
if (compressed.type === "session:event") {
|
|
16781
|
+
const sid = compressed.sessionId;
|
|
16782
|
+
const ev = compressed.event;
|
|
16783
|
+
if (sid && ev) {
|
|
16784
|
+
const subs = this.eventSubscribers.get(sid);
|
|
16785
|
+
if (subs && subs.size > 0) {
|
|
16786
|
+
for (const fn of subs) {
|
|
16787
|
+
try {
|
|
16788
|
+
fn(ev);
|
|
16789
|
+
} catch {
|
|
16790
|
+
}
|
|
16791
|
+
}
|
|
16792
|
+
}
|
|
16793
|
+
}
|
|
16794
|
+
}
|
|
16452
16795
|
if (this.currentCollector) {
|
|
16453
16796
|
this.currentCollector.push({ frame: compressed, target });
|
|
16454
16797
|
return;
|
|
@@ -16891,10 +17234,189 @@ var SessionManager = class {
|
|
|
16891
17234
|
ensureSession(file) {
|
|
16892
17235
|
let r = this.runners.get(file.sessionId);
|
|
16893
17236
|
if (r) return r;
|
|
16894
|
-
|
|
17237
|
+
const subSessionMeta = this.subSessionMetaBySid.get(file.sessionId);
|
|
17238
|
+
r = this.newRunner(file, { subSessionMeta });
|
|
16895
17239
|
this.runners.set(file.sessionId, r);
|
|
16896
17240
|
return r;
|
|
16897
17241
|
}
|
|
17242
|
+
// ---------------- persona / sub-session 专用 API ----------------
|
|
17243
|
+
//
|
|
17244
|
+
// PersonaManager 是 SessionManager 之上的薄编排层,sub-session 的持久化(SessionFile)
|
|
17245
|
+
// 仍由 SessionManager 持有。区别于普通 session:
|
|
17246
|
+
// - SessionFile 落到 sessions/<personaId>/ 而非 sessions/default/
|
|
17247
|
+
// - reducer state.subSessionMeta.idleKillEnabled = true(turn_end 自动 idle-kill)
|
|
17248
|
+
// - sessionId 由 PersonaManager 派生(personaId-<tokenHash>),不走自动 newSessionId
|
|
17249
|
+
/** 按 agentId 读取 SessionFile;不存在返回 null */
|
|
17250
|
+
readForAgent(sessionId, agentId) {
|
|
17251
|
+
return this.storeFor(agentId).read(sessionId);
|
|
17252
|
+
}
|
|
17253
|
+
/**
|
|
17254
|
+
* 按 agentId 列出该命名空间下所有 SessionFile(persona:listSubSessions 入口)。
|
|
17255
|
+
* agentId 还未访问过 → storeFor 第一次创建对应 SessionStore(root 不存在则 list 返回 [])
|
|
17256
|
+
*/
|
|
17257
|
+
listForAgent(agentId) {
|
|
17258
|
+
return this.storeFor(agentId).list();
|
|
17259
|
+
}
|
|
17260
|
+
/**
|
|
17261
|
+
* 创建 sub-session 的 SessionFile + 准备 runner(不 spawn)。
|
|
17262
|
+
* subSessionMeta 不进 SessionFile schema,仅缓存进 runner state。
|
|
17263
|
+
* 同一 sessionId 重复调用:抛错(PersonaManager 应先调 readForAgent 命中复用)
|
|
17264
|
+
*/
|
|
17265
|
+
createForAgent(args) {
|
|
17266
|
+
try {
|
|
17267
|
+
const stat = import_node_fs5.default.statSync(args.cwd);
|
|
17268
|
+
if (!stat.isDirectory()) throw new Error("not dir");
|
|
17269
|
+
} catch {
|
|
17270
|
+
throw new ClawdError(ERROR_CODES.INVALID_CWD, `cwd not a directory: ${args.cwd}`);
|
|
17271
|
+
}
|
|
17272
|
+
const tool = args.tool ?? "claude";
|
|
17273
|
+
this.deps.getAdapter(tool);
|
|
17274
|
+
const store = this.storeFor(args.agentId);
|
|
17275
|
+
if (store.read(args.sessionId)) {
|
|
17276
|
+
throw new Error(`session already exists for agent ${args.agentId}: ${args.sessionId}`);
|
|
17277
|
+
}
|
|
17278
|
+
const iso = nowIso2(this.deps);
|
|
17279
|
+
const file = {
|
|
17280
|
+
sessionId: args.sessionId,
|
|
17281
|
+
cwd: args.cwd,
|
|
17282
|
+
tool,
|
|
17283
|
+
label: args.label,
|
|
17284
|
+
model: args.model,
|
|
17285
|
+
permissionMode: args.permissionMode,
|
|
17286
|
+
createdAt: iso,
|
|
17287
|
+
updatedAt: iso
|
|
17288
|
+
};
|
|
17289
|
+
const written = store.write(file);
|
|
17290
|
+
if (args.subSessionMeta || args.hardcodedToolAllowlist) {
|
|
17291
|
+
const meta = {
|
|
17292
|
+
idleKillEnabled: args.subSessionMeta?.idleKillEnabled ?? false,
|
|
17293
|
+
...args.hardcodedToolAllowlist && args.hardcodedToolAllowlist.length > 0 ? {
|
|
17294
|
+
toolAllowlist: args.hardcodedToolAllowlist,
|
|
17295
|
+
permissionMode: args.permissionMode
|
|
17296
|
+
} : {}
|
|
17297
|
+
};
|
|
17298
|
+
this.subSessionMetaBySid.set(args.sessionId, meta);
|
|
17299
|
+
}
|
|
17300
|
+
return written;
|
|
17301
|
+
}
|
|
17302
|
+
/**
|
|
17303
|
+
* persona-bound transport 用:读取 sub-session 的历史 ParsedEvent[]。
|
|
17304
|
+
* 实现策略:保证 runner 存在(buffer 是 reducer 唯一权威源),返回 buffer 里所有事件。
|
|
17305
|
+
* - 第一次访问:lazy ensureSession,buffer 为空 → 返回 []。
|
|
17306
|
+
* - 之前 send 过 / observer 喂过:buffer 已经有累积事件,直接返回。
|
|
17307
|
+
*
|
|
17308
|
+
* 不读 jsonl:
|
|
17309
|
+
* 1. observer 路径在 spawn 后会自动接管 jsonl 回灌,进 reducer buffer。
|
|
17310
|
+
* 2. 第一次握手时 sub-session 还没 spawn → toolSessionId 为空 → jsonl 不存在。
|
|
17311
|
+
* sessionFile 不存在抛 SESSION_NOT_FOUND;上层应先调 createForAgent。
|
|
17312
|
+
*/
|
|
17313
|
+
readHistoryEvents(sessionId, agentId) {
|
|
17314
|
+
const store = this.storeFor(agentId);
|
|
17315
|
+
const file = store.read(sessionId);
|
|
17316
|
+
if (!file) {
|
|
17317
|
+
throw new ClawdError(
|
|
17318
|
+
ERROR_CODES.SESSION_NOT_FOUND,
|
|
17319
|
+
`sub-session not found: ${agentId}/${sessionId}`
|
|
17320
|
+
);
|
|
17321
|
+
}
|
|
17322
|
+
const runner = this.ensureRunnerFor(file, agentId);
|
|
17323
|
+
return runner.getState().buffer.map((e) => e.event);
|
|
17324
|
+
}
|
|
17325
|
+
/**
|
|
17326
|
+
* persona-bound transport 用:订阅 sub-session 实时 ParsedEvent。
|
|
17327
|
+
* 返回 unsubscribe;不破坏现有 wire 广播路径——routeFromRunner 同时 fan-out 到
|
|
17328
|
+
* eventSubscribers 和 deps.broadcastFrame
|
|
17329
|
+
*/
|
|
17330
|
+
subscribe(_sessionId, _agentId, listener) {
|
|
17331
|
+
const sid = _sessionId;
|
|
17332
|
+
let subs = this.eventSubscribers.get(sid);
|
|
17333
|
+
if (!subs) {
|
|
17334
|
+
subs = /* @__PURE__ */ new Set();
|
|
17335
|
+
this.eventSubscribers.set(sid, subs);
|
|
17336
|
+
}
|
|
17337
|
+
subs.add(listener);
|
|
17338
|
+
return () => {
|
|
17339
|
+
const cur = this.eventSubscribers.get(sid);
|
|
17340
|
+
if (!cur) return;
|
|
17341
|
+
cur.delete(listener);
|
|
17342
|
+
if (cur.size === 0) this.eventSubscribers.delete(sid);
|
|
17343
|
+
};
|
|
17344
|
+
}
|
|
17345
|
+
/**
|
|
17346
|
+
* persona-bound transport 用:sub-session 路径的 send(按 agentId 路由 SessionStore)。
|
|
17347
|
+
* 现状 send(args.sessionId, args.text) 默认 'default' agent;这里按 agentId 拿对应 store
|
|
17348
|
+
* + ensureRunnerFor 保证 runner 用同一个 store 写盘
|
|
17349
|
+
*/
|
|
17350
|
+
sendForAgent(args) {
|
|
17351
|
+
const store = this.storeFor(args.agentId);
|
|
17352
|
+
const file = store.read(args.sessionId);
|
|
17353
|
+
if (!file) {
|
|
17354
|
+
throw new ClawdError(
|
|
17355
|
+
ERROR_CODES.SESSION_NOT_FOUND,
|
|
17356
|
+
`sub-session not found: ${args.agentId}/${args.sessionId}`
|
|
17357
|
+
);
|
|
17358
|
+
}
|
|
17359
|
+
const runner = this.ensureRunnerFor(file, args.agentId);
|
|
17360
|
+
const { broadcast } = this.withCollector(() => {
|
|
17361
|
+
runner.input({ kind: "command", command: { kind: "send", text: args.text } });
|
|
17362
|
+
});
|
|
17363
|
+
return { response: { ok: true }, broadcast };
|
|
17364
|
+
}
|
|
17365
|
+
/**
|
|
17366
|
+
* persona-bound transport 用:sub-session reset。
|
|
17367
|
+
* 复用 reducer 'new' 命令:清 toolSessionId / buffer / nextSeq / pending* + kill proc + emit
|
|
17368
|
+
* session:cleared。语义上等价 alice 端"清空当前会话上下文"。
|
|
17369
|
+
* 归档已写盘的 jsonl 不在本路径处理(CC 后续 spawn 会写新的 toolSessionId.jsonl,旧的留盘);
|
|
17370
|
+
* 物理归档可在后续单独 task 加(spec § 6 待 plan 决定项)。
|
|
17371
|
+
*/
|
|
17372
|
+
resetForAgent(args) {
|
|
17373
|
+
const store = this.storeFor(args.agentId);
|
|
17374
|
+
const file = store.read(args.sessionId);
|
|
17375
|
+
if (!file) {
|
|
17376
|
+
throw new ClawdError(
|
|
17377
|
+
ERROR_CODES.SESSION_NOT_FOUND,
|
|
17378
|
+
`sub-session not found: ${args.agentId}/${args.sessionId}`
|
|
17379
|
+
);
|
|
17380
|
+
}
|
|
17381
|
+
const runner = this.ensureRunnerFor(file, args.agentId);
|
|
17382
|
+
const { broadcast } = this.withCollector(() => {
|
|
17383
|
+
runner.input({ kind: "command", command: { kind: "new" } });
|
|
17384
|
+
});
|
|
17385
|
+
return { response: { ok: true }, broadcast };
|
|
17386
|
+
}
|
|
17387
|
+
/** ensureSession 的 agentId-aware 版本:复用现有 runner 或按 agentId 派生 store 创建 */
|
|
17388
|
+
ensureRunnerFor(file, agentId) {
|
|
17389
|
+
const existing = this.runners.get(file.sessionId);
|
|
17390
|
+
if (existing) return existing;
|
|
17391
|
+
const store = this.storeFor(agentId);
|
|
17392
|
+
const subSessionMeta = this.subSessionMetaBySid.get(file.sessionId);
|
|
17393
|
+
const runner = this.newRunner(file, { store, subSessionMeta });
|
|
17394
|
+
this.runners.set(file.sessionId, runner);
|
|
17395
|
+
return runner;
|
|
17396
|
+
}
|
|
17397
|
+
/**
|
|
17398
|
+
* 老板插话:把 'inject-owner-text' input 喂给对应 runner,
|
|
17399
|
+
* runner 不存在时按 ensureSession 路径懒创建(以 subSessionMeta 缓存为准)。
|
|
17400
|
+
* frames 走异步路径(broadcastFrame):调用方一般在 PersonaManager.appendOwnerMessage
|
|
17401
|
+
* 内部走 RPC 处理流,没有同步 collector 上下文
|
|
17402
|
+
*/
|
|
17403
|
+
injectOwnerMessage(args) {
|
|
17404
|
+
const store = this.storeFor(args.agentId);
|
|
17405
|
+
const file = store.read(args.sessionId);
|
|
17406
|
+
if (!file) {
|
|
17407
|
+
throw new ClawdError(
|
|
17408
|
+
ERROR_CODES.SESSION_NOT_FOUND,
|
|
17409
|
+
`sub-session not found: ${args.agentId}/${args.sessionId}`
|
|
17410
|
+
);
|
|
17411
|
+
}
|
|
17412
|
+
let runner = this.runners.get(args.sessionId);
|
|
17413
|
+
if (!runner) {
|
|
17414
|
+
const subSessionMeta = this.subSessionMetaBySid.get(args.sessionId);
|
|
17415
|
+
runner = this.newRunner(file, { store, subSessionMeta });
|
|
17416
|
+
this.runners.set(args.sessionId, runner);
|
|
17417
|
+
}
|
|
17418
|
+
runner.input({ kind: "inject-owner-text", text: args.text });
|
|
17419
|
+
}
|
|
16898
17420
|
// observer 把 stdout line 转发到指定 runner;单一 reducer 入口保证 seq 分配统一
|
|
16899
17421
|
feedObserverLine(sessionId, line) {
|
|
16900
17422
|
const runner = this.runners.get(sessionId);
|
|
@@ -16959,28 +17481,268 @@ var SessionManager = class {
|
|
|
16959
17481
|
}
|
|
16960
17482
|
};
|
|
16961
17483
|
|
|
17484
|
+
// src/persona/store.ts
|
|
17485
|
+
var import_node_fs6 = __toESM(require("fs"), 1);
|
|
17486
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
17487
|
+
init_protocol();
|
|
17488
|
+
var PersonaStore = class {
|
|
17489
|
+
root;
|
|
17490
|
+
constructor(opts) {
|
|
17491
|
+
this.root = import_node_path6.default.join(opts.dataDir, "personas");
|
|
17492
|
+
}
|
|
17493
|
+
filePath(personaId) {
|
|
17494
|
+
return import_node_path6.default.join(this.root, `${safeFileName(personaId)}.json`);
|
|
17495
|
+
}
|
|
17496
|
+
ensureDir() {
|
|
17497
|
+
import_node_fs6.default.mkdirSync(this.root, { recursive: true });
|
|
17498
|
+
}
|
|
17499
|
+
read(personaId) {
|
|
17500
|
+
try {
|
|
17501
|
+
const raw = import_node_fs6.default.readFileSync(this.filePath(personaId), "utf8");
|
|
17502
|
+
return PersonaFileSchema.parse(JSON.parse(raw));
|
|
17503
|
+
} catch (err) {
|
|
17504
|
+
const code = err?.code;
|
|
17505
|
+
if (code === "ENOENT") return null;
|
|
17506
|
+
return null;
|
|
17507
|
+
}
|
|
17508
|
+
}
|
|
17509
|
+
write(file) {
|
|
17510
|
+
const validated = PersonaFileSchema.parse(file);
|
|
17511
|
+
this.ensureDir();
|
|
17512
|
+
const target = this.filePath(validated.personaId);
|
|
17513
|
+
const tmp = `${target}.tmp-${process.pid}-${Date.now()}`;
|
|
17514
|
+
import_node_fs6.default.writeFileSync(tmp, JSON.stringify(validated, null, 2), { encoding: "utf8", mode: 384 });
|
|
17515
|
+
import_node_fs6.default.renameSync(tmp, target);
|
|
17516
|
+
return validated;
|
|
17517
|
+
}
|
|
17518
|
+
delete(personaId) {
|
|
17519
|
+
try {
|
|
17520
|
+
import_node_fs6.default.unlinkSync(this.filePath(personaId));
|
|
17521
|
+
} catch (err) {
|
|
17522
|
+
const code = err?.code;
|
|
17523
|
+
if (code !== "ENOENT") throw err;
|
|
17524
|
+
}
|
|
17525
|
+
}
|
|
17526
|
+
list() {
|
|
17527
|
+
if (!import_node_fs6.default.existsSync(this.root)) return [];
|
|
17528
|
+
const files = import_node_fs6.default.readdirSync(this.root).filter((f) => f.endsWith(".json") && !f.includes(".tmp"));
|
|
17529
|
+
const items = [];
|
|
17530
|
+
for (const f of files) {
|
|
17531
|
+
try {
|
|
17532
|
+
const raw = import_node_fs6.default.readFileSync(import_node_path6.default.join(this.root, f), "utf8");
|
|
17533
|
+
items.push(PersonaFileSchema.parse(JSON.parse(raw)));
|
|
17534
|
+
} catch {
|
|
17535
|
+
}
|
|
17536
|
+
}
|
|
17537
|
+
return items.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
17538
|
+
}
|
|
17539
|
+
};
|
|
17540
|
+
|
|
17541
|
+
// src/persona/registry.ts
|
|
17542
|
+
var PersonaRegistry = class {
|
|
17543
|
+
constructor(store) {
|
|
17544
|
+
this.store = store;
|
|
17545
|
+
this.reload();
|
|
17546
|
+
}
|
|
17547
|
+
store;
|
|
17548
|
+
cache = /* @__PURE__ */ new Map();
|
|
17549
|
+
/** 从 store 全量重建缓存(boot 时 + 测试 setup 时) */
|
|
17550
|
+
reload() {
|
|
17551
|
+
this.cache.clear();
|
|
17552
|
+
for (const p of this.store.list()) this.cache.set(p.personaId, p);
|
|
17553
|
+
}
|
|
17554
|
+
get(personaId) {
|
|
17555
|
+
return this.cache.get(personaId);
|
|
17556
|
+
}
|
|
17557
|
+
/** PersonaManager 写盘后同步 cache;不主动写盘 */
|
|
17558
|
+
set(file) {
|
|
17559
|
+
this.cache.set(file.personaId, file);
|
|
17560
|
+
}
|
|
17561
|
+
remove(personaId) {
|
|
17562
|
+
this.cache.delete(personaId);
|
|
17563
|
+
}
|
|
17564
|
+
list() {
|
|
17565
|
+
return Array.from(this.cache.values());
|
|
17566
|
+
}
|
|
17567
|
+
verifyToken(personaId, token) {
|
|
17568
|
+
const persona = this.cache.get(personaId);
|
|
17569
|
+
if (!persona) return { ok: false, code: "PERSONA_DELETED" };
|
|
17570
|
+
if (!persona.public) return { ok: false, code: "PERSONA_NOT_PUBLIC" };
|
|
17571
|
+
const entry = persona.tokenMap[token];
|
|
17572
|
+
if (!entry) return { ok: false, code: "TOKEN_INVALID" };
|
|
17573
|
+
if (entry.revoked) return { ok: false, code: "TOKEN_REVOKED" };
|
|
17574
|
+
return { ok: true, label: entry.label };
|
|
17575
|
+
}
|
|
17576
|
+
};
|
|
17577
|
+
|
|
17578
|
+
// src/persona/manager.ts
|
|
17579
|
+
var import_node_crypto3 = __toESM(require("crypto"), 1);
|
|
17580
|
+
var import_node_fs7 = __toESM(require("fs"), 1);
|
|
17581
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
17582
|
+
var PersonaManager = class {
|
|
17583
|
+
constructor(deps) {
|
|
17584
|
+
this.deps = deps;
|
|
17585
|
+
}
|
|
17586
|
+
deps;
|
|
17587
|
+
create(args) {
|
|
17588
|
+
const personaId = this.generatePersonaId(args.label);
|
|
17589
|
+
const iso = this.deps.now().toISOString();
|
|
17590
|
+
const file = {
|
|
17591
|
+
personaId,
|
|
17592
|
+
label: args.label,
|
|
17593
|
+
cwd: args.cwd,
|
|
17594
|
+
model: args.model,
|
|
17595
|
+
systemPromptAppend: args.systemPromptAppend,
|
|
17596
|
+
// L1 锁死:plan 模式 + 只读三件套
|
|
17597
|
+
permissionMode: "plan",
|
|
17598
|
+
toolAllowlist: ["Read", "Grep", "Glob"],
|
|
17599
|
+
public: true,
|
|
17600
|
+
tokenMap: {},
|
|
17601
|
+
createdAt: iso,
|
|
17602
|
+
updatedAt: iso
|
|
17603
|
+
};
|
|
17604
|
+
const written = this.deps.store.write(file);
|
|
17605
|
+
this.deps.registry.set(written);
|
|
17606
|
+
return written;
|
|
17607
|
+
}
|
|
17608
|
+
update(personaId, patch) {
|
|
17609
|
+
const existing = this.deps.registry.get(personaId);
|
|
17610
|
+
if (!existing) throw new Error(`persona not found: ${personaId}`);
|
|
17611
|
+
const updated = {
|
|
17612
|
+
...existing,
|
|
17613
|
+
...patch,
|
|
17614
|
+
updatedAt: this.deps.now().toISOString()
|
|
17615
|
+
};
|
|
17616
|
+
const written = this.deps.store.write(updated);
|
|
17617
|
+
this.deps.registry.set(written);
|
|
17618
|
+
return written;
|
|
17619
|
+
}
|
|
17620
|
+
/** 删除 persona + 级联清掉 sessions/<personaId>/ 目录 */
|
|
17621
|
+
delete(personaId) {
|
|
17622
|
+
this.deps.store.delete(personaId);
|
|
17623
|
+
this.deps.registry.remove(personaId);
|
|
17624
|
+
const dir = import_node_path7.default.join(this.deps.dataDir, "sessions", personaId);
|
|
17625
|
+
try {
|
|
17626
|
+
import_node_fs7.default.rmSync(dir, { recursive: true, force: true });
|
|
17627
|
+
} catch (err) {
|
|
17628
|
+
this.deps.logger.warn(`PersonaManager.delete: cleanup ${dir} failed`, {
|
|
17629
|
+
err: err.message
|
|
17630
|
+
});
|
|
17631
|
+
}
|
|
17632
|
+
}
|
|
17633
|
+
/** 生成 32-char base64url token(24 bytes 随机),label 必填 */
|
|
17634
|
+
issueToken(personaId, label) {
|
|
17635
|
+
const existing = this.deps.registry.get(personaId);
|
|
17636
|
+
if (!existing) throw new Error(`persona not found: ${personaId}`);
|
|
17637
|
+
const token = import_node_crypto3.default.randomBytes(24).toString("base64url");
|
|
17638
|
+
const entry = {
|
|
17639
|
+
label,
|
|
17640
|
+
issuedAt: this.deps.now().toISOString()
|
|
17641
|
+
};
|
|
17642
|
+
const updated = {
|
|
17643
|
+
...existing,
|
|
17644
|
+
tokenMap: { ...existing.tokenMap, [token]: entry },
|
|
17645
|
+
updatedAt: this.deps.now().toISOString()
|
|
17646
|
+
};
|
|
17647
|
+
const written = this.deps.store.write(updated);
|
|
17648
|
+
this.deps.registry.set(written);
|
|
17649
|
+
return { token, persona: written };
|
|
17650
|
+
}
|
|
17651
|
+
revokeToken(personaId, token) {
|
|
17652
|
+
const existing = this.deps.registry.get(personaId);
|
|
17653
|
+
if (!existing) throw new Error(`persona not found: ${personaId}`);
|
|
17654
|
+
const tokenEntry = existing.tokenMap[token];
|
|
17655
|
+
if (!tokenEntry) throw new Error(`token not found in persona ${personaId}`);
|
|
17656
|
+
const updated = {
|
|
17657
|
+
...existing,
|
|
17658
|
+
tokenMap: {
|
|
17659
|
+
...existing.tokenMap,
|
|
17660
|
+
[token]: { ...tokenEntry, revoked: true }
|
|
17661
|
+
},
|
|
17662
|
+
updatedAt: this.deps.now().toISOString()
|
|
17663
|
+
};
|
|
17664
|
+
const written = this.deps.store.write(updated);
|
|
17665
|
+
this.deps.registry.set(written);
|
|
17666
|
+
return written;
|
|
17667
|
+
}
|
|
17668
|
+
/**
|
|
17669
|
+
* 拿到(或按需创建)该 token 对应的 sub-session。subSessionId 由 personaId + token 哈希派生
|
|
17670
|
+
* 保证 idempotent:同一 token 永远复用同一个 sub-session。
|
|
17671
|
+
* 创建路径:开启 idleKillEnabled,cwd / model / tool 等字段从 PersonaFile 模板复制
|
|
17672
|
+
*/
|
|
17673
|
+
getOrCreateSubSession(personaId, token) {
|
|
17674
|
+
const persona = this.deps.registry.get(personaId);
|
|
17675
|
+
if (!persona) throw new Error(`persona not found: ${personaId}`);
|
|
17676
|
+
const subSessionId = this.deriveSubSessionId(personaId, token);
|
|
17677
|
+
const existing = this.deps.sessionManager.readForAgent(subSessionId, personaId);
|
|
17678
|
+
if (existing) {
|
|
17679
|
+
return { sessionFile: existing, isNew: false };
|
|
17680
|
+
}
|
|
17681
|
+
const tokenEntry = persona.tokenMap[token];
|
|
17682
|
+
const subLabel = tokenEntry?.label ?? "unknown";
|
|
17683
|
+
const sessionFile = this.deps.sessionManager.createForAgent({
|
|
17684
|
+
sessionId: subSessionId,
|
|
17685
|
+
agentId: personaId,
|
|
17686
|
+
cwd: persona.cwd,
|
|
17687
|
+
tool: "claude",
|
|
17688
|
+
label: subLabel,
|
|
17689
|
+
model: persona.model,
|
|
17690
|
+
permissionMode: "plan",
|
|
17691
|
+
// L1 锁定(task 14):把 PersonaFile.toolAllowlist 透传到 SessionManager,
|
|
17692
|
+
// 最终在 buildSpawnArgs 拼成 --add-dir + --disallowed-tools,runner stdout 拦截层
|
|
17693
|
+
// 也读 SubSessionMeta.toolAllowlist 做工具名 / cwd 越界拦截
|
|
17694
|
+
hardcodedToolAllowlist: persona.toolAllowlist,
|
|
17695
|
+
subSessionMeta: { idleKillEnabled: true }
|
|
17696
|
+
});
|
|
17697
|
+
return { sessionFile, isNew: true };
|
|
17698
|
+
}
|
|
17699
|
+
/**
|
|
17700
|
+
* 老板插话:把"老板的话"作为 meta-text + metaSource='owner' 推到 sub-session 的事件流。
|
|
17701
|
+
* 委托 SessionManager.injectOwnerMessage 路由到 reducer 'inject-owner-text' input
|
|
17702
|
+
*/
|
|
17703
|
+
appendOwnerMessage(personaId, subSessionId, text) {
|
|
17704
|
+
this.deps.sessionManager.injectOwnerMessage({
|
|
17705
|
+
sessionId: subSessionId,
|
|
17706
|
+
agentId: personaId,
|
|
17707
|
+
text
|
|
17708
|
+
});
|
|
17709
|
+
}
|
|
17710
|
+
// ---------------- 内部 ----------------
|
|
17711
|
+
/** label 转 4-16 char slug + 4 char base64url 后缀,避免冲突且文件名安全 */
|
|
17712
|
+
generatePersonaId(label) {
|
|
17713
|
+
const slug = label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 16) || "persona";
|
|
17714
|
+
const rand = import_node_crypto3.default.randomBytes(3).toString("base64url").slice(0, 4);
|
|
17715
|
+
return `persona-${slug}-${rand}`;
|
|
17716
|
+
}
|
|
17717
|
+
/** subSessionId = persona-<short>-<tokenHash12>;同 token 始终复用同一 sub-session */
|
|
17718
|
+
deriveSubSessionId(personaId, token) {
|
|
17719
|
+
const tokenHash = import_node_crypto3.default.createHash("sha256").update(token).digest("hex").slice(0, 12);
|
|
17720
|
+
return `${personaId}-${tokenHash}`;
|
|
17721
|
+
}
|
|
17722
|
+
};
|
|
17723
|
+
|
|
16962
17724
|
// src/index.ts
|
|
16963
17725
|
init_claude();
|
|
16964
17726
|
init_claude_history();
|
|
16965
17727
|
|
|
16966
17728
|
// src/workspace/browser.ts
|
|
16967
|
-
var
|
|
17729
|
+
var import_node_fs10 = __toESM(require("fs"), 1);
|
|
16968
17730
|
var import_node_os4 = __toESM(require("os"), 1);
|
|
16969
|
-
var
|
|
17731
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
16970
17732
|
init_protocol();
|
|
16971
17733
|
var MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
16972
17734
|
function resolveInsideCwd(cwd, subpath) {
|
|
16973
|
-
const absCwd =
|
|
16974
|
-
const joined =
|
|
16975
|
-
const rel =
|
|
16976
|
-
if (rel.startsWith("..") ||
|
|
17735
|
+
const absCwd = import_node_path10.default.resolve(cwd);
|
|
17736
|
+
const joined = import_node_path10.default.resolve(absCwd, subpath ?? ".");
|
|
17737
|
+
const rel = import_node_path10.default.relative(absCwd, joined);
|
|
17738
|
+
if (rel.startsWith("..") || import_node_path10.default.isAbsolute(rel)) {
|
|
16977
17739
|
throw new ClawdError(ERROR_CODES.INVALID_PATH, `path escapes cwd: ${subpath}`);
|
|
16978
17740
|
}
|
|
16979
17741
|
return joined;
|
|
16980
17742
|
}
|
|
16981
17743
|
function ensureCwd(cwd) {
|
|
16982
17744
|
try {
|
|
16983
|
-
const stat =
|
|
17745
|
+
const stat = import_node_fs10.default.statSync(cwd);
|
|
16984
17746
|
if (!stat.isDirectory()) {
|
|
16985
17747
|
throw new ClawdError(ERROR_CODES.INVALID_CWD, `not a directory: ${cwd}`);
|
|
16986
17748
|
}
|
|
@@ -16994,7 +17756,7 @@ var WorkspaceBrowser = class {
|
|
|
16994
17756
|
const cwd = args.cwd && args.cwd.length > 0 ? args.cwd : import_node_os4.default.homedir();
|
|
16995
17757
|
ensureCwd(cwd);
|
|
16996
17758
|
const full = resolveInsideCwd(cwd, args.path);
|
|
16997
|
-
const dirents =
|
|
17759
|
+
const dirents = import_node_fs10.default.readdirSync(full, { withFileTypes: true });
|
|
16998
17760
|
const entries = [];
|
|
16999
17761
|
for (const d of dirents) {
|
|
17000
17762
|
if (!args.showHidden && d.name.startsWith(".")) continue;
|
|
@@ -17004,7 +17766,7 @@ var WorkspaceBrowser = class {
|
|
|
17004
17766
|
mtime: ""
|
|
17005
17767
|
};
|
|
17006
17768
|
try {
|
|
17007
|
-
const st =
|
|
17769
|
+
const st = import_node_fs10.default.statSync(import_node_path10.default.join(full, d.name));
|
|
17008
17770
|
entry.mtime = new Date(st.mtimeMs).toISOString();
|
|
17009
17771
|
if (d.isFile()) entry.size = st.size;
|
|
17010
17772
|
} catch {
|
|
@@ -17020,14 +17782,14 @@ var WorkspaceBrowser = class {
|
|
|
17020
17782
|
read(args) {
|
|
17021
17783
|
ensureCwd(args.cwd);
|
|
17022
17784
|
const full = resolveInsideCwd(args.cwd, args.path);
|
|
17023
|
-
const st =
|
|
17785
|
+
const st = import_node_fs10.default.statSync(full);
|
|
17024
17786
|
if (!st.isFile()) {
|
|
17025
17787
|
throw new ClawdError(ERROR_CODES.INVALID_PATH, `not a file: ${args.path}`);
|
|
17026
17788
|
}
|
|
17027
17789
|
if (st.size > MAX_FILE_BYTES) {
|
|
17028
17790
|
throw new ClawdError(ERROR_CODES.FILE_TOO_LARGE, `file > ${MAX_FILE_BYTES} bytes`);
|
|
17029
17791
|
}
|
|
17030
|
-
const buf =
|
|
17792
|
+
const buf = import_node_fs10.default.readFileSync(full);
|
|
17031
17793
|
const isBinary = buf.includes(0);
|
|
17032
17794
|
if (isBinary) {
|
|
17033
17795
|
return {
|
|
@@ -17049,9 +17811,9 @@ var WorkspaceBrowser = class {
|
|
|
17049
17811
|
};
|
|
17050
17812
|
|
|
17051
17813
|
// src/skills/scanner.ts
|
|
17052
|
-
var
|
|
17814
|
+
var import_node_fs11 = __toESM(require("fs"), 1);
|
|
17053
17815
|
var import_node_os5 = __toESM(require("os"), 1);
|
|
17054
|
-
var
|
|
17816
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
17055
17817
|
function parseFrontmatter(content) {
|
|
17056
17818
|
if (!content.startsWith("---")) return { name: "", description: "" };
|
|
17057
17819
|
const end = content.indexOf("---", 3);
|
|
@@ -17087,7 +17849,7 @@ function parseFrontmatter(content) {
|
|
|
17087
17849
|
}
|
|
17088
17850
|
function isDirLikeSync(p) {
|
|
17089
17851
|
try {
|
|
17090
|
-
return
|
|
17852
|
+
return import_node_fs11.default.statSync(p).isDirectory();
|
|
17091
17853
|
} catch {
|
|
17092
17854
|
return false;
|
|
17093
17855
|
}
|
|
@@ -17095,19 +17857,19 @@ function isDirLikeSync(p) {
|
|
|
17095
17857
|
function scanSkillDir(dir, source, seen, out, pluginName) {
|
|
17096
17858
|
let entries;
|
|
17097
17859
|
try {
|
|
17098
|
-
entries =
|
|
17860
|
+
entries = import_node_fs11.default.readdirSync(dir, { withFileTypes: true });
|
|
17099
17861
|
} catch {
|
|
17100
17862
|
return;
|
|
17101
17863
|
}
|
|
17102
17864
|
for (const ent of entries) {
|
|
17103
|
-
const entryPath =
|
|
17865
|
+
const entryPath = import_node_path11.default.join(dir, ent.name);
|
|
17104
17866
|
if (!ent.isDirectory() && !(ent.isSymbolicLink() && isDirLikeSync(entryPath))) continue;
|
|
17105
17867
|
let content;
|
|
17106
17868
|
try {
|
|
17107
|
-
content =
|
|
17869
|
+
content = import_node_fs11.default.readFileSync(import_node_path11.default.join(entryPath, "SKILL.md"), "utf8");
|
|
17108
17870
|
} catch {
|
|
17109
17871
|
try {
|
|
17110
|
-
content =
|
|
17872
|
+
content = import_node_fs11.default.readFileSync(import_node_path11.default.join(entryPath, "skill.md"), "utf8");
|
|
17111
17873
|
} catch {
|
|
17112
17874
|
continue;
|
|
17113
17875
|
}
|
|
@@ -17125,26 +17887,26 @@ function scanSkillDir(dir, source, seen, out, pluginName) {
|
|
|
17125
17887
|
function scanCommandDir(dir, source, seen, out, pluginName) {
|
|
17126
17888
|
let entries;
|
|
17127
17889
|
try {
|
|
17128
|
-
entries =
|
|
17890
|
+
entries = import_node_fs11.default.readdirSync(dir, { withFileTypes: true });
|
|
17129
17891
|
} catch {
|
|
17130
17892
|
return;
|
|
17131
17893
|
}
|
|
17132
17894
|
for (const ent of entries) {
|
|
17133
|
-
const entryPath =
|
|
17895
|
+
const entryPath = import_node_path11.default.join(dir, ent.name);
|
|
17134
17896
|
if (ent.isDirectory() || ent.isSymbolicLink() && isDirLikeSync(entryPath)) {
|
|
17135
17897
|
const ns = ent.name;
|
|
17136
17898
|
let subEntries;
|
|
17137
17899
|
try {
|
|
17138
|
-
subEntries =
|
|
17900
|
+
subEntries = import_node_fs11.default.readdirSync(entryPath, { withFileTypes: true });
|
|
17139
17901
|
} catch {
|
|
17140
17902
|
continue;
|
|
17141
17903
|
}
|
|
17142
17904
|
for (const se of subEntries) {
|
|
17143
17905
|
if (!se.name.endsWith(".md")) continue;
|
|
17144
|
-
const sePath =
|
|
17906
|
+
const sePath = import_node_path11.default.join(entryPath, se.name);
|
|
17145
17907
|
let content;
|
|
17146
17908
|
try {
|
|
17147
|
-
content =
|
|
17909
|
+
content = import_node_fs11.default.readFileSync(sePath, "utf8");
|
|
17148
17910
|
} catch {
|
|
17149
17911
|
continue;
|
|
17150
17912
|
}
|
|
@@ -17161,7 +17923,7 @@ function scanCommandDir(dir, source, seen, out, pluginName) {
|
|
|
17161
17923
|
} else if (ent.name.endsWith(".md")) {
|
|
17162
17924
|
let content;
|
|
17163
17925
|
try {
|
|
17164
|
-
content =
|
|
17926
|
+
content = import_node_fs11.default.readFileSync(entryPath, "utf8");
|
|
17165
17927
|
} catch {
|
|
17166
17928
|
continue;
|
|
17167
17929
|
}
|
|
@@ -17177,10 +17939,10 @@ function scanCommandDir(dir, source, seen, out, pluginName) {
|
|
|
17177
17939
|
}
|
|
17178
17940
|
}
|
|
17179
17941
|
function readInstalledPlugins(home) {
|
|
17180
|
-
const file =
|
|
17942
|
+
const file = import_node_path11.default.join(home, ".claude", "plugins", "installed_plugins.json");
|
|
17181
17943
|
let raw;
|
|
17182
17944
|
try {
|
|
17183
|
-
raw =
|
|
17945
|
+
raw = import_node_fs11.default.readFileSync(file, "utf8");
|
|
17184
17946
|
} catch {
|
|
17185
17947
|
return [];
|
|
17186
17948
|
}
|
|
@@ -17218,14 +17980,14 @@ var SkillsScanner = class {
|
|
|
17218
17980
|
list(args) {
|
|
17219
17981
|
const seen = /* @__PURE__ */ new Set();
|
|
17220
17982
|
const out = [];
|
|
17221
|
-
scanSkillDir(
|
|
17222
|
-
scanCommandDir(
|
|
17223
|
-
scanSkillDir(
|
|
17224
|
-
scanCommandDir(
|
|
17983
|
+
scanSkillDir(import_node_path11.default.join(this.home, ".claude", "skills"), "global", seen, out);
|
|
17984
|
+
scanCommandDir(import_node_path11.default.join(this.home, ".claude", "commands"), "global", seen, out);
|
|
17985
|
+
scanSkillDir(import_node_path11.default.join(args.cwd, ".claude", "skills"), "project", seen, out);
|
|
17986
|
+
scanCommandDir(import_node_path11.default.join(args.cwd, ".claude", "commands"), "project", seen, out);
|
|
17225
17987
|
const plugins = [...readInstalledPlugins(this.home), ...this.extraPluginRoots];
|
|
17226
17988
|
for (const { name, root } of plugins) {
|
|
17227
|
-
scanSkillDir(
|
|
17228
|
-
scanCommandDir(
|
|
17989
|
+
scanSkillDir(import_node_path11.default.join(root, "skills"), "plugin", seen, out, name);
|
|
17990
|
+
scanCommandDir(import_node_path11.default.join(root, "commands"), "plugin", seen, out, name);
|
|
17229
17991
|
}
|
|
17230
17992
|
out.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
17231
17993
|
return out;
|
|
@@ -17233,9 +17995,9 @@ var SkillsScanner = class {
|
|
|
17233
17995
|
};
|
|
17234
17996
|
|
|
17235
17997
|
// src/observer/session-observer.ts
|
|
17236
|
-
var
|
|
17998
|
+
var import_node_fs12 = __toESM(require("fs"), 1);
|
|
17237
17999
|
var import_node_os6 = __toESM(require("os"), 1);
|
|
17238
|
-
var
|
|
18000
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
17239
18001
|
init_claude_history();
|
|
17240
18002
|
var SessionObserver = class {
|
|
17241
18003
|
constructor(opts) {
|
|
@@ -17247,14 +18009,14 @@ var SessionObserver = class {
|
|
|
17247
18009
|
watches = /* @__PURE__ */ new Map();
|
|
17248
18010
|
resolveJsonlPath(cwd, toolSessionId, override) {
|
|
17249
18011
|
if (override) return override;
|
|
17250
|
-
return
|
|
18012
|
+
return import_node_path12.default.join(this.home, ".claude", "projects", cwdToHashDir(cwd), `${toolSessionId}.jsonl`);
|
|
17251
18013
|
}
|
|
17252
18014
|
start(args) {
|
|
17253
18015
|
this.stop(args.sessionId);
|
|
17254
18016
|
const filePath = this.resolveJsonlPath(args.cwd, args.toolSessionId, args.jsonlPath);
|
|
17255
18017
|
let size = 0;
|
|
17256
18018
|
try {
|
|
17257
|
-
size =
|
|
18019
|
+
size = import_node_fs12.default.statSync(filePath).size;
|
|
17258
18020
|
} catch {
|
|
17259
18021
|
}
|
|
17260
18022
|
const w = {
|
|
@@ -17267,10 +18029,10 @@ var SessionObserver = class {
|
|
|
17267
18029
|
adapter: args.adapter
|
|
17268
18030
|
};
|
|
17269
18031
|
try {
|
|
17270
|
-
|
|
18032
|
+
import_node_fs12.default.mkdirSync(import_node_path12.default.dirname(filePath), { recursive: true });
|
|
17271
18033
|
} catch {
|
|
17272
18034
|
}
|
|
17273
|
-
w.watcher =
|
|
18035
|
+
w.watcher = import_node_fs12.default.watch(import_node_path12.default.dirname(filePath), { persistent: false }, (_event, changedName) => {
|
|
17274
18036
|
if (!changedName || !filePath.endsWith(changedName)) return;
|
|
17275
18037
|
this.poll(w);
|
|
17276
18038
|
});
|
|
@@ -17285,7 +18047,7 @@ var SessionObserver = class {
|
|
|
17285
18047
|
// reducer.shallowEqMeta diff 让重复 patch 静默吞掉;异常静默吞,不阻塞 watcher 启动
|
|
17286
18048
|
hydrateMetaTail(w, maxLines = 200) {
|
|
17287
18049
|
try {
|
|
17288
|
-
const raw =
|
|
18050
|
+
const raw = import_node_fs12.default.readFileSync(w.filePath, "utf8");
|
|
17289
18051
|
if (!raw) return;
|
|
17290
18052
|
const allLines = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
17291
18053
|
if (allLines.length === 0) return;
|
|
@@ -17306,7 +18068,7 @@ var SessionObserver = class {
|
|
|
17306
18068
|
poll(w) {
|
|
17307
18069
|
let size = 0;
|
|
17308
18070
|
try {
|
|
17309
|
-
size =
|
|
18071
|
+
size = import_node_fs12.default.statSync(w.filePath).size;
|
|
17310
18072
|
} catch {
|
|
17311
18073
|
return;
|
|
17312
18074
|
}
|
|
@@ -17315,11 +18077,11 @@ var SessionObserver = class {
|
|
|
17315
18077
|
w.buf = "";
|
|
17316
18078
|
}
|
|
17317
18079
|
if (size === w.lastSize) return;
|
|
17318
|
-
const fd =
|
|
18080
|
+
const fd = import_node_fs12.default.openSync(w.filePath, "r");
|
|
17319
18081
|
try {
|
|
17320
18082
|
const len = size - w.lastSize;
|
|
17321
18083
|
const buf = Buffer.alloc(len);
|
|
17322
|
-
|
|
18084
|
+
import_node_fs12.default.readSync(fd, buf, 0, len, w.lastSize);
|
|
17323
18085
|
w.lastSize = size;
|
|
17324
18086
|
w.buf += buf.toString("utf8");
|
|
17325
18087
|
let newlineIndex;
|
|
@@ -17333,7 +18095,7 @@ var SessionObserver = class {
|
|
|
17333
18095
|
this.maybeReportUserMessage(w.sessionId, line);
|
|
17334
18096
|
}
|
|
17335
18097
|
} finally {
|
|
17336
|
-
|
|
18098
|
+
import_node_fs12.default.closeSync(fd);
|
|
17337
18099
|
}
|
|
17338
18100
|
}
|
|
17339
18101
|
// 解析 JSONL 单行:仅当是主链 user 文本行(非 sidechain / 非 sub-agent / message.role='user'
|
|
@@ -17431,6 +18193,7 @@ var import_websocket = __toESM(require_websocket(), 1);
|
|
|
17431
18193
|
var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
17432
18194
|
|
|
17433
18195
|
// src/transport/local-ws-server.ts
|
|
18196
|
+
var PERSONA_PATH_RE = /^\/personas\/([a-zA-Z0-9._-]+)$/;
|
|
17434
18197
|
var LocalWsServer = class {
|
|
17435
18198
|
constructor(opts) {
|
|
17436
18199
|
this.opts = opts;
|
|
@@ -17459,7 +18222,7 @@ var LocalWsServer = class {
|
|
|
17459
18222
|
this.logger?.error("ws server error", { err: err.message });
|
|
17460
18223
|
reject(err);
|
|
17461
18224
|
});
|
|
17462
|
-
wss.on("connection", (socket, req) => this.
|
|
18225
|
+
wss.on("connection", (socket, req) => this.routeConnection(socket, req));
|
|
17463
18226
|
this.wss = wss;
|
|
17464
18227
|
});
|
|
17465
18228
|
}
|
|
@@ -17524,6 +18287,35 @@ var LocalWsServer = class {
|
|
|
17524
18287
|
if (!c) return;
|
|
17525
18288
|
this.safeSend(c.ws, frame);
|
|
17526
18289
|
}
|
|
18290
|
+
// URL path 路由:'/' → owner mode(first-message authToken gate 走 onConnection),
|
|
18291
|
+
// '/personas/<id>' → personaBoundHandler,其它 → close 4404
|
|
18292
|
+
routeConnection(socket, req) {
|
|
18293
|
+
const remoteAddress = req?.socket?.remoteAddress;
|
|
18294
|
+
const urlPath = (() => {
|
|
18295
|
+
try {
|
|
18296
|
+
return new URL(req.url ?? "/", "http://localhost").pathname;
|
|
18297
|
+
} catch {
|
|
18298
|
+
return "/";
|
|
18299
|
+
}
|
|
18300
|
+
})();
|
|
18301
|
+
if (urlPath === "/") {
|
|
18302
|
+
this.onConnection(socket, remoteAddress);
|
|
18303
|
+
return;
|
|
18304
|
+
}
|
|
18305
|
+
const personaMatch = urlPath.match(PERSONA_PATH_RE);
|
|
18306
|
+
if (personaMatch && this.opts.personaBoundHandler) {
|
|
18307
|
+
this.logger?.info("persona ws connected", {
|
|
18308
|
+
personaId: personaMatch[1],
|
|
18309
|
+
remoteAddress
|
|
18310
|
+
});
|
|
18311
|
+
this.opts.personaBoundHandler.handle(socket, personaMatch[1]);
|
|
18312
|
+
return;
|
|
18313
|
+
}
|
|
18314
|
+
try {
|
|
18315
|
+
socket.close(4404, "unknown path");
|
|
18316
|
+
} catch {
|
|
18317
|
+
}
|
|
18318
|
+
}
|
|
17527
18319
|
onConnection(socket, remoteAddress) {
|
|
17528
18320
|
const id = v4_default();
|
|
17529
18321
|
const subscribedSessions = /* @__PURE__ */ new Map();
|
|
@@ -17661,6 +18453,141 @@ function removeSubscription(client, sessionId) {
|
|
|
17661
18453
|
else client.subscribedSessions.set(sessionId, next);
|
|
17662
18454
|
}
|
|
17663
18455
|
|
|
18456
|
+
// src/transport/persona-bound-handler.ts
|
|
18457
|
+
init_protocol();
|
|
18458
|
+
var PersonaBoundHandler = class {
|
|
18459
|
+
constructor(deps) {
|
|
18460
|
+
this.deps = deps;
|
|
18461
|
+
}
|
|
18462
|
+
deps;
|
|
18463
|
+
handle(ws, personaId) {
|
|
18464
|
+
let scope = null;
|
|
18465
|
+
let unsubscribe = null;
|
|
18466
|
+
const send = (frame) => {
|
|
18467
|
+
try {
|
|
18468
|
+
ws.send(JSON.stringify(frame));
|
|
18469
|
+
} catch (err) {
|
|
18470
|
+
this.deps.logger.warn(`persona ws send failed: ${err.message}`);
|
|
18471
|
+
}
|
|
18472
|
+
};
|
|
18473
|
+
ws.on("message", (raw) => {
|
|
18474
|
+
let parsedRaw;
|
|
18475
|
+
try {
|
|
18476
|
+
parsedRaw = JSON.parse(raw.toString());
|
|
18477
|
+
} catch {
|
|
18478
|
+
ws.close(4400, "PROTOCOL_VIOLATION");
|
|
18479
|
+
return;
|
|
18480
|
+
}
|
|
18481
|
+
const parseResult = PersonaClientFrameSchema.safeParse(parsedRaw);
|
|
18482
|
+
if (!parseResult.success) {
|
|
18483
|
+
ws.close(4400, "PROTOCOL_VIOLATION");
|
|
18484
|
+
return;
|
|
18485
|
+
}
|
|
18486
|
+
const frame = parseResult.data;
|
|
18487
|
+
if (!scope) {
|
|
18488
|
+
if (frame.kind !== "persona-hello") {
|
|
18489
|
+
ws.close(4400, "PROTOCOL_VIOLATION");
|
|
18490
|
+
return;
|
|
18491
|
+
}
|
|
18492
|
+
const verify = this.deps.registry.verifyToken(personaId, frame.token);
|
|
18493
|
+
if (!verify.ok) {
|
|
18494
|
+
const code = verify.code === "PERSONA_DELETED" ? 4404 : verify.code === "PERSONA_NOT_PUBLIC" ? 4403 : 4401;
|
|
18495
|
+
ws.close(code, verify.code);
|
|
18496
|
+
return;
|
|
18497
|
+
}
|
|
18498
|
+
const persona = this.deps.registry.get(personaId);
|
|
18499
|
+
if (!persona) {
|
|
18500
|
+
ws.close(4404, "PERSONA_DELETED");
|
|
18501
|
+
return;
|
|
18502
|
+
}
|
|
18503
|
+
let subSessionFile;
|
|
18504
|
+
try {
|
|
18505
|
+
const { sessionFile } = this.deps.personaManager.getOrCreateSubSession(
|
|
18506
|
+
personaId,
|
|
18507
|
+
frame.token
|
|
18508
|
+
);
|
|
18509
|
+
subSessionFile = sessionFile;
|
|
18510
|
+
} catch (err) {
|
|
18511
|
+
this.deps.logger.warn(
|
|
18512
|
+
`persona getOrCreateSubSession failed: ${err.message}`
|
|
18513
|
+
);
|
|
18514
|
+
ws.close(1011, "INTERNAL");
|
|
18515
|
+
return;
|
|
18516
|
+
}
|
|
18517
|
+
scope = { personaId, subSessionId: subSessionFile.sessionId };
|
|
18518
|
+
send({
|
|
18519
|
+
kind: "persona-ready",
|
|
18520
|
+
subSessionId: subSessionFile.sessionId,
|
|
18521
|
+
personaLabel: persona.label,
|
|
18522
|
+
cwd: persona.cwd,
|
|
18523
|
+
model: persona.model
|
|
18524
|
+
});
|
|
18525
|
+
unsubscribe = this.deps.sessionManager.subscribe(
|
|
18526
|
+
subSessionFile.sessionId,
|
|
18527
|
+
personaId,
|
|
18528
|
+
(event) => {
|
|
18529
|
+
send({ kind: "event", event });
|
|
18530
|
+
}
|
|
18531
|
+
);
|
|
18532
|
+
try {
|
|
18533
|
+
const history = this.deps.sessionManager.readHistoryEvents(
|
|
18534
|
+
subSessionFile.sessionId,
|
|
18535
|
+
personaId
|
|
18536
|
+
);
|
|
18537
|
+
for (const event of history) {
|
|
18538
|
+
send({ kind: "history-event", event });
|
|
18539
|
+
}
|
|
18540
|
+
} catch (err) {
|
|
18541
|
+
this.deps.logger.warn(
|
|
18542
|
+
`persona history read failed: ${err.message}`
|
|
18543
|
+
);
|
|
18544
|
+
}
|
|
18545
|
+
send({ kind: "history-done" });
|
|
18546
|
+
return;
|
|
18547
|
+
}
|
|
18548
|
+
switch (frame.kind) {
|
|
18549
|
+
case "persona-hello":
|
|
18550
|
+
ws.close(4400, "PROTOCOL_VIOLATION");
|
|
18551
|
+
return;
|
|
18552
|
+
case "send": {
|
|
18553
|
+
try {
|
|
18554
|
+
this.deps.sessionManager.sendForAgent({
|
|
18555
|
+
sessionId: scope.subSessionId,
|
|
18556
|
+
agentId: scope.personaId,
|
|
18557
|
+
text: frame.text
|
|
18558
|
+
});
|
|
18559
|
+
} catch (err) {
|
|
18560
|
+
this.deps.logger.warn(
|
|
18561
|
+
`persona sendForAgent failed: ${err.message}`
|
|
18562
|
+
);
|
|
18563
|
+
}
|
|
18564
|
+
return;
|
|
18565
|
+
}
|
|
18566
|
+
case "reset": {
|
|
18567
|
+
try {
|
|
18568
|
+
this.deps.sessionManager.resetForAgent({
|
|
18569
|
+
sessionId: scope.subSessionId,
|
|
18570
|
+
agentId: scope.personaId
|
|
18571
|
+
});
|
|
18572
|
+
} catch (err) {
|
|
18573
|
+
this.deps.logger.warn(
|
|
18574
|
+
`persona resetForAgent failed: ${err.message}`
|
|
18575
|
+
);
|
|
18576
|
+
}
|
|
18577
|
+
return;
|
|
18578
|
+
}
|
|
18579
|
+
case "ping":
|
|
18580
|
+
send({ kind: "pong" });
|
|
18581
|
+
return;
|
|
18582
|
+
}
|
|
18583
|
+
});
|
|
18584
|
+
ws.on("close", () => {
|
|
18585
|
+
unsubscribe?.();
|
|
18586
|
+
unsubscribe = null;
|
|
18587
|
+
});
|
|
18588
|
+
}
|
|
18589
|
+
};
|
|
18590
|
+
|
|
17664
18591
|
// src/transport/auth.ts
|
|
17665
18592
|
init_protocol();
|
|
17666
18593
|
var AuthGate = class {
|
|
@@ -17756,10 +18683,10 @@ function isLocalhost(addr) {
|
|
|
17756
18683
|
}
|
|
17757
18684
|
|
|
17758
18685
|
// src/discovery/state-file.ts
|
|
17759
|
-
var
|
|
17760
|
-
var
|
|
18686
|
+
var import_node_fs13 = __toESM(require("fs"), 1);
|
|
18687
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
17761
18688
|
function defaultStateFilePath(dataDir) {
|
|
17762
|
-
return
|
|
18689
|
+
return import_node_path13.default.join(dataDir, "state.json");
|
|
17763
18690
|
}
|
|
17764
18691
|
function isPidAlive(pid) {
|
|
17765
18692
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
@@ -17781,7 +18708,7 @@ var StateFileManager = class {
|
|
|
17781
18708
|
}
|
|
17782
18709
|
read() {
|
|
17783
18710
|
try {
|
|
17784
|
-
const raw =
|
|
18711
|
+
const raw = import_node_fs13.default.readFileSync(this.file, "utf8");
|
|
17785
18712
|
const parsed = JSON.parse(raw);
|
|
17786
18713
|
return parsed;
|
|
17787
18714
|
} catch {
|
|
@@ -17795,34 +18722,34 @@ var StateFileManager = class {
|
|
|
17795
18722
|
return { status: "stale", existing };
|
|
17796
18723
|
}
|
|
17797
18724
|
write(state) {
|
|
17798
|
-
|
|
18725
|
+
import_node_fs13.default.mkdirSync(import_node_path13.default.dirname(this.file), { recursive: true });
|
|
17799
18726
|
const tmp = `${this.file}.tmp.${process.pid}.${Date.now()}`;
|
|
17800
|
-
|
|
17801
|
-
|
|
18727
|
+
import_node_fs13.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
|
|
18728
|
+
import_node_fs13.default.renameSync(tmp, this.file);
|
|
17802
18729
|
if (process.platform !== "win32") {
|
|
17803
18730
|
try {
|
|
17804
|
-
|
|
18731
|
+
import_node_fs13.default.chmodSync(this.file, 384);
|
|
17805
18732
|
} catch {
|
|
17806
18733
|
}
|
|
17807
18734
|
}
|
|
17808
18735
|
}
|
|
17809
18736
|
delete() {
|
|
17810
18737
|
try {
|
|
17811
|
-
|
|
18738
|
+
import_node_fs13.default.unlinkSync(this.file);
|
|
17812
18739
|
} catch {
|
|
17813
18740
|
}
|
|
17814
18741
|
}
|
|
17815
18742
|
};
|
|
17816
18743
|
|
|
17817
18744
|
// src/tunnel/tunnel-manager.ts
|
|
17818
|
-
var
|
|
17819
|
-
var
|
|
17820
|
-
var
|
|
18745
|
+
var import_node_fs16 = __toESM(require("fs"), 1);
|
|
18746
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
18747
|
+
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
17821
18748
|
var import_node_child_process4 = require("child_process");
|
|
17822
18749
|
|
|
17823
18750
|
// src/tunnel/tunnel-store.ts
|
|
17824
|
-
var
|
|
17825
|
-
var
|
|
18751
|
+
var import_node_fs14 = __toESM(require("fs"), 1);
|
|
18752
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
17826
18753
|
var TunnelStore = class {
|
|
17827
18754
|
constructor(filePath) {
|
|
17828
18755
|
this.filePath = filePath;
|
|
@@ -17830,7 +18757,7 @@ var TunnelStore = class {
|
|
|
17830
18757
|
filePath;
|
|
17831
18758
|
async get() {
|
|
17832
18759
|
try {
|
|
17833
|
-
const raw = await
|
|
18760
|
+
const raw = await import_node_fs14.default.promises.readFile(this.filePath, "utf8");
|
|
17834
18761
|
const obj = JSON.parse(raw);
|
|
17835
18762
|
if (!isPersistedTunnel(obj)) return null;
|
|
17836
18763
|
return obj;
|
|
@@ -17841,22 +18768,22 @@ var TunnelStore = class {
|
|
|
17841
18768
|
}
|
|
17842
18769
|
}
|
|
17843
18770
|
async set(v) {
|
|
17844
|
-
const dir =
|
|
17845
|
-
await
|
|
18771
|
+
const dir = import_node_path14.default.dirname(this.filePath);
|
|
18772
|
+
await import_node_fs14.default.promises.mkdir(dir, { recursive: true });
|
|
17846
18773
|
const data = JSON.stringify(v, null, 2);
|
|
17847
18774
|
const tmp = `${this.filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
17848
|
-
await
|
|
18775
|
+
await import_node_fs14.default.promises.writeFile(tmp, data, { mode: 384 });
|
|
17849
18776
|
if (process.platform !== "win32") {
|
|
17850
18777
|
try {
|
|
17851
|
-
await
|
|
18778
|
+
await import_node_fs14.default.promises.chmod(tmp, 384);
|
|
17852
18779
|
} catch {
|
|
17853
18780
|
}
|
|
17854
18781
|
}
|
|
17855
|
-
await
|
|
18782
|
+
await import_node_fs14.default.promises.rename(tmp, this.filePath);
|
|
17856
18783
|
}
|
|
17857
18784
|
async clear() {
|
|
17858
18785
|
try {
|
|
17859
|
-
await
|
|
18786
|
+
await import_node_fs14.default.promises.unlink(this.filePath);
|
|
17860
18787
|
} catch (err) {
|
|
17861
18788
|
const code = err?.code;
|
|
17862
18789
|
if (code !== "ENOENT") throw err;
|
|
@@ -17951,9 +18878,9 @@ function escape(v) {
|
|
|
17951
18878
|
}
|
|
17952
18879
|
|
|
17953
18880
|
// src/tunnel/frpc-binary.ts
|
|
17954
|
-
var
|
|
18881
|
+
var import_node_fs15 = __toESM(require("fs"), 1);
|
|
17955
18882
|
var import_node_os7 = __toESM(require("os"), 1);
|
|
17956
|
-
var
|
|
18883
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
17957
18884
|
var import_node_child_process3 = require("child_process");
|
|
17958
18885
|
var import_node_stream = require("stream");
|
|
17959
18886
|
var import_promises = require("stream/promises");
|
|
@@ -17985,20 +18912,20 @@ function frpcDownloadUrl(version2, p) {
|
|
|
17985
18912
|
}
|
|
17986
18913
|
async function ensureFrpcBinary(opts) {
|
|
17987
18914
|
if (opts.override) {
|
|
17988
|
-
if (!
|
|
18915
|
+
if (!import_node_fs15.default.existsSync(opts.override)) {
|
|
17989
18916
|
throw new Error(`frpc binary not found at override path: ${opts.override}`);
|
|
17990
18917
|
}
|
|
17991
18918
|
return opts.override;
|
|
17992
18919
|
}
|
|
17993
18920
|
const version2 = opts.version ?? FRPC_VERSION;
|
|
17994
18921
|
const platform = opts.platform ?? detectPlatform();
|
|
17995
|
-
const binDir =
|
|
17996
|
-
|
|
18922
|
+
const binDir = import_node_path15.default.join(opts.dataDir, "bin");
|
|
18923
|
+
import_node_fs15.default.mkdirSync(binDir, { recursive: true });
|
|
17997
18924
|
cleanupStaleArtifacts(binDir);
|
|
17998
|
-
const stableBin =
|
|
17999
|
-
if (
|
|
18925
|
+
const stableBin = import_node_path15.default.join(binDir, "frpc");
|
|
18926
|
+
if (import_node_fs15.default.existsSync(stableBin)) return stableBin;
|
|
18000
18927
|
const partialBin = `${stableBin}.partial`;
|
|
18001
|
-
const tarballPath =
|
|
18928
|
+
const tarballPath = import_node_path15.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
|
|
18002
18929
|
try {
|
|
18003
18930
|
const url = frpcDownloadUrl(version2, platform);
|
|
18004
18931
|
await downloadToFile(url, tarballPath, opts.fetchImpl);
|
|
@@ -18007,8 +18934,8 @@ async function ensureFrpcBinary(opts) {
|
|
|
18007
18934
|
} else {
|
|
18008
18935
|
await extractFrpcFromTarball(tarballPath, binDir, version2, platform, partialBin);
|
|
18009
18936
|
}
|
|
18010
|
-
|
|
18011
|
-
|
|
18937
|
+
import_node_fs15.default.chmodSync(partialBin, 493);
|
|
18938
|
+
import_node_fs15.default.renameSync(partialBin, stableBin);
|
|
18012
18939
|
} finally {
|
|
18013
18940
|
safeUnlink(tarballPath);
|
|
18014
18941
|
safeUnlink(partialBin);
|
|
@@ -18018,15 +18945,15 @@ async function ensureFrpcBinary(opts) {
|
|
|
18018
18945
|
function cleanupStaleArtifacts(binDir) {
|
|
18019
18946
|
let entries;
|
|
18020
18947
|
try {
|
|
18021
|
-
entries =
|
|
18948
|
+
entries = import_node_fs15.default.readdirSync(binDir);
|
|
18022
18949
|
} catch {
|
|
18023
18950
|
return;
|
|
18024
18951
|
}
|
|
18025
18952
|
for (const name of entries) {
|
|
18026
18953
|
if (name.endsWith(".partial") || name.startsWith("extract-")) {
|
|
18027
|
-
const full =
|
|
18954
|
+
const full = import_node_path15.default.join(binDir, name);
|
|
18028
18955
|
try {
|
|
18029
|
-
|
|
18956
|
+
import_node_fs15.default.rmSync(full, { recursive: true, force: true });
|
|
18030
18957
|
} catch {
|
|
18031
18958
|
}
|
|
18032
18959
|
}
|
|
@@ -18034,7 +18961,7 @@ function cleanupStaleArtifacts(binDir) {
|
|
|
18034
18961
|
}
|
|
18035
18962
|
function safeUnlink(p) {
|
|
18036
18963
|
try {
|
|
18037
|
-
|
|
18964
|
+
import_node_fs15.default.unlinkSync(p);
|
|
18038
18965
|
} catch {
|
|
18039
18966
|
}
|
|
18040
18967
|
}
|
|
@@ -18045,13 +18972,13 @@ async function downloadToFile(url, dest, fetchImpl) {
|
|
|
18045
18972
|
if (!res.ok || !res.body) {
|
|
18046
18973
|
throw new Error(`download failed: ${res.status} ${res.statusText}`);
|
|
18047
18974
|
}
|
|
18048
|
-
const out =
|
|
18975
|
+
const out = import_node_fs15.default.createWriteStream(dest);
|
|
18049
18976
|
const nodeStream = import_node_stream.Readable.fromWeb(res.body);
|
|
18050
18977
|
await (0, import_promises.pipeline)(nodeStream, out);
|
|
18051
18978
|
}
|
|
18052
18979
|
async function extractFrpcFromTarball(tarball, binDir, version2, platform, destBin) {
|
|
18053
|
-
const work =
|
|
18054
|
-
|
|
18980
|
+
const work = import_node_path15.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
|
|
18981
|
+
import_node_fs15.default.mkdirSync(work, { recursive: true });
|
|
18055
18982
|
try {
|
|
18056
18983
|
await new Promise((resolve, reject) => {
|
|
18057
18984
|
const proc = (0, import_node_child_process3.spawn)("tar", ["xzf", tarball, "-C", work], { stdio: "pipe" });
|
|
@@ -18059,13 +18986,13 @@ async function extractFrpcFromTarball(tarball, binDir, version2, platform, destB
|
|
|
18059
18986
|
proc.on("exit", (code) => code === 0 ? resolve() : reject(new Error(`tar exited ${code}`)));
|
|
18060
18987
|
});
|
|
18061
18988
|
const dirName = `frp_${version2}_${platform.os}_${platform.arch}`;
|
|
18062
|
-
const src =
|
|
18063
|
-
if (!
|
|
18989
|
+
const src = import_node_path15.default.join(work, dirName, "frpc");
|
|
18990
|
+
if (!import_node_fs15.default.existsSync(src)) {
|
|
18064
18991
|
throw new Error(`frpc not found inside tarball at ${src}`);
|
|
18065
18992
|
}
|
|
18066
|
-
|
|
18993
|
+
import_node_fs15.default.copyFileSync(src, destBin);
|
|
18067
18994
|
} finally {
|
|
18068
|
-
|
|
18995
|
+
import_node_fs15.default.rmSync(work, { recursive: true, force: true });
|
|
18069
18996
|
}
|
|
18070
18997
|
}
|
|
18071
18998
|
|
|
@@ -18074,7 +19001,7 @@ var DEFAULT_TUNNEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
|
18074
19001
|
var TunnelManager = class {
|
|
18075
19002
|
constructor(deps) {
|
|
18076
19003
|
this.deps = deps;
|
|
18077
|
-
this.store = deps.store ?? new TunnelStore(
|
|
19004
|
+
this.store = deps.store ?? new TunnelStore(import_node_path16.default.join(deps.dataDir, "tunnel.json"));
|
|
18078
19005
|
this.ttlMs = deps.ttlMs ?? DEFAULT_TUNNEL_TTL_MS;
|
|
18079
19006
|
this.startupTimeoutMs = deps.startupTimeoutMs ?? 15e3;
|
|
18080
19007
|
}
|
|
@@ -18191,8 +19118,8 @@ var TunnelManager = class {
|
|
|
18191
19118
|
dataDir: this.deps.dataDir,
|
|
18192
19119
|
override: this.deps.frpcBinaryOverride ?? void 0
|
|
18193
19120
|
});
|
|
18194
|
-
const tomlPath =
|
|
18195
|
-
const proxyName = `clawd-${t.subdomain}-${localPort}-${
|
|
19121
|
+
const tomlPath = import_node_path16.default.join(this.deps.dataDir, "frpc.toml");
|
|
19122
|
+
const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto4.default.randomBytes(3).toString("hex")}`;
|
|
18196
19123
|
const toml = buildFrpcToml({
|
|
18197
19124
|
serverAddr: t.frpsHost,
|
|
18198
19125
|
serverPort: t.frpsPort,
|
|
@@ -18202,12 +19129,12 @@ var TunnelManager = class {
|
|
|
18202
19129
|
localPort,
|
|
18203
19130
|
logLevel: "info"
|
|
18204
19131
|
});
|
|
18205
|
-
await
|
|
19132
|
+
await import_node_fs16.default.promises.writeFile(tomlPath, toml, { mode: 384 });
|
|
18206
19133
|
const proc = (this.deps.spawnImpl ?? import_node_child_process4.spawn)(frpcBin, ["-c", tomlPath], {
|
|
18207
19134
|
stdio: ["ignore", "pipe", "pipe"]
|
|
18208
19135
|
});
|
|
18209
|
-
const logFilePath =
|
|
18210
|
-
const logStream =
|
|
19136
|
+
const logFilePath = import_node_path16.default.join(this.deps.dataDir, "frpc.log");
|
|
19137
|
+
const logStream = import_node_fs16.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
|
|
18211
19138
|
logStream.on("error", () => {
|
|
18212
19139
|
});
|
|
18213
19140
|
const tee = (chunk) => {
|
|
@@ -18289,22 +19216,22 @@ async function waitForFrpcReady(proc, timeoutMs) {
|
|
|
18289
19216
|
|
|
18290
19217
|
// src/tunnel/device-key.ts
|
|
18291
19218
|
var import_node_os8 = __toESM(require("os"), 1);
|
|
18292
|
-
var
|
|
19219
|
+
var import_node_crypto5 = __toESM(require("crypto"), 1);
|
|
18293
19220
|
var DERIVE_SALT = "clawd-tunnel-device-v1";
|
|
18294
19221
|
function deriveStableDeviceKey(opts = {}) {
|
|
18295
19222
|
const hostname = opts.hostname ?? import_node_os8.default.hostname();
|
|
18296
19223
|
const uid = opts.uid ?? (typeof import_node_os8.default.userInfo === "function" ? import_node_os8.default.userInfo().uid : 0);
|
|
18297
19224
|
const input = `${hostname}::${uid}`;
|
|
18298
|
-
return
|
|
19225
|
+
return import_node_crypto5.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
|
|
18299
19226
|
}
|
|
18300
19227
|
|
|
18301
19228
|
// src/auth-store.ts
|
|
18302
|
-
var
|
|
18303
|
-
var
|
|
18304
|
-
var
|
|
19229
|
+
var import_node_fs17 = __toESM(require("fs"), 1);
|
|
19230
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
19231
|
+
var import_node_crypto6 = __toESM(require("crypto"), 1);
|
|
18305
19232
|
var AUTH_FILE_NAME = "auth.json";
|
|
18306
19233
|
function authFilePath(dataDir) {
|
|
18307
|
-
return
|
|
19234
|
+
return import_node_path17.default.join(dataDir, AUTH_FILE_NAME);
|
|
18308
19235
|
}
|
|
18309
19236
|
function loadOrCreateAuthToken(opts) {
|
|
18310
19237
|
const file = authFilePath(opts.dataDir);
|
|
@@ -18316,11 +19243,11 @@ function loadOrCreateAuthToken(opts) {
|
|
|
18316
19243
|
return token;
|
|
18317
19244
|
}
|
|
18318
19245
|
function defaultGenerate() {
|
|
18319
|
-
return
|
|
19246
|
+
return import_node_crypto6.default.randomBytes(32).toString("base64url");
|
|
18320
19247
|
}
|
|
18321
19248
|
function readAuthFile(file) {
|
|
18322
19249
|
try {
|
|
18323
|
-
const raw =
|
|
19250
|
+
const raw = import_node_fs17.default.readFileSync(file, "utf8");
|
|
18324
19251
|
const parsed = JSON.parse(raw);
|
|
18325
19252
|
if (typeof parsed?.token === "string" && parsed.token.length > 0) {
|
|
18326
19253
|
return {
|
|
@@ -18336,10 +19263,10 @@ function readAuthFile(file) {
|
|
|
18336
19263
|
}
|
|
18337
19264
|
}
|
|
18338
19265
|
function writeAuthFile(file, content) {
|
|
18339
|
-
|
|
18340
|
-
|
|
19266
|
+
import_node_fs17.default.mkdirSync(import_node_path17.default.dirname(file), { recursive: true });
|
|
19267
|
+
import_node_fs17.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
|
|
18341
19268
|
try {
|
|
18342
|
-
|
|
19269
|
+
import_node_fs17.default.chmodSync(file, 384);
|
|
18343
19270
|
} catch {
|
|
18344
19271
|
}
|
|
18345
19272
|
}
|
|
@@ -18351,12 +19278,12 @@ init_protocol();
|
|
|
18351
19278
|
init_protocol();
|
|
18352
19279
|
|
|
18353
19280
|
// src/session/fork.ts
|
|
18354
|
-
var
|
|
19281
|
+
var import_node_fs18 = __toESM(require("fs"), 1);
|
|
18355
19282
|
var import_node_os9 = __toESM(require("os"), 1);
|
|
18356
|
-
var
|
|
19283
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
18357
19284
|
init_claude_history();
|
|
18358
19285
|
function readJsonlEntries(file) {
|
|
18359
|
-
const raw =
|
|
19286
|
+
const raw = import_node_fs18.default.readFileSync(file, "utf8");
|
|
18360
19287
|
const out = [];
|
|
18361
19288
|
for (const line of raw.split("\n")) {
|
|
18362
19289
|
const t = line.trim();
|
|
@@ -18369,10 +19296,10 @@ function readJsonlEntries(file) {
|
|
|
18369
19296
|
return out;
|
|
18370
19297
|
}
|
|
18371
19298
|
function forkSession(input) {
|
|
18372
|
-
const baseDir = input.baseDir ??
|
|
18373
|
-
const projectDir =
|
|
18374
|
-
const sourceFile =
|
|
18375
|
-
if (!
|
|
19299
|
+
const baseDir = input.baseDir ?? import_node_path18.default.join(import_node_os9.default.homedir(), ".claude");
|
|
19300
|
+
const projectDir = import_node_path18.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
|
|
19301
|
+
const sourceFile = import_node_path18.default.join(projectDir, `${input.toolSessionId}.jsonl`);
|
|
19302
|
+
if (!import_node_fs18.default.existsSync(sourceFile)) {
|
|
18376
19303
|
throw new Error(`fork: source transcript not found: ${sourceFile}`);
|
|
18377
19304
|
}
|
|
18378
19305
|
const entries = readJsonlEntries(sourceFile);
|
|
@@ -18402,9 +19329,9 @@ function forkSession(input) {
|
|
|
18402
19329
|
}
|
|
18403
19330
|
forkedLines.push(JSON.stringify(forked));
|
|
18404
19331
|
}
|
|
18405
|
-
const forkedFilePath =
|
|
18406
|
-
|
|
18407
|
-
|
|
19332
|
+
const forkedFilePath = import_node_path18.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
|
|
19333
|
+
import_node_fs18.default.mkdirSync(projectDir, { recursive: true });
|
|
19334
|
+
import_node_fs18.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
|
|
18408
19335
|
return { forkedToolSessionId, forkedFilePath };
|
|
18409
19336
|
}
|
|
18410
19337
|
|
|
@@ -18674,9 +19601,9 @@ init_protocol();
|
|
|
18674
19601
|
|
|
18675
19602
|
// src/workspace/git.ts
|
|
18676
19603
|
var import_node_child_process5 = require("child_process");
|
|
18677
|
-
var
|
|
19604
|
+
var import_node_fs19 = __toESM(require("fs"), 1);
|
|
18678
19605
|
var import_node_os10 = __toESM(require("os"), 1);
|
|
18679
|
-
var
|
|
19606
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
18680
19607
|
var import_node_util = require("util");
|
|
18681
19608
|
var pexec = (0, import_node_util.promisify)(import_node_child_process5.execFile);
|
|
18682
19609
|
function formatChildProcessError(err) {
|
|
@@ -18691,9 +19618,9 @@ function formatChildProcessError(err) {
|
|
|
18691
19618
|
return e.message ?? "unknown error";
|
|
18692
19619
|
}
|
|
18693
19620
|
function normalizePath(p) {
|
|
18694
|
-
const resolved =
|
|
19621
|
+
const resolved = import_node_path19.default.resolve(p);
|
|
18695
19622
|
try {
|
|
18696
|
-
return
|
|
19623
|
+
return import_node_fs19.default.realpathSync(resolved);
|
|
18697
19624
|
} catch {
|
|
18698
19625
|
return resolved;
|
|
18699
19626
|
}
|
|
@@ -18794,13 +19721,13 @@ function flattenToDirName(branch) {
|
|
|
18794
19721
|
}
|
|
18795
19722
|
function encodeClaudeProjectDir(absPath) {
|
|
18796
19723
|
if (!absPath || typeof absPath !== "string") return "";
|
|
18797
|
-
let canonical =
|
|
19724
|
+
let canonical = import_node_path19.default.resolve(absPath);
|
|
18798
19725
|
try {
|
|
18799
|
-
canonical =
|
|
19726
|
+
canonical = import_node_fs19.default.realpathSync(canonical);
|
|
18800
19727
|
} catch {
|
|
18801
19728
|
try {
|
|
18802
|
-
const parent =
|
|
18803
|
-
canonical =
|
|
19729
|
+
const parent = import_node_fs19.default.realpathSync(import_node_path19.default.dirname(canonical));
|
|
19730
|
+
canonical = import_node_path19.default.join(parent, import_node_path19.default.basename(canonical));
|
|
18804
19731
|
} catch {
|
|
18805
19732
|
}
|
|
18806
19733
|
}
|
|
@@ -18824,11 +19751,11 @@ async function createWorktree(input) {
|
|
|
18824
19751
|
if (!isGitRoot) {
|
|
18825
19752
|
throw new Error(`\u76EE\u5F55 ${cwd} \u4E0D\u662F git repo \u6839`);
|
|
18826
19753
|
}
|
|
18827
|
-
const parent =
|
|
18828
|
-
if (parent === "/" || parent ===
|
|
19754
|
+
const parent = import_node_path19.default.dirname(import_node_path19.default.resolve(cwd));
|
|
19755
|
+
if (parent === "/" || parent === import_node_path19.default.resolve(cwd)) {
|
|
18829
19756
|
throw new Error("repo \u5728\u78C1\u76D8\u6839\u76EE\u5F55\uFF0C\u65E0\u6CD5\u5728\u540C\u7EA7\u521B\u5EFA worktree");
|
|
18830
19757
|
}
|
|
18831
|
-
const worktreeRoot =
|
|
19758
|
+
const worktreeRoot = import_node_path19.default.join(parent, dirName);
|
|
18832
19759
|
try {
|
|
18833
19760
|
await pexec("git", ["-C", cwd, "rev-parse", "--verify", `refs/heads/${baseBranch}`], {
|
|
18834
19761
|
timeout: 3e3
|
|
@@ -18845,7 +19772,7 @@ async function createWorktree(input) {
|
|
|
18845
19772
|
const msg = err.message;
|
|
18846
19773
|
if (msg.startsWith("\u5206\u652F ")) throw err;
|
|
18847
19774
|
}
|
|
18848
|
-
if (
|
|
19775
|
+
if (import_node_fs19.default.existsSync(worktreeRoot)) {
|
|
18849
19776
|
throw new Error(`\u76EE\u5F55 ${worktreeRoot} \u5DF2\u5B58\u5728\uFF0C\u8BF7\u6362\u4E00\u4E2A label \u6216\u6E05\u7406\u540E\u91CD\u8BD5`);
|
|
18850
19777
|
}
|
|
18851
19778
|
try {
|
|
@@ -18873,8 +19800,8 @@ async function removeWorktree(input) {
|
|
|
18873
19800
|
);
|
|
18874
19801
|
const gitCommonDir = stdout.trim();
|
|
18875
19802
|
if (!gitCommonDir) throw new Error("empty git-common-dir");
|
|
18876
|
-
const absGitCommon =
|
|
18877
|
-
repoRoot =
|
|
19803
|
+
const absGitCommon = import_node_path19.default.isAbsolute(gitCommonDir) ? gitCommonDir : import_node_path19.default.resolve(worktreeRoot, gitCommonDir);
|
|
19804
|
+
repoRoot = import_node_path19.default.dirname(absGitCommon);
|
|
18878
19805
|
} catch {
|
|
18879
19806
|
repoRoot = null;
|
|
18880
19807
|
}
|
|
@@ -18886,7 +19813,7 @@ async function removeWorktree(input) {
|
|
|
18886
19813
|
} catch (err) {
|
|
18887
19814
|
const stderr = err.stderr ?? "";
|
|
18888
19815
|
const lower = stderr.toLowerCase();
|
|
18889
|
-
const vanished = lower.includes("not a working tree") || lower.includes("is not a working tree") || !
|
|
19816
|
+
const vanished = lower.includes("not a working tree") || lower.includes("is not a working tree") || !import_node_fs19.default.existsSync(worktreeRoot);
|
|
18890
19817
|
if (!vanished) {
|
|
18891
19818
|
throw new Error(`\u6E05\u7406 worktree \u5931\u8D25\uFF1A${formatChildProcessError(err)}`);
|
|
18892
19819
|
}
|
|
@@ -18905,10 +19832,10 @@ async function removeWorktree(input) {
|
|
|
18905
19832
|
try {
|
|
18906
19833
|
const encoded = encodeClaudeProjectDir(worktreeRoot);
|
|
18907
19834
|
if (encoded) {
|
|
18908
|
-
const projectsRoot =
|
|
18909
|
-
const target =
|
|
18910
|
-
if (target.startsWith(projectsRoot +
|
|
18911
|
-
|
|
19835
|
+
const projectsRoot = import_node_path19.default.join(import_node_os10.default.homedir(), ".claude", "projects");
|
|
19836
|
+
const target = import_node_path19.default.resolve(projectsRoot, encoded);
|
|
19837
|
+
if (target.startsWith(projectsRoot + import_node_path19.default.sep) && target !== projectsRoot) {
|
|
19838
|
+
import_node_fs19.default.rmSync(target, { recursive: true, force: true });
|
|
18912
19839
|
}
|
|
18913
19840
|
}
|
|
18914
19841
|
} catch {
|
|
@@ -18998,13 +19925,15 @@ function buildReadyFrame(deps) {
|
|
|
18998
19925
|
tools.push({ id, available: false });
|
|
18999
19926
|
}
|
|
19000
19927
|
}
|
|
19928
|
+
const tunnelUrl = deps.getTunnelUrl ? deps.getTunnelUrl() : null;
|
|
19001
19929
|
return {
|
|
19002
19930
|
version,
|
|
19003
19931
|
protocolVersion: PROTOCOL_VERSION,
|
|
19004
19932
|
hostname: import_node_os11.default.hostname(),
|
|
19005
19933
|
os: process.platform,
|
|
19006
19934
|
tools,
|
|
19007
|
-
runningSessions: info.runningSessions
|
|
19935
|
+
runningSessions: info.runningSessions,
|
|
19936
|
+
tunnelUrl
|
|
19008
19937
|
};
|
|
19009
19938
|
}
|
|
19010
19939
|
function buildMetaHandlers(deps) {
|
|
@@ -19020,6 +19949,79 @@ function buildMetaHandlers(deps) {
|
|
|
19020
19949
|
};
|
|
19021
19950
|
}
|
|
19022
19951
|
|
|
19952
|
+
// src/handlers/persona.ts
|
|
19953
|
+
init_protocol();
|
|
19954
|
+
function buildPersonaHandlers(deps) {
|
|
19955
|
+
const { personaManager, personaRegistry, sessionManager } = deps;
|
|
19956
|
+
const create = async (frame) => {
|
|
19957
|
+
const args = PersonaCreateArgs.parse(frame);
|
|
19958
|
+
const persona = personaManager.create(args);
|
|
19959
|
+
return { response: { type: "persona:info", ...persona } };
|
|
19960
|
+
};
|
|
19961
|
+
const list = async () => {
|
|
19962
|
+
return {
|
|
19963
|
+
response: { type: "persona:list", personas: personaRegistry.list() }
|
|
19964
|
+
};
|
|
19965
|
+
};
|
|
19966
|
+
const get = async (frame) => {
|
|
19967
|
+
const args = PersonaIdArgs.parse(frame);
|
|
19968
|
+
const persona = personaRegistry.get(args.personaId) ?? null;
|
|
19969
|
+
if (!persona) {
|
|
19970
|
+
return { response: { type: "persona:info", persona: null } };
|
|
19971
|
+
}
|
|
19972
|
+
return { response: { type: "persona:info", ...persona } };
|
|
19973
|
+
};
|
|
19974
|
+
const update = async (frame) => {
|
|
19975
|
+
const args = PersonaUpdateArgs.parse(frame);
|
|
19976
|
+
const persona = personaManager.update(args.personaId, args.patch);
|
|
19977
|
+
return { response: { type: "persona:info", ...persona } };
|
|
19978
|
+
};
|
|
19979
|
+
const del = async (frame) => {
|
|
19980
|
+
const args = PersonaIdArgs.parse(frame);
|
|
19981
|
+
personaManager.delete(args.personaId);
|
|
19982
|
+
return {
|
|
19983
|
+
response: { type: "persona:deleted", personaId: args.personaId }
|
|
19984
|
+
};
|
|
19985
|
+
};
|
|
19986
|
+
const issueToken = async (frame) => {
|
|
19987
|
+
const args = PersonaIssueTokenArgs.parse(frame);
|
|
19988
|
+
const { token, persona } = personaManager.issueToken(args.personaId, args.label);
|
|
19989
|
+
return {
|
|
19990
|
+
response: { type: "persona:tokenIssued", token, persona }
|
|
19991
|
+
};
|
|
19992
|
+
};
|
|
19993
|
+
const revokeToken = async (frame) => {
|
|
19994
|
+
const args = PersonaRevokeTokenArgs.parse(frame);
|
|
19995
|
+
const persona = personaManager.revokeToken(args.personaId, args.token);
|
|
19996
|
+
return { response: { type: "persona:info", ...persona } };
|
|
19997
|
+
};
|
|
19998
|
+
const listSubSessions = async (frame) => {
|
|
19999
|
+
const args = PersonaIdArgs.parse(frame);
|
|
20000
|
+
const sessions = sessionManager.listForAgent(args.personaId);
|
|
20001
|
+
return {
|
|
20002
|
+
response: { type: "persona:subSessions", sessions }
|
|
20003
|
+
};
|
|
20004
|
+
};
|
|
20005
|
+
const appendOwnerMessage = async (frame) => {
|
|
20006
|
+
const args = PersonaAppendOwnerMessageArgs.parse(frame);
|
|
20007
|
+
personaManager.appendOwnerMessage(args.personaId, args.subSessionId, args.text);
|
|
20008
|
+
return {
|
|
20009
|
+
response: { type: "persona:appendOwnerMessage", ok: true }
|
|
20010
|
+
};
|
|
20011
|
+
};
|
|
20012
|
+
return {
|
|
20013
|
+
"persona:create": create,
|
|
20014
|
+
"persona:list": list,
|
|
20015
|
+
"persona:get": get,
|
|
20016
|
+
"persona:update": update,
|
|
20017
|
+
"persona:delete": del,
|
|
20018
|
+
"persona:issueToken": issueToken,
|
|
20019
|
+
"persona:revokeToken": revokeToken,
|
|
20020
|
+
"persona:listSubSessions": listSubSessions,
|
|
20021
|
+
"persona:appendOwnerMessage": appendOwnerMessage
|
|
20022
|
+
};
|
|
20023
|
+
}
|
|
20024
|
+
|
|
19023
20025
|
// src/handlers/index.ts
|
|
19024
20026
|
function buildMethodHandlers(deps) {
|
|
19025
20027
|
return {
|
|
@@ -19029,7 +20031,12 @@ function buildMethodHandlers(deps) {
|
|
|
19029
20031
|
...buildWorkspaceHandlers(deps),
|
|
19030
20032
|
...buildGitHandlers(),
|
|
19031
20033
|
...buildCapabilitiesHandlers(deps),
|
|
19032
|
-
...buildMetaHandlers(deps)
|
|
20034
|
+
...buildMetaHandlers(deps),
|
|
20035
|
+
...buildPersonaHandlers({
|
|
20036
|
+
personaManager: deps.personaManager,
|
|
20037
|
+
personaRegistry: deps.personaRegistry,
|
|
20038
|
+
sessionManager: deps.manager
|
|
20039
|
+
})
|
|
19033
20040
|
};
|
|
19034
20041
|
}
|
|
19035
20042
|
|
|
@@ -19037,7 +20044,7 @@ function buildMethodHandlers(deps) {
|
|
|
19037
20044
|
async function startDaemon(config) {
|
|
19038
20045
|
const logger = createLogger({
|
|
19039
20046
|
level: config.logLevel,
|
|
19040
|
-
file:
|
|
20047
|
+
file: import_node_path20.default.join(config.dataDir, "clawd.log")
|
|
19041
20048
|
});
|
|
19042
20049
|
logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
|
|
19043
20050
|
const stateMgr = new StateFileManager({ dataDir: config.dataDir });
|
|
@@ -19117,14 +20124,43 @@ async function startDaemon(config) {
|
|
|
19117
20124
|
manager.recordRealUserUuid({ sessionId, realUuid, text });
|
|
19118
20125
|
}
|
|
19119
20126
|
});
|
|
19120
|
-
const
|
|
20127
|
+
const personaStore = new PersonaStore({ dataDir: config.dataDir });
|
|
20128
|
+
const personaRegistry = new PersonaRegistry(personaStore);
|
|
20129
|
+
const personaManager = new PersonaManager({
|
|
20130
|
+
store: personaStore,
|
|
20131
|
+
registry: personaRegistry,
|
|
20132
|
+
sessionManager: manager,
|
|
20133
|
+
dataDir: config.dataDir,
|
|
20134
|
+
now: () => /* @__PURE__ */ new Date(),
|
|
20135
|
+
logger
|
|
20136
|
+
});
|
|
20137
|
+
let currentTunnelUrl = null;
|
|
20138
|
+
const handlers = buildMethodHandlers({
|
|
20139
|
+
manager,
|
|
20140
|
+
workspace,
|
|
20141
|
+
skills,
|
|
20142
|
+
history,
|
|
20143
|
+
observer,
|
|
20144
|
+
getAdapter,
|
|
20145
|
+
store,
|
|
20146
|
+
personaManager,
|
|
20147
|
+
personaRegistry,
|
|
20148
|
+
getTunnelUrl: () => currentTunnelUrl
|
|
20149
|
+
});
|
|
20150
|
+
const personaBoundHandler = new PersonaBoundHandler({
|
|
20151
|
+
registry: personaRegistry,
|
|
20152
|
+
personaManager,
|
|
20153
|
+
sessionManager: manager,
|
|
20154
|
+
logger
|
|
20155
|
+
});
|
|
19121
20156
|
wsServer = new LocalWsServer({
|
|
19122
20157
|
host: config.host,
|
|
19123
20158
|
port: config.port,
|
|
19124
20159
|
logger,
|
|
19125
|
-
readyFrameBuilder: () => buildReadyFrame({ manager, getAdapter }),
|
|
20160
|
+
readyFrameBuilder: () => buildReadyFrame({ manager, getAdapter, getTunnelUrl: () => currentTunnelUrl }),
|
|
19126
20161
|
protocolVersion: PROTOCOL_VERSION,
|
|
19127
20162
|
authGate: authGate ?? void 0,
|
|
20163
|
+
personaBoundHandler,
|
|
19128
20164
|
// 订阅成功后给该 client 重放 in-flight pendingQuestions(plan: clawd-question-server-truth)。
|
|
19129
20165
|
// daemon 是 pendingQuestions 的唯一 source of truth;新 client 接入 / 刷新页面时
|
|
19130
20166
|
// 把当前所有未决 question 以 session:question 帧定向回放,让 UI 不再误显示 Ended。
|
|
@@ -19217,12 +20253,13 @@ async function startDaemon(config) {
|
|
|
19217
20253
|
const r = await tunnelMgr.start({ localPort: config.port });
|
|
19218
20254
|
stateSnapshot = { ...stateSnapshot, tunnelUrl: r.url };
|
|
19219
20255
|
stateMgr.write(stateSnapshot);
|
|
20256
|
+
currentTunnelUrl = r.url;
|
|
19220
20257
|
const connectUrl = resolvedAuthToken ? `${r.url}#token=${resolvedAuthToken}` : r.url;
|
|
19221
20258
|
const lines = [
|
|
19222
20259
|
`Tunnel: ${r.url}`,
|
|
19223
20260
|
...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
|
|
19224
|
-
`Frpc config: ${
|
|
19225
|
-
`Frpc log: ${
|
|
20261
|
+
`Frpc config: ${import_node_path20.default.join(config.dataDir, "frpc.toml")}`,
|
|
20262
|
+
`Frpc log: ${import_node_path20.default.join(config.dataDir, "frpc.log")}`
|
|
19226
20263
|
];
|
|
19227
20264
|
const width = Math.max(...lines.map((l) => l.length));
|
|
19228
20265
|
const bar = "\u2550".repeat(width + 4);
|
|
@@ -19235,8 +20272,8 @@ ${bar}
|
|
|
19235
20272
|
|
|
19236
20273
|
`);
|
|
19237
20274
|
try {
|
|
19238
|
-
const connectPath =
|
|
19239
|
-
|
|
20275
|
+
const connectPath = import_node_path20.default.join(config.dataDir, "connect.txt");
|
|
20276
|
+
import_node_fs20.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
|
|
19240
20277
|
} catch {
|
|
19241
20278
|
}
|
|
19242
20279
|
} catch (err) {
|