@lmzhen/dsh-evolution-review 0.3.67 → 0.3.69
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 +185 -60
- package/lib/types/index.d.ts +3 -2
- package/package.json +10 -10
package/lib/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
4
|
-
import { COMPLETION_SKILL_REVIEW_PROMPT, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_REVIEW_CONTEXT_MESSAGES, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_MESSAGE_CHARS, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_REVIEW_TIMEOUT_MS, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_USER_CHAR_LIMIT, PROMPT_BUNDLE, SkillLibrary, advanceReview, clampedNumber, contentHash, evolutionIoAdapter, foldTurn, redactSecrets, resolveOrigins, resolveRootConfig, resolveSkillsRoot, reviewPrompt, verifyPromptBundle } from "@lmzhen/dsh-evolution-core";
|
|
4
|
+
import { COMPLETION_SKILL_REVIEW_PROMPT, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MEMORY_REVIEW_MODEL, DEFAULT_REVIEW_CONTEXT_MESSAGES, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_MESSAGE_CHARS, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_REVIEW_TIMEOUT_MS, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_MODEL, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS, DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS, DEFAULT_SUBSTANTIVE_MIN_USER_CHARS, DEFAULT_USER_CHAR_LIMIT, MAX_TIMER_DELAY_MS, PROMPT_BUNDLE, SkillLibrary, advanceReview, assertSkillsRootAliasRetired, clampedNumber, contentHash, evolutionIoAdapter, foldTurn, redactSecrets, resolveOrigins, resolveRootConfig, resolveSkillsRoot, reviewPrompt, verifyPromptBundle } from "@lmzhen/dsh-evolution-core";
|
|
5
5
|
import { validateEvolutionPlan } from "@lmzhen/dsh-evolution-plan-validator";
|
|
6
6
|
//#region lib/types/index.js
|
|
7
7
|
/**
|
|
@@ -10,13 +10,6 @@ import { validateEvolutionPlan } from "@lmzhen/dsh-evolution-plan-validator";
|
|
|
10
10
|
*/
|
|
11
11
|
const name = "evolution-review";
|
|
12
12
|
const inject = ["agents"];
|
|
13
|
-
/** Node's 32-bit timer-delay ceiling (`AbortSignal.timeout`/`setTimeout`):
|
|
14
|
-
* a larger value throws RangeError. B-2 (v18): without a max, a misconfigured
|
|
15
|
-
* `reviewTimeoutMs` made `AbortSignal.timeout` throw inside the subagent
|
|
16
|
-
* start call; the outer catch logged it and silently degraded the review to
|
|
17
|
-
* the inject path. The schema and the assembly clamp both reject it (same
|
|
18
|
-
* bound as commands/maintenance). */
|
|
19
|
-
const MAX_TIMER_DELAY_MS = 2147483647;
|
|
20
13
|
const Config = z.object({
|
|
21
14
|
reviewEnabled: z.boolean().default(true),
|
|
22
15
|
reviewMode: z.union([z.const("subagent"), z.const("inject")]).default("subagent"),
|
|
@@ -78,11 +71,17 @@ function clampReviewConfig(rawConfig, ctx) {
|
|
|
78
71
|
if (clamped.length > 0) ctx.logger.warn(`dsh-evolution-review: ${clamped.join(", ")} provided an invalid value; falling back to the default`);
|
|
79
72
|
return config;
|
|
80
73
|
}
|
|
74
|
+
/** v30 REV-04/REV-02: read the policy snapshot off the (optional) policy
|
|
75
|
+
* service through an `unknown` boundary — the Context augmentation types the
|
|
76
|
+
* getter non-optionally, but at runtime the row can be absent. */
|
|
77
|
+
function policySnapshotOf(source) {
|
|
78
|
+
return source?.get?.();
|
|
79
|
+
}
|
|
81
80
|
function apply(ctx, rawConfig = {}) {
|
|
82
81
|
if (!verifyPromptBundle(PROMPT_BUNDLE)) throw new Error("dsh-evolution prompt bundle integrity check failed; refusing to schedule review work");
|
|
83
82
|
const config = clampReviewConfig(rawConfig, ctx);
|
|
83
|
+
assertSkillsRootAliasRetired(rawConfig);
|
|
84
84
|
const rootConfig = resolveRootConfig(rawConfig);
|
|
85
|
-
if (rootConfig.usedDeprecatedAlias) ctx.logger.warn("evolution-review: config \"skillsRoot\" is deprecated (E-7); use \"root\" — the alias is honoured until 0.3.65");
|
|
86
85
|
const turnStarts = /* @__PURE__ */ new Map();
|
|
87
86
|
let statelessReviewStateWarned = false;
|
|
88
87
|
const cumulativeToolCalls = /* @__PURE__ */ new Map();
|
|
@@ -160,9 +159,9 @@ function apply(ctx, rawConfig = {}) {
|
|
|
160
159
|
advanced.kind = advanceReview(state, event.data.turn, signal, {
|
|
161
160
|
memoryInterval: snapshot?.reviewMemoryInterval ?? config.memoryInterval,
|
|
162
161
|
skillInterval: snapshot?.reviewSkillInterval ?? config.skillInterval,
|
|
163
|
-
substantiveMinToolCalls: snapshot?.substantiveMinToolCalls ??
|
|
164
|
-
substantiveMinUserChars: snapshot?.substantiveMinUserChars ??
|
|
165
|
-
substantiveMinAgentChars: snapshot?.substantiveMinAgentChars ??
|
|
162
|
+
substantiveMinToolCalls: snapshot?.substantiveMinToolCalls ?? DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS,
|
|
163
|
+
substantiveMinUserChars: snapshot?.substantiveMinUserChars ?? DEFAULT_SUBSTANTIVE_MIN_USER_CHARS,
|
|
164
|
+
substantiveMinAgentChars: snapshot?.substantiveMinAgentChars ?? DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS,
|
|
166
165
|
resetOnFire: false
|
|
167
166
|
});
|
|
168
167
|
await stateService?.saveReviewState(session.id, state);
|
|
@@ -205,7 +204,10 @@ function apply(ctx, rawConfig = {}) {
|
|
|
205
204
|
} catch (emitError) {
|
|
206
205
|
ctx.logger.warn(`dsh-evolution-review: review-scheduled emit failed: ${emitError instanceof Error ? emitError.message : String(emitError)}`);
|
|
207
206
|
}
|
|
208
|
-
else if (reviewOutcome
|
|
207
|
+
else if (reviewOutcome === "dropped") {
|
|
208
|
+
pendingCadenceReviews.set(session.id, pendingKind);
|
|
209
|
+
return;
|
|
210
|
+
} else if (reviewOutcome !== "deferred") try {
|
|
209
211
|
deliverMessage(agent, reviewPrompt(pendingKind), "auto-review");
|
|
210
212
|
try {
|
|
211
213
|
ctx.emit("evolution/review-scheduled", {
|
|
@@ -329,30 +331,54 @@ function apply(ctx, rawConfig = {}) {
|
|
|
329
331
|
ctx.effect(() => () => {
|
|
330
332
|
deferredFallbackReviews.length = 0;
|
|
331
333
|
}, "dsh-evolution-review.deferred-drain");
|
|
334
|
+
/** v32 REV-06(b): content hash of every tree skill at REVIEW SCHEDULE time.
|
|
335
|
+
* The subagent's read-time hash is not plumbed through the plan, so this
|
|
336
|
+
* pre-run snapshot is what the execute-time staleness check can honestly
|
|
337
|
+
* compare against: a skill whose hash changed while the review ran was
|
|
338
|
+
* touched concurrently, and the plan (authored against the old state) is
|
|
339
|
+
* refused as stale. */
|
|
340
|
+
async function treeSkillHashes() {
|
|
341
|
+
const hashes = /* @__PURE__ */ new Map();
|
|
342
|
+
const io = ctx.get("evolutionIo");
|
|
343
|
+
if (!io) return hashes;
|
|
344
|
+
const library = new SkillLibrary(resolveSkillsRoot({ root: rootConfig.root }), evolutionIoAdapter(() => io.provider()));
|
|
345
|
+
for (const summary of await library.list().catch((error) => {
|
|
346
|
+
ctx.logger.warn(`dsh-evolution-review: skill tree scan failed (${error instanceof Error ? error.message : String(error)}) — pre-run staleness hashes are unavailable; full-content updates will not be drift-checked this review`);
|
|
347
|
+
return [];
|
|
348
|
+
})) {
|
|
349
|
+
const text = await library.read(summary.name).catch(() => null);
|
|
350
|
+
if (text !== null) hashes.set(summary.name, contentHash(text));
|
|
351
|
+
}
|
|
352
|
+
return hashes;
|
|
353
|
+
}
|
|
332
354
|
async function trySubagentReview(session, agent, kind, signal) {
|
|
333
355
|
if ((policy()?.reviewMode ?? config.reviewMode) === "inject") return false;
|
|
334
356
|
const subagents = ctx.get("subagents");
|
|
335
357
|
if (!subagents) return false;
|
|
336
358
|
if (reviewInFlight) {
|
|
337
|
-
if (deferredFallbackReviews.length < DEFERRED_REVIEW_CAP)
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
359
|
+
if (deferredFallbackReviews.length < DEFERRED_REVIEW_CAP) {
|
|
360
|
+
deferredFallbackReviews.push({
|
|
361
|
+
agent,
|
|
362
|
+
sessionId: session.id,
|
|
363
|
+
kind,
|
|
364
|
+
prompt: reviewPrompt(kind),
|
|
365
|
+
label: "auto-review",
|
|
366
|
+
channel: "inject",
|
|
367
|
+
counts: signal
|
|
368
|
+
});
|
|
369
|
+
return "deferred";
|
|
370
|
+
}
|
|
371
|
+
ctx.logger.warn(`dsh-evolution-review: deferred-review queue at cap (${DEFERRED_REVIEW_CAP}) — dropping one fallback review prompt`);
|
|
372
|
+
return "dropped";
|
|
348
373
|
}
|
|
349
374
|
reviewInFlight = true;
|
|
350
375
|
try {
|
|
351
376
|
const snapshot = policy();
|
|
352
|
-
const model = kind === "memory" ? snapshot?.memoryReviewModel ??
|
|
377
|
+
const model = kind === "memory" ? snapshot?.memoryReviewModel ?? DEFAULT_MEMORY_REVIEW_MODEL : snapshot?.skillReviewModel ?? DEFAULT_SKILL_REVIEW_MODEL;
|
|
353
378
|
const reviewText = redactSecrets(buildReviewRequest(session, kind, signal, config.reviewContextMessages, config.reviewMessageChars));
|
|
354
379
|
const agentOptions = { model };
|
|
355
380
|
if (config.reviewProvider) agentOptions.provider = config.reviewProvider;
|
|
381
|
+
const preRunHashes = await treeSkillHashes();
|
|
356
382
|
const run = await subagents.start("spawn", {
|
|
357
383
|
label: "dsh-evolution-review",
|
|
358
384
|
prompt: [{
|
|
@@ -391,12 +417,42 @@ function apply(ctx, rawConfig = {}) {
|
|
|
391
417
|
});
|
|
392
418
|
const acceptedSkillOps = validation.accepted.skillOps ?? [];
|
|
393
419
|
const skippedUnread = filterUnreadSkillOps(acceptedSkillOps, new Set([...collectReadSkillNames(session), ...childReads]));
|
|
394
|
-
const executed = await withTimeout(executePlan(validation.accepted, session), config.reviewTimeoutMs, "review plan execution");
|
|
395
|
-
const actions = executed.actions;
|
|
396
420
|
const evidenceQuotes = [...validation.accepted.memoryOps ?? [], ...acceptedSkillOps].reduce((total, op) => total + (Array.isArray(op.evidence) ? op.evidence.length : 0), 0);
|
|
421
|
+
const emitApplied = (report) => {
|
|
422
|
+
try {
|
|
423
|
+
ctx.emit("evolution/plan-applied", {
|
|
424
|
+
sessionId: session.id,
|
|
425
|
+
planId: randomUUID(),
|
|
426
|
+
policyFingerprint,
|
|
427
|
+
memoryApplied: report.actions.filter((action) => action.startsWith("Memory")).length,
|
|
428
|
+
skillApplied: report.actions.filter((action) => action.startsWith("Skill ")).length,
|
|
429
|
+
rejectedOps: validation.rejected.length,
|
|
430
|
+
...skippedUnread > 0 ? { skippedUnread } : {},
|
|
431
|
+
executionFailures: report.failedOps?.length ?? 0,
|
|
432
|
+
...report.executionError !== void 0 ? { executionError: report.executionError } : {},
|
|
433
|
+
evidenceQuotes,
|
|
434
|
+
estimatedInputChars: reviewText.length
|
|
435
|
+
});
|
|
436
|
+
} catch (emitError) {
|
|
437
|
+
ctx.logger.warn(`dsh-evolution-review: plan-applied emit failed: ${emitError instanceof Error ? emitError.message : String(emitError)}`);
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
const landed = [];
|
|
441
|
+
let executed;
|
|
442
|
+
try {
|
|
443
|
+
executed = await withTimeout(executePlan(validation.accepted, session, (action) => landed.push(action), preRunHashes), config.reviewTimeoutMs, "review plan execution");
|
|
444
|
+
} catch (error) {
|
|
445
|
+
emitApplied({
|
|
446
|
+
actions: landed,
|
|
447
|
+
executionError: `execution timed out after ${config.reviewTimeoutMs}ms`
|
|
448
|
+
});
|
|
449
|
+
throw error;
|
|
450
|
+
}
|
|
451
|
+
const actions = executed.actions;
|
|
397
452
|
if (actions.length > 0) {
|
|
398
453
|
const applied = actions.join(" · ");
|
|
399
|
-
const
|
|
454
|
+
const failedNote = executed.failedOps.length > 0 ? ` 失败 ${executed.failedOps.length} 个:${executed.failedOps.join(";")}。` : "";
|
|
455
|
+
const note = executed.ok ? "" : `\n部分操作失败。${failedNote}以上操作已应用,请勿重复执行。`;
|
|
400
456
|
try {
|
|
401
457
|
deliverMessage(agent, `💾 Self-improvement review: ${applied}${note}`, "self-improvement review");
|
|
402
458
|
} catch (injectError) {
|
|
@@ -415,23 +471,11 @@ function apply(ctx, rawConfig = {}) {
|
|
|
415
471
|
} catch (injectError) {
|
|
416
472
|
ctx.logger.warn(`dsh-evolution-review: zero-landing notice inject failed: ${injectError instanceof Error ? injectError.message : String(injectError)}`);
|
|
417
473
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
memoryApplied: actions.filter((action) => action.startsWith("Memory")).length,
|
|
424
|
-
skillApplied: actions.filter((action) => action.startsWith("Skill ")).length,
|
|
425
|
-
rejectedOps: validation.rejected.length + skippedUnread,
|
|
426
|
-
executionFailures: executed.failedOps.length,
|
|
427
|
-
...executed.aborted !== void 0 ? { executionError: executed.aborted } : {},
|
|
428
|
-
...executed.failedOps[0] !== void 0 && executed.aborted === void 0 ? { executionError: executed.failedOps[0] } : {},
|
|
429
|
-
evidenceQuotes,
|
|
430
|
-
estimatedInputChars: reviewText.length
|
|
431
|
-
});
|
|
432
|
-
} catch (emitError) {
|
|
433
|
-
ctx.logger.warn(`dsh-evolution-review: plan-applied emit failed: ${emitError instanceof Error ? emitError.message : String(emitError)}`);
|
|
434
|
-
}
|
|
474
|
+
emitApplied({
|
|
475
|
+
actions,
|
|
476
|
+
failedOps: executed.failedOps,
|
|
477
|
+
...executed.aborted !== void 0 ? { executionError: executed.aborted } : executed.failedOps[0] !== void 0 ? { executionError: executed.failedOps[0] } : {}
|
|
478
|
+
});
|
|
435
479
|
return true;
|
|
436
480
|
} finally {
|
|
437
481
|
try {
|
|
@@ -470,7 +514,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
470
514
|
}
|
|
471
515
|
}
|
|
472
516
|
}
|
|
473
|
-
async function executePlan(plan, session) {
|
|
517
|
+
async function executePlan(plan, session, onLanded, preRunHashes) {
|
|
474
518
|
const sessionId = session?.id;
|
|
475
519
|
const memory = ctx.get("memory");
|
|
476
520
|
const approval = ctx.get("evolutionApproval");
|
|
@@ -497,8 +541,10 @@ function apply(ctx, rawConfig = {}) {
|
|
|
497
541
|
old_text: op.old_text
|
|
498
542
|
};
|
|
499
543
|
const result = approval ? await runApproved("memory", `memory ${normalized.target} ${normalized.action}`, normalized, normalized, session) : await memory?.applyBatch(normalized.target, [normalized]);
|
|
500
|
-
if (result?.ok)
|
|
501
|
-
|
|
544
|
+
if (result?.ok) {
|
|
545
|
+
actions.push("Memory updated");
|
|
546
|
+
onLanded?.("Memory updated");
|
|
547
|
+
} else {
|
|
502
548
|
ok = false;
|
|
503
549
|
failedOps.push(`memory ${normalized.action} ${normalized.target}: ${result?.message ?? "service unavailable"}`);
|
|
504
550
|
}
|
|
@@ -517,13 +563,34 @@ function apply(ctx, rawConfig = {}) {
|
|
|
517
563
|
const stageCurrent = await hashLibrary.read(args.name).catch(() => null);
|
|
518
564
|
if (stageCurrent !== null) args.staged_from_sha256 = contentHash(stageCurrent);
|
|
519
565
|
}
|
|
566
|
+
if ((args.action === "write_file" || args.action === "remove_file") && hashLibrary && typeof args.name === "string" && args.name !== "" && typeof args.file_path === "string" && args.file_path !== "") {
|
|
567
|
+
const stageFile = await hashLibrary.readSupportFile(args.name, args.file_path).catch(() => void 0);
|
|
568
|
+
if (stageFile !== void 0) args.staged_from_sha256 = stageFile === null ? "absent" : contentHash(stageFile);
|
|
569
|
+
}
|
|
570
|
+
const opName = typeof args.name === "string" ? args.name : "";
|
|
571
|
+
const hashChecked = (args.action === "update" || args.action === "edit") && opName !== "" && preRunHashes?.has(opName) === true;
|
|
572
|
+
if (hashChecked) {
|
|
573
|
+
const live = await hashLibrary?.read(opName).catch(() => null) ?? null;
|
|
574
|
+
const preRun = preRunHashes.get(opName);
|
|
575
|
+
if (live === null || preRun !== void 0 && contentHash(live) !== preRun) {
|
|
576
|
+
ok = false;
|
|
577
|
+
failedOps.push(`skill ${args.action} ${args.name}: the skill changed while this review ran — update refused as stale; produce a fresh plan`);
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
520
581
|
const runnerArgs = {
|
|
521
582
|
operation: args,
|
|
522
583
|
origin: origins.library
|
|
523
584
|
};
|
|
524
585
|
const result = approval ? await runApproved("skill", `skill ${op.action ?? "patch"} ${op.name}`, runnerArgs, runnerArgs, session) : await executeSkillDirect(args);
|
|
525
|
-
if (result?.ok)
|
|
526
|
-
|
|
586
|
+
if (result?.ok) {
|
|
587
|
+
actions.push(`Skill ${op.name} ${op.action ?? "patch"}`);
|
|
588
|
+
onLanded?.(`Skill ${op.name} ${op.action ?? "patch"}`);
|
|
589
|
+
if (hashChecked) {
|
|
590
|
+
const landed = await hashLibrary?.read(opName).catch(() => null) ?? null;
|
|
591
|
+
if (landed !== null) preRunHashes.set(opName, contentHash(landed));
|
|
592
|
+
}
|
|
593
|
+
} else {
|
|
527
594
|
ok = false;
|
|
528
595
|
failedOps.push(`skill ${op.action ?? "patch"} ${op.name}: ${result?.message ?? "service unavailable"}`);
|
|
529
596
|
}
|
|
@@ -597,19 +664,55 @@ function apply(ctx, rawConfig = {}) {
|
|
|
597
664
|
ok: false,
|
|
598
665
|
message: "evolution-io service not mounted"
|
|
599
666
|
};
|
|
600
|
-
const
|
|
667
|
+
const policySnapshot = policySnapshotOf(ctx.get("evolutionPolicy"));
|
|
668
|
+
const library = new SkillLibrary(resolveSkillsRoot({ root: rootConfig.root }), evolutionIoAdapter(() => io.provider()), {
|
|
669
|
+
...DEFAULT_SKILL_LIMITS,
|
|
670
|
+
maxSkillContentChars: policySnapshot?.skillContentChars ?? DEFAULT_SKILL_LIMITS.maxSkillContentChars
|
|
671
|
+
}, (event) => {
|
|
601
672
|
ctx.emit("evolution/skill-mutated", event);
|
|
602
673
|
});
|
|
603
674
|
const op = skillArgs;
|
|
604
675
|
const name = op.name ?? "";
|
|
605
676
|
const origin = origins.library;
|
|
677
|
+
const protectedNames = policySnapshot?.protectedSkillNames;
|
|
678
|
+
if (origin !== "foreground" && protectedNames?.includes(name) && op.action !== "create") return {
|
|
679
|
+
ok: false,
|
|
680
|
+
message: `Skill "${name}" is protected by the current policy (protectedSkillNames); autonomous writes are refused.`
|
|
681
|
+
};
|
|
682
|
+
if (op.action === "write_file" || op.action === "remove_file") {
|
|
683
|
+
const expected = op.staged_from_sha256;
|
|
684
|
+
if (typeof expected === "string" && expected !== "") {
|
|
685
|
+
const current = await library.readSupportFile(name, op.file_path ?? "").catch(() => void 0);
|
|
686
|
+
const actual = current === void 0 || current === null ? "absent" : contentHash(current);
|
|
687
|
+
if (current === void 0 || actual !== expected) return {
|
|
688
|
+
ok: false,
|
|
689
|
+
message: `Support file "${op.file_path ?? ""}" of "${name}" changed since this plan was produced — the staged file operation was refused as stale. Re-read the skill tree and produce a fresh plan.`
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
}
|
|
606
693
|
if (op.action === "create") {
|
|
607
694
|
const created = await library.create(name, op.content ?? "", origin);
|
|
608
695
|
if (created.ok) await ctx.get("skillUsage")?.markAgentCreated?.(name);
|
|
609
696
|
return created;
|
|
610
697
|
}
|
|
611
|
-
if (op.action === "edit" || op.action === "update")
|
|
612
|
-
|
|
698
|
+
if (op.action === "edit" || op.action === "update") {
|
|
699
|
+
const expected = op.staged_from_sha256;
|
|
700
|
+
if (typeof expected === "string" && expected !== "") {
|
|
701
|
+
const current = await library.read(name).catch(() => null);
|
|
702
|
+
if (current !== null && contentHash(current) !== expected) return {
|
|
703
|
+
ok: false,
|
|
704
|
+
message: `Skill "${name}" changed since this plan was produced — the full-content update was refused as stale. Re-read the skill and produce a fresh plan.`
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
const updated = await library.update(name, op.content ?? "", origin);
|
|
708
|
+
if (updated.ok) await ctx.get("skillUsage")?.record?.(name, "patch").catch(() => {});
|
|
709
|
+
return updated;
|
|
710
|
+
}
|
|
711
|
+
if (op.action === "patch") {
|
|
712
|
+
const patched = await library.patch(name, op.old_string ?? "", op.new_string ?? "", op.file_path ?? "", false, origin);
|
|
713
|
+
if (patched.ok) await ctx.get("skillUsage")?.record?.(name, "patch").catch(() => {});
|
|
714
|
+
return patched;
|
|
715
|
+
}
|
|
613
716
|
if (op.action === "delete") {
|
|
614
717
|
const into = (op.absorbed_into ?? "").trim();
|
|
615
718
|
if (!into || !await library.read(into)) return {
|
|
@@ -620,8 +723,21 @@ function apply(ctx, rawConfig = {}) {
|
|
|
620
723
|
if (archived.ok) await ctx.get("skillUsage")?.markArchived?.(name);
|
|
621
724
|
return archived;
|
|
622
725
|
}
|
|
623
|
-
if (op.action === "write_file"
|
|
624
|
-
|
|
726
|
+
if (op.action === "write_file" || op.action === "remove_file") {
|
|
727
|
+
const expected = op.staged_from_sha256;
|
|
728
|
+
if (typeof expected === "string" && expected !== "") {
|
|
729
|
+
const current = await library.readSupportFile(name, op.file_path ?? "").catch(() => void 0);
|
|
730
|
+
const actual = current === void 0 || current === null ? "absent" : contentHash(current);
|
|
731
|
+
if (current === void 0 || actual !== expected) return {
|
|
732
|
+
ok: false,
|
|
733
|
+
message: `Support file "${op.file_path ?? ""}" of "${name}" changed since this plan was produced — the staged file operation was refused as stale. Re-read the skill tree and produce a fresh plan.`
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
if (op.action === "write_file") return await library.writeSupportFile(name, op.file_path ?? "", op.file_content ?? "", origin);
|
|
737
|
+
const removedSupport = await library.removeSupportFile(name, op.file_path ?? "", origin);
|
|
738
|
+
if (removedSupport.ok) await ctx.get("skillUsage")?.record?.(name, "patch").catch(() => {});
|
|
739
|
+
return removedSupport;
|
|
740
|
+
}
|
|
625
741
|
if (op.action === "restructure") {
|
|
626
742
|
const moves = (op.restructure ?? []).filter((move) => move !== null).map((move) => ({
|
|
627
743
|
heading: move.heading ?? "",
|
|
@@ -656,9 +772,9 @@ function shouldCompletionReview(reason, sessionToolCalls, minToolCalls) {
|
|
|
656
772
|
* per-skill read action (its `list`/`review` are whole-library), so a specific
|
|
657
773
|
* skill read through it cannot be tracked — see README Known Limitations. */
|
|
658
774
|
function collectReadSkillNames(session) {
|
|
659
|
-
const
|
|
660
|
-
|
|
661
|
-
|
|
775
|
+
const callNames = /* @__PURE__ */ new Map();
|
|
776
|
+
const okCallIds = /* @__PURE__ */ new Set();
|
|
777
|
+
for (const event of session.events) if (event.type === "tool/call") {
|
|
662
778
|
if (event.data.name !== "skill") continue;
|
|
663
779
|
const raw = event.data.arguments;
|
|
664
780
|
let parsed = {};
|
|
@@ -667,11 +783,20 @@ function collectReadSkillNames(session) {
|
|
|
667
783
|
} catch {
|
|
668
784
|
continue;
|
|
669
785
|
}
|
|
670
|
-
else parsed = raw
|
|
786
|
+
else parsed = raw;
|
|
671
787
|
const parsedObj = typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
672
788
|
const name = typeof parsedObj.name === "string" ? parsedObj.name : typeof parsedObj.skill === "string" ? parsedObj.skill : "";
|
|
673
|
-
if (name)
|
|
789
|
+
if (name) callNames.set(event.data.callId, name);
|
|
790
|
+
} else if (event.type === "tool/result") {
|
|
791
|
+
const blocks = event.data?.message?.content;
|
|
792
|
+
if (!Array.isArray(blocks)) continue;
|
|
793
|
+
for (const block of blocks) {
|
|
794
|
+
const typed = block;
|
|
795
|
+
if (typed.type === "tool-result" && typed.isError !== true && typeof typed.toolCallId === "string") okCallIds.add(typed.toolCallId);
|
|
796
|
+
}
|
|
674
797
|
}
|
|
798
|
+
const names = /* @__PURE__ */ new Set();
|
|
799
|
+
for (const [callId, name] of callNames) if (okCallIds.has(callId)) names.add(name);
|
|
675
800
|
return names;
|
|
676
801
|
}
|
|
677
802
|
/** Map/set size that triggers a dead-session counter sweep (bounded, not a hard cap). */
|
package/lib/types/index.d.ts
CHANGED
|
@@ -47,8 +47,9 @@ export interface Config {
|
|
|
47
47
|
* skills in the SAME tree the catalog/tools read instead of writing a
|
|
48
48
|
* parallel tree the rest of the family cannot see. E-7 (v18): canonical key. */
|
|
49
49
|
root?: string;
|
|
50
|
-
/**
|
|
51
|
-
*
|
|
50
|
+
/** V27 G2.4 (M-08): the retired `skillsRoot` alias, kept in the schema so a
|
|
51
|
+
* config that still sets it reaches {@link assertSkillsRootAliasRetired} and
|
|
52
|
+
* fails the load instead of being silently dropped. Never read. */
|
|
52
53
|
skillsRoot?: string;
|
|
53
54
|
}
|
|
54
55
|
export declare const Config: z<Config>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmzhen/dsh-evolution-review",
|
|
3
3
|
"description": "Background review orchestration (community build)",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.69",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -31,9 +31,9 @@
|
|
|
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.
|
|
36
|
-
"@lmzhen/dsh-evolution-plan-validator": "^0.3.
|
|
34
|
+
"@lmzhen/dsh-evolution-approval": "^0.3.69",
|
|
35
|
+
"@lmzhen/dsh-evolution-core": "^0.3.69",
|
|
36
|
+
"@lmzhen/dsh-evolution-plan-validator": "^0.3.69"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
|
|
43
43
|
"@deepseek-ai/dsh-session": "^0.1.1-rc.2",
|
|
44
44
|
"@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
|
|
45
|
-
"@lmzhen/dsh-evolution-state": "^0.3.
|
|
46
|
-
"@lmzhen/dsh-evolution-policy": "^0.3.
|
|
45
|
+
"@lmzhen/dsh-evolution-state": "^0.3.69",
|
|
46
|
+
"@lmzhen/dsh-evolution-policy": "^0.3.69"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@deepseek-ai/dsh-agent": "^0.1.1-rc.2",
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
"@deepseek-ai/dsh-session-persistence": "^0.1.1-rc.2",
|
|
55
55
|
"@deepseek-ai/dsh-session-persistence-jsonl": "^0.1.1-rc.2",
|
|
56
56
|
"@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
|
|
57
|
-
"@lmzhen/dsh-evolution-approval": "^0.3.
|
|
58
|
-
"@lmzhen/dsh-evolution-core": "^0.3.
|
|
59
|
-
"@lmzhen/dsh-evolution-plan-validator": "^0.3.
|
|
60
|
-
"@lmzhen/dsh-evolution-state": "^0.3.
|
|
57
|
+
"@lmzhen/dsh-evolution-approval": "^0.3.69",
|
|
58
|
+
"@lmzhen/dsh-evolution-core": "^0.3.69",
|
|
59
|
+
"@lmzhen/dsh-evolution-plan-validator": "^0.3.69",
|
|
60
|
+
"@lmzhen/dsh-evolution-state": "^0.3.69"
|
|
61
61
|
}
|
|
62
62
|
}
|