@clawos-dev/clawd 0.2.27 → 0.2.28-beta.38.7eb315d

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