@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.
Files changed (2) hide show
  1. package/dist/cli.cjs +1309 -272
  2. 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 path20 = req.path;
300
- _req.url = typeof path20 === "string" ? path20 : req.url ? req.url.path || req.url : void 0;
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(path20) {
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 < path20.length; i++) {
472
- const char = path20[i];
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 path20 of paths) {
604
- const parts = parsePath(path20);
603
+ for (const path23 of paths) {
604
+ const parts = parsePath(path23);
605
605
  if (parts.includes("*")) {
606
- redactWildcardPath(obj, parts, censor, path20, remove);
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, path20) => {
692
- const fullPath = [...pathArray.slice(0, pathLength), ...path20];
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 path20 of pathsToClone) {
728
- const parts = parsePath(path20);
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(path20) {
781
- if (typeof path20 !== "string") {
780
+ function validatePath(path23) {
781
+ if (typeof path23 !== "string") {
782
782
  throw new Error("Paths must be (non-empty) strings");
783
783
  }
784
- if (path20 === "") {
784
+ if (path23 === "") {
785
785
  throw new Error("Invalid redaction path ()");
786
786
  }
787
- if (path20.includes("..")) {
788
- throw new Error(`Invalid redaction path (${path20})`);
787
+ if (path23.includes("..")) {
788
+ throw new Error(`Invalid redaction path (${path23})`);
789
789
  }
790
- if (path20.includes(",")) {
791
- throw new Error(`Invalid redaction path (${path20})`);
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 < path20.length; i++) {
797
- const char = path20[i];
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 (${path20})`);
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 (${path20})`);
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 path20 of paths) {
824
- validatePath(path20);
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, path20) => {
993
- return censor(value, [k, ...path20]);
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 fs20 = require("fs");
1211
+ var fs22 = require("fs");
1212
1212
  var EventEmitter = require("events");
1213
1213
  var inherits = require("util").inherits;
1214
- var path20 = require("path");
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) fs20.mkdirSync(path20.dirname(file), { recursive: true });
1269
- const fd = fs20.openSync(file, flags, mode);
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
- fs20.mkdir(path20.dirname(file), { recursive: true }, (err) => {
1276
+ fs22.mkdir(path23.dirname(file), { recursive: true }, (err) => {
1277
1277
  if (err) return fileOpened(err);
1278
- fs20.open(file, flags, mode, fileOpened);
1278
+ fs22.open(file, flags, mode, fileOpened);
1279
1279
  });
1280
1280
  } else {
1281
- fs20.open(file, flags, mode, fileOpened);
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 = () => fs20.writeSync(this.fd, this._writingBuf);
1323
- fsWrite = () => fs20.write(this.fd, this._writingBuf, this.release);
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 fs20.writeSync(this.fd, this._writingBuf);
1332
+ return fs22.writeSync(this.fd, this._writingBuf);
1333
1333
  }
1334
- return fs20.writeSync(this.fd, this._writingBuf, "utf8");
1334
+ return fs22.writeSync(this.fd, this._writingBuf, "utf8");
1335
1335
  };
1336
1336
  fsWrite = () => {
1337
1337
  if (Buffer.isBuffer(this._writingBuf)) {
1338
- return fs20.write(this.fd, this._writingBuf, this.release);
1338
+ return fs22.write(this.fd, this._writingBuf, this.release);
1339
1339
  }
1340
- return fs20.write(this.fd, this._writingBuf, "utf8", this.release);
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
- fs20.fsyncSync(this.fd);
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
- fs20.fsync(this.fd, (err) => {
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
- fs20.close(fd, (err) => {
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) ? fs20.writeSync(this.fd, buf) : fs20.writeSync(this.fd, buf, "utf8");
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
- fs20.fsyncSync(this.fd);
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 = fs20.writeSync(this.fd, buf);
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) ? fs20.writeSync(this.fd, this._writingBuf) : fs20.writeSync(this.fd, this._writingBuf, "utf8");
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
- fs20.write(this.fd, this._writingBuf, release);
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 = fs20.writeSync(this.fd, this._writingBuf);
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
- fs20.write(this.fd, this._writingBuf, release);
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
- fs20.fsync(sonic.fd, closeWrapped);
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
- fs20.close(sonic.fd, done);
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: path20, errorMaps, issueData } = params;
4877
- const fullPath = [...path20, ...issueData.path || []];
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, path20, key) {
5198
+ constructor(parent, value, path23, key) {
5189
5199
  this._cachedPath = [];
5190
5200
  this.parent = parent;
5191
5201
  this.data = value;
5192
- this._path = path20;
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(path20, added, removed, oldPosInc, options) {
9688
- var last = path20.lastComponent;
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: path20.oldPos + oldPosInc,
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: path20.oldPos + oldPosInc,
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 import_node_fs6.default.statSync(p).mtimeMs;
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 = import_node_fs6.default.readFileSync(file, "utf8");
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 path20 = typeof rec.path === "string" ? rec.path : null;
10276
+ const path23 = typeof rec.path === "string" ? rec.path : null;
10133
10277
  const content = typeof rec.content === "string" ? rec.content : null;
10134
- if (!path20 || content == null) return null;
10135
- const entry = { path: path20, content };
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 import_node_fs6.default.readFileSync(
10172
- import_node_path5.default.join(fileHistoryRoot, toolSessionId, backupFileName),
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 import_node_fs6.default.readFileSync(filePath, "utf8");
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 import_node_fs6, import_node_os2, import_node_path5, 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;
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
- import_node_fs6 = __toESM(require("fs"), 1);
10335
+ import_node_fs8 = __toESM(require("fs"), 1);
10192
10336
  import_node_os2 = __toESM(require("os"), 1);
10193
- import_node_path5 = __toESM(require("path"), 1);
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 ?? import_node_path5.default.join(import_node_os2.default.homedir(), ".claude");
10218
- this.projectsRoot = import_node_path5.default.join(base, "projects");
10219
- this.fileHistoryRoot = import_node_path5.default.join(base, "file-history");
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 = import_node_fs6.default.readdirSync(this.projectsRoot, { withFileTypes: true });
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 = import_node_path5.default.join(this.projectsRoot, ent.name);
10233
- const files = import_node_fs6.default.readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
10234
- const updatedAtMs = files.reduce((m, f) => Math.max(m, safeStatMtime(import_node_path5.default.join(dir, f))), 0);
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 = import_node_path5.default.join(this.projectsRoot, cwdToHashDir(args.projectPath));
10390
+ const dir = import_node_path8.default.join(this.projectsRoot, cwdToHashDir(args.projectPath));
10247
10391
  let files;
10248
10392
  try {
10249
- files = import_node_fs6.default.readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
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 = import_node_path5.default.join(dir, f);
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 = import_node_path5.default.join(
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 = import_node_path5.default.join(
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 = import_node_fs6.default.readdirSync(dir, { withFileTypes: true });
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 = import_node_path5.default.join(dir, e.name);
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 = import_node_path5.default.join(
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 = import_node_path5.default.join(
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 = import_node_fs6.default.statSync(file).isFile();
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 = import_node_path5.default.join(
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 = import_node_path5.default.isAbsolute(rawPath) ? rawPath : import_node_path5.default.join(args.cwd, rawPath);
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 = import_node_path5.default.isAbsolute(rawPath) ? rawPath : import_node_path5.default.join(args.cwd, rawPath);
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 = import_node_path5.default.join(
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
- import_node_path6.default.join(home, "Applications", "Claude.app", "Contents", "Resources", "app.asar.unpacked", "node_modules", "@anthropic-ai", "claude-code", "cli.js"),
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 && import_node_fs7.default.existsSync(out)) return 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 && import_node_fs7.default.existsSync(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 (import_node_fs7.default.existsSync(candidate)) {
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
- if (ctx.permissionMode) args.push("--permission-mode", ctx.permissionMode);
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 path20 = typeof rec.path === "string" ? rec.path : null;
11073
+ const path23 = typeof rec.path === "string" ? rec.path : null;
10923
11074
  const content = typeof rec.content === "string" ? rec.content : null;
10924
- if (!path20 || content == null) return null;
10925
- const out = { path: path20, content };
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, import_node_fs7, import_node_os3, import_node_path6, ATTACHMENT_SILENT_SUBTYPES2, unknownTypeHandler, ATTACHMENT_RE, IMAGE_EXT_MIME, CLAUDE_MODELS, CLAUDE_PERMISSION_MODES, CLAUDE_CAPABILITIES, ClaudeAdapter;
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
- import_node_fs7 = __toESM(require("fs"), 1);
11186
+ import_node_fs9 = __toESM(require("fs"), 1);
11036
11187
  import_node_os3 = __toESM(require("os"), 1);
11037
- import_node_path6 = __toESM(require("path"), 1);
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 = import_node_path18.default.dirname(opts.recordPath);
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
- import_node_fs19.default.mkdirSync(dir, { recursive: true });
14807
- stream = import_node_fs19.default.createWriteStream(opts.recordPath, { flags: "a" });
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 import_node_fs19, import_node_path18;
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
- import_node_fs19 = __toESM(require("fs"), 1);
14837
- import_node_path18 = __toESM(require("path"), 1);
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, import_node_fs20.mkdtempSync)(import_node_path19.default.join(import_node_os12.default.tmpdir(), "clawd-runcase-"));
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, import_node_fs20.rmSync)(cwd, { recursive: true, force: true });
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 import_node_fs20, import_node_os12, import_node_path19;
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
- import_node_fs20 = require("fs");
15203
+ import_node_fs22 = require("fs");
15052
15204
  import_node_os12 = __toESM(require("os"), 1);
15053
- import_node_path19 = __toESM(require("path"), 1);
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 import_node_path17 = __toESM(require("path"), 1);
15279
- var import_node_fs18 = __toESM(require("fs"), 1);
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(file) {
15534
- return {
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
- return {
15676
- state: next,
15677
- effects: [emitSessionEvent(next.file.sessionId, withSeq)]
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.file) });
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.file) });
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
- newRunner(file) {
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 runner = new SessionRunner(makeInitialState(file), {
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: this.deps.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
- r = this.newRunner(file);
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 import_node_fs8 = __toESM(require("fs"), 1);
17729
+ var import_node_fs10 = __toESM(require("fs"), 1);
16968
17730
  var import_node_os4 = __toESM(require("os"), 1);
16969
- var import_node_path7 = __toESM(require("path"), 1);
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 = import_node_path7.default.resolve(cwd);
16974
- const joined = import_node_path7.default.resolve(absCwd, subpath ?? ".");
16975
- const rel = import_node_path7.default.relative(absCwd, joined);
16976
- if (rel.startsWith("..") || import_node_path7.default.isAbsolute(rel)) {
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 = import_node_fs8.default.statSync(cwd);
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 = import_node_fs8.default.readdirSync(full, { withFileTypes: true });
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 = import_node_fs8.default.statSync(import_node_path7.default.join(full, d.name));
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 = import_node_fs8.default.statSync(full);
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 = import_node_fs8.default.readFileSync(full);
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 import_node_fs9 = __toESM(require("fs"), 1);
17814
+ var import_node_fs11 = __toESM(require("fs"), 1);
17053
17815
  var import_node_os5 = __toESM(require("os"), 1);
17054
- var import_node_path8 = __toESM(require("path"), 1);
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 import_node_fs9.default.statSync(p).isDirectory();
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 = import_node_fs9.default.readdirSync(dir, { withFileTypes: true });
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 = import_node_path8.default.join(dir, ent.name);
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 = import_node_fs9.default.readFileSync(import_node_path8.default.join(entryPath, "SKILL.md"), "utf8");
17869
+ content = import_node_fs11.default.readFileSync(import_node_path11.default.join(entryPath, "SKILL.md"), "utf8");
17108
17870
  } catch {
17109
17871
  try {
17110
- content = import_node_fs9.default.readFileSync(import_node_path8.default.join(entryPath, "skill.md"), "utf8");
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 = import_node_fs9.default.readdirSync(dir, { withFileTypes: true });
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 = import_node_path8.default.join(dir, ent.name);
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 = import_node_fs9.default.readdirSync(entryPath, { withFileTypes: true });
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 = import_node_path8.default.join(entryPath, se.name);
17906
+ const sePath = import_node_path11.default.join(entryPath, se.name);
17145
17907
  let content;
17146
17908
  try {
17147
- content = import_node_fs9.default.readFileSync(sePath, "utf8");
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 = import_node_fs9.default.readFileSync(entryPath, "utf8");
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 = import_node_path8.default.join(home, ".claude", "plugins", "installed_plugins.json");
17942
+ const file = import_node_path11.default.join(home, ".claude", "plugins", "installed_plugins.json");
17181
17943
  let raw;
17182
17944
  try {
17183
- raw = import_node_fs9.default.readFileSync(file, "utf8");
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(import_node_path8.default.join(this.home, ".claude", "skills"), "global", seen, out);
17222
- scanCommandDir(import_node_path8.default.join(this.home, ".claude", "commands"), "global", seen, out);
17223
- scanSkillDir(import_node_path8.default.join(args.cwd, ".claude", "skills"), "project", seen, out);
17224
- scanCommandDir(import_node_path8.default.join(args.cwd, ".claude", "commands"), "project", seen, out);
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(import_node_path8.default.join(root, "skills"), "plugin", seen, out, name);
17228
- scanCommandDir(import_node_path8.default.join(root, "commands"), "plugin", seen, out, name);
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 import_node_fs10 = __toESM(require("fs"), 1);
17998
+ var import_node_fs12 = __toESM(require("fs"), 1);
17237
17999
  var import_node_os6 = __toESM(require("os"), 1);
17238
- var import_node_path9 = __toESM(require("path"), 1);
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 import_node_path9.default.join(this.home, ".claude", "projects", cwdToHashDir(cwd), `${toolSessionId}.jsonl`);
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 = import_node_fs10.default.statSync(filePath).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
- import_node_fs10.default.mkdirSync(import_node_path9.default.dirname(filePath), { recursive: true });
18032
+ import_node_fs12.default.mkdirSync(import_node_path12.default.dirname(filePath), { recursive: true });
17271
18033
  } catch {
17272
18034
  }
17273
- w.watcher = import_node_fs10.default.watch(import_node_path9.default.dirname(filePath), { persistent: false }, (_event, changedName) => {
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 = import_node_fs10.default.readFileSync(w.filePath, "utf8");
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 = import_node_fs10.default.statSync(w.filePath).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 = import_node_fs10.default.openSync(w.filePath, "r");
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
- import_node_fs10.default.readSync(fd, buf, 0, len, w.lastSize);
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
- import_node_fs10.default.closeSync(fd);
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.onConnection(socket, req?.socket?.remoteAddress));
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 import_node_fs11 = __toESM(require("fs"), 1);
17760
- var import_node_path10 = __toESM(require("path"), 1);
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 import_node_path10.default.join(dataDir, "state.json");
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 = import_node_fs11.default.readFileSync(this.file, "utf8");
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
- import_node_fs11.default.mkdirSync(import_node_path10.default.dirname(this.file), { recursive: true });
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
- import_node_fs11.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
17801
- import_node_fs11.default.renameSync(tmp, this.file);
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
- import_node_fs11.default.chmodSync(this.file, 384);
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
- import_node_fs11.default.unlinkSync(this.file);
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 import_node_fs14 = __toESM(require("fs"), 1);
17819
- var import_node_path13 = __toESM(require("path"), 1);
17820
- var import_node_crypto3 = __toESM(require("crypto"), 1);
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 import_node_fs12 = __toESM(require("fs"), 1);
17825
- var import_node_path11 = __toESM(require("path"), 1);
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 import_node_fs12.default.promises.readFile(this.filePath, "utf8");
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 = import_node_path11.default.dirname(this.filePath);
17845
- await import_node_fs12.default.promises.mkdir(dir, { recursive: true });
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 import_node_fs12.default.promises.writeFile(tmp, data, { mode: 384 });
18775
+ await import_node_fs14.default.promises.writeFile(tmp, data, { mode: 384 });
17849
18776
  if (process.platform !== "win32") {
17850
18777
  try {
17851
- await import_node_fs12.default.promises.chmod(tmp, 384);
18778
+ await import_node_fs14.default.promises.chmod(tmp, 384);
17852
18779
  } catch {
17853
18780
  }
17854
18781
  }
17855
- await import_node_fs12.default.promises.rename(tmp, this.filePath);
18782
+ await import_node_fs14.default.promises.rename(tmp, this.filePath);
17856
18783
  }
17857
18784
  async clear() {
17858
18785
  try {
17859
- await import_node_fs12.default.promises.unlink(this.filePath);
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 import_node_fs13 = __toESM(require("fs"), 1);
18881
+ var import_node_fs15 = __toESM(require("fs"), 1);
17955
18882
  var import_node_os7 = __toESM(require("os"), 1);
17956
- var import_node_path12 = __toESM(require("path"), 1);
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 (!import_node_fs13.default.existsSync(opts.override)) {
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 = import_node_path12.default.join(opts.dataDir, "bin");
17996
- import_node_fs13.default.mkdirSync(binDir, { recursive: true });
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 = import_node_path12.default.join(binDir, "frpc");
17999
- if (import_node_fs13.default.existsSync(stableBin)) return stableBin;
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 = import_node_path12.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
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
- import_node_fs13.default.chmodSync(partialBin, 493);
18011
- import_node_fs13.default.renameSync(partialBin, stableBin);
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 = import_node_fs13.default.readdirSync(binDir);
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 = import_node_path12.default.join(binDir, name);
18954
+ const full = import_node_path15.default.join(binDir, name);
18028
18955
  try {
18029
- import_node_fs13.default.rmSync(full, { recursive: true, force: true });
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
- import_node_fs13.default.unlinkSync(p);
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 = import_node_fs13.default.createWriteStream(dest);
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 = import_node_path12.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
18054
- import_node_fs13.default.mkdirSync(work, { recursive: true });
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 = import_node_path12.default.join(work, dirName, "frpc");
18063
- if (!import_node_fs13.default.existsSync(src)) {
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
- import_node_fs13.default.copyFileSync(src, destBin);
18993
+ import_node_fs15.default.copyFileSync(src, destBin);
18067
18994
  } finally {
18068
- import_node_fs13.default.rmSync(work, { recursive: true, force: true });
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(import_node_path13.default.join(deps.dataDir, "tunnel.json"));
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 = import_node_path13.default.join(this.deps.dataDir, "frpc.toml");
18195
- const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto3.default.randomBytes(3).toString("hex")}`;
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 import_node_fs14.default.promises.writeFile(tomlPath, toml, { mode: 384 });
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 = import_node_path13.default.join(this.deps.dataDir, "frpc.log");
18210
- const logStream = import_node_fs14.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
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 import_node_crypto4 = __toESM(require("crypto"), 1);
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 import_node_crypto4.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
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 import_node_fs15 = __toESM(require("fs"), 1);
18303
- var import_node_path14 = __toESM(require("path"), 1);
18304
- var import_node_crypto5 = __toESM(require("crypto"), 1);
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 import_node_path14.default.join(dataDir, AUTH_FILE_NAME);
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 import_node_crypto5.default.randomBytes(32).toString("base64url");
19246
+ return import_node_crypto6.default.randomBytes(32).toString("base64url");
18320
19247
  }
18321
19248
  function readAuthFile(file) {
18322
19249
  try {
18323
- const raw = import_node_fs15.default.readFileSync(file, "utf8");
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
- import_node_fs15.default.mkdirSync(import_node_path14.default.dirname(file), { recursive: true });
18340
- import_node_fs15.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
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
- import_node_fs15.default.chmodSync(file, 384);
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 import_node_fs16 = __toESM(require("fs"), 1);
19281
+ var import_node_fs18 = __toESM(require("fs"), 1);
18355
19282
  var import_node_os9 = __toESM(require("os"), 1);
18356
- var import_node_path15 = __toESM(require("path"), 1);
19283
+ var import_node_path18 = __toESM(require("path"), 1);
18357
19284
  init_claude_history();
18358
19285
  function readJsonlEntries(file) {
18359
- const raw = import_node_fs16.default.readFileSync(file, "utf8");
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 ?? import_node_path15.default.join(import_node_os9.default.homedir(), ".claude");
18373
- const projectDir = import_node_path15.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
18374
- const sourceFile = import_node_path15.default.join(projectDir, `${input.toolSessionId}.jsonl`);
18375
- if (!import_node_fs16.default.existsSync(sourceFile)) {
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 = import_node_path15.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
18406
- import_node_fs16.default.mkdirSync(projectDir, { recursive: true });
18407
- import_node_fs16.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
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 import_node_fs17 = __toESM(require("fs"), 1);
19604
+ var import_node_fs19 = __toESM(require("fs"), 1);
18678
19605
  var import_node_os10 = __toESM(require("os"), 1);
18679
- var import_node_path16 = __toESM(require("path"), 1);
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 = import_node_path16.default.resolve(p);
19621
+ const resolved = import_node_path19.default.resolve(p);
18695
19622
  try {
18696
- return import_node_fs17.default.realpathSync(resolved);
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 = import_node_path16.default.resolve(absPath);
19724
+ let canonical = import_node_path19.default.resolve(absPath);
18798
19725
  try {
18799
- canonical = import_node_fs17.default.realpathSync(canonical);
19726
+ canonical = import_node_fs19.default.realpathSync(canonical);
18800
19727
  } catch {
18801
19728
  try {
18802
- const parent = import_node_fs17.default.realpathSync(import_node_path16.default.dirname(canonical));
18803
- canonical = import_node_path16.default.join(parent, import_node_path16.default.basename(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 = import_node_path16.default.dirname(import_node_path16.default.resolve(cwd));
18828
- if (parent === "/" || parent === import_node_path16.default.resolve(cwd)) {
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 = import_node_path16.default.join(parent, dirName);
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 (import_node_fs17.default.existsSync(worktreeRoot)) {
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 = import_node_path16.default.isAbsolute(gitCommonDir) ? gitCommonDir : import_node_path16.default.resolve(worktreeRoot, gitCommonDir);
18877
- repoRoot = import_node_path16.default.dirname(absGitCommon);
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") || !import_node_fs17.default.existsSync(worktreeRoot);
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 = import_node_path16.default.join(import_node_os10.default.homedir(), ".claude", "projects");
18909
- const target = import_node_path16.default.resolve(projectsRoot, encoded);
18910
- if (target.startsWith(projectsRoot + import_node_path16.default.sep) && target !== projectsRoot) {
18911
- import_node_fs17.default.rmSync(target, { recursive: true, force: true });
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: import_node_path17.default.join(config.dataDir, "clawd.log")
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 handlers = buildMethodHandlers({ manager, workspace, skills, history, observer, getAdapter, store });
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: ${import_node_path17.default.join(config.dataDir, "frpc.toml")}`,
19225
- `Frpc log: ${import_node_path17.default.join(config.dataDir, "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 = import_node_path17.default.join(config.dataDir, "connect.txt");
19239
- import_node_fs18.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
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) {