@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.
- package/dist/cli.cjs +580 -514
- 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
|
|
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 =
|
|
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
|
|
10319
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
10339
|
-
|
|
10340
|
-
|
|
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 ??
|
|
10365
|
-
this.projectsRoot =
|
|
10366
|
-
this.fileHistoryRoot =
|
|
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 =
|
|
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 =
|
|
10380
|
-
const files =
|
|
10381
|
-
const updatedAtMs = files.reduce((m, f) => Math.max(m, safeStatMtime(
|
|
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 =
|
|
10418
|
+
const dir = import_node_path8.default.join(this.projectsRoot, cwdToHashDir(args.projectPath));
|
|
10394
10419
|
let files;
|
|
10395
10420
|
try {
|
|
10396
|
-
files =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 &&
|
|
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 =
|
|
10758
|
-
if (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 (
|
|
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,
|
|
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
|
-
|
|
11200
|
-
|
|
11201
|
-
|
|
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
|
-
|
|
23518
|
-
|
|
23519
|
-
|
|
23520
|
-
|
|
23521
|
-
|
|
23522
|
-
|
|
23523
|
-
|
|
23524
|
-
|
|
23525
|
-
|
|
23526
|
-
|
|
23527
|
-
|
|
23528
|
-
|
|
23529
|
-
|
|
23530
|
-
|
|
23531
|
-
|
|
23532
|
-
|
|
23533
|
-
|
|
23534
|
-
|
|
23535
|
-
|
|
23536
|
-
|
|
23537
|
-
|
|
23538
|
-
|
|
23539
|
-
|
|
23540
|
-
|
|
23541
|
-
|
|
23542
|
-
|
|
23543
|
-
|
|
23544
|
-
|
|
23545
|
-
|
|
23546
|
-
|
|
23547
|
-
|
|
23548
|
-
|
|
23549
|
-
|
|
23550
|
-
|
|
23551
|
-
|
|
23552
|
-
|
|
23553
|
-
|
|
23554
|
-
|
|
23555
|
-
|
|
23556
|
-
|
|
23557
|
-
|
|
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/
|
|
23672
|
-
var
|
|
23673
|
-
|
|
23674
|
-
|
|
23675
|
-
|
|
23676
|
-
|
|
23677
|
-
{
|
|
23678
|
-
|
|
23679
|
-
|
|
23680
|
-
|
|
23681
|
-
|
|
23682
|
-
|
|
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
|
-
|
|
23686
|
-
|
|
23687
|
-
|
|
23688
|
-
|
|
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
|
-
|
|
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 =
|
|
23710
|
-
candidates.push(
|
|
23711
|
-
candidates.push(
|
|
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 =
|
|
23716
|
-
candidates.push(
|
|
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 (
|
|
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 =
|
|
23734
|
-
if (!
|
|
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 =
|
|
23743
|
-
if (!
|
|
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 =
|
|
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
|
|
24094
|
+
for (const entry of fs8.readdirSync(srcDir, { withFileTypes: true })) {
|
|
23770
24095
|
if (entry.name === "CLAUDE.md" || entry.name === ".clawd") continue;
|
|
23771
|
-
const srcPath =
|
|
23772
|
-
const dstPath =
|
|
24096
|
+
const srcPath = path9.join(srcDir, entry.name);
|
|
24097
|
+
const dstPath = path9.join(dstDir, entry.name);
|
|
23773
24098
|
if (entry.isDirectory()) {
|
|
23774
|
-
|
|
24099
|
+
fs8.cpSync(srcPath, dstPath, { recursive: true, dereference: true });
|
|
23775
24100
|
} else if (entry.isFile()) {
|
|
23776
|
-
|
|
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
|
|
23786
|
-
var
|
|
23787
|
-
var
|
|
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 =
|
|
24241
|
-
const file =
|
|
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
|
|
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
|
|
24254
|
-
var
|
|
24255
|
-
var
|
|
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 =
|
|
24260
|
-
const joined =
|
|
24261
|
-
const rel =
|
|
24262
|
-
if (rel.startsWith("..") ||
|
|
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 =
|
|
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 :
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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: {
|
|
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) => {
|