@lmzhen/dsh-tool-skill-manage 0.8.0 → 0.10.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/README.md +67 -2
- package/lib/index.js +358 -50
- package/lib/types/index.d.ts +4 -0
- package/lib/types/write-gates.d.ts +83 -0
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -6,15 +6,44 @@ library nor its lifecycle rules.
|
|
|
6
6
|
|
|
7
7
|
## Model surface
|
|
8
8
|
|
|
9
|
-
- **Model-visible:** the `skill_manage` tool schema and its success/validation messages; result tokens scale with what is returned.
|
|
9
|
+
- **Model-visible:** the `skill_manage` tool schema and its success/validation messages; result tokens scale with what is returned. The skills-guidance section (`evolution-skills-guidance`) ends with the policy's protected-skill list, carried by the prompt variable `evolution_skill_guard`: the provider runs at EVERY assembly (a policy change needs no reload), and an empty list leaves the section byte-identical to the guidance alone, so the guidance's prefix-cache behaviour is unchanged.
|
|
10
10
|
- **Prompt prefix / KV cache:** the tool schema is prefix-stable, and skill writes do not alter the current request prompt; catalog invalidation affects the next request; family rules: `packages/README.md` §"Model-visible prompt prefix and the KV cache".
|
|
11
11
|
- **Mount it?** yes — the `tool-skill-manage` row, carried by the `evolution-all` and one-click `evolution-preset` bundles and the Evolution agent preset delta.
|
|
12
12
|
|
|
13
13
|
## Safety model
|
|
14
14
|
|
|
15
|
+
### Write gates
|
|
16
|
+
|
|
17
|
+
Every mutation THROUGH THIS TOOL passes ONE ordered admission sequence (`src/write-gates.ts`): the
|
|
18
|
+
scalar argument shape, the required arguments per action, the policy's protected list,
|
|
19
|
+
read-before-write, and the operator confirmation. The
|
|
20
|
+
ADMISSION point runs the sequence before the approval seam, so a write that is refused is never
|
|
21
|
+
staged for approval and never spends the operator's attention; the EXECUTION point (`executeCore`)
|
|
22
|
+
re-runs the gates a replay or a direct write can still fail, because a replay's stored arguments
|
|
23
|
+
passed no schema and the protected list can change while a record sits pending. Both points read the
|
|
24
|
+
same table, so they cannot diverge.
|
|
25
|
+
|
|
26
|
+
The sequence covers the MODEL tool path. The family's other writers reach their own library
|
|
27
|
+
handles: the `/evolution` and `/graph` command face writes what the operator typed (the operator is
|
|
28
|
+
the authority there), the review's plan executor writes a plan its own filter already screened
|
|
29
|
+
(`filterUnreadSkillOps`), and a record staged outside this tool replays into the execution point
|
|
30
|
+
without re-deciding the admission-only gates — there is no writing session to read.
|
|
31
|
+
|
|
32
|
+
Read-before-write is admission-only (the replayed record has no writing session): a non-foreground
|
|
33
|
+
write whose target the session never read is refused with `E-318`, and only a read that did not fail
|
|
34
|
+
counts. A session log the tool cannot read proceeds with one warning rather than blocking every
|
|
35
|
+
autonomous write.
|
|
36
|
+
|
|
37
|
+
A foreground `create`, or a bare foreground `delete`, asks the operator once before it writes and is
|
|
38
|
+
refused with `E-317` when the answer is not the confirm label; a `delete` carrying `absorbed_into` is the
|
|
39
|
+
merge protocol and does not ask. This gate is UX, not a security door: it is admission-only (a
|
|
40
|
+
replayed record already carries the human release that staged it), and an unmounted question
|
|
41
|
+
service, a caller that is not the registry's exact live root agent, or a failing ask all PROCEED
|
|
42
|
+
with one warning — the operator's own session stays the authority that asked for the write.
|
|
43
|
+
|
|
15
44
|
### Approval seam
|
|
16
45
|
|
|
17
|
-
Mutations (create/edit/update/patch/delete/write_file/remove_file/restructure) pass through the evolution approval seam when `evolution-approval` is mounted; approved/staged writes are replayed by the registered runner with the library origin preserved. The
|
|
46
|
+
Mutations (create/edit/update/patch/delete/write_file/remove_file/restructure) pass through the evolution approval seam when `evolution-approval` is mounted; approved/staged writes are replayed by the registered runner with the library origin preserved. The admission gates run BEFORE that boundary, so a write that cannot execute is refused, never staged for approval.
|
|
18
47
|
|
|
19
48
|
### pin/unpin: explicit exception
|
|
20
49
|
|
|
@@ -29,6 +58,42 @@ Mutations (create/edit/update/patch/delete/write_file/remove_file/restructure) p
|
|
|
29
58
|
per user. Reading the legacy name still works; writing it is refused. Removal was planned for 0.7.0 and is deferred:
|
|
30
59
|
the alias still ships, so no later version is claimed here.
|
|
31
60
|
|
|
61
|
+
### Description length: the three numbers
|
|
62
|
+
|
|
63
|
+
A skill description is measured against three different bounds. They are not interchangeable, and
|
|
64
|
+
only the second one refuses a write:
|
|
65
|
+
|
|
66
|
+
| Bound | Value | Source | What it does |
|
|
67
|
+
|---|---|---|---|
|
|
68
|
+
| Authoring bar | 60 chars | `AUTHORING_DESCRIPTION_BAR` (the upstream 60-char rule) | advisory feedback on every create/edit/update; `descriptionStrict: true` turns it into a refusal |
|
|
69
|
+
| Family storage ceiling | 1024 chars | `MAX_DESCRIPTION_LENGTH`, lowered per deployment by this row's `maxDescriptionLength` | the hard validation limit — the ONLY refusal threshold of the three |
|
|
70
|
+
| Platform catalog view | 500 chars by default | `catalogDescriptionMaxLength` on the platform's `tool-skill` row (`PLATFORM_CATALOG_DESCRIPTION_DEFAULT`) | truncates the description in the skill catalog the model reads; the family's own rows set that field to 60 (`evolution-host`, `evolution-all` and `evolution-preset` each carry it, and the composer injects it onto a generated preset row too), so 500 governs only a composition that overrides none of them; `packages/README.md` states which of those rows takes effect per install form |
|
|
71
|
+
|
|
72
|
+
The third row is a view, not a rule: a description the family accepts can still be cut in a catalog
|
|
73
|
+
viewer, which is why the authoring feedback names the bar rather than treating the cut as a limit.
|
|
74
|
+
|
|
75
|
+
### The create-time duplicate hint
|
|
76
|
+
|
|
77
|
+
A `create` compares its candidate against the library listing before it writes, over the name +
|
|
78
|
+
description projection (core `nearDuplicateSummaries`, at `SUMMARY_DUPLICATE_HINT_THRESHOLD`), and
|
|
79
|
+
names the closest existing skills in the same "Authoring check" block. Three properties are
|
|
80
|
+
deliberate: it adds no read of its own (`library.list()` already reads every SKILL.md to parse
|
|
81
|
+
its frontmatter — that listing is what publishes `description` — and the hint never calls
|
|
82
|
+
`library.read`, never holds a body and never compares content); it is a HINT (the write proceeds
|
|
83
|
+
— only the model's next decision changes); and a listing failure degrades to a named line instead of
|
|
84
|
+
silence, because "nothing similar was checked" and "nothing similar exists" must not look alike. The
|
|
85
|
+
library-wide BODY scan stays where it was, in the `review` action.
|
|
86
|
+
|
|
87
|
+
### The retention line
|
|
88
|
+
|
|
89
|
+
A whole-body replacement that keeps less than half of the previous SKILL.md says so in the same result
|
|
90
|
+
message (`Content kept 26% of the previous body (2104 of 8123 characters); the replaced version is
|
|
91
|
+
preserved in this skill's history.`). It is computed where the write path still holds both bodies — the
|
|
92
|
+
library's own in-lock read, core `contentRetentionFeedback` — because a caller re-reading the file
|
|
93
|
+
would race the writers the library serializes. Like the duplicate hint it is FEEDBACK, not a gate: no ratio
|
|
94
|
+
refuses a write, small skills stay quiet (a 40-character skill cut in half is not news), and patches,
|
|
95
|
+
support-file writes, creates and archives have no ratio to report.
|
|
96
|
+
|
|
32
97
|
## Known limitations
|
|
33
98
|
|
|
34
99
|
- No known durable consumer gaps at this time. Runtime contracts are covered by package and boundary tests.
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,171 @@
|
|
|
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_ARCHIVE_RETENTION_POLICY, DEFAULT_CITATION_POLICY, DEFAULT_REFERENCE_REWRITE_POLICY, DEFAULT_SKILL_LIMITS, DEFAULT_SUPPORT_FILE_CHAR_POLICY, DSH_AUTHORING_STANDARDS, SKILLS_GUIDANCE, SKILLS_GUIDANCE_SECTION_ORDER, SKILL_ACTION_REQUIRED_FIELDS, authoringFeedback, callingScope, clampedNumber, computeDedupGroups, contentHash, evolutionIoAdapter, installParamSection, isPresent, isUnknown, newSkillLibrary, paramNamespace, parseFrontmatter, policyStageLimits, probePresent, probeUnknown, readNumberParam, resolveExecOrigins } from "@lmzhen/dsh-evolution-core";
|
|
4
|
+
import { DEFAULT_ARCHIVE_RETENTION_POLICY, DEFAULT_CITATION_POLICY, DEFAULT_REFERENCE_REWRITE_POLICY, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_VERSION_KEEP, DEFAULT_SUPPORT_FILE_CHAR_POLICY, DSH_AUTHORING_STANDARDS, SKILLS_GUIDANCE, SKILLS_GUIDANCE_SECTION_ORDER, SKILL_ACTION_REQUIRED_FIELDS, authoringFeedback, callingScope, clampedNumber, computeDedupGroups, contentHash, errorText, evolutionIoAdapter, installParamSection, isPresent, isUnknown, isUnreadWrite, nearDuplicateSummaries, newSkillLibrary, paramNamespace, parseFrontmatter, policyStageLimits, probePresent, probeUnknown, readNumberParam, resolveExecOrigins, sessionReadSkillNames } from "@lmzhen/dsh-evolution-core";
|
|
5
|
+
//#region lib/types/write-gates.js
|
|
6
|
+
/**
|
|
7
|
+
* The write-admission sequence: ONE ordered table of the reasons a skill write is refused.
|
|
8
|
+
*
|
|
9
|
+
* Two points read this table (design `dsh-evolution-write-gate-design.md` §3). The ADMISSION
|
|
10
|
+
* point (`execute`, BEFORE the approval seam) runs every gate that applies to `'admission'`, so a
|
|
11
|
+
* write that is refused is never staged and never spends the operator's attention twice. The
|
|
12
|
+
* EXECUTION point (the `executeCore` funnel, reached by the direct path and by the approval
|
|
13
|
+
* replay) runs the gates that apply to `'execution'`: the stored plan of a replayed write passed
|
|
14
|
+
* no schema, and the protected list can change while a record sits pending, so those verdicts are
|
|
15
|
+
* re-decided at the bytes.
|
|
16
|
+
*
|
|
17
|
+
* Order is part of the contract: a gate that can only refuse comes before one that needs a round
|
|
18
|
+
* trip with the operator, so a write that is refused anyway never asks anything.
|
|
19
|
+
*
|
|
20
|
+
* A gate decides whether a write may be ATTEMPTED. The library decides whether it can be
|
|
21
|
+
* PERFORMED — its own structured refusals (a restructure element that is not an object, a patch
|
|
22
|
+
* anchor that no longer matches, an `absorbed_into` that does not exist) stay where the bytes are
|
|
23
|
+
* read.
|
|
24
|
+
*
|
|
25
|
+
* Rule bodies live in `evolution-core` (the read-before-write rule in `skill-reads`, the
|
|
26
|
+
* required-argument table in `SKILL_ACTION_REQUIRED_FIELDS`); this module owns the ORDER and the
|
|
27
|
+
* applicability of each gate, which is an execution-point fact of this tool.
|
|
28
|
+
* @module @lmzhen/dsh-tool-skill-manage/write-gates
|
|
29
|
+
*/
|
|
30
|
+
/** The confirm question's stable id — one question per write, so an answer to an earlier prompt
|
|
31
|
+
* can never be read as the answer to a later one. */
|
|
32
|
+
const CONFIRM_QUESTION_ID = "evolution-skill-write";
|
|
33
|
+
/** The cancel label every confirm question offers. */
|
|
34
|
+
const CANCEL_LABEL = "Cancel";
|
|
35
|
+
/** The scalar fields the tool schema types as strings; the replay channel has no schema.
|
|
36
|
+
* `action` is one of them: a non-string action used to fall through to the library as "Unknown
|
|
37
|
+
* action" while the read gate re-defaulted it to `patch` and refused with E-318 (review
|
|
38
|
+
* 2026-09-26, P2), so the shape gate refuses it by name instead. */
|
|
39
|
+
const SCALAR_ARG_FIELDS = [
|
|
40
|
+
"action",
|
|
41
|
+
"name",
|
|
42
|
+
"content",
|
|
43
|
+
"old_string",
|
|
44
|
+
"new_string",
|
|
45
|
+
"file_path",
|
|
46
|
+
"file_content",
|
|
47
|
+
"absorbed_into"
|
|
48
|
+
];
|
|
49
|
+
/**
|
|
50
|
+
* Narrow the arguments once for every gate.
|
|
51
|
+
*
|
|
52
|
+
* The admission point reaches the gates AFTER the schema, the replay channel BEFORE any
|
|
53
|
+
* validation, so a gate reads only what it can prove: anything else counts as absent, exactly as
|
|
54
|
+
* the missing-argument check already treated a non-string action.
|
|
55
|
+
* @param args - the arguments as the caller sent them.
|
|
56
|
+
* @returns the narrowed view.
|
|
57
|
+
*/
|
|
58
|
+
function gateArgsOf(args) {
|
|
59
|
+
const scalars = typeof args === "object" && args !== null ? args : {};
|
|
60
|
+
const action = scalars.action;
|
|
61
|
+
const name = scalars.name;
|
|
62
|
+
const absorbedInto = scalars.absorbed_into;
|
|
63
|
+
return {
|
|
64
|
+
action: typeof action === "string" ? action : void 0,
|
|
65
|
+
name: typeof name === "string" ? name : "",
|
|
66
|
+
absorbedInto: typeof absorbedInto === "string" && absorbedInto.trim() !== "" ? absorbedInto : void 0,
|
|
67
|
+
scalars
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** The sequence, in order. A gate whose `appliesTo` omits the asking point is skipped. */
|
|
71
|
+
const GATES = [
|
|
72
|
+
{
|
|
73
|
+
id: "argument-shape",
|
|
74
|
+
appliesTo: ["admission", "execution"],
|
|
75
|
+
run: ({ view }) => {
|
|
76
|
+
for (const field of SCALAR_ARG_FIELDS) {
|
|
77
|
+
const value = view.scalars[field];
|
|
78
|
+
if (value === void 0 || value === null || typeof value === "string") continue;
|
|
79
|
+
return `skill_manage: "${field}" must be a string (got ${typeof value}); refusing the write.`;
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: "missing-args",
|
|
86
|
+
appliesTo: ["admission", "execution"],
|
|
87
|
+
run: ({ view }) => {
|
|
88
|
+
const action = view.action;
|
|
89
|
+
const missing = (action === void 0 || !Object.hasOwn(SKILL_ACTION_REQUIRED_FIELDS, action) ? [] : SKILL_ACTION_REQUIRED_FIELDS[action] ?? []).filter((field) => view.scalars[field] === void 0 || view.scalars[field] === null);
|
|
90
|
+
if (missing.length === 0) return null;
|
|
91
|
+
return `skill_manage ${action} requires ${missing.join(", ")}; the tool description lists the arguments per action.`;
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "policy-protection",
|
|
96
|
+
appliesTo: ["admission", "execution"],
|
|
97
|
+
run: ({ view, origin, protectedNames }) => {
|
|
98
|
+
if (origin === "foreground") return null;
|
|
99
|
+
if (view.name === "" || !protectedNames?.includes(view.name)) return null;
|
|
100
|
+
return `skill_manage: "${view.name}" is protected by the current policy (protectedSkillNames); replayed/autonomous writes are refused.`;
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: "read-before-write",
|
|
105
|
+
appliesTo: ["admission"],
|
|
106
|
+
run: ({ view, origin, readNames, warn }) => {
|
|
107
|
+
if (origin === "foreground") return null;
|
|
108
|
+
if (readNames === void 0) {
|
|
109
|
+
warn("skill_manage: the read-before-write check could not run for this call — the session log is not readable here, so the write is allowed.");
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
if (view.action === void 0) return null;
|
|
113
|
+
if (!isUnreadWrite({
|
|
114
|
+
action: view.action,
|
|
115
|
+
name: view.name
|
|
116
|
+
}, readNames)) return null;
|
|
117
|
+
return errorText("e-318-skill-write-without-a-read", { a1: view.name });
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: "human-confirm",
|
|
122
|
+
appliesTo: ["admission"],
|
|
123
|
+
run: async ({ view, origin, confirm, warn }) => {
|
|
124
|
+
const action = view.action;
|
|
125
|
+
if (action === void 0 || origin !== "foreground" || view.name === "") return null;
|
|
126
|
+
if (!(action === "create" || action === "delete" && view.absorbedInto === void 0)) return null;
|
|
127
|
+
if (confirm === void 0) {
|
|
128
|
+
warn("skill_manage: no confirmation channel is mounted, so the write proceeds unconfirmed.");
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const confirmLabel = action === "create" ? "Create" : "Delete";
|
|
132
|
+
if (await confirm({
|
|
133
|
+
action,
|
|
134
|
+
name: view.name,
|
|
135
|
+
confirmLabel,
|
|
136
|
+
question: {
|
|
137
|
+
id: CONFIRM_QUESTION_ID,
|
|
138
|
+
header: "Confirm",
|
|
139
|
+
question: action === "create" ? `Create skill "${view.name}"? A new skill directory is written into the family tree.` : `Delete skill "${view.name}"? It is archived under .archive and leaves the catalog.`,
|
|
140
|
+
options: [{ label: confirmLabel }, { label: CANCEL_LABEL }]
|
|
141
|
+
}
|
|
142
|
+
})) return null;
|
|
143
|
+
return errorText("e-317-skill-write-not-confirmed", {
|
|
144
|
+
a1: view.name,
|
|
145
|
+
a2: action === "create" ? "created" : "deleted"
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
];
|
|
150
|
+
/**
|
|
151
|
+
* Run the sequence for one write attempt.
|
|
152
|
+
* @param context - the asking point, the write, and the seams the human-facing gate needs.
|
|
153
|
+
* @returns the FIRST refusal, or null when every applicable gate passed. Both points return the
|
|
154
|
+
* message as the tool result unchanged, so the two entries refuse with identical wording.
|
|
155
|
+
*/
|
|
156
|
+
async function runWriteGates(context) {
|
|
157
|
+
const input = {
|
|
158
|
+
...context,
|
|
159
|
+
view: gateArgsOf(context.args)
|
|
160
|
+
};
|
|
161
|
+
for (const gate of GATES) {
|
|
162
|
+
if (!gate.appliesTo.includes(context.point)) continue;
|
|
163
|
+
const refusal = await gate.run(input);
|
|
164
|
+
if (refusal !== null) return refusal;
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
//#endregion
|
|
5
169
|
//#region lib/types/index.js
|
|
6
170
|
/**
|
|
7
171
|
* Model-facing skill_manage tool over ctx.evolutionIo + ctx.skillUsage.
|
|
@@ -31,6 +195,16 @@ const inject = [
|
|
|
31
195
|
/** 0.3.18 (E-70): review output lists at most this many near-duplicate groups
|
|
32
196
|
* (a cap, not a per-group limit) — the one named bound for the slice below. */
|
|
33
197
|
const MAX_DEDUP_GROUPS_IN_REVIEW = 3;
|
|
198
|
+
/** B4 (design §4.3): the prompt variable that carries the protected-skill list into the guidance
|
|
199
|
+
* section. Names must match the platform's `[a-z][a-z0-9_]*` reference syntax. */
|
|
200
|
+
const PROTECTED_SKILLS_VARIABLE = "evolution_skill_guard";
|
|
201
|
+
/** Names shown inline before the line collapses to a count — the guidance is in EVERY request, so
|
|
202
|
+
* the line is bounded even when the policy protects a long list. */
|
|
203
|
+
const PROTECTED_SKILLS_INLINE_MAX = 12;
|
|
204
|
+
/** The guidance section's text: `SKILLS_GUIDANCE` followed by the variable reference. Composed HERE
|
|
205
|
+
* rather than inside the core constant, because the review prompts share that constant and no
|
|
206
|
+
* variable is registered for them. */
|
|
207
|
+
const GUIDANCE_WITH_GUARD = `${SKILLS_GUIDANCE}{{${PROTECTED_SKILLS_VARIABLE}}}`;
|
|
34
208
|
const Config = z.object({
|
|
35
209
|
root: z.string().default(""),
|
|
36
210
|
maxSkillNameLength: z.number().min(1).default(DEFAULT_SKILL_LIMITS.maxNameLength),
|
|
@@ -39,7 +213,8 @@ const Config = z.object({
|
|
|
39
213
|
maxSkillFileBytes: z.number().min(1).default(DEFAULT_SKILL_LIMITS.maxSkillFileBytes),
|
|
40
214
|
descriptionStrict: z.boolean().default(false),
|
|
41
215
|
threatExemptLabels: z.array(z.string()).default([]),
|
|
42
|
-
strictCrossSource: z.boolean().default(false)
|
|
216
|
+
strictCrossSource: z.boolean().default(false),
|
|
217
|
+
skillVersionKeep: z.number().min(1).default(DEFAULT_SKILL_LIMITS.versionKeep ?? DEFAULT_SKILL_VERSION_KEEP)
|
|
43
218
|
});
|
|
44
219
|
/** Schema the platform validates the user layer against; defaults mirror the core
|
|
45
220
|
* constants and the row schema, so an empty document resolves to today's behaviour. */
|
|
@@ -70,31 +245,126 @@ const SKILL_SETTINGS_CAPS = [
|
|
|
70
245
|
function validateSkillSettings(value, ceilings) {
|
|
71
246
|
for (const key of SKILL_SETTINGS_CAPS) if (value[key] > ceilings[key]) throw new Error(`${key} may only be tightened: ${value[key]} exceeds the deployment value ${ceilings[key]}`);
|
|
72
247
|
}
|
|
248
|
+
/** The platform error code a question-service failure carries, when it carries one. */
|
|
249
|
+
function questionErrorCode(error) {
|
|
250
|
+
const code = error?.code;
|
|
251
|
+
return typeof code === "string" ? code : void 0;
|
|
252
|
+
}
|
|
253
|
+
/** The message of a caught platform error, for the one warning a degraded gate emits. */
|
|
254
|
+
function reasonOfError(error) {
|
|
255
|
+
return error instanceof Error ? error.message : String(error);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* The registry's live ROOT agent for this call's session, or `undefined`.
|
|
259
|
+
*
|
|
260
|
+
* The registry keys agents by their shared agent/session id (platform `agent/src/index.ts:567`), and
|
|
261
|
+
* the platform's `ask()` accepts only an agent that is BOTH its exact live instance and a root
|
|
262
|
+
* (platform `user-questions/src/index.ts:96-106`). An id that resolves to an agent owned by another
|
|
263
|
+
* agent is not a root: the caller then proceeds unconfirmed rather than blocking a human question on
|
|
264
|
+
* an agent no human is watching.
|
|
265
|
+
* @param probe - the context, read through the optional-service probe.
|
|
266
|
+
* @param exec - this call's execution context.
|
|
267
|
+
* @returns the live root agent, when this session has one.
|
|
268
|
+
*/
|
|
269
|
+
function liveRootAgentOf(probe, exec) {
|
|
270
|
+
const agents = probe.get("agents");
|
|
271
|
+
const sessionId = exec.agent?.session?.id;
|
|
272
|
+
if (agents === void 0 || typeof sessionId !== "string") return void 0;
|
|
273
|
+
const candidate = agents.get(sessionId);
|
|
274
|
+
return candidate !== void 0 && agents.roots().includes(candidate) ? candidate : void 0;
|
|
275
|
+
}
|
|
276
|
+
/** The one thing the operator's answer decides: was the confirm label selected? */
|
|
277
|
+
function confirmedBy(answer, questionId, confirmLabel) {
|
|
278
|
+
return (answer.answers.find((entry) => entry.id === questionId)?.selected ?? []).includes(confirmLabel);
|
|
279
|
+
}
|
|
73
280
|
/** v30 REV-02: read the protected-skill list off the (optional) policy
|
|
74
281
|
* snapshot through an `unknown` boundary — the Context augmentation types the
|
|
75
282
|
* getter non-optionally, but at runtime the row can be absent. */
|
|
76
283
|
function policySnapshotOf(source) {
|
|
77
284
|
return source?.get?.();
|
|
78
285
|
}
|
|
79
|
-
function missingRequiredArgs(args) {
|
|
80
|
-
const action = args.action;
|
|
81
|
-
const scalarArgs = args;
|
|
82
|
-
return (action === void 0 || typeof action !== "string" || !Object.hasOwn(SKILL_ACTION_REQUIRED_FIELDS, action) ? [] : SKILL_ACTION_REQUIRED_FIELDS[action] ?? []).filter((field) => scalarArgs[field] === void 0 || scalarArgs[field] === null);
|
|
83
|
-
}
|
|
84
|
-
function missingArgsRefusal(action, missing) {
|
|
85
|
-
return {
|
|
86
|
-
ok: false,
|
|
87
|
-
message: `skill_manage ${action} requires ${missing.join(", ")}; the tool description lists the arguments per action.`,
|
|
88
|
-
skills: []
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
286
|
function apply(ctx, rawConfig = {}) {
|
|
92
287
|
const systemPrompt = ctx.get("systemPrompt");
|
|
93
|
-
if (systemPrompt) ctx.effect(() =>
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
288
|
+
if (systemPrompt) ctx.effect(() => {
|
|
289
|
+
const disposeVariable = systemPrompt.variable(PROTECTED_SKILLS_VARIABLE, () => protectedSkillsLine());
|
|
290
|
+
const disposeSection = systemPrompt.section({
|
|
291
|
+
name: "evolution-skills-guidance",
|
|
292
|
+
order: SKILLS_GUIDANCE_SECTION_ORDER,
|
|
293
|
+
text: GUIDANCE_WITH_GUARD
|
|
294
|
+
});
|
|
295
|
+
return () => {
|
|
296
|
+
disposeSection();
|
|
297
|
+
disposeVariable();
|
|
298
|
+
};
|
|
299
|
+
}, "tool-skill-manage.skills-guidance");
|
|
300
|
+
/** The policy snapshot's protected list, or `undefined` when the row is not mounted — read by
|
|
301
|
+
* the write gates at BOTH points, through the one unknown-boundary helper above. */
|
|
302
|
+
const protectedSkillNamesOf = () => policySnapshotOf(ctx.get("evolutionPolicy"))?.protectedSkillNames;
|
|
303
|
+
/**
|
|
304
|
+
* The protected-skill line appended to the skills guidance (B4, design §4.3).
|
|
305
|
+
*
|
|
306
|
+
* The list is otherwise visible only to the REVIEW prompts, so the tool-path model could learn a
|
|
307
|
+
* name was protected only by having a write refused — this line is what lets it avoid the call.
|
|
308
|
+
* It returns `''` — never `undefined`, which the platform refuses to render — when no policy
|
|
309
|
+
* row is mounted or nothing is protected, and the empty value leaves the section byte-identical
|
|
310
|
+
* to the guidance alone, so the guidance's prefix-cache stability is untouched.
|
|
311
|
+
* @returns the line including the newline that joins it to the guidance.
|
|
312
|
+
*/
|
|
313
|
+
const protectedSkillsLine = () => {
|
|
314
|
+
try {
|
|
315
|
+
const names = protectedSkillNamesOf() ?? [];
|
|
316
|
+
if (names.length === 0) return "";
|
|
317
|
+
const shown = names.slice(0, PROTECTED_SKILLS_INLINE_MAX);
|
|
318
|
+
const rest = names.length - shown.length;
|
|
319
|
+
return `\nProtected skills (do not edit): ${shown.join(", ")}${rest > 0 ? `, and ${rest} more` : ""}`;
|
|
320
|
+
} catch {
|
|
321
|
+
return "";
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
let writeGateWarned = false;
|
|
325
|
+
const warnWriteGateOnce = (message) => {
|
|
326
|
+
if (writeGateWarned) return;
|
|
327
|
+
writeGateWarned = true;
|
|
328
|
+
ctx.logger.warn(message);
|
|
329
|
+
};
|
|
330
|
+
const probe = ctx;
|
|
331
|
+
let confirmWarned = false;
|
|
332
|
+
const confirmUnavailable = (reason) => {
|
|
333
|
+
if (!confirmWarned) {
|
|
334
|
+
confirmWarned = true;
|
|
335
|
+
ctx.logger.warn(`skill_manage: the confirmation prompt could not be shown (${reason}) — the write proceeds unconfirmed.`);
|
|
336
|
+
}
|
|
337
|
+
return true;
|
|
338
|
+
};
|
|
339
|
+
const isDismissed = (error) => questionErrorCode(error) === "ASK_ABORTED";
|
|
340
|
+
const confirmSkillWrite = async (request, exec) => {
|
|
341
|
+
const questions = probe.get("userQuestions");
|
|
342
|
+
if (questions === void 0) return confirmUnavailable("no user-questions service is mounted");
|
|
343
|
+
const ask = (agent) => questions.ask({
|
|
344
|
+
questions: [{
|
|
345
|
+
id: request.question.id,
|
|
346
|
+
header: request.question.header,
|
|
347
|
+
question: request.question.question,
|
|
348
|
+
options: request.question.options.map((option) => ({ label: option.label }))
|
|
349
|
+
}],
|
|
350
|
+
agent,
|
|
351
|
+
...exec.signal !== void 0 ? { signal: exec.signal } : {}
|
|
352
|
+
});
|
|
353
|
+
const declared = exec.agent;
|
|
354
|
+
if (declared !== void 0) try {
|
|
355
|
+
return confirmedBy(await ask(declared), request.question.id, request.confirmLabel);
|
|
356
|
+
} catch (error) {
|
|
357
|
+
if (isDismissed(error)) return false;
|
|
358
|
+
if (questionErrorCode(error) !== "CALLER_NOT_LIVE") return confirmUnavailable(reasonOfError(error));
|
|
359
|
+
}
|
|
360
|
+
const located = liveRootAgentOf(probe, exec);
|
|
361
|
+
if (located === void 0) return confirmUnavailable("the calling session has no live root agent");
|
|
362
|
+
try {
|
|
363
|
+
return confirmedBy(await ask(located), request.question.id, request.confirmLabel);
|
|
364
|
+
} catch (error) {
|
|
365
|
+
return isDismissed(error) ? false : confirmUnavailable(reasonOfError(error));
|
|
366
|
+
}
|
|
367
|
+
};
|
|
98
368
|
const io = evolutionIoAdapter(() => ctx.evolutionIo.provider());
|
|
99
369
|
const numericClamped = [];
|
|
100
370
|
const limit = (name, value, fallback) => {
|
|
@@ -107,7 +377,8 @@ function apply(ctx, rawConfig = {}) {
|
|
|
107
377
|
maxNameLength: limit("maxSkillNameLength", rawConfig.maxSkillNameLength, DEFAULT_SKILL_LIMITS.maxNameLength),
|
|
108
378
|
maxDescriptionLength: limit("maxDescriptionLength", rawConfig.maxDescriptionLength, DEFAULT_SKILL_LIMITS.maxDescriptionLength),
|
|
109
379
|
maxSkillContentChars: limit("maxSkillContentChars", readNumberParam(rawConfig, "skillContentChars"), DEFAULT_SKILL_LIMITS.maxSkillContentChars),
|
|
110
|
-
maxSkillFileBytes: limit("maxSkillFileBytes", rawConfig.maxSkillFileBytes, DEFAULT_SKILL_LIMITS.maxSkillFileBytes)
|
|
380
|
+
maxSkillFileBytes: limit("maxSkillFileBytes", rawConfig.maxSkillFileBytes, DEFAULT_SKILL_LIMITS.maxSkillFileBytes),
|
|
381
|
+
versionKeep: limit("skillVersionKeep", rawConfig.skillVersionKeep, DEFAULT_SKILL_LIMITS.versionKeep ?? DEFAULT_SKILL_VERSION_KEEP)
|
|
111
382
|
};
|
|
112
383
|
const library = newSkillLibrary({
|
|
113
384
|
config: rawConfig,
|
|
@@ -238,38 +509,28 @@ function apply(ctx, rawConfig = {}) {
|
|
|
238
509
|
skills: []
|
|
239
510
|
};
|
|
240
511
|
}
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
skills: []
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
if (origin !== "foreground") {
|
|
259
|
-
if ((policySnapshotOf(ctx.get("evolutionPolicy"))?.protectedSkillNames)?.includes(name)) return {
|
|
260
|
-
ok: false,
|
|
261
|
-
message: `skill_manage: "${name}" is protected by the current policy (protectedSkillNames); replayed/autonomous writes are refused.`,
|
|
262
|
-
skills: []
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
const missing = missingRequiredArgs(args);
|
|
266
|
-
if (missing.length > 0) return missingArgsRefusal(action, missing);
|
|
512
|
+
const refusal = await runWriteGates({
|
|
513
|
+
point: "execution",
|
|
514
|
+
args,
|
|
515
|
+
origin,
|
|
516
|
+
protectedNames: protectedSkillNamesOf(),
|
|
517
|
+
readNames: void 0,
|
|
518
|
+
confirm: void 0,
|
|
519
|
+
warn: warnWriteGateOnce
|
|
520
|
+
});
|
|
521
|
+
if (refusal !== null) return {
|
|
522
|
+
ok: false,
|
|
523
|
+
message: refusal,
|
|
524
|
+
skills: []
|
|
525
|
+
};
|
|
267
526
|
let feedbackLines = [];
|
|
268
527
|
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;
|
|
528
|
+
let candidateDescription = "";
|
|
269
529
|
if ((action === "create" || action === "edit" || action === "update") && args.content) {
|
|
270
530
|
const parsed = parseFrontmatter(args.content);
|
|
271
531
|
if (parsed) {
|
|
272
532
|
const feedback = authoringFeedback(parsed.frontmatter);
|
|
533
|
+
candidateDescription = parsed.frontmatter.description ?? "";
|
|
273
534
|
feedbackLines = feedback.lines;
|
|
274
535
|
if (settings().descriptionStrict && feedback.over60) return {
|
|
275
536
|
ok: false,
|
|
@@ -279,8 +540,10 @@ function apply(ctx, rawConfig = {}) {
|
|
|
279
540
|
}
|
|
280
541
|
}
|
|
281
542
|
let result;
|
|
282
|
-
if (action === "create")
|
|
283
|
-
|
|
543
|
+
if (action === "create") {
|
|
544
|
+
feedbackLines.push(...await duplicateHintLines(name, candidateDescription));
|
|
545
|
+
result = await library.create(name, args.content ?? "", origin);
|
|
546
|
+
} else if (action === "edit" || action === "update") result = await library.update(name, args.content ?? "", origin, stagedAnchor);
|
|
284
547
|
else if (action === "patch") result = await library.patch(name, args.old_string ?? "", args.new_string ?? "", args.file_path ?? "", args.replace_all === true, origin);
|
|
285
548
|
else if (action === "delete") result = await library.archive(name, args.absorbed_into ? { absorbedInto: args.absorbed_into } : {});
|
|
286
549
|
else if (action === "write_file") result = await library.writeSupportFile(name, args.file_path ?? "", args.file_content ?? "", origin, stagedAnchor);
|
|
@@ -322,6 +585,39 @@ function apply(ctx, rawConfig = {}) {
|
|
|
322
585
|
skills: []
|
|
323
586
|
};
|
|
324
587
|
}
|
|
588
|
+
/** Batch C (2026-09-27, design §3C-2): the create-time near-duplicate hint — one
|
|
589
|
+
* line naming the existing skill(s) this candidate is about to near-copy, over
|
|
590
|
+
* core's `summary` projection (name + description). Three properties are the
|
|
591
|
+
* design's, not stylistic choices:
|
|
592
|
+
*
|
|
593
|
+
* - It adds NO read of its own. `library.list()` reads each SKILL.md to parse its
|
|
594
|
+
* frontmatter — that listing is what publishes `description` — and the hint never
|
|
595
|
+
* calls `library.read`, never holds a body and never compares content, so a create
|
|
596
|
+
* pays no SECOND whole-tree pass for it.
|
|
597
|
+
* - It is a HINT: the write proceeds either way (G4 — create's foreground
|
|
598
|
+
* behavior is unchanged). The model gets the news, not a refusal.
|
|
599
|
+
* - A failed listing degrades to a NAMED line, never to silence: "nothing
|
|
600
|
+
* similar was checked" and "nothing similar exists" must not look alike
|
|
601
|
+
* (the PLAN-R2 P2-5 posture the review path already takes).
|
|
602
|
+
*
|
|
603
|
+
* The level and the math live in core (`nearDuplicateSummaries`,
|
|
604
|
+
* `SUMMARY_DUPLICATE_HINT_THRESHOLD`) — this function owns the wording only. */
|
|
605
|
+
async function duplicateHintLines(name, description) {
|
|
606
|
+
let matches;
|
|
607
|
+
try {
|
|
608
|
+
matches = nearDuplicateSummaries({
|
|
609
|
+
candidate: {
|
|
610
|
+
name,
|
|
611
|
+
description
|
|
612
|
+
},
|
|
613
|
+
existing: await library.list()
|
|
614
|
+
});
|
|
615
|
+
} catch (error) {
|
|
616
|
+
return [`Duplicate check skipped: the skill listing failed (${error instanceof Error ? error.message : String(error)}).`];
|
|
617
|
+
}
|
|
618
|
+
if (matches.length === 0) return [];
|
|
619
|
+
return [`Near-duplicate of existing skill(s): ${matches.map((match) => `${match.name} (${match.score.toFixed(2)})`).join(", ")} — prefer patch/update on that skill over adding a near-copy.`];
|
|
620
|
+
}
|
|
325
621
|
async function buildSkillReviewText() {
|
|
326
622
|
const list = await library.list();
|
|
327
623
|
const report = await ctx.skillUsage.report();
|
|
@@ -445,8 +741,20 @@ function apply(ctx, rawConfig = {}) {
|
|
|
445
741
|
skills: []
|
|
446
742
|
};
|
|
447
743
|
}
|
|
448
|
-
const
|
|
449
|
-
|
|
744
|
+
const refusal = await runWriteGates({
|
|
745
|
+
point: "admission",
|
|
746
|
+
args,
|
|
747
|
+
origin: libraryOrigin,
|
|
748
|
+
protectedNames: protectedSkillNamesOf(),
|
|
749
|
+
readNames: sessionReadSkillNames(exec.agent?.session),
|
|
750
|
+
confirm: async (request) => confirmSkillWrite(request, exec),
|
|
751
|
+
warn: warnWriteGateOnce
|
|
752
|
+
});
|
|
753
|
+
if (refusal !== null) return {
|
|
754
|
+
ok: false,
|
|
755
|
+
message: refusal,
|
|
756
|
+
skills: []
|
|
757
|
+
};
|
|
450
758
|
const approval = ctx.get("evolutionApproval");
|
|
451
759
|
if (approval && args.action !== "list" && args.action !== "review" && args.action !== "pin" && args.action !== "unpin") {
|
|
452
760
|
if ((args.action === "update" || args.action === "edit") && typeof args.name === "string" && args.name !== "") {
|
package/lib/types/index.d.ts
CHANGED
|
@@ -46,6 +46,10 @@ export interface Config {
|
|
|
46
46
|
* the family tree) is REFUSED instead of warned about. Default false —
|
|
47
47
|
* warn only, keeping the family tree an autonomous evolution zone. */
|
|
48
48
|
strictCrossSource?: boolean;
|
|
49
|
+
/** Content versions retained per skill (skill-history.ts). E2: a deployment value on this row,
|
|
50
|
+
* like the four caps above — the settings layer deliberately has no card for it (retention is
|
|
51
|
+
* storage policy, not an authoring knob). */
|
|
52
|
+
skillVersionKeep?: number;
|
|
49
53
|
}
|
|
50
54
|
export declare const Config: z<Config>;
|
|
51
55
|
/** Write behaviour a user may change (G3/S3.4). Field names are the CANONICAL
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The write-admission sequence: ONE ordered table of the reasons a skill write is refused.
|
|
3
|
+
*
|
|
4
|
+
* Two points read this table (design `dsh-evolution-write-gate-design.md` §3). The ADMISSION
|
|
5
|
+
* point (`execute`, BEFORE the approval seam) runs every gate that applies to `'admission'`, so a
|
|
6
|
+
* write that is refused is never staged and never spends the operator's attention twice. The
|
|
7
|
+
* EXECUTION point (the `executeCore` funnel, reached by the direct path and by the approval
|
|
8
|
+
* replay) runs the gates that apply to `'execution'`: the stored plan of a replayed write passed
|
|
9
|
+
* no schema, and the protected list can change while a record sits pending, so those verdicts are
|
|
10
|
+
* re-decided at the bytes.
|
|
11
|
+
*
|
|
12
|
+
* Order is part of the contract: a gate that can only refuse comes before one that needs a round
|
|
13
|
+
* trip with the operator, so a write that is refused anyway never asks anything.
|
|
14
|
+
*
|
|
15
|
+
* A gate decides whether a write may be ATTEMPTED. The library decides whether it can be
|
|
16
|
+
* PERFORMED — its own structured refusals (a restructure element that is not an object, a patch
|
|
17
|
+
* anchor that no longer matches, an `absorbed_into` that does not exist) stay where the bytes are
|
|
18
|
+
* read.
|
|
19
|
+
*
|
|
20
|
+
* Rule bodies live in `evolution-core` (the read-before-write rule in `skill-reads`, the
|
|
21
|
+
* required-argument table in `SKILL_ACTION_REQUIRED_FIELDS`); this module owns the ORDER and the
|
|
22
|
+
* applicability of each gate, which is an execution-point fact of this tool.
|
|
23
|
+
* @module @lmzhen/dsh-tool-skill-manage/write-gates
|
|
24
|
+
*/
|
|
25
|
+
import { type WriteOrigin } from '@lmzhen/dsh-evolution-core';
|
|
26
|
+
/** The point whose verdict is being taken. */
|
|
27
|
+
export type WriteGatePoint = 'admission' | 'execution';
|
|
28
|
+
/** One confirm question, as the gate writes it and the seam asks it. */
|
|
29
|
+
export interface WriteConfirmRequest {
|
|
30
|
+
/** The action being confirmed (`create` or `delete`). */
|
|
31
|
+
readonly action: string;
|
|
32
|
+
/** The skill the write would create or archive. */
|
|
33
|
+
readonly name: string;
|
|
34
|
+
/** The question for the operator: the options are the confirm label, then the cancel label. */
|
|
35
|
+
readonly question: {
|
|
36
|
+
readonly id: string;
|
|
37
|
+
readonly header: string;
|
|
38
|
+
readonly question: string;
|
|
39
|
+
readonly options: readonly {
|
|
40
|
+
readonly label: string;
|
|
41
|
+
}[];
|
|
42
|
+
};
|
|
43
|
+
/** The option label that means "proceed". */
|
|
44
|
+
readonly confirmLabel: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Ask the human to confirm one irreversible skill write.
|
|
48
|
+
*
|
|
49
|
+
* MUST be total: an implementation resolves every unavailability — no question service, no live root
|
|
50
|
+
* agent, a failing ask — to `true`, because this gate is an operator convenience and no failure of the
|
|
51
|
+
* question service may block the operator's own write. `false` means the human answered something
|
|
52
|
+
* other than the confirm label.
|
|
53
|
+
* @param request - the question to put to the operator and the label that means "proceed".
|
|
54
|
+
* @returns true to proceed with the write.
|
|
55
|
+
*/
|
|
56
|
+
export type WriteConfirm = (request: WriteConfirmRequest) => Promise<boolean>;
|
|
57
|
+
/** What every gate reads. */
|
|
58
|
+
export interface WriteGateContext {
|
|
59
|
+
/** The point asking: the tool's admission of a call, or its execution at the bytes. */
|
|
60
|
+
readonly point: WriteGatePoint;
|
|
61
|
+
/** The parsed (admission) or stored (replay) arguments — unvalidated, hence the shape gate. */
|
|
62
|
+
readonly args: unknown;
|
|
63
|
+
/** The library write origin: `foreground` is the operator's own session. */
|
|
64
|
+
readonly origin: WriteOrigin;
|
|
65
|
+
/** The policy snapshot's protected list; `undefined` when no policy row is mounted. */
|
|
66
|
+
readonly protectedNames: readonly string[] | undefined;
|
|
67
|
+
/** Names this session read successfully; `undefined` when the session log is not readable. */
|
|
68
|
+
readonly readNames: ReadonlySet<string> | undefined;
|
|
69
|
+
/** The human confirm seam — read only by the gates that apply to `'admission'`. */
|
|
70
|
+
readonly confirm: WriteConfirm | undefined;
|
|
71
|
+
/** Report a degraded gate; must not throw. The implementation decides how often it speaks — the
|
|
72
|
+
* shipped seam latches once per PROCESS, because the conditions it reports (no question service,
|
|
73
|
+
* an unreadable session log) belong to the deployment, not to one write. */
|
|
74
|
+
readonly warn: (message: string) => void;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Run the sequence for one write attempt.
|
|
78
|
+
* @param context - the asking point, the write, and the seams the human-facing gate needs.
|
|
79
|
+
* @returns the FIRST refusal, or null when every applicable gate passed. Both points return the
|
|
80
|
+
* message as the tool result unchanged, so the two entries refuse with identical wording.
|
|
81
|
+
*/
|
|
82
|
+
export declare function runWriteGates(context: WriteGateContext): Promise<string | null>;
|
|
83
|
+
//# sourceMappingURL=write-gates.d.ts.map
|
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.
|
|
4
|
+
"version": "0.10.0",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -27,24 +27,24 @@
|
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
30
|
-
"@lmzhen/dsh-evolution-approval": "^0.
|
|
31
|
-
"@lmzhen/dsh-evolution-core": "^0.
|
|
30
|
+
"@lmzhen/dsh-evolution-approval": "^0.10.0",
|
|
31
|
+
"@lmzhen/dsh-evolution-core": "^0.10.0"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
35
35
|
"@deepseek-ai/dsh-skill": "^0.1.5-rc.2",
|
|
36
36
|
"@deepseek-ai/dsh-system-prompt": "^0.1.5-rc.2",
|
|
37
37
|
"@deepseek-ai/dsh-tools": "^0.1.5-rc.2",
|
|
38
|
-
"@lmzhen/dsh-evolution-io": "^0.
|
|
39
|
-
"@lmzhen/dsh-skill-usage": "^0.
|
|
38
|
+
"@lmzhen/dsh-evolution-io": "^0.10.0",
|
|
39
|
+
"@lmzhen/dsh-skill-usage": "^0.10.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@deepseek-ai/dsh-agent-loop-testkit": "^0.1.5-rc.2",
|
|
43
43
|
"@deepseek-ai/dsh-skill": "^0.1.5-rc.2",
|
|
44
44
|
"@deepseek-ai/dsh-system-prompt": "^0.1.5-rc.2",
|
|
45
45
|
"@deepseek-ai/dsh-tools": "^0.1.5-rc.2",
|
|
46
|
-
"@lmzhen/dsh-evolution-core": "^0.
|
|
47
|
-
"@lmzhen/dsh-evolution-io": "^0.
|
|
48
|
-
"@lmzhen/dsh-skill-usage": "^0.
|
|
46
|
+
"@lmzhen/dsh-evolution-core": "^0.10.0",
|
|
47
|
+
"@lmzhen/dsh-evolution-io": "^0.10.0",
|
|
48
|
+
"@lmzhen/dsh-skill-usage": "^0.10.0"
|
|
49
49
|
}
|
|
50
50
|
}
|