@clawos-dev/clawd 0.2.53 → 0.2.54

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 +580 -514
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -4297,7 +4297,7 @@ var init_zod = __esm({
4297
4297
  });
4298
4298
 
4299
4299
  // ../protocol/src/persona-schemas.ts
4300
- var PersonaTokenEntrySchema, PersonaFileSchema, PersonaInfoResponseSchema, PersonaCreateArgsSchema, PersonaIdArgsSchema, PersonaUpdateArgsSchema, PersonaIssueTokenArgsSchema, PersonaRevokeTokenArgsSchema, PersonaAppendOwnerMessageArgsSchema;
4300
+ var PersonaTokenEntrySchema, PersonaFileSchema, PersonaSkillSummarySchema, PersonaSandboxSettingsSchema, PersonaInfoResponseSchema, PersonaCreateArgsSchema, PersonaIdArgsSchema, PersonaUpdateArgsSchema, PersonaIssueTokenArgsSchema, PersonaRevokeTokenArgsSchema, PersonaAppendOwnerMessageArgsSchema;
4301
4301
  var init_persona_schemas = __esm({
4302
4302
  "../protocol/src/persona-schemas.ts"() {
4303
4303
  "use strict";
@@ -4319,8 +4319,33 @@ var init_persona_schemas = __esm({
4319
4319
  createdAt: external_exports.number(),
4320
4320
  updatedAt: external_exports.number()
4321
4321
  }).strict();
4322
+ PersonaSkillSummarySchema = external_exports.object({
4323
+ // 目录名作为 skill name(对齐 CC:frontmatter `name:` 仅作 displayName 用)
4324
+ name: external_exports.string().min(1),
4325
+ // SKILL.md frontmatter `description:`;缺省时省略字段
4326
+ description: external_exports.string().optional()
4327
+ });
4328
+ PersonaSandboxSettingsSchema = external_exports.object({
4329
+ permissions: external_exports.object({
4330
+ defaultMode: external_exports.string().optional()
4331
+ }).optional(),
4332
+ sandbox: external_exports.object({
4333
+ enabled: external_exports.boolean().optional(),
4334
+ autoAllowBashIfSandboxed: external_exports.boolean().optional(),
4335
+ allowUnsandboxedCommands: external_exports.boolean().optional(),
4336
+ filesystem: external_exports.object({
4337
+ denyRead: external_exports.array(external_exports.string()).optional(),
4338
+ allowRead: external_exports.array(external_exports.string()).optional(),
4339
+ denyWrite: external_exports.array(external_exports.string()).optional(),
4340
+ allowWrite: external_exports.array(external_exports.string()).optional()
4341
+ }).optional()
4342
+ }).optional()
4343
+ });
4322
4344
  PersonaInfoResponseSchema = PersonaFileSchema.extend({
4323
- personality: external_exports.string().optional()
4345
+ personality: external_exports.string().optional(),
4346
+ // 仅 'persona:get' 携带;null = 文件不存在 / parse 失败;undefined = 不在该响应类型上
4347
+ skills: external_exports.array(PersonaSkillSummarySchema).optional(),
4348
+ sandboxSettings: PersonaSandboxSettingsSchema.nullable().optional()
4324
4349
  });
4325
4350
  PersonaCreateArgsSchema = external_exports.object({
4326
4351
  label: external_exports.string().min(1),
@@ -10091,7 +10116,7 @@ function hashDirToCwd(hash) {
10091
10116
  }
10092
10117
  function safeStatMtime(p) {
10093
10118
  try {
10094
- return import_node_fs6.default.statSync(p).mtimeMs;
10119
+ return import_node_fs7.default.statSync(p).mtimeMs;
10095
10120
  } catch {
10096
10121
  return 0;
10097
10122
  }
@@ -10099,7 +10124,7 @@ function safeStatMtime(p) {
10099
10124
  function readJsonlLines(file) {
10100
10125
  let raw;
10101
10126
  try {
10102
- raw = import_node_fs6.default.readFileSync(file, "utf8");
10127
+ raw = import_node_fs7.default.readFileSync(file, "utf8");
10103
10128
  } catch (err) {
10104
10129
  if (err.code === "ENOENT") return [];
10105
10130
  throw err;
@@ -10315,8 +10340,8 @@ function attachmentDeferredToolsText(a) {
10315
10340
  function readBackupContent(fileHistoryRoot, toolSessionId, backupFileName) {
10316
10341
  if (backupFileName === null) return null;
10317
10342
  try {
10318
- return import_node_fs6.default.readFileSync(
10319
- import_node_path7.default.join(fileHistoryRoot, toolSessionId, backupFileName),
10343
+ return import_node_fs7.default.readFileSync(
10344
+ import_node_path8.default.join(fileHistoryRoot, toolSessionId, backupFileName),
10320
10345
  "utf8"
10321
10346
  );
10322
10347
  } catch {
@@ -10325,19 +10350,19 @@ function readBackupContent(fileHistoryRoot, toolSessionId, backupFileName) {
10325
10350
  }
10326
10351
  function readCurrentContent(filePath) {
10327
10352
  try {
10328
- return import_node_fs6.default.readFileSync(filePath, "utf8");
10353
+ return import_node_fs7.default.readFileSync(filePath, "utf8");
10329
10354
  } catch (err) {
10330
10355
  if (err.code === "ENOENT") return null;
10331
10356
  return null;
10332
10357
  }
10333
10358
  }
10334
- var import_node_fs6, import_node_os2, import_node_path7, 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;
10359
+ var import_node_fs7, import_node_os3, 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;
10335
10360
  var init_claude_history = __esm({
10336
10361
  "src/tools/claude-history.ts"() {
10337
10362
  "use strict";
10338
- import_node_fs6 = __toESM(require("fs"), 1);
10339
- import_node_os2 = __toESM(require("os"), 1);
10340
- import_node_path7 = __toESM(require("path"), 1);
10363
+ import_node_fs7 = __toESM(require("fs"), 1);
10364
+ import_node_os3 = __toESM(require("os"), 1);
10365
+ import_node_path8 = __toESM(require("path"), 1);
10341
10366
  init_lib();
10342
10367
  init_tool_result_extra();
10343
10368
  TASK_NOTIFICATION_RE = /<task-notification\b[\s\S]*?<\/task-notification>/i;
@@ -10361,14 +10386,14 @@ var init_claude_history = __esm({
10361
10386
  // 每次 user 提交前 trackEdit 拷一份,作为 rewind 回退目标
10362
10387
  fileHistoryRoot;
10363
10388
  constructor(opts = {}) {
10364
- const base = opts.baseDir ?? import_node_path7.default.join(import_node_os2.default.homedir(), ".claude");
10365
- this.projectsRoot = import_node_path7.default.join(base, "projects");
10366
- this.fileHistoryRoot = import_node_path7.default.join(base, "file-history");
10389
+ const base = opts.baseDir ?? import_node_path8.default.join(import_node_os3.default.homedir(), ".claude");
10390
+ this.projectsRoot = import_node_path8.default.join(base, "projects");
10391
+ this.fileHistoryRoot = import_node_path8.default.join(base, "file-history");
10367
10392
  }
10368
10393
  async listProjects() {
10369
10394
  let entries;
10370
10395
  try {
10371
- entries = import_node_fs6.default.readdirSync(this.projectsRoot, { withFileTypes: true });
10396
+ entries = import_node_fs7.default.readdirSync(this.projectsRoot, { withFileTypes: true });
10372
10397
  } catch (err) {
10373
10398
  if (err.code === "ENOENT") return [];
10374
10399
  throw err;
@@ -10376,9 +10401,9 @@ var init_claude_history = __esm({
10376
10401
  const out = [];
10377
10402
  for (const ent of entries) {
10378
10403
  if (!ent.isDirectory()) continue;
10379
- const dir = import_node_path7.default.join(this.projectsRoot, ent.name);
10380
- const files = import_node_fs6.default.readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
10381
- const updatedAtMs = files.reduce((m, f) => Math.max(m, safeStatMtime(import_node_path7.default.join(dir, f))), 0);
10404
+ const dir = import_node_path8.default.join(this.projectsRoot, ent.name);
10405
+ const files = import_node_fs7.default.readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
10406
+ const updatedAtMs = files.reduce((m, f) => Math.max(m, safeStatMtime(import_node_path8.default.join(dir, f))), 0);
10382
10407
  out.push({
10383
10408
  projectPath: hashDirToCwd(ent.name),
10384
10409
  hashDir: ent.name,
@@ -10390,17 +10415,17 @@ var init_claude_history = __esm({
10390
10415
  return out;
10391
10416
  }
10392
10417
  async listSessions(args) {
10393
- const dir = import_node_path7.default.join(this.projectsRoot, cwdToHashDir(args.projectPath));
10418
+ const dir = import_node_path8.default.join(this.projectsRoot, cwdToHashDir(args.projectPath));
10394
10419
  let files;
10395
10420
  try {
10396
- files = import_node_fs6.default.readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
10421
+ files = import_node_fs7.default.readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
10397
10422
  } catch (err) {
10398
10423
  if (err.code === "ENOENT") return [];
10399
10424
  throw err;
10400
10425
  }
10401
10426
  const out = [];
10402
10427
  for (const f of files) {
10403
- const full = import_node_path7.default.join(dir, f);
10428
+ const full = import_node_path8.default.join(dir, f);
10404
10429
  const toolSessionId = f.slice(0, -".jsonl".length);
10405
10430
  const lines = readJsonlLines(full);
10406
10431
  let summary = "";
@@ -10455,7 +10480,7 @@ var init_claude_history = __esm({
10455
10480
  return out;
10456
10481
  }
10457
10482
  async read(args) {
10458
- const file = import_node_path7.default.join(
10483
+ const file = import_node_path8.default.join(
10459
10484
  this.projectsRoot,
10460
10485
  cwdToHashDir(args.cwd),
10461
10486
  `${args.toolSessionId}.jsonl`
@@ -10488,7 +10513,7 @@ var init_claude_history = __esm({
10488
10513
  // 独立目录路径:<projectsRoot>/<cwdHash>/<toolSessionId>/subagents/*.jsonl
10489
10514
  // 返回 null 表示目录不存在(调用方回退旧实现);返回空数组表示目录存在但无 jsonl
10490
10515
  listSubagentsFromDirectory(cwd, toolSessionId) {
10491
- const dir = import_node_path7.default.join(
10516
+ const dir = import_node_path8.default.join(
10492
10517
  this.projectsRoot,
10493
10518
  cwdToHashDir(cwd),
10494
10519
  toolSessionId,
@@ -10496,7 +10521,7 @@ var init_claude_history = __esm({
10496
10521
  );
10497
10522
  let entries;
10498
10523
  try {
10499
- entries = import_node_fs6.default.readdirSync(dir, { withFileTypes: true });
10524
+ entries = import_node_fs7.default.readdirSync(dir, { withFileTypes: true });
10500
10525
  } catch (err) {
10501
10526
  if (err.code === "ENOENT") return null;
10502
10527
  return null;
@@ -10506,7 +10531,7 @@ var init_claude_history = __esm({
10506
10531
  if (!e.isFile()) continue;
10507
10532
  if (!e.name.startsWith("agent-") || !e.name.endsWith(".jsonl")) continue;
10508
10533
  const subagentId = e.name.slice("agent-".length, -".jsonl".length);
10509
- const filePath = import_node_path7.default.join(dir, e.name);
10534
+ const filePath = import_node_path8.default.join(dir, e.name);
10510
10535
  const lines = readJsonlLines(filePath);
10511
10536
  let firstText = "";
10512
10537
  let messageCount = 0;
@@ -10523,7 +10548,7 @@ var init_claude_history = __esm({
10523
10548
  return out;
10524
10549
  }
10525
10550
  listSubagentsFromMainJsonl(cwd, toolSessionId) {
10526
- const file = import_node_path7.default.join(
10551
+ const file = import_node_path8.default.join(
10527
10552
  this.projectsRoot,
10528
10553
  cwdToHashDir(cwd),
10529
10554
  `${toolSessionId}.jsonl`
@@ -10558,7 +10583,7 @@ var init_claude_history = __esm({
10558
10583
  }
10559
10584
  // 独立文件路径:agent-<subagentId>.jsonl;文件不存在返回 null 让调用方回退旧实现
10560
10585
  readSubagentFromFile(cwd, toolSessionId, subagentId) {
10561
- const file = import_node_path7.default.join(
10586
+ const file = import_node_path8.default.join(
10562
10587
  this.projectsRoot,
10563
10588
  cwdToHashDir(cwd),
10564
10589
  toolSessionId,
@@ -10567,7 +10592,7 @@ var init_claude_history = __esm({
10567
10592
  );
10568
10593
  let exists = false;
10569
10594
  try {
10570
- exists = import_node_fs6.default.statSync(file).isFile();
10595
+ exists = import_node_fs7.default.statSync(file).isFile();
10571
10596
  } catch {
10572
10597
  return null;
10573
10598
  }
@@ -10586,7 +10611,7 @@ var init_claude_history = __esm({
10586
10611
  * "那一刻每个 tracked 文件对应的 backup 文件名"
10587
10612
  */
10588
10613
  readFileHistorySnapshots(args) {
10589
- const file = import_node_path7.default.join(
10614
+ const file = import_node_path8.default.join(
10590
10615
  this.projectsRoot,
10591
10616
  cwdToHashDir(args.cwd),
10592
10617
  `${args.toolSessionId}.jsonl`
@@ -10631,7 +10656,7 @@ var init_claude_history = __esm({
10631
10656
  for (const [anchorId, target] of snapshots) {
10632
10657
  let hasAny = false;
10633
10658
  for (const [rawPath, backup] of Object.entries(target)) {
10634
- const absPath = import_node_path7.default.isAbsolute(rawPath) ? rawPath : import_node_path7.default.join(args.cwd, rawPath);
10659
+ const absPath = import_node_path8.default.isAbsolute(rawPath) ? rawPath : import_node_path8.default.join(args.cwd, rawPath);
10635
10660
  const backupContent = readBackupContent(
10636
10661
  this.fileHistoryRoot,
10637
10662
  args.toolSessionId,
@@ -10671,7 +10696,7 @@ var init_claude_history = __esm({
10671
10696
  let totalInsertions = 0;
10672
10697
  let totalDeletions = 0;
10673
10698
  for (const [rawPath, backup] of Object.entries(target)) {
10674
- const absPath = import_node_path7.default.isAbsolute(rawPath) ? rawPath : import_node_path7.default.join(args.cwd, rawPath);
10699
+ const absPath = import_node_path8.default.isAbsolute(rawPath) ? rawPath : import_node_path8.default.join(args.cwd, rawPath);
10675
10700
  const backupContent = readBackupContent(
10676
10701
  this.fileHistoryRoot,
10677
10702
  args.toolSessionId,
@@ -10718,7 +10743,7 @@ var init_claude_history = __esm({
10718
10743
  };
10719
10744
  }
10720
10745
  readSubagentFromMainJsonl(cwd, toolSessionId, subagentId) {
10721
- const file = import_node_path7.default.join(
10746
+ const file = import_node_path8.default.join(
10722
10747
  this.projectsRoot,
10723
10748
  cwdToHashDir(cwd),
10724
10749
  `${toolSessionId}.jsonl`
@@ -10742,27 +10767,27 @@ var init_claude_history = __esm({
10742
10767
  // src/tools/claude.ts
10743
10768
  function macOSDesktopCandidates(home) {
10744
10769
  return [
10745
- import_node_path8.default.join(home, "Applications", "Claude.app", "Contents", "Resources", "app.asar.unpacked", "node_modules", "@anthropic-ai", "claude-code", "cli.js"),
10770
+ import_node_path9.default.join(home, "Applications", "Claude.app", "Contents", "Resources", "app.asar.unpacked", "node_modules", "@anthropic-ai", "claude-code", "cli.js"),
10746
10771
  "/Applications/Claude.app/Contents/Resources/app.asar.unpacked/node_modules/@anthropic-ai/claude-code/cli.js"
10747
10772
  ];
10748
10773
  }
10749
10774
  function probeViaWhich() {
10750
10775
  try {
10751
10776
  const out = (0, import_node_child_process2.execFileSync)("which", ["claude"], { encoding: "utf8" }).trim();
10752
- if (out && import_node_fs7.default.existsSync(out)) return out;
10777
+ if (out && import_node_fs8.default.existsSync(out)) return out;
10753
10778
  } catch {
10754
10779
  }
10755
10780
  return null;
10756
10781
  }
10757
- async function probeClaude(env = process.env, home = import_node_os3.default.homedir()) {
10758
- if (env.CLAUDE_BIN && import_node_fs7.default.existsSync(env.CLAUDE_BIN)) {
10782
+ async function probeClaude(env = process.env, home = import_node_os4.default.homedir()) {
10783
+ if (env.CLAUDE_BIN && import_node_fs8.default.existsSync(env.CLAUDE_BIN)) {
10759
10784
  return { available: true, path: env.CLAUDE_BIN };
10760
10785
  }
10761
10786
  const w = probeViaWhich();
10762
10787
  if (w) return { available: true, path: w };
10763
10788
  if (process.platform === "darwin") {
10764
10789
  for (const candidate of macOSDesktopCandidates(home)) {
10765
- if (import_node_fs7.default.existsSync(candidate)) {
10790
+ if (import_node_fs8.default.existsSync(candidate)) {
10766
10791
  return { available: true, path: candidate };
10767
10792
  }
10768
10793
  }
@@ -11190,15 +11215,15 @@ function encodeClaudeStdin(text) {
11190
11215
  };
11191
11216
  return JSON.stringify(frame) + "\n";
11192
11217
  }
11193
- var import_node_child_process, import_node_child_process2, import_node_fs7, import_node_os3, import_node_path8, ATTACHMENT_SILENT_SUBTYPES2, unknownTypeHandler, ATTACHMENT_RE, IMAGE_EXT_MIME, CLAUDE_MODELS, CLAUDE_PERMISSION_MODES, CLAUDE_CAPABILITIES, ClaudeAdapter;
11218
+ var import_node_child_process, import_node_child_process2, import_node_fs8, import_node_os4, import_node_path9, ATTACHMENT_SILENT_SUBTYPES2, unknownTypeHandler, ATTACHMENT_RE, IMAGE_EXT_MIME, CLAUDE_MODELS, CLAUDE_PERMISSION_MODES, CLAUDE_CAPABILITIES, ClaudeAdapter;
11194
11219
  var init_claude = __esm({
11195
11220
  "src/tools/claude.ts"() {
11196
11221
  "use strict";
11197
11222
  import_node_child_process = require("child_process");
11198
11223
  import_node_child_process2 = require("child_process");
11199
- import_node_fs7 = __toESM(require("fs"), 1);
11200
- import_node_os3 = __toESM(require("os"), 1);
11201
- import_node_path8 = __toESM(require("path"), 1);
11224
+ import_node_fs8 = __toESM(require("fs"), 1);
11225
+ import_node_os4 = __toESM(require("os"), 1);
11226
+ import_node_path9 = __toESM(require("path"), 1);
11202
11227
  init_protocol();
11203
11228
  init_claude_history();
11204
11229
  init_tool_result_extra();
@@ -23452,6 +23477,23 @@ var PersonaStore = class {
23452
23477
  if (!fs6.existsSync(p)) return null;
23453
23478
  return fs6.readFileSync(p, "utf8");
23454
23479
  }
23480
+ /**
23481
+ * 读 sandbox-settings.json 当前内容;文件不存在 / JSON 解析失败均返回 null。
23482
+ * owner 手改文件可能写出非法形状,daemon 不强校验,UI 拿到 null → 显示空状态。
23483
+ */
23484
+ readSandboxSettings(personaId) {
23485
+ const p = this.sandboxSettingsPath(personaId);
23486
+ if (!fs6.existsSync(p)) return null;
23487
+ try {
23488
+ return JSON.parse(fs6.readFileSync(p, "utf8"));
23489
+ } catch {
23490
+ return null;
23491
+ }
23492
+ }
23493
+ /** Persona 私有 skills 目录路径:<personaDir>/.claude/skills */
23494
+ skillsDir(personaId) {
23495
+ return path7.join(this.personaDir(personaId), ".claude", "skills");
23496
+ }
23455
23497
  list() {
23456
23498
  if (!fs6.existsSync(this.root)) return [];
23457
23499
  return fs6.readdirSync(this.root).filter((name) => {
@@ -23514,182 +23556,465 @@ var PersonaRegistry = class {
23514
23556
 
23515
23557
  // src/persona/manager.ts
23516
23558
  var import_node_crypto3 = __toESM(require("crypto"), 1);
23517
- var PersonaManager = class {
23518
- constructor(deps) {
23519
- this.deps = deps;
23520
- }
23521
- deps;
23522
- create(args) {
23523
- const personaId = this.generatePersonaId(args.label);
23524
- const now = Date.now();
23525
- const persona = {
23526
- personaId,
23527
- label: args.label,
23528
- model: args.model,
23529
- public: args.public ?? false,
23530
- iconKey: args.iconKey,
23531
- tokenMap: {},
23532
- createdAt: now,
23533
- updatedAt: now
23534
- };
23535
- this.deps.store.write(persona, args.personality);
23536
- this.deps.registry.set(persona);
23537
- return persona;
23538
- }
23539
- update(personaId, patch, personality) {
23540
- const existing = this.deps.registry.get(personaId);
23541
- if (!existing) throw new Error(`persona not found: ${personaId}`);
23542
- const { iconKey: iconKeyPatch, ...restPatch } = patch;
23543
- const updated = {
23544
- ...existing,
23545
- ...restPatch,
23546
- updatedAt: Date.now()
23547
- };
23548
- if (iconKeyPatch === null) {
23549
- delete updated.iconKey;
23550
- } else if (iconKeyPatch !== void 0) {
23551
- updated.iconKey = iconKeyPatch;
23552
- }
23553
- if (personality !== void 0) {
23554
- this.deps.store.writePersonality(personaId, personality);
23555
- }
23556
- this.deps.store.writeMeta(updated);
23557
- this.deps.registry.set(updated);
23558
- return updated;
23559
- }
23560
- /** 读取 CLAUDE.md 内容;persona 目录或文件不存在时返回 null。透传给 handler 拼响应。 */
23561
- readPersonality(personaId) {
23562
- return this.deps.store.readPersonality(personaId);
23563
- }
23564
- /**
23565
- * 删除 persona。
23566
- * PersonaStore.remove(personaId) 已级联删除整个 <personaRoot>/<personaId>/ 目录,
23567
- * 其中包含 .clawd/sub-sessions/——无需额外清理。
23568
- * 旧的 <dataDir>/sessions/<personaId>/ 路径已废弃,不再维护。
23569
- */
23570
- delete(personaId) {
23571
- this.deps.store.remove(personaId);
23572
- this.deps.registry.remove(personaId);
23573
- }
23574
- /** 生成 32-char base64url token(24 bytes 随机),label 必填 */
23575
- issueToken(personaId, label) {
23576
- const existing = this.deps.registry.get(personaId);
23577
- if (!existing) throw new Error(`persona not found: ${personaId}`);
23578
- const token = import_node_crypto3.default.randomBytes(24).toString("base64url");
23579
- const entry = {
23580
- label,
23581
- issuedAt: Date.now(),
23582
- revoked: false
23583
- };
23584
- const updated = {
23585
- ...existing,
23586
- tokenMap: { ...existing.tokenMap, [token]: entry },
23587
- updatedAt: Date.now()
23588
- };
23589
- this.deps.store.writeMeta(updated);
23590
- this.deps.registry.set(updated);
23591
- return { token, persona: updated };
23592
- }
23593
- revokeToken(personaId, token) {
23594
- const existing = this.deps.registry.get(personaId);
23595
- if (!existing) throw new Error(`persona not found: ${personaId}`);
23596
- const tokenEntry = existing.tokenMap[token];
23597
- if (!tokenEntry) throw new Error(`token not found in persona ${personaId}`);
23598
- const updated = {
23599
- ...existing,
23600
- tokenMap: {
23601
- ...existing.tokenMap,
23602
- [token]: { ...tokenEntry, revoked: true }
23603
- },
23604
- updatedAt: Date.now()
23605
- };
23606
- this.deps.store.writeMeta(updated);
23607
- this.deps.registry.set(updated);
23608
- return updated;
23609
- }
23610
- /**
23611
- * 拿到(或按需创建)该 token 对应的 sub-session。subSessionId 由 personaId + token 哈希派生
23612
- * 保证 idempotent:同一 token 永远复用同一个 sub-session。
23613
- * 创建路径:开启 idleKillEnabled,cwd 取 persona 目录,不再硬编码 permission mode / tool allowlist
23614
- * (sandbox 约束移到 persona dir 下的 .claude/settings.json,由 OS-level Seatbelt 执行)
23615
- */
23616
- getOrCreateSubSession(personaId, token) {
23617
- const persona = this.deps.registry.get(personaId);
23618
- if (!persona) throw new Error(`persona not found: ${personaId}`);
23619
- const subSessionId = this.deriveSubSessionId(personaId, token);
23620
- const scope = { kind: "persona", personaId, mode: "listener" };
23621
- const existing = this.deps.sessionManager.readForScope(subSessionId, scope);
23622
- if (existing) {
23623
- return { sessionFile: existing, isNew: false };
23624
- }
23625
- const tokenEntry = persona.tokenMap[token];
23626
- const subLabel = tokenEntry?.label ?? "unknown";
23627
- const sessionFile = this.deps.sessionManager.createForScope({
23628
- sessionId: subSessionId,
23629
- scope,
23630
- cwd: this.deps.store.personaDirPath(personaId),
23631
- tool: "claude",
23632
- label: subLabel,
23633
- model: persona.model
23634
- });
23635
- return { sessionFile, isNew: true };
23636
- }
23637
- /**
23638
- * 老板插话:把"老板的话"作为 meta-text + metaSource='owner' 推到 sub-session 的事件流。
23639
- * 委托 SessionManager.injectOwnerMessage 路由到 reducer 'inject-owner-text' input
23640
- */
23641
- appendOwnerMessage(personaId, subSessionId, text) {
23642
- this.deps.sessionManager.injectOwnerMessage({
23643
- sessionId: subSessionId,
23644
- scope: { kind: "persona", personaId, mode: "listener" },
23645
- text
23646
- });
23647
- }
23648
- // ---------------- 内部 ----------------
23649
- /**
23650
- * label 转 4-16 char slug。优先用 `persona-<slug>`;若 slug 已被占用,追加 4 char
23651
- * base64url 随机后缀直到不撞为止(最多 5 次,理论冲突 ≈ 2^-24,留个 panic 上限)。
23652
- */
23653
- generatePersonaId(label) {
23654
- const slug = label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 16) || "persona";
23655
- const base = `persona-${slug}`;
23656
- if (!this.deps.store.has(base)) return base;
23657
- for (let i = 0; i < 5; i++) {
23658
- const rand = import_node_crypto3.default.randomBytes(3).toString("base64url").slice(0, 4);
23659
- const candidate = `${base}-${rand}`;
23660
- if (!this.deps.store.has(candidate)) return candidate;
23559
+
23560
+ // src/skills/scanner.ts
23561
+ var import_node_fs6 = __toESM(require("fs"), 1);
23562
+ var import_node_os2 = __toESM(require("os"), 1);
23563
+ var import_node_path7 = __toESM(require("path"), 1);
23564
+
23565
+ // src/skills/frontmatter.ts
23566
+ var STRIP_QUOTES = /^["']|["']$/g;
23567
+ function strip(s) {
23568
+ return s.trim().replace(STRIP_QUOTES, "");
23569
+ }
23570
+ function parseFrontmatter(content, keys) {
23571
+ const out = {};
23572
+ if (!content.startsWith("---")) return out;
23573
+ const end = content.indexOf("---", 3);
23574
+ if (end === -1) return out;
23575
+ const lines = content.slice(3, end).split("\n");
23576
+ for (let i = 0; i < lines.length; i++) {
23577
+ const trimmed = lines[i].trim();
23578
+ for (const key of keys) {
23579
+ const prefix = `${key}:`;
23580
+ if (!trimmed.startsWith(prefix)) continue;
23581
+ const rest = trimmed.slice(prefix.length).trim();
23582
+ if (rest === "|" || rest === ">" || rest === "|-" || rest === ">-") {
23583
+ const parts = [];
23584
+ for (let j = i + 1; j < lines.length; j++) {
23585
+ const next = lines[j];
23586
+ if (next.length === 0) {
23587
+ parts.push("");
23588
+ continue;
23589
+ }
23590
+ if (!/^\s/.test(next)) break;
23591
+ parts.push(next.replace(/^\s+/, ""));
23592
+ }
23593
+ const value = parts.filter(Boolean).join(" ").trim();
23594
+ if (value) out[key] = value;
23595
+ } else {
23596
+ const value = strip(rest);
23597
+ if (value) out[key] = value;
23598
+ }
23599
+ break;
23661
23600
  }
23662
- throw new Error(`failed to generate unique personaId for label=${label}`);
23663
- }
23664
- /** subSessionId = persona-<short>-<tokenHash12>;同 token 始终复用同一 sub-session */
23665
- deriveSubSessionId(personaId, token) {
23666
- const tokenHash = import_node_crypto3.default.createHash("sha256").update(token).digest("hex").slice(0, 12);
23667
- return `${personaId}-${tokenHash}`;
23668
23601
  }
23669
- };
23602
+ return out;
23603
+ }
23670
23604
 
23671
- // src/persona/seed.ts
23672
- var fs7 = __toESM(require("fs"), 1);
23673
- var path8 = __toESM(require("path"), 1);
23674
- var import_node_url = require("url");
23675
- var import_meta = {};
23676
- var DEFAULT_PERSONAS = [
23677
- {
23678
- personaId: "persona-researcher",
23679
- label: "\u8C03\u7814\u5458",
23680
- model: "opus",
23681
- iconKey: "research",
23682
- public: false
23683
- },
23605
+ // src/skills/builtin-cc-resources.ts
23606
+ var BUILTIN_SKILLS = [
23607
+ { name: "update-config", source: "builtin", description: "Update Claude Code configuration." },
23608
+ { name: "keybindings-help", source: "builtin", description: "Show available keybindings." },
23609
+ { name: "verify", source: "builtin", description: "Verify the implementation." },
23610
+ { name: "debug", source: "builtin", description: "Debug current issue." },
23611
+ { name: "lorem-ipsum", source: "builtin", description: "Generate lorem ipsum placeholder text." },
23612
+ { name: "skillify", source: "builtin", description: "Convert a process into a reusable skill." },
23613
+ { name: "remember", source: "builtin", description: "Remember context for later use." },
23614
+ { name: "simplify", source: "builtin", description: "Simplify the code or message." },
23615
+ { name: "batch", source: "builtin", description: "Run a batch of similar operations." },
23616
+ { name: "stuck", source: "builtin", description: "Help unstick a stalled task." },
23617
+ { name: "loop", source: "builtin", description: "Loop a task until a condition is met." },
23618
+ { name: "cron-list", source: "builtin", description: "List scheduled cron tasks." },
23619
+ { name: "cron-delete", source: "builtin", description: "Delete a scheduled cron task." },
23620
+ { name: "dream", source: "builtin", description: "Brainstorm freely without constraints." },
23621
+ { name: "hunter", source: "builtin", description: "Hunt down a bug or root cause." },
23622
+ { name: "schedule", source: "builtin", description: "Schedule a task to run later." },
23623
+ { name: "claude-api", source: "builtin", description: "Use the Claude API directly." },
23624
+ { name: "claude-in-chrome", source: "builtin", description: "Drive Claude inside Chrome via the Chrome extension." }
23625
+ ];
23626
+ var BUILTIN_AGENTS = [
23684
23627
  {
23685
- personaId: "persona-knowledge-base",
23686
- label: "\u77E5\u8BC6\u5E93\u7BA1\u7406\u5458",
23687
- model: "opus",
23688
- iconKey: "reading",
23689
- public: false
23628
+ name: "general-purpose",
23629
+ source: "builtin",
23630
+ description: "General-purpose agent for researching complex questions and executing multi-step tasks.",
23631
+ whenToUse: "When you are searching for a keyword or file and are not confident you will find the right match in the first few tries."
23690
23632
  },
23691
23633
  {
23692
- personaId: "persona-clawd-helper",
23634
+ name: "statusline-setup",
23635
+ source: "builtin",
23636
+ description: "Configure the user's Claude Code status line setting.",
23637
+ whenToUse: "When the user is setting up or changing their status line configuration."
23638
+ },
23639
+ {
23640
+ name: "Explore",
23641
+ source: "builtin",
23642
+ description: "Explore the codebase to gather context.",
23643
+ whenToUse: "Before making changes that require understanding the surrounding code."
23644
+ },
23645
+ {
23646
+ name: "Plan",
23647
+ source: "builtin",
23648
+ description: "Produce an implementation plan before writing code.",
23649
+ whenToUse: "When the task is non-trivial and would benefit from an explicit plan."
23650
+ },
23651
+ {
23652
+ name: "claude-code-guide",
23653
+ source: "builtin",
23654
+ description: "Guide the user through Claude Code features.",
23655
+ whenToUse: "When the user needs help understanding what Claude Code can do."
23656
+ },
23657
+ {
23658
+ name: "verification",
23659
+ source: "builtin",
23660
+ description: "Verify that a change works as intended.",
23661
+ whenToUse: "After implementing a change, before declaring it done."
23662
+ }
23663
+ ];
23664
+
23665
+ // src/skills/scanner.ts
23666
+ function parseDescription(content) {
23667
+ const fields = parseFrontmatter(content, ["description"]);
23668
+ return { description: fields.description ?? "" };
23669
+ }
23670
+ function isDirLikeSync(p) {
23671
+ try {
23672
+ return import_node_fs6.default.statSync(p).isDirectory();
23673
+ } catch {
23674
+ return false;
23675
+ }
23676
+ }
23677
+ function scanSkillDir(dir, source, seen, out, pluginName) {
23678
+ let entries;
23679
+ try {
23680
+ entries = import_node_fs6.default.readdirSync(dir, { withFileTypes: true });
23681
+ } catch {
23682
+ return;
23683
+ }
23684
+ for (const ent of entries) {
23685
+ const entryPath = import_node_path7.default.join(dir, ent.name);
23686
+ if (!ent.isDirectory() && !(ent.isSymbolicLink() && isDirLikeSync(entryPath))) continue;
23687
+ let content;
23688
+ try {
23689
+ content = import_node_fs6.default.readFileSync(import_node_path7.default.join(entryPath, "SKILL.md"), "utf8");
23690
+ } catch {
23691
+ try {
23692
+ content = import_node_fs6.default.readFileSync(import_node_path7.default.join(entryPath, "skill.md"), "utf8");
23693
+ } catch {
23694
+ continue;
23695
+ }
23696
+ }
23697
+ const { description } = parseDescription(content);
23698
+ const baseName = ent.name;
23699
+ const name = pluginName ? `${pluginName}:${baseName}` : baseName;
23700
+ if (seen.has(name)) continue;
23701
+ seen.add(name);
23702
+ const info = { name, source, path: entryPath, description: description || void 0 };
23703
+ if (pluginName) info.plugin = pluginName;
23704
+ out.push(info);
23705
+ }
23706
+ }
23707
+ function listSkillsForDir(dir, source) {
23708
+ const seen = /* @__PURE__ */ new Set();
23709
+ const out = [];
23710
+ scanSkillDir(dir, source, seen, out);
23711
+ return out;
23712
+ }
23713
+ function scanCommandDir(dir, source, seen, out, pluginName) {
23714
+ let entries;
23715
+ try {
23716
+ entries = import_node_fs6.default.readdirSync(dir, { withFileTypes: true });
23717
+ } catch {
23718
+ return;
23719
+ }
23720
+ for (const ent of entries) {
23721
+ const entryPath = import_node_path7.default.join(dir, ent.name);
23722
+ if (ent.isDirectory() || ent.isSymbolicLink() && isDirLikeSync(entryPath)) {
23723
+ const ns = ent.name;
23724
+ let subEntries;
23725
+ try {
23726
+ subEntries = import_node_fs6.default.readdirSync(entryPath, { withFileTypes: true });
23727
+ } catch {
23728
+ continue;
23729
+ }
23730
+ for (const se of subEntries) {
23731
+ if (!se.name.endsWith(".md")) continue;
23732
+ const sePath = import_node_path7.default.join(entryPath, se.name);
23733
+ let content;
23734
+ try {
23735
+ content = import_node_fs6.default.readFileSync(sePath, "utf8");
23736
+ } catch {
23737
+ continue;
23738
+ }
23739
+ const cmd = se.name.replace(/\.md$/, "");
23740
+ const { description } = parseDescription(content);
23741
+ const qualified = `${ns}:${cmd}`;
23742
+ const name = pluginName ? `${pluginName}:${qualified}` : qualified;
23743
+ if (seen.has(name)) continue;
23744
+ seen.add(name);
23745
+ const info = { name, source, path: sePath, description: description || void 0 };
23746
+ if (pluginName) info.plugin = pluginName;
23747
+ out.push(info);
23748
+ }
23749
+ } else if (ent.name.endsWith(".md")) {
23750
+ let content;
23751
+ try {
23752
+ content = import_node_fs6.default.readFileSync(entryPath, "utf8");
23753
+ } catch {
23754
+ continue;
23755
+ }
23756
+ const cmd = ent.name.replace(/\.md$/, "");
23757
+ const { description } = parseDescription(content);
23758
+ const name = pluginName ? `${pluginName}:${cmd}` : cmd;
23759
+ if (seen.has(name)) continue;
23760
+ seen.add(name);
23761
+ const info = { name, source, path: entryPath, description: description || void 0 };
23762
+ if (pluginName) info.plugin = pluginName;
23763
+ out.push(info);
23764
+ }
23765
+ }
23766
+ }
23767
+ function readInstalledPlugins(home) {
23768
+ const file = import_node_path7.default.join(home, ".claude", "plugins", "installed_plugins.json");
23769
+ let raw;
23770
+ try {
23771
+ raw = import_node_fs6.default.readFileSync(file, "utf8");
23772
+ } catch {
23773
+ return [];
23774
+ }
23775
+ let parsed;
23776
+ try {
23777
+ parsed = JSON.parse(raw);
23778
+ } catch {
23779
+ return [];
23780
+ }
23781
+ const out = [];
23782
+ for (const [key, entries] of Object.entries(parsed.plugins ?? {})) {
23783
+ if (!Array.isArray(entries) || entries.length === 0) continue;
23784
+ const entry = entries[0];
23785
+ if (!entry?.installPath) continue;
23786
+ const pluginName = key.includes("@") ? key.slice(0, key.indexOf("@")) : key;
23787
+ if (!pluginName) continue;
23788
+ out.push({ name: pluginName, root: entry.installPath });
23789
+ }
23790
+ return out;
23791
+ }
23792
+ var SkillsScanner = class {
23793
+ home;
23794
+ extraPluginRoots;
23795
+ constructor(opts = {}) {
23796
+ this.home = opts.home ?? import_node_os2.default.homedir();
23797
+ this.extraPluginRoots = opts.extraPluginRoots ?? [];
23798
+ }
23799
+ /**
23800
+ * 对齐 cc-direct(openclaw-plugin-clawos/src/claude-code/skills-scanner.ts):
23801
+ * 1. global: ~/.claude/skills + ~/.claude/commands
23802
+ * 2. project: <cwd>/.claude/skills + <cwd>/.claude/commands
23803
+ * 3. plugin: installed_plugins.json 里每个 plugin 的 skills + commands
23804
+ * 顺序 global → project → plugin,同名去重(先到先得)
23805
+ */
23806
+ list(args) {
23807
+ const seen = /* @__PURE__ */ new Set();
23808
+ const builtinBlock = [];
23809
+ for (const b of BUILTIN_SKILLS) {
23810
+ if (seen.has(b.name)) continue;
23811
+ seen.add(b.name);
23812
+ builtinBlock.push({
23813
+ name: b.name,
23814
+ source: "builtin",
23815
+ description: b.description
23816
+ });
23817
+ }
23818
+ const fsBlock = [];
23819
+ scanSkillDir(import_node_path7.default.join(this.home, ".claude", "skills"), "global", seen, fsBlock);
23820
+ scanCommandDir(import_node_path7.default.join(this.home, ".claude", "commands"), "global", seen, fsBlock);
23821
+ scanSkillDir(import_node_path7.default.join(args.cwd, ".claude", "skills"), "project", seen, fsBlock);
23822
+ scanCommandDir(import_node_path7.default.join(args.cwd, ".claude", "commands"), "project", seen, fsBlock);
23823
+ const plugins = [...readInstalledPlugins(this.home), ...this.extraPluginRoots];
23824
+ for (const { name, root } of plugins) {
23825
+ scanSkillDir(import_node_path7.default.join(root, "skills"), "plugin", seen, fsBlock, name);
23826
+ scanCommandDir(import_node_path7.default.join(root, "commands"), "plugin", seen, fsBlock, name);
23827
+ }
23828
+ fsBlock.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
23829
+ return [...builtinBlock, ...fsBlock];
23830
+ }
23831
+ };
23832
+
23833
+ // src/persona/manager.ts
23834
+ var PersonaManager = class {
23835
+ constructor(deps) {
23836
+ this.deps = deps;
23837
+ }
23838
+ deps;
23839
+ create(args) {
23840
+ const personaId = this.generatePersonaId(args.label);
23841
+ const now = Date.now();
23842
+ const persona = {
23843
+ personaId,
23844
+ label: args.label,
23845
+ model: args.model,
23846
+ public: args.public ?? false,
23847
+ iconKey: args.iconKey,
23848
+ tokenMap: {},
23849
+ createdAt: now,
23850
+ updatedAt: now
23851
+ };
23852
+ this.deps.store.write(persona, args.personality);
23853
+ this.deps.registry.set(persona);
23854
+ return persona;
23855
+ }
23856
+ update(personaId, patch, personality) {
23857
+ const existing = this.deps.registry.get(personaId);
23858
+ if (!existing) throw new Error(`persona not found: ${personaId}`);
23859
+ const { iconKey: iconKeyPatch, ...restPatch } = patch;
23860
+ const updated = {
23861
+ ...existing,
23862
+ ...restPatch,
23863
+ updatedAt: Date.now()
23864
+ };
23865
+ if (iconKeyPatch === null) {
23866
+ delete updated.iconKey;
23867
+ } else if (iconKeyPatch !== void 0) {
23868
+ updated.iconKey = iconKeyPatch;
23869
+ }
23870
+ if (personality !== void 0) {
23871
+ this.deps.store.writePersonality(personaId, personality);
23872
+ }
23873
+ this.deps.store.writeMeta(updated);
23874
+ this.deps.registry.set(updated);
23875
+ return updated;
23876
+ }
23877
+ /** 读取 CLAUDE.md 内容;persona 目录或文件不存在时返回 null。透传给 handler 拼响应。 */
23878
+ readPersonality(personaId) {
23879
+ return this.deps.store.readPersonality(personaId);
23880
+ }
23881
+ /** 扫 persona 私有 skills 目录;目录不存在返回空数组。供 persona:get handler 拼响应。 */
23882
+ listSkills(personaId) {
23883
+ return listSkillsForDir(this.deps.store.skillsDir(personaId), "project");
23884
+ }
23885
+ /** 读 sandbox-settings.json;不存在 / 损坏返回 null。供 persona:get handler 拼响应。 */
23886
+ readSandboxSettings(personaId) {
23887
+ return this.deps.store.readSandboxSettings(personaId);
23888
+ }
23889
+ /**
23890
+ * 删除 persona。
23891
+ * PersonaStore.remove(personaId) 已级联删除整个 <personaRoot>/<personaId>/ 目录,
23892
+ * 其中包含 .clawd/sub-sessions/——无需额外清理。
23893
+ * 旧的 <dataDir>/sessions/<personaId>/ 路径已废弃,不再维护。
23894
+ */
23895
+ delete(personaId) {
23896
+ this.deps.store.remove(personaId);
23897
+ this.deps.registry.remove(personaId);
23898
+ }
23899
+ /** 生成 32-char base64url token(24 bytes 随机),label 必填 */
23900
+ issueToken(personaId, label) {
23901
+ const existing = this.deps.registry.get(personaId);
23902
+ if (!existing) throw new Error(`persona not found: ${personaId}`);
23903
+ const token = import_node_crypto3.default.randomBytes(24).toString("base64url");
23904
+ const entry = {
23905
+ label,
23906
+ issuedAt: Date.now(),
23907
+ revoked: false
23908
+ };
23909
+ const updated = {
23910
+ ...existing,
23911
+ tokenMap: { ...existing.tokenMap, [token]: entry },
23912
+ updatedAt: Date.now()
23913
+ };
23914
+ this.deps.store.writeMeta(updated);
23915
+ this.deps.registry.set(updated);
23916
+ return { token, persona: updated };
23917
+ }
23918
+ revokeToken(personaId, token) {
23919
+ const existing = this.deps.registry.get(personaId);
23920
+ if (!existing) throw new Error(`persona not found: ${personaId}`);
23921
+ const tokenEntry = existing.tokenMap[token];
23922
+ if (!tokenEntry) throw new Error(`token not found in persona ${personaId}`);
23923
+ const updated = {
23924
+ ...existing,
23925
+ tokenMap: {
23926
+ ...existing.tokenMap,
23927
+ [token]: { ...tokenEntry, revoked: true }
23928
+ },
23929
+ updatedAt: Date.now()
23930
+ };
23931
+ this.deps.store.writeMeta(updated);
23932
+ this.deps.registry.set(updated);
23933
+ return updated;
23934
+ }
23935
+ /**
23936
+ * 拿到(或按需创建)该 token 对应的 sub-session。subSessionId 由 personaId + token 哈希派生
23937
+ * 保证 idempotent:同一 token 永远复用同一个 sub-session。
23938
+ * 创建路径:开启 idleKillEnabled,cwd 取 persona 目录,不再硬编码 permission mode / tool allowlist
23939
+ * (sandbox 约束移到 persona dir 下的 .claude/settings.json,由 OS-level Seatbelt 执行)
23940
+ */
23941
+ getOrCreateSubSession(personaId, token) {
23942
+ const persona = this.deps.registry.get(personaId);
23943
+ if (!persona) throw new Error(`persona not found: ${personaId}`);
23944
+ const subSessionId = this.deriveSubSessionId(personaId, token);
23945
+ const scope = { kind: "persona", personaId, mode: "listener" };
23946
+ const existing = this.deps.sessionManager.readForScope(subSessionId, scope);
23947
+ if (existing) {
23948
+ return { sessionFile: existing, isNew: false };
23949
+ }
23950
+ const tokenEntry = persona.tokenMap[token];
23951
+ const subLabel = tokenEntry?.label ?? "unknown";
23952
+ const sessionFile = this.deps.sessionManager.createForScope({
23953
+ sessionId: subSessionId,
23954
+ scope,
23955
+ cwd: this.deps.store.personaDirPath(personaId),
23956
+ tool: "claude",
23957
+ label: subLabel,
23958
+ model: persona.model
23959
+ });
23960
+ return { sessionFile, isNew: true };
23961
+ }
23962
+ /**
23963
+ * 老板插话:把"老板的话"作为 meta-text + metaSource='owner' 推到 sub-session 的事件流。
23964
+ * 委托 SessionManager.injectOwnerMessage 路由到 reducer 'inject-owner-text' input
23965
+ */
23966
+ appendOwnerMessage(personaId, subSessionId, text) {
23967
+ this.deps.sessionManager.injectOwnerMessage({
23968
+ sessionId: subSessionId,
23969
+ scope: { kind: "persona", personaId, mode: "listener" },
23970
+ text
23971
+ });
23972
+ }
23973
+ // ---------------- 内部 ----------------
23974
+ /**
23975
+ * label 转 4-16 char slug。优先用 `persona-<slug>`;若 slug 已被占用,追加 4 char
23976
+ * base64url 随机后缀直到不撞为止(最多 5 次,理论冲突 ≈ 2^-24,留个 panic 上限)。
23977
+ */
23978
+ generatePersonaId(label) {
23979
+ const slug = label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 16) || "persona";
23980
+ const base = `persona-${slug}`;
23981
+ if (!this.deps.store.has(base)) return base;
23982
+ for (let i = 0; i < 5; i++) {
23983
+ const rand = import_node_crypto3.default.randomBytes(3).toString("base64url").slice(0, 4);
23984
+ const candidate = `${base}-${rand}`;
23985
+ if (!this.deps.store.has(candidate)) return candidate;
23986
+ }
23987
+ throw new Error(`failed to generate unique personaId for label=${label}`);
23988
+ }
23989
+ /** subSessionId = persona-<short>-<tokenHash12>;同 token 始终复用同一 sub-session */
23990
+ deriveSubSessionId(personaId, token) {
23991
+ const tokenHash = import_node_crypto3.default.createHash("sha256").update(token).digest("hex").slice(0, 12);
23992
+ return `${personaId}-${tokenHash}`;
23993
+ }
23994
+ };
23995
+
23996
+ // src/persona/seed.ts
23997
+ var fs8 = __toESM(require("fs"), 1);
23998
+ var path9 = __toESM(require("path"), 1);
23999
+ var import_node_url = require("url");
24000
+ var import_meta = {};
24001
+ var DEFAULT_PERSONAS = [
24002
+ {
24003
+ personaId: "persona-researcher",
24004
+ label: "\u8C03\u7814\u5458",
24005
+ model: "opus",
24006
+ iconKey: "research",
24007
+ public: false
24008
+ },
24009
+ {
24010
+ personaId: "persona-knowledge-base",
24011
+ label: "\u77E5\u8BC6\u5E93\u7BA1\u7406\u5458",
24012
+ model: "opus",
24013
+ iconKey: "reading",
24014
+ public: false
24015
+ },
24016
+ {
24017
+ personaId: "persona-clawd-helper",
23693
24018
  label: "clawd\u4F7F\u7528\u52A9\u624B",
23694
24019
  model: "opus",
23695
24020
  iconKey: "assist",
@@ -23706,18 +24031,18 @@ var DEFAULT_PERSONAS = [
23706
24031
  function findDefaultsRoot() {
23707
24032
  const candidates = [];
23708
24033
  try {
23709
- const here = path8.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
23710
- candidates.push(path8.resolve(here, "defaults"));
23711
- candidates.push(path8.resolve(here, "persona-defaults"));
24034
+ const here = path9.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
24035
+ candidates.push(path9.resolve(here, "defaults"));
24036
+ candidates.push(path9.resolve(here, "persona-defaults"));
23712
24037
  } catch {
23713
24038
  }
23714
24039
  if (process.argv[1]) {
23715
- const argvDir = path8.dirname(process.argv[1]);
23716
- candidates.push(path8.resolve(argvDir, "persona-defaults"));
24040
+ const argvDir = path9.dirname(process.argv[1]);
24041
+ candidates.push(path9.resolve(argvDir, "persona-defaults"));
23717
24042
  }
23718
24043
  for (const c of candidates) {
23719
24044
  try {
23720
- if (fs7.statSync(c).isDirectory()) return c;
24045
+ if (fs8.statSync(c).isDirectory()) return c;
23721
24046
  } catch {
23722
24047
  }
23723
24048
  }
@@ -23730,8 +24055,8 @@ function seedDefaultPersonas(args) {
23730
24055
  args.logger.info("persona.seed.skip", { personaId: entry.personaId, reason: "exists" });
23731
24056
  continue;
23732
24057
  }
23733
- const bundleDir = path8.join(args.defaultsRoot, entry.personaId);
23734
- if (!fs7.existsSync(bundleDir)) {
24058
+ const bundleDir = path9.join(args.defaultsRoot, entry.personaId);
24059
+ if (!fs8.existsSync(bundleDir)) {
23735
24060
  args.logger.warn("persona.seed.skip", {
23736
24061
  personaId: entry.personaId,
23737
24062
  reason: "bundle-missing",
@@ -23739,8 +24064,8 @@ function seedDefaultPersonas(args) {
23739
24064
  });
23740
24065
  continue;
23741
24066
  }
23742
- const claudeMdPath = path8.join(bundleDir, "CLAUDE.md");
23743
- if (!fs7.existsSync(claudeMdPath)) {
24067
+ const claudeMdPath = path9.join(bundleDir, "CLAUDE.md");
24068
+ if (!fs8.existsSync(claudeMdPath)) {
23744
24069
  args.logger.warn("persona.seed.skip", {
23745
24070
  personaId: entry.personaId,
23746
24071
  reason: "no-CLAUDE.md",
@@ -23748,7 +24073,7 @@ function seedDefaultPersonas(args) {
23748
24073
  });
23749
24074
  continue;
23750
24075
  }
23751
- const personality = fs7.readFileSync(claudeMdPath, "utf8");
24076
+ const personality = fs8.readFileSync(claudeMdPath, "utf8");
23752
24077
  const now = Date.now();
23753
24078
  const persona = {
23754
24079
  personaId: entry.personaId,
@@ -23766,14 +24091,14 @@ function seedDefaultPersonas(args) {
23766
24091
  }
23767
24092
  }
23768
24093
  function copyBundleExtras(srcDir, dstDir) {
23769
- for (const entry of fs7.readdirSync(srcDir, { withFileTypes: true })) {
24094
+ for (const entry of fs8.readdirSync(srcDir, { withFileTypes: true })) {
23770
24095
  if (entry.name === "CLAUDE.md" || entry.name === ".clawd") continue;
23771
- const srcPath = path8.join(srcDir, entry.name);
23772
- const dstPath = path8.join(dstDir, entry.name);
24096
+ const srcPath = path9.join(srcDir, entry.name);
24097
+ const dstPath = path9.join(dstDir, entry.name);
23773
24098
  if (entry.isDirectory()) {
23774
- fs7.cpSync(srcPath, dstPath, { recursive: true, dereference: true });
24099
+ fs8.cpSync(srcPath, dstPath, { recursive: true, dereference: true });
23775
24100
  } else if (entry.isFile()) {
23776
- fs7.copyFileSync(srcPath, dstPath);
24101
+ fs8.copyFileSync(srcPath, dstPath);
23777
24102
  }
23778
24103
  }
23779
24104
  }
@@ -23782,9 +24107,9 @@ function copyBundleExtras(srcDir, dstDir) {
23782
24107
  init_claude();
23783
24108
 
23784
24109
  // src/tools/claude-tui.ts
23785
- var import_node_fs8 = __toESM(require("fs"), 1);
23786
- var import_node_os4 = __toESM(require("os"), 1);
23787
- var import_node_path9 = __toESM(require("path"), 1);
24110
+ var import_node_fs9 = __toESM(require("fs"), 1);
24111
+ var import_node_os5 = __toESM(require("os"), 1);
24112
+ var import_node_path10 = __toESM(require("path"), 1);
23788
24113
  var headlessNs = __toESM(require_xterm_headless(), 1);
23789
24114
  var serializeNs = __toESM(require_addon_serialize(), 1);
23790
24115
  init_claude();
@@ -24237,10 +24562,10 @@ function buildTuiSpawnArgs(ctx, isResume = false) {
24237
24562
  }
24238
24563
  function jsonlExistsForCtx(ctx) {
24239
24564
  if (!ctx.toolSessionId) return false;
24240
- const home = import_node_os4.default.homedir();
24241
- const file = import_node_path9.default.join(home, ".claude", "projects", cwdToHashDir(ctx.cwd), `${ctx.toolSessionId}.jsonl`);
24565
+ const home = import_node_os5.default.homedir();
24566
+ const file = import_node_path10.default.join(home, ".claude", "projects", cwdToHashDir(ctx.cwd), `${ctx.toolSessionId}.jsonl`);
24242
24567
  try {
24243
- return import_node_fs8.default.statSync(file).isFile();
24568
+ return import_node_fs9.default.statSync(file).isFile();
24244
24569
  } catch {
24245
24570
  return false;
24246
24571
  }
@@ -24250,23 +24575,23 @@ function jsonlExistsForCtx(ctx) {
24250
24575
  init_claude_history();
24251
24576
 
24252
24577
  // src/workspace/browser.ts
24253
- var import_node_fs9 = __toESM(require("fs"), 1);
24254
- var import_node_os5 = __toESM(require("os"), 1);
24255
- var import_node_path10 = __toESM(require("path"), 1);
24578
+ var import_node_fs10 = __toESM(require("fs"), 1);
24579
+ var import_node_os6 = __toESM(require("os"), 1);
24580
+ var import_node_path11 = __toESM(require("path"), 1);
24256
24581
  init_protocol();
24257
24582
  var MAX_FILE_BYTES = 2 * 1024 * 1024;
24258
24583
  function resolveInsideCwd(cwd, subpath) {
24259
- const absCwd = import_node_path10.default.resolve(cwd);
24260
- const joined = import_node_path10.default.resolve(absCwd, subpath ?? ".");
24261
- const rel = import_node_path10.default.relative(absCwd, joined);
24262
- if (rel.startsWith("..") || import_node_path10.default.isAbsolute(rel)) {
24584
+ const absCwd = import_node_path11.default.resolve(cwd);
24585
+ const joined = import_node_path11.default.resolve(absCwd, subpath ?? ".");
24586
+ const rel = import_node_path11.default.relative(absCwd, joined);
24587
+ if (rel.startsWith("..") || import_node_path11.default.isAbsolute(rel)) {
24263
24588
  throw new ClawdError(ERROR_CODES.INVALID_PATH, `path escapes cwd: ${subpath}`);
24264
24589
  }
24265
24590
  return joined;
24266
24591
  }
24267
24592
  function ensureCwd(cwd) {
24268
24593
  try {
24269
- const stat = import_node_fs9.default.statSync(cwd);
24594
+ const stat = import_node_fs10.default.statSync(cwd);
24270
24595
  if (!stat.isDirectory()) {
24271
24596
  throw new ClawdError(ERROR_CODES.INVALID_CWD, `not a directory: ${cwd}`);
24272
24597
  }
@@ -24277,10 +24602,10 @@ function ensureCwd(cwd) {
24277
24602
  }
24278
24603
  var WorkspaceBrowser = class {
24279
24604
  list(args) {
24280
- const cwd = args.cwd && args.cwd.length > 0 ? args.cwd : import_node_os5.default.homedir();
24605
+ const cwd = args.cwd && args.cwd.length > 0 ? args.cwd : import_node_os6.default.homedir();
24281
24606
  ensureCwd(cwd);
24282
24607
  const full = resolveInsideCwd(cwd, args.path);
24283
- const dirents = import_node_fs9.default.readdirSync(full, { withFileTypes: true });
24608
+ const dirents = import_node_fs10.default.readdirSync(full, { withFileTypes: true });
24284
24609
  const entries = [];
24285
24610
  for (const d of dirents) {
24286
24611
  if (!args.showHidden && d.name.startsWith(".")) continue;
@@ -24290,7 +24615,7 @@ var WorkspaceBrowser = class {
24290
24615
  mtime: ""
24291
24616
  };
24292
24617
  try {
24293
- const st = import_node_fs9.default.statSync(import_node_path10.default.join(full, d.name));
24618
+ const st = import_node_fs10.default.statSync(import_node_path11.default.join(full, d.name));
24294
24619
  entry.mtime = new Date(st.mtimeMs).toISOString();
24295
24620
  if (d.isFile()) entry.size = st.size;
24296
24621
  } catch {
@@ -24306,14 +24631,14 @@ var WorkspaceBrowser = class {
24306
24631
  read(args) {
24307
24632
  ensureCwd(args.cwd);
24308
24633
  const full = resolveInsideCwd(args.cwd, args.path);
24309
- const st = import_node_fs9.default.statSync(full);
24634
+ const st = import_node_fs10.default.statSync(full);
24310
24635
  if (!st.isFile()) {
24311
24636
  throw new ClawdError(ERROR_CODES.INVALID_PATH, `not a file: ${args.path}`);
24312
24637
  }
24313
24638
  if (st.size > MAX_FILE_BYTES) {
24314
24639
  throw new ClawdError(ERROR_CODES.FILE_TOO_LARGE, `file > ${MAX_FILE_BYTES} bytes`);
24315
24640
  }
24316
- const buf = import_node_fs9.default.readFileSync(full);
24641
+ const buf = import_node_fs10.default.readFileSync(full);
24317
24642
  const isBinary = buf.includes(0);
24318
24643
  if (isBinary) {
24319
24644
  return {
@@ -24334,273 +24659,6 @@ var WorkspaceBrowser = class {
24334
24659
  }
24335
24660
  };
24336
24661
 
24337
- // src/skills/scanner.ts
24338
- var import_node_fs10 = __toESM(require("fs"), 1);
24339
- var import_node_os6 = __toESM(require("os"), 1);
24340
- var import_node_path11 = __toESM(require("path"), 1);
24341
-
24342
- // src/skills/frontmatter.ts
24343
- var STRIP_QUOTES = /^["']|["']$/g;
24344
- function strip(s) {
24345
- return s.trim().replace(STRIP_QUOTES, "");
24346
- }
24347
- function parseFrontmatter(content, keys) {
24348
- const out = {};
24349
- if (!content.startsWith("---")) return out;
24350
- const end = content.indexOf("---", 3);
24351
- if (end === -1) return out;
24352
- const lines = content.slice(3, end).split("\n");
24353
- for (let i = 0; i < lines.length; i++) {
24354
- const trimmed = lines[i].trim();
24355
- for (const key of keys) {
24356
- const prefix = `${key}:`;
24357
- if (!trimmed.startsWith(prefix)) continue;
24358
- const rest = trimmed.slice(prefix.length).trim();
24359
- if (rest === "|" || rest === ">" || rest === "|-" || rest === ">-") {
24360
- const parts = [];
24361
- for (let j = i + 1; j < lines.length; j++) {
24362
- const next = lines[j];
24363
- if (next.length === 0) {
24364
- parts.push("");
24365
- continue;
24366
- }
24367
- if (!/^\s/.test(next)) break;
24368
- parts.push(next.replace(/^\s+/, ""));
24369
- }
24370
- const value = parts.filter(Boolean).join(" ").trim();
24371
- if (value) out[key] = value;
24372
- } else {
24373
- const value = strip(rest);
24374
- if (value) out[key] = value;
24375
- }
24376
- break;
24377
- }
24378
- }
24379
- return out;
24380
- }
24381
-
24382
- // src/skills/builtin-cc-resources.ts
24383
- var BUILTIN_SKILLS = [
24384
- { name: "update-config", source: "builtin", description: "Update Claude Code configuration." },
24385
- { name: "keybindings-help", source: "builtin", description: "Show available keybindings." },
24386
- { name: "verify", source: "builtin", description: "Verify the implementation." },
24387
- { name: "debug", source: "builtin", description: "Debug current issue." },
24388
- { name: "lorem-ipsum", source: "builtin", description: "Generate lorem ipsum placeholder text." },
24389
- { name: "skillify", source: "builtin", description: "Convert a process into a reusable skill." },
24390
- { name: "remember", source: "builtin", description: "Remember context for later use." },
24391
- { name: "simplify", source: "builtin", description: "Simplify the code or message." },
24392
- { name: "batch", source: "builtin", description: "Run a batch of similar operations." },
24393
- { name: "stuck", source: "builtin", description: "Help unstick a stalled task." },
24394
- { name: "loop", source: "builtin", description: "Loop a task until a condition is met." },
24395
- { name: "cron-list", source: "builtin", description: "List scheduled cron tasks." },
24396
- { name: "cron-delete", source: "builtin", description: "Delete a scheduled cron task." },
24397
- { name: "dream", source: "builtin", description: "Brainstorm freely without constraints." },
24398
- { name: "hunter", source: "builtin", description: "Hunt down a bug or root cause." },
24399
- { name: "schedule", source: "builtin", description: "Schedule a task to run later." },
24400
- { name: "claude-api", source: "builtin", description: "Use the Claude API directly." },
24401
- { name: "claude-in-chrome", source: "builtin", description: "Drive Claude inside Chrome via the Chrome extension." }
24402
- ];
24403
- var BUILTIN_AGENTS = [
24404
- {
24405
- name: "general-purpose",
24406
- source: "builtin",
24407
- description: "General-purpose agent for researching complex questions and executing multi-step tasks.",
24408
- whenToUse: "When you are searching for a keyword or file and are not confident you will find the right match in the first few tries."
24409
- },
24410
- {
24411
- name: "statusline-setup",
24412
- source: "builtin",
24413
- description: "Configure the user's Claude Code status line setting.",
24414
- whenToUse: "When the user is setting up or changing their status line configuration."
24415
- },
24416
- {
24417
- name: "Explore",
24418
- source: "builtin",
24419
- description: "Explore the codebase to gather context.",
24420
- whenToUse: "Before making changes that require understanding the surrounding code."
24421
- },
24422
- {
24423
- name: "Plan",
24424
- source: "builtin",
24425
- description: "Produce an implementation plan before writing code.",
24426
- whenToUse: "When the task is non-trivial and would benefit from an explicit plan."
24427
- },
24428
- {
24429
- name: "claude-code-guide",
24430
- source: "builtin",
24431
- description: "Guide the user through Claude Code features.",
24432
- whenToUse: "When the user needs help understanding what Claude Code can do."
24433
- },
24434
- {
24435
- name: "verification",
24436
- source: "builtin",
24437
- description: "Verify that a change works as intended.",
24438
- whenToUse: "After implementing a change, before declaring it done."
24439
- }
24440
- ];
24441
-
24442
- // src/skills/scanner.ts
24443
- function parseDescription(content) {
24444
- const fields = parseFrontmatter(content, ["description"]);
24445
- return { description: fields.description ?? "" };
24446
- }
24447
- function isDirLikeSync(p) {
24448
- try {
24449
- return import_node_fs10.default.statSync(p).isDirectory();
24450
- } catch {
24451
- return false;
24452
- }
24453
- }
24454
- function scanSkillDir(dir, source, seen, out, pluginName) {
24455
- let entries;
24456
- try {
24457
- entries = import_node_fs10.default.readdirSync(dir, { withFileTypes: true });
24458
- } catch {
24459
- return;
24460
- }
24461
- for (const ent of entries) {
24462
- const entryPath = import_node_path11.default.join(dir, ent.name);
24463
- if (!ent.isDirectory() && !(ent.isSymbolicLink() && isDirLikeSync(entryPath))) continue;
24464
- let content;
24465
- try {
24466
- content = import_node_fs10.default.readFileSync(import_node_path11.default.join(entryPath, "SKILL.md"), "utf8");
24467
- } catch {
24468
- try {
24469
- content = import_node_fs10.default.readFileSync(import_node_path11.default.join(entryPath, "skill.md"), "utf8");
24470
- } catch {
24471
- continue;
24472
- }
24473
- }
24474
- const { description } = parseDescription(content);
24475
- const baseName = ent.name;
24476
- const name = pluginName ? `${pluginName}:${baseName}` : baseName;
24477
- if (seen.has(name)) continue;
24478
- seen.add(name);
24479
- const info = { name, source, path: entryPath, description: description || void 0 };
24480
- if (pluginName) info.plugin = pluginName;
24481
- out.push(info);
24482
- }
24483
- }
24484
- function scanCommandDir(dir, source, seen, out, pluginName) {
24485
- let entries;
24486
- try {
24487
- entries = import_node_fs10.default.readdirSync(dir, { withFileTypes: true });
24488
- } catch {
24489
- return;
24490
- }
24491
- for (const ent of entries) {
24492
- const entryPath = import_node_path11.default.join(dir, ent.name);
24493
- if (ent.isDirectory() || ent.isSymbolicLink() && isDirLikeSync(entryPath)) {
24494
- const ns = ent.name;
24495
- let subEntries;
24496
- try {
24497
- subEntries = import_node_fs10.default.readdirSync(entryPath, { withFileTypes: true });
24498
- } catch {
24499
- continue;
24500
- }
24501
- for (const se of subEntries) {
24502
- if (!se.name.endsWith(".md")) continue;
24503
- const sePath = import_node_path11.default.join(entryPath, se.name);
24504
- let content;
24505
- try {
24506
- content = import_node_fs10.default.readFileSync(sePath, "utf8");
24507
- } catch {
24508
- continue;
24509
- }
24510
- const cmd = se.name.replace(/\.md$/, "");
24511
- const { description } = parseDescription(content);
24512
- const qualified = `${ns}:${cmd}`;
24513
- const name = pluginName ? `${pluginName}:${qualified}` : qualified;
24514
- if (seen.has(name)) continue;
24515
- seen.add(name);
24516
- const info = { name, source, path: sePath, description: description || void 0 };
24517
- if (pluginName) info.plugin = pluginName;
24518
- out.push(info);
24519
- }
24520
- } else if (ent.name.endsWith(".md")) {
24521
- let content;
24522
- try {
24523
- content = import_node_fs10.default.readFileSync(entryPath, "utf8");
24524
- } catch {
24525
- continue;
24526
- }
24527
- const cmd = ent.name.replace(/\.md$/, "");
24528
- const { description } = parseDescription(content);
24529
- const name = pluginName ? `${pluginName}:${cmd}` : cmd;
24530
- if (seen.has(name)) continue;
24531
- seen.add(name);
24532
- const info = { name, source, path: entryPath, description: description || void 0 };
24533
- if (pluginName) info.plugin = pluginName;
24534
- out.push(info);
24535
- }
24536
- }
24537
- }
24538
- function readInstalledPlugins(home) {
24539
- const file = import_node_path11.default.join(home, ".claude", "plugins", "installed_plugins.json");
24540
- let raw;
24541
- try {
24542
- raw = import_node_fs10.default.readFileSync(file, "utf8");
24543
- } catch {
24544
- return [];
24545
- }
24546
- let parsed;
24547
- try {
24548
- parsed = JSON.parse(raw);
24549
- } catch {
24550
- return [];
24551
- }
24552
- const out = [];
24553
- for (const [key, entries] of Object.entries(parsed.plugins ?? {})) {
24554
- if (!Array.isArray(entries) || entries.length === 0) continue;
24555
- const entry = entries[0];
24556
- if (!entry?.installPath) continue;
24557
- const pluginName = key.includes("@") ? key.slice(0, key.indexOf("@")) : key;
24558
- if (!pluginName) continue;
24559
- out.push({ name: pluginName, root: entry.installPath });
24560
- }
24561
- return out;
24562
- }
24563
- var SkillsScanner = class {
24564
- home;
24565
- extraPluginRoots;
24566
- constructor(opts = {}) {
24567
- this.home = opts.home ?? import_node_os6.default.homedir();
24568
- this.extraPluginRoots = opts.extraPluginRoots ?? [];
24569
- }
24570
- /**
24571
- * 对齐 cc-direct(openclaw-plugin-clawos/src/claude-code/skills-scanner.ts):
24572
- * 1. global: ~/.claude/skills + ~/.claude/commands
24573
- * 2. project: <cwd>/.claude/skills + <cwd>/.claude/commands
24574
- * 3. plugin: installed_plugins.json 里每个 plugin 的 skills + commands
24575
- * 顺序 global → project → plugin,同名去重(先到先得)
24576
- */
24577
- list(args) {
24578
- const seen = /* @__PURE__ */ new Set();
24579
- const builtinBlock = [];
24580
- for (const b of BUILTIN_SKILLS) {
24581
- if (seen.has(b.name)) continue;
24582
- seen.add(b.name);
24583
- builtinBlock.push({
24584
- name: b.name,
24585
- source: "builtin",
24586
- description: b.description
24587
- });
24588
- }
24589
- const fsBlock = [];
24590
- scanSkillDir(import_node_path11.default.join(this.home, ".claude", "skills"), "global", seen, fsBlock);
24591
- scanCommandDir(import_node_path11.default.join(this.home, ".claude", "commands"), "global", seen, fsBlock);
24592
- scanSkillDir(import_node_path11.default.join(args.cwd, ".claude", "skills"), "project", seen, fsBlock);
24593
- scanCommandDir(import_node_path11.default.join(args.cwd, ".claude", "commands"), "project", seen, fsBlock);
24594
- const plugins = [...readInstalledPlugins(this.home), ...this.extraPluginRoots];
24595
- for (const { name, root } of plugins) {
24596
- scanSkillDir(import_node_path11.default.join(root, "skills"), "plugin", seen, fsBlock, name);
24597
- scanCommandDir(import_node_path11.default.join(root, "commands"), "plugin", seen, fsBlock, name);
24598
- }
24599
- fsBlock.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
24600
- return [...builtinBlock, ...fsBlock];
24601
- }
24602
- };
24603
-
24604
24662
  // src/skills/agents-scanner.ts
24605
24663
  var import_node_fs11 = __toESM(require("fs"), 1);
24606
24664
  var import_node_os7 = __toESM(require("os"), 1);
@@ -26930,8 +26988,16 @@ function buildPersonaHandlers(deps) {
26930
26988
  return { response: { type: "persona:info", persona: null } };
26931
26989
  }
26932
26990
  const personality = personaManager.readPersonality(args.personaId) ?? "";
26991
+ const skills = personaManager.listSkills(args.personaId);
26992
+ const sandboxSettings = personaManager.readSandboxSettings(args.personaId);
26933
26993
  return {
26934
- response: { type: "persona:info", ...persona, personality }
26994
+ response: {
26995
+ type: "persona:info",
26996
+ ...persona,
26997
+ personality,
26998
+ skills,
26999
+ sandboxSettings
27000
+ }
26935
27001
  };
26936
27002
  };
26937
27003
  const update = async (frame) => {