@lmzhen/dsh-tool-skill-manage 0.1.0-rc.8 → 0.1.0
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/lib/index.js +97 -23
- package/lib/types/index.d.ts +2 -0
- package/package.json +12 -12
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import z from "@deepseek-ai/schemastery";
|
|
2
2
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
3
|
-
import { DEFAULT_SKILL_LIMITS, DSH_AUTHORING_STANDARDS, SkillLibrary, evolutionIoAdapter } from "@lmzhen/dsh-evolution-core";
|
|
3
|
+
import { DEFAULT_SKILL_LIMITS, DSH_AUTHORING_STANDARDS, SKILLS_GUIDANCE, SkillLibrary, authoringFeedback, computeDedupGroups, evolutionIoAdapter, parseFrontmatter, resolveOrigins } from "@lmzhen/dsh-evolution-core";
|
|
4
4
|
//#region lib/types/index.js
|
|
5
5
|
/**
|
|
6
6
|
* Model-facing skill_manage tool over ctx.evolutionIo + ctx.skillUsage.
|
|
@@ -22,19 +22,49 @@ const Config = z.object({
|
|
|
22
22
|
maxSkillNameLength: z.number().default(DEFAULT_SKILL_LIMITS.maxNameLength),
|
|
23
23
|
maxDescriptionLength: z.number().default(DEFAULT_SKILL_LIMITS.maxDescriptionLength),
|
|
24
24
|
maxSkillContentChars: z.number().default(DEFAULT_SKILL_LIMITS.maxSkillContentChars),
|
|
25
|
-
maxSkillFileBytes: z.number().default(DEFAULT_SKILL_LIMITS.maxSkillFileBytes)
|
|
25
|
+
maxSkillFileBytes: z.number().default(DEFAULT_SKILL_LIMITS.maxSkillFileBytes),
|
|
26
|
+
descriptionStrict: z.boolean().default(false)
|
|
26
27
|
});
|
|
28
|
+
/**
|
|
29
|
+
* The requesting session's effective approval policy, mirroring
|
|
30
|
+
* `dsh-user-approval` (override ?? configured default). Returns undefined when
|
|
31
|
+
* the approval service is not mounted or no session is available — callers
|
|
32
|
+
* keep their previous behavior.
|
|
33
|
+
*/
|
|
34
|
+
function effectiveSessionPolicy(ctx, session) {
|
|
35
|
+
const approval = ctx.get("approval");
|
|
36
|
+
if (!approval || session === void 0) return void 0;
|
|
37
|
+
return approval.overrideOf(session) ?? approval.config.policy ?? "ask";
|
|
38
|
+
}
|
|
27
39
|
function apply(ctx, rawConfig = {}) {
|
|
40
|
+
const systemPrompt = ctx.get("systemPrompt");
|
|
41
|
+
if (systemPrompt) ctx.effect(() => systemPrompt.section({
|
|
42
|
+
name: "evolution-skills-guidance",
|
|
43
|
+
order: 900,
|
|
44
|
+
text: SKILLS_GUIDANCE
|
|
45
|
+
}), "tool-skill-manage.skills-guidance");
|
|
28
46
|
const io = evolutionIoAdapter(() => ctx.evolutionIo.provider());
|
|
29
47
|
const library = new SkillLibrary(rawConfig.root || void 0, io, {
|
|
30
48
|
maxNameLength: rawConfig.maxSkillNameLength ?? DEFAULT_SKILL_LIMITS.maxNameLength,
|
|
31
49
|
maxDescriptionLength: rawConfig.maxDescriptionLength ?? DEFAULT_SKILL_LIMITS.maxDescriptionLength,
|
|
32
50
|
maxSkillContentChars: rawConfig.maxSkillContentChars ?? DEFAULT_SKILL_LIMITS.maxSkillContentChars,
|
|
33
51
|
maxSkillFileBytes: rawConfig.maxSkillFileBytes ?? DEFAULT_SKILL_LIMITS.maxSkillFileBytes
|
|
52
|
+
}, (event) => {
|
|
53
|
+
ctx.emit("evolution/skill-mutated", event);
|
|
34
54
|
});
|
|
35
55
|
async function executeCore(args, origin = "foreground") {
|
|
36
56
|
const action = args.action;
|
|
37
57
|
const name = args.name ?? "";
|
|
58
|
+
if (action === "review") return {
|
|
59
|
+
ok: true,
|
|
60
|
+
message: await buildSkillReviewText(),
|
|
61
|
+
skills: []
|
|
62
|
+
};
|
|
63
|
+
if (action === "skip") return {
|
|
64
|
+
ok: true,
|
|
65
|
+
message: "Skipped; no skill changes this pass.",
|
|
66
|
+
skills: []
|
|
67
|
+
};
|
|
38
68
|
if (action === "list") {
|
|
39
69
|
const list = await library.list();
|
|
40
70
|
return {
|
|
@@ -43,40 +73,77 @@ function apply(ctx, rawConfig = {}) {
|
|
|
43
73
|
skills: list.map((s) => s.name)
|
|
44
74
|
};
|
|
45
75
|
}
|
|
76
|
+
let feedbackLines = [];
|
|
77
|
+
if ((action === "create" || action === "edit" || action === "update") && args.content) {
|
|
78
|
+
const parsed = parseFrontmatter(args.content);
|
|
79
|
+
if (parsed) {
|
|
80
|
+
const feedback = authoringFeedback(parsed.frontmatter);
|
|
81
|
+
feedbackLines = feedback.lines;
|
|
82
|
+
if (rawConfig.descriptionStrict === true && feedback.over60) return {
|
|
83
|
+
ok: false,
|
|
84
|
+
message: `Authoring check: description ${feedback.descriptionChars}/60 characters exceeds the strict bar; tighten it to <=60 or set descriptionStrict=false.`,
|
|
85
|
+
skills: []
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
46
89
|
let result;
|
|
47
90
|
if (action === "create") result = await library.create(name, args.content ?? "", origin);
|
|
48
|
-
else if (action === "edit" || action === "update") result = await library.update(name, args.content ?? "");
|
|
49
|
-
else if (action === "patch") result = await library.patch(name, args.old_string ?? "", args.new_string ?? "", args.file_path ?? "", args.replace_all === true);
|
|
50
|
-
else if (action === "delete") result = await library.archive(name, args.absorbed_into
|
|
51
|
-
else if (action === "write_file") result = await library.writeSupportFile(name, args.file_path ?? "", args.file_content ?? "");
|
|
52
|
-
else if (action === "remove_file") result = await library.removeSupportFile(name, args.file_path ?? "");
|
|
91
|
+
else if (action === "edit" || action === "update") result = await library.update(name, args.content ?? "", origin);
|
|
92
|
+
else if (action === "patch") result = await library.patch(name, args.old_string ?? "", args.new_string ?? "", args.file_path ?? "", args.replace_all === true, origin);
|
|
93
|
+
else if (action === "delete") result = await library.archive(name, args.absorbed_into ? { absorbedInto: args.absorbed_into } : {});
|
|
94
|
+
else if (action === "write_file") result = await library.writeSupportFile(name, args.file_path ?? "", args.file_content ?? "", origin);
|
|
95
|
+
else if (action === "remove_file") result = await library.removeSupportFile(name, args.file_path ?? "", origin);
|
|
96
|
+
else if (action === "pin") result = await library.setPinned(name, true, origin);
|
|
97
|
+
else if (action === "unpin") result = await library.setPinned(name, false, origin);
|
|
53
98
|
else result = {
|
|
54
99
|
ok: false,
|
|
55
100
|
message: `Unknown action "${action}".`
|
|
56
101
|
};
|
|
57
102
|
if (result.ok) {
|
|
58
|
-
|
|
59
|
-
if (name && action
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
103
|
+
const mutating = action !== "list" && action !== "review" && action !== "skip" && action !== "pin" && action !== "unpin";
|
|
104
|
+
if (name && action === "create") {
|
|
105
|
+
await ctx.skillUsage.ensureRecord(name);
|
|
106
|
+
if (origin !== "foreground") await ctx.skillUsage.markAgentCreated(name);
|
|
107
|
+
}
|
|
108
|
+
if (name && action === "delete") await ctx.skillUsage.markArchived(name);
|
|
109
|
+
else if (name && mutating && action !== "create") await ctx.skillUsage.record(name, "patch");
|
|
65
110
|
}
|
|
66
111
|
return {
|
|
67
112
|
ok: result.ok,
|
|
68
|
-
message: result.message,
|
|
113
|
+
message: result.ok && feedbackLines.length > 0 ? `${result.message}\n\nAuthoring check:\n${feedbackLines.map((line) => `- ${line}`).join("\n")}` : result.message,
|
|
69
114
|
skills: []
|
|
70
115
|
};
|
|
71
116
|
}
|
|
117
|
+
async function buildSkillReviewText() {
|
|
118
|
+
const list = await library.list();
|
|
119
|
+
const report = await ctx.skillUsage.report();
|
|
120
|
+
const lines = list.map((summary) => {
|
|
121
|
+
const record = report.get(summary.name);
|
|
122
|
+
const quality = record?.quality_score !== void 0 ? ` quality:${record.quality_score.toFixed(2)}${record.quality_warn ? "⚠" : ""}` : "";
|
|
123
|
+
return `- ${summary.name} | ${record?.state ?? "active"} | use:${record?.use_count ?? 0} view:${record?.view_count ?? 0} patch:${record?.patch_count ?? 0}${quality}${summary.protectedBy ? ` [${summary.protectedBy}]` : ""}`;
|
|
124
|
+
});
|
|
125
|
+
const groups = computeDedupGroups({ contents: new Map(await Promise.all(list.map(async (summary) => [summary.name, await library.read(summary.name) ?? ""]))) });
|
|
126
|
+
const dedupLines = groups.slice(0, 3).map((group) => `- ${group.join(" ~ ")}`);
|
|
127
|
+
const warned = list.filter((summary) => report.get(summary.name)?.quality_warn === true).map((summary) => summary.name);
|
|
128
|
+
const warningLine = warned.length === 0 ? "" : `\nWarning skills (${warned.length}): ${warned.join(", ")} — low quality; consider consolidating them before authoring new skills.`;
|
|
129
|
+
return [
|
|
130
|
+
list.length === 0 ? "No skills yet. Create one with action=create, or it is safe to author a new class-level umbrella." : `Skills: ${list.length} total. Below each name: state, use/view/patch counts, quality (0-1, ⚠ = low) and protection.${groups.length > 0 ? `\n\nNear-duplicate groups (${groups.length}):` : ""}`,
|
|
131
|
+
"",
|
|
132
|
+
...lines,
|
|
133
|
+
...dedupLines,
|
|
134
|
+
warningLine
|
|
135
|
+
].join("\n");
|
|
136
|
+
}
|
|
72
137
|
ctx.tools.register(defineTool({
|
|
73
138
|
name: "skill_manage",
|
|
74
|
-
description: "Manage reusable skills. create/edit take full SKILL.md content; patch applies old_string -> new_string; delete archives to .archive. Protected bundled/hub skills reject mutation; pinned skills reject
|
|
139
|
+
description: "Manage reusable skills. review returns the library review text (state/usage/quality per skill); list returns names only; create/edit take full SKILL.md content; patch applies old_string -> new_string; delete archives to .archive; pin protects a skill from deletion, background review, and the lifecycle (pin/unpin are never allowed from a background review — foreground and delegated subagents may). Protected bundled/hub skills reject any mutation; pinned skills reject deletion and are read-only to the background review.Prefer patching an umbrella over creating narrow skills. Created/edited SKILL.md MUST start with YAML frontmatter (a name/description block), or creation is rejected. " + DSH_AUTHORING_STANDARDS,
|
|
75
140
|
parameters: {
|
|
76
141
|
action: {
|
|
77
142
|
type: "string",
|
|
78
143
|
required: true,
|
|
79
144
|
enum: [
|
|
145
|
+
"review",
|
|
146
|
+
"list",
|
|
80
147
|
"create",
|
|
81
148
|
"edit",
|
|
82
149
|
"update",
|
|
@@ -84,7 +151,9 @@ function apply(ctx, rawConfig = {}) {
|
|
|
84
151
|
"delete",
|
|
85
152
|
"write_file",
|
|
86
153
|
"remove_file",
|
|
87
|
-
"
|
|
154
|
+
"skip",
|
|
155
|
+
"pin",
|
|
156
|
+
"unpin"
|
|
88
157
|
]
|
|
89
158
|
},
|
|
90
159
|
name: { type: "string" },
|
|
@@ -124,17 +193,22 @@ function apply(ctx, rawConfig = {}) {
|
|
|
124
193
|
},
|
|
125
194
|
isConcurrencySafe: () => false,
|
|
126
195
|
async execute(args, exec) {
|
|
127
|
-
const
|
|
196
|
+
const origins = resolveOrigins(exec.agent?.session.header.origin);
|
|
197
|
+
const reviewOrigin = origins.approval;
|
|
198
|
+
const libraryOrigin = origins.library;
|
|
199
|
+
const sessionPolicy = effectiveSessionPolicy(ctx, exec.agent?.session);
|
|
128
200
|
const approval = ctx.get("evolutionApproval");
|
|
129
|
-
if (approval && args.action !== "list") {
|
|
201
|
+
if (approval && args.action !== "list" && args.action !== "review" && args.action !== "skip" && args.action !== "pin" && args.action !== "unpin") {
|
|
130
202
|
const decision = await approval.request({
|
|
131
203
|
kind: "skill",
|
|
132
204
|
summary: `skill ${args.action ?? "?"} ${args.name ?? ""}`.trim(),
|
|
133
205
|
args: {
|
|
134
206
|
operation: args,
|
|
135
|
-
origin
|
|
207
|
+
origin: reviewOrigin,
|
|
208
|
+
libraryOrigin
|
|
136
209
|
},
|
|
137
|
-
origin
|
|
210
|
+
origin: reviewOrigin,
|
|
211
|
+
...sessionPolicy !== void 0 ? { sessionPolicy } : {}
|
|
138
212
|
});
|
|
139
213
|
if (decision.action === "staged") return {
|
|
140
214
|
ok: true,
|
|
@@ -143,13 +217,13 @@ function apply(ctx, rawConfig = {}) {
|
|
|
143
217
|
pending_id: decision.pendingId ?? ""
|
|
144
218
|
};
|
|
145
219
|
}
|
|
146
|
-
return await executeCore(args,
|
|
220
|
+
return await executeCore(args, libraryOrigin);
|
|
147
221
|
}
|
|
148
222
|
}));
|
|
149
223
|
ctx.inject(["evolutionApproval"], (approvalCtx) => {
|
|
150
224
|
const dispose = approvalCtx.evolutionApproval.registerRunner("skill", (args) => {
|
|
151
225
|
const wrapped = args ?? {};
|
|
152
|
-
return executeCore(wrapped.operation ?? {}, wrapped.origin ?? "background_review");
|
|
226
|
+
return executeCore(wrapped.operation ?? {}, wrapped.libraryOrigin ?? wrapped.origin ?? "background_review");
|
|
153
227
|
});
|
|
154
228
|
approvalCtx.effect(() => dispose, "tool-skill-manage.approval-runner");
|
|
155
229
|
});
|
package/lib/types/index.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export interface Config {
|
|
|
18
18
|
maxDescriptionLength?: number;
|
|
19
19
|
maxSkillContentChars?: number;
|
|
20
20
|
maxSkillFileBytes?: number;
|
|
21
|
+
/** When true, create/update refuse a description over the 60-char authoring bar (default: advisory feedback only). */
|
|
22
|
+
descriptionStrict?: boolean;
|
|
21
23
|
}
|
|
22
24
|
export declare const Config: z<Config>;
|
|
23
25
|
export declare function apply(ctx: Context, rawConfig?: Config): void;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmzhen/dsh-tool-skill-manage",
|
|
3
3
|
"description": "Model-facing skill_manage tool (community build)",
|
|
4
|
-
"version": "0.1.0
|
|
4
|
+
"version": "0.1.0",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -33,21 +33,21 @@
|
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
36
|
-
"@lmzhen/dsh-evolution-core": "^0.1.0
|
|
36
|
+
"@lmzhen/dsh-evolution-core": "^0.1.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
40
|
-
"@deepseek-ai/dsh-invariants": "^0.1.
|
|
41
|
-
"@deepseek-ai/dsh-tools": "^0.1.
|
|
42
|
-
"@lmzhen/dsh-evolution-io": "^0.1.0
|
|
43
|
-
"@lmzhen/dsh-skill-usage": "^0.1.0
|
|
40
|
+
"@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
|
|
41
|
+
"@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
|
|
42
|
+
"@lmzhen/dsh-evolution-io": "^0.1.0",
|
|
43
|
+
"@lmzhen/dsh-skill-usage": "^0.1.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@deepseek-ai/dsh-agent-loop-testkit": "^0.1.
|
|
47
|
-
"@deepseek-ai/dsh-invariants": "^0.1.
|
|
48
|
-
"@deepseek-ai/dsh-tools": "^0.1.
|
|
49
|
-
"@lmzhen/dsh-evolution-core": "^0.1.0
|
|
50
|
-
"@lmzhen/dsh-evolution-io": "^0.1.0
|
|
51
|
-
"@lmzhen/dsh-skill-usage": "^0.1.0
|
|
46
|
+
"@deepseek-ai/dsh-agent-loop-testkit": "^0.1.1-rc.2",
|
|
47
|
+
"@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
|
|
48
|
+
"@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
|
|
49
|
+
"@lmzhen/dsh-evolution-core": "^0.1.0",
|
|
50
|
+
"@lmzhen/dsh-evolution-io": "^0.1.0",
|
|
51
|
+
"@lmzhen/dsh-skill-usage": "^0.1.0"
|
|
52
52
|
}
|
|
53
53
|
}
|