@lmzhen/dsh-tool-skill-manage 0.3.70 → 0.3.72
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 +45 -47
- package/lib/types/index.d.ts +5 -0
- package/package.json +10 -8
package/lib/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { effectiveSessionPolicy } from "@lmzhen/dsh-evolution-approval";
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
4
|
-
import { DEFAULT_SKILL_LIMITS, DSH_AUTHORING_STANDARDS, SKILLS_GUIDANCE, SKILLS_GUIDANCE_SECTION_ORDER, SkillLibrary, authoringFeedback, clampedNumber, computeDedupGroups, contentHash, evolutionIoAdapter, parseFrontmatter, resolveOrigins, resolveSkillsRoot } from "@lmzhen/dsh-evolution-core";
|
|
4
|
+
import { DEFAULT_SKILL_LIMITS, DSH_AUTHORING_STANDARDS, SKILLS_GUIDANCE, SKILLS_GUIDANCE_SECTION_ORDER, SKILL_ACTION_REQUIRED_FIELDS, SkillLibrary, authoringFeedback, clampedNumber, computeDedupGroups, contentHash, evolutionIoAdapter, parseFrontmatter, resolveOrigins, resolveSkillsRoot } from "@lmzhen/dsh-evolution-core";
|
|
5
5
|
//#region lib/types/index.js
|
|
6
6
|
/**
|
|
7
7
|
* Model-facing skill_manage tool over ctx.evolutionIo + ctx.skillUsage.
|
|
@@ -35,7 +35,8 @@ const Config = z.object({
|
|
|
35
35
|
maxSkillContentChars: z.number().min(1).default(DEFAULT_SKILL_LIMITS.maxSkillContentChars),
|
|
36
36
|
maxSkillFileBytes: z.number().min(1).default(DEFAULT_SKILL_LIMITS.maxSkillFileBytes),
|
|
37
37
|
descriptionStrict: z.boolean().default(false),
|
|
38
|
-
threatExemptLabels: z.array(z.string()).default([])
|
|
38
|
+
threatExemptLabels: z.array(z.string()).default([]),
|
|
39
|
+
strictCrossSource: z.boolean().default(false)
|
|
39
40
|
});
|
|
40
41
|
/** v30 REV-02: read the protected-skill list off the (optional) policy
|
|
41
42
|
* snapshot through an `unknown` boundary — the Context augmentation types the
|
|
@@ -67,6 +68,15 @@ function apply(ctx, rawConfig = {}) {
|
|
|
67
68
|
ctx.emit("evolution/skill-mutated", event);
|
|
68
69
|
}, void 0, [...rawConfig.threatExemptLabels ?? []]);
|
|
69
70
|
if (numericClamped.length > 0) ctx.logger.warn(`tool-skill-manage: ${numericClamped.join(", ")} provided an invalid value; falling back to the default`);
|
|
71
|
+
const catalogWinner = async (name) => {
|
|
72
|
+
const catalog = ctx.get("skills");
|
|
73
|
+
if (catalog === void 0) return void 0;
|
|
74
|
+
try {
|
|
75
|
+
return (await catalog.list()).find((summary) => summary.name === name);
|
|
76
|
+
} catch {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
70
80
|
async function executeCore(args, origin = "foreground") {
|
|
71
81
|
const action = args.action;
|
|
72
82
|
const name = args.name ?? "";
|
|
@@ -121,26 +131,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
121
131
|
skills: []
|
|
122
132
|
};
|
|
123
133
|
}
|
|
124
|
-
const REQUIRED_ARGS =
|
|
125
|
-
create: ["name", "content"],
|
|
126
|
-
edit: ["name", "content"],
|
|
127
|
-
update: ["name", "content"],
|
|
128
|
-
patch: [
|
|
129
|
-
"name",
|
|
130
|
-
"old_string",
|
|
131
|
-
"new_string"
|
|
132
|
-
],
|
|
133
|
-
delete: ["name"],
|
|
134
|
-
write_file: [
|
|
135
|
-
"name",
|
|
136
|
-
"file_path",
|
|
137
|
-
"file_content"
|
|
138
|
-
],
|
|
139
|
-
remove_file: ["name", "file_path"],
|
|
140
|
-
restructure: ["name"],
|
|
141
|
-
pin: ["name"],
|
|
142
|
-
unpin: ["name"]
|
|
143
|
-
};
|
|
134
|
+
const REQUIRED_ARGS = SKILL_ACTION_REQUIRED_FIELDS;
|
|
144
135
|
const missing = (action === void 0 || typeof action !== "string" || !Object.hasOwn(REQUIRED_ARGS, action) ? [] : REQUIRED_ARGS[action] ?? []).filter((field) => scalarArgs[field] === void 0 || scalarArgs[field] === null);
|
|
145
136
|
if (missing.length > 0) return {
|
|
146
137
|
ok: false,
|
|
@@ -148,24 +139,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
148
139
|
skills: []
|
|
149
140
|
};
|
|
150
141
|
let feedbackLines = [];
|
|
151
|
-
|
|
152
|
-
const currentContent = await library.read(name).catch(() => null);
|
|
153
|
-
const actual = contentHash(currentContent ?? "");
|
|
154
|
-
if (currentContent === null || actual !== args.staged_from_sha256) return {
|
|
155
|
-
ok: false,
|
|
156
|
-
message: `Skill "${name}" changed after this write was staged (content ${currentContent === null ? "no longer exists" : "hash mismatch"}); refusing to replay the stale snapshot. Re-apply the edit to stage a fresh copy.`,
|
|
157
|
-
skills: []
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
if ((action === "write_file" || action === "remove_file") && typeof args.staged_from_sha256 === "string" && args.staged_from_sha256 !== "") {
|
|
161
|
-
const currentBytes = await library.readSupportFile(name, args.file_path ?? "").catch(() => void 0);
|
|
162
|
-
const actual = currentBytes === void 0 || currentBytes === null ? "absent" : contentHash(currentBytes);
|
|
163
|
-
if (currentBytes === void 0 || actual !== args.staged_from_sha256) return {
|
|
164
|
-
ok: false,
|
|
165
|
-
message: `Support file "${args.file_path ?? ""}" of "${name}" changed after this write was staged (or its state could not be verified); refusing to replay the stale snapshot. Re-stage the file operation.`,
|
|
166
|
-
skills: []
|
|
167
|
-
};
|
|
168
|
-
}
|
|
142
|
+
const stagedAnchor = typeof args.staged_from_sha256 === "string" && args.staged_from_sha256 !== "" ? args.staged_from_sha256 === "absent" ? { absent: true } : { sha256: args.staged_from_sha256 } : void 0;
|
|
169
143
|
if ((action === "create" || action === "edit" || action === "update") && args.content) {
|
|
170
144
|
const parsed = parseFrontmatter(args.content);
|
|
171
145
|
if (parsed) {
|
|
@@ -180,11 +154,11 @@ function apply(ctx, rawConfig = {}) {
|
|
|
180
154
|
}
|
|
181
155
|
let result;
|
|
182
156
|
if (action === "create") result = await library.create(name, args.content ?? "", origin);
|
|
183
|
-
else if (action === "edit" || action === "update") result = await library.update(name, args.content ?? "", origin);
|
|
157
|
+
else if (action === "edit" || action === "update") result = await library.update(name, args.content ?? "", origin, stagedAnchor);
|
|
184
158
|
else if (action === "patch") result = await library.patch(name, args.old_string ?? "", args.new_string ?? "", args.file_path ?? "", args.replace_all === true, origin);
|
|
185
159
|
else if (action === "delete") result = await library.archive(name, args.absorbed_into ? { absorbedInto: args.absorbed_into } : {});
|
|
186
|
-
else if (action === "write_file") result = await library.writeSupportFile(name, args.file_path ?? "", args.file_content ?? "", origin);
|
|
187
|
-
else if (action === "remove_file") result = await library.removeSupportFile(name, args.file_path ?? "", origin);
|
|
160
|
+
else if (action === "write_file") result = await library.writeSupportFile(name, args.file_path ?? "", args.file_content ?? "", origin, stagedAnchor);
|
|
161
|
+
else if (action === "remove_file") result = await library.removeSupportFile(name, args.file_path ?? "", origin, stagedAnchor);
|
|
188
162
|
else if (action === "restructure") {
|
|
189
163
|
const moves = Array.isArray(args.restructure) ? args.restructure : [];
|
|
190
164
|
if (moves.some((move) => {
|
|
@@ -204,6 +178,11 @@ function apply(ctx, rawConfig = {}) {
|
|
|
204
178
|
ok: false,
|
|
205
179
|
message: `Unknown action "${action}".`
|
|
206
180
|
};
|
|
181
|
+
if (result.stale === true) return {
|
|
182
|
+
ok: false,
|
|
183
|
+
message: action === "write_file" || action === "remove_file" ? `Support file "${args.file_path ?? ""}" of "${name}" changed after this write was staged (or its state could not be verified).` : `Skill "${name}" changed after this write was staged (content ${result.anchor === "missing" ? "no longer exists" : "hash mismatch"}).`,
|
|
184
|
+
skills: []
|
|
185
|
+
};
|
|
207
186
|
if (result.ok) {
|
|
208
187
|
if (result.normalizedFrontmatterFields && result.normalizedFrontmatterFields.length > 0) feedbackLines.push(`frontmatter auto-quoted (YAML compatibility): ${result.normalizedFrontmatterFields.join(", ")}`);
|
|
209
188
|
const mutating = action !== "list" && action !== "review" && action !== "pin" && action !== "unpin";
|
|
@@ -318,25 +297,44 @@ function apply(ctx, rawConfig = {}) {
|
|
|
318
297
|
},
|
|
319
298
|
isConcurrencySafe: () => false,
|
|
320
299
|
async execute(args, exec) {
|
|
300
|
+
let operation = args;
|
|
321
301
|
const origins = resolveOrigins(exec.agent?.session?.header.origin);
|
|
322
302
|
const reviewOrigin = origins.approval;
|
|
323
303
|
const libraryOrigin = origins.library;
|
|
324
304
|
const sessionPolicy = effectiveSessionPolicy(ctx, exec.agent?.session);
|
|
305
|
+
if (typeof args.name === "string" && args.name !== "" && args.action !== "list" && args.action !== "review" && args.action !== "pin" && args.action !== "unpin") {
|
|
306
|
+
const winner = await catalogWinner(args.name);
|
|
307
|
+
if (winner !== void 0 && winner.provider !== "dsh-evolution") {
|
|
308
|
+
const message = `skill "${args.name}" resolves to a higher-priority "${winner.source}" skill (provider "${winner.provider}"); this write lands on the family copy, which the catalog does NOT serve — edit the "${winner.source}" copy or rename.`;
|
|
309
|
+
if (rawConfig.strictCrossSource === true) return {
|
|
310
|
+
ok: false,
|
|
311
|
+
message: `Refused: ${message}`,
|
|
312
|
+
skills: []
|
|
313
|
+
};
|
|
314
|
+
ctx.logger.warn(`skill_manage: ${message}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
325
317
|
const approval = ctx.get("evolutionApproval");
|
|
326
318
|
if (approval && args.action !== "list" && args.action !== "review" && args.action !== "pin" && args.action !== "unpin") {
|
|
327
319
|
if ((args.action === "update" || args.action === "edit") && typeof args.name === "string" && args.name !== "") {
|
|
328
320
|
const stageCurrent = await library.read(args.name).catch(() => null);
|
|
329
|
-
if (stageCurrent !== null)
|
|
321
|
+
if (stageCurrent !== null) operation = {
|
|
322
|
+
...args,
|
|
323
|
+
staged_from_sha256: contentHash(stageCurrent)
|
|
324
|
+
};
|
|
330
325
|
}
|
|
331
326
|
if ((args.action === "write_file" || args.action === "remove_file") && typeof args.name === "string" && args.name !== "" && typeof args.file_path === "string" && args.file_path !== "") {
|
|
332
327
|
const stageFile = await library.readSupportFile(args.name, args.file_path).catch(() => void 0);
|
|
333
|
-
if (stageFile !== void 0)
|
|
328
|
+
if (stageFile !== void 0) operation = {
|
|
329
|
+
...args,
|
|
330
|
+
staged_from_sha256: stageFile === null ? "absent" : contentHash(stageFile)
|
|
331
|
+
};
|
|
334
332
|
}
|
|
335
333
|
const decision = await approval.request({
|
|
336
334
|
kind: "skill",
|
|
337
335
|
summary: `skill ${args.action ?? "?"} ${args.name ?? ""}`.trim(),
|
|
338
336
|
args: {
|
|
339
|
-
operation
|
|
337
|
+
operation,
|
|
340
338
|
origin: reviewOrigin,
|
|
341
339
|
libraryOrigin
|
|
342
340
|
},
|
|
@@ -352,7 +350,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
352
350
|
...decision.pendingId !== void 0 ? { pending_id: decision.pendingId } : {}
|
|
353
351
|
};
|
|
354
352
|
}
|
|
355
|
-
return await executeCore(
|
|
353
|
+
return await executeCore(operation, libraryOrigin);
|
|
356
354
|
}
|
|
357
355
|
}));
|
|
358
356
|
ctx.inject(["evolutionApproval"], (approvalCtx) => {
|
package/lib/types/index.d.ts
CHANGED
|
@@ -32,6 +32,11 @@ export interface Config {
|
|
|
32
32
|
* strict ANY-hit-blocks behavior is unchanged; deployments opt in per
|
|
33
33
|
* label for known false-positive content. */
|
|
34
34
|
threatExemptLabels?: string[];
|
|
35
|
+
/** OPT-19 (2026-09, plan D3): when true, a write whose name the platform
|
|
36
|
+
* catalog resolves to a NON-family source (project/custom sources outrank
|
|
37
|
+
* the family tree) is REFUSED instead of warned about. Default false —
|
|
38
|
+
* warn only, keeping the family tree an autonomous evolution zone. */
|
|
39
|
+
strictCrossSource?: boolean;
|
|
35
40
|
}
|
|
36
41
|
export declare const Config: z<Config>;
|
|
37
42
|
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.3.
|
|
4
|
+
"version": "0.3.72",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -31,24 +31,26 @@
|
|
|
31
31
|
"license": "MIT",
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
34
|
-
"@lmzhen/dsh-evolution-approval": "^0.3.
|
|
35
|
-
"@lmzhen/dsh-evolution-core": "^0.3.
|
|
34
|
+
"@lmzhen/dsh-evolution-approval": "^0.3.72",
|
|
35
|
+
"@lmzhen/dsh-evolution-core": "^0.3.72"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
39
39
|
"@deepseek-ai/dsh-invariants": "^0.1.5-rc.2",
|
|
40
|
+
"@deepseek-ai/dsh-skill": "^0.1.5-rc.2",
|
|
40
41
|
"@deepseek-ai/dsh-system-prompt": "^0.1.5-rc.2",
|
|
41
42
|
"@deepseek-ai/dsh-tools": "^0.1.5-rc.2",
|
|
42
|
-
"@lmzhen/dsh-evolution-io": "^0.3.
|
|
43
|
-
"@lmzhen/dsh-skill-usage": "^0.3.
|
|
43
|
+
"@lmzhen/dsh-evolution-io": "^0.3.72",
|
|
44
|
+
"@lmzhen/dsh-skill-usage": "^0.3.72"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
47
|
"@deepseek-ai/dsh-agent-loop-testkit": "^0.1.5-rc.2",
|
|
47
48
|
"@deepseek-ai/dsh-invariants": "^0.1.5-rc.2",
|
|
49
|
+
"@deepseek-ai/dsh-skill": "^0.1.5-rc.2",
|
|
48
50
|
"@deepseek-ai/dsh-system-prompt": "^0.1.5-rc.2",
|
|
49
51
|
"@deepseek-ai/dsh-tools": "^0.1.5-rc.2",
|
|
50
|
-
"@lmzhen/dsh-evolution-core": "^0.3.
|
|
51
|
-
"@lmzhen/dsh-evolution-io": "^0.3.
|
|
52
|
-
"@lmzhen/dsh-skill-usage": "^0.3.
|
|
52
|
+
"@lmzhen/dsh-evolution-core": "^0.3.72",
|
|
53
|
+
"@lmzhen/dsh-evolution-io": "^0.3.72",
|
|
54
|
+
"@lmzhen/dsh-skill-usage": "^0.3.72"
|
|
53
55
|
}
|
|
54
56
|
}
|