@lmzhen/dsh-evolution-review 0.26.26 → 0.27.1
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 +1 -0
- package/lib/index.js +134 -145
- package/lib/types/index.d.ts +8 -0
- package/package.json +27 -24
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ own, and the delivery mode lives on the `evolution-policy` row, not here.
|
|
|
15
15
|
|
|
16
16
|
- **The autonomous write leg does not pass `tools.guard`.** `executePlan` calls the service seam directly (`memory.applyBatch`, `library.*`), so the guard `evolution-threat` installs on the TOOL channel never sees these writes (`grep "tools.execute(" src` = 0 by design). The scan still happens one layer down: `MemoryStore` and `SkillLibrary` refuse threat-shaped payloads at the write point through core's own pattern table, which is the same table the guard consumes. A deployment that wants the tool channel's guard wording on a plan write should route it through a tool — nothing in the plan path claims that coverage.
|
|
17
17
|
- The review's model/context routing below applies to `reviewMode: 'subagent'` (opt-in since 0.3.74); in the default `'inject'` mode the review runs on the parent agent's model inside the parent thread.
|
|
18
|
+
- Review subagents run under a **restricted preset** (a read-only child preset): the tool surface is the caller's `reviewToolAllow` (schema default `[skill]`), and the child scope is the only place the allow-list is applied. A review run never inherits write authority from the parent session.
|
|
18
19
|
- Review subagents are spawned with the plain `skill` tool only (`reviewToolAllow` default and the host/preset config both = `[skill]`: the DSH tool catalog has no `skill_search`/`skill_load` discovery pair, so the Hermes-lineage Anchored Standard `skill_search`/`skill_load` allow-list does not exist here).
|
|
19
20
|
- Review subagents run as `spawn` children on the deployment default preset rather than inheriting the parent agent's composition (`fork`): a fork child is always promoted by the Anchored Standard bootstrap and its narrowed resident catalog would drop the plain `skill` tool from the review allow-list.
|
|
20
21
|
- The review request text is redacted for credential-shaped patterns before it reaches the subagent, but redaction is pattern-based and best-effort, not a security boundary.
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
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, advanceReview, assertSkillsRootAliasRetired, authorityFrom, clampOnce, clearReviewChannel, contentHash, evolutionIoAdapter, filterUnreadSkillOps, filterUnreadSkillOps as filterUnreadSkillOps$1, foldUntrustedLines, isPresent, isUnknown, liveBool, markReviewChannel, newSkillLibrary, paramRowId, payloadText, pickWithPolicy, policyStageLimits, readDispatchSignal, redactSecrets, resolveOrigins, resolveRootConfig, reviewPrompt, sessionAudited, sessionEvidenceIndex, sessionReadNames, sessionTurnSignals, sweepReviewChannelSessions, unwrapLive, userSetKeys, verifyPromptBundle } from "@lmzhen/dsh-evolution-core";
|
|
1
|
+
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, advanceReview, assertSkillsRootAliasRetired, authorityFrom, clampOnce, classifySessionEvent, clearReviewChannel, contentHash, createSubagentChannel, evolutionIoAdapter, filterUnreadSkillOps, filterUnreadSkillOps as filterUnreadSkillOps$1, foldUntrustedLines, isPresent, isUnknown, liveBool, markReviewChannel, newSkillLibrary, paramRowId, payloadText, pickWithPolicy, policyStageLimits, readDispatchSignal, redactSecrets, resolveOrigins, resolveRootConfig, reviewPrompt, sessionAudited, sessionEvidenceIndex, sessionReadNames, sessionTurnSignals, sweepReviewChannelSessions, unwrapLive, userSetKeys, verifyPromptBundle } from "@lmzhen/dsh-evolution-core";
|
|
2
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
3
|
import z from "@deepseek-ai/schemastery";
|
|
4
4
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
@@ -140,9 +140,7 @@ function emitReviewScheduled(ctx, event) {
|
|
|
140
140
|
* 2026-09-16) and caps the sum at the timer ceiling (PLAN-R2 P2-1). The
|
|
141
141
|
* `run.dispose` arm point counts this margin ALONE — its clock starts after
|
|
142
142
|
* the result settled, not at the review timeout (PLAN-R2 P2-2, 2026-09-16). */
|
|
143
|
-
const REVIEW_SETTLE_MARGIN_MS = 5e3;
|
|
144
143
|
/** Error name marking the S2-6 watchdog expiry (see the catch in trySubagentReview). */
|
|
145
|
-
const REVIEW_SETTLE_TIMEOUT = "ReviewSettleTimeout";
|
|
146
144
|
const name = "evolution-review";
|
|
147
145
|
const inject = ["agents"];
|
|
148
146
|
const Config = z.object({
|
|
@@ -249,6 +247,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
249
247
|
});
|
|
250
248
|
if (!verifyPromptBundle(PROMPT_BUNDLE)) throw new Error("dsh-evolution prompt bundle integrity check failed; refusing to schedule review work");
|
|
251
249
|
const config = clampReviewConfig(rawConfig, ctx);
|
|
250
|
+
const reviewChannel = createSubagentChannel(ctx);
|
|
252
251
|
assertSkillsRootAliasRetired(rawConfig);
|
|
253
252
|
const rootConfig = resolveRootConfig(rawConfig);
|
|
254
253
|
const sessionState = new SessionScopedState();
|
|
@@ -263,6 +262,8 @@ function apply(ctx, rawConfig = {}) {
|
|
|
263
262
|
const pendingReviewNotices = sessionState.add("pendingReviewNotices", /* @__PURE__ */ new Map());
|
|
264
263
|
const cadenceResetWarned = sessionState.add("cadenceResetWarned", /* @__PURE__ */ new Set());
|
|
265
264
|
let reviewInFlight = false;
|
|
265
|
+
/** W5 G3-S3: capability-missing is warned once per plugin instance (the error signal is per trigger). */
|
|
266
|
+
let channelUnavailableWarned = false;
|
|
266
267
|
const policy = () => ctx.get("evolutionPolicy")?.get();
|
|
267
268
|
/**
|
|
268
269
|
* This row's knobs as they stand right now (G1 §8.3): the live fields are resolved
|
|
@@ -334,6 +335,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
334
335
|
}
|
|
335
336
|
ctx.on("session/event", (session, event) => {
|
|
336
337
|
if (!sessionAudited(ctx, session.id, config.sessionScoped)) return;
|
|
338
|
+
if (classifySessionEvent(event) === "plugin-record") return;
|
|
337
339
|
appendDispatchFrame(dispatchDigest, session.id, event);
|
|
338
340
|
if (event.type === "user/message") {
|
|
339
341
|
if ((event.data?.source)?.kind === "user") clearReviewChannel(session.id);
|
|
@@ -666,31 +668,6 @@ function apply(ctx, rawConfig = {}) {
|
|
|
666
668
|
return false;
|
|
667
669
|
}
|
|
668
670
|
};
|
|
669
|
-
let settleBudgetCapWarned = false;
|
|
670
|
-
const resultSettleBudgetMs = () => {
|
|
671
|
-
const budget = config.reviewTimeoutMs + Math.min(REVIEW_SETTLE_MARGIN_MS, config.reviewTimeoutMs);
|
|
672
|
-
if (budget <= MAX_TIMER_DELAY_MS) return budget;
|
|
673
|
-
if (!settleBudgetCapWarned) {
|
|
674
|
-
settleBudgetCapWarned = true;
|
|
675
|
-
ctx.logger.warn(`dsh-evolution-review: reviewTimeoutMs ${config.reviewTimeoutMs}ms plus the settle margin exceeds the 32-bit timer delay ceiling (${MAX_TIMER_DELAY_MS}ms) — the settle watchdog arms at the ceiling`);
|
|
676
|
-
}
|
|
677
|
-
return MAX_TIMER_DELAY_MS;
|
|
678
|
-
};
|
|
679
|
-
const disposeSettleBudgetMs = () => Math.min(REVIEW_SETTLE_MARGIN_MS, config.reviewTimeoutMs);
|
|
680
|
-
const withSettleWatchdog = (promise, label, budgetMs, overdue) => new Promise((resolve, reject) => {
|
|
681
|
-
const timer = setTimeout(() => {
|
|
682
|
-
const error = /* @__PURE__ */ new Error(`dsh-evolution-review: ${label} ${overdue(budgetMs)} — abandoning the handle`);
|
|
683
|
-
error.name = REVIEW_SETTLE_TIMEOUT;
|
|
684
|
-
reject(error);
|
|
685
|
-
}, budgetMs);
|
|
686
|
-
promise.then((value) => {
|
|
687
|
-
clearTimeout(timer);
|
|
688
|
-
resolve(value);
|
|
689
|
-
}, (error) => {
|
|
690
|
-
clearTimeout(timer);
|
|
691
|
-
reject(error instanceof Error ? error : new Error(String(error)));
|
|
692
|
-
});
|
|
693
|
-
});
|
|
694
671
|
/**
|
|
695
672
|
* T3-09/A37: the ONE abort channel for this fiber's long work.
|
|
696
673
|
*
|
|
@@ -743,8 +720,19 @@ function apply(ctx, rawConfig = {}) {
|
|
|
743
720
|
}
|
|
744
721
|
async function trySubagentReview(session, agent, kind, signal) {
|
|
745
722
|
if (params().reviewMode === "inject") return false;
|
|
746
|
-
const
|
|
747
|
-
if (
|
|
723
|
+
const channelState = reviewChannel.state;
|
|
724
|
+
if (channelState.kind !== "available") {
|
|
725
|
+
if (!channelUnavailableWarned) {
|
|
726
|
+
channelUnavailableWarned = true;
|
|
727
|
+
ctx.logger.warn("dsh-evolution-review: subagent channel unavailable (" + channelState.code + ": " + channelState.reason + ") — reviews fall back to the inject path until the capability returns");
|
|
728
|
+
}
|
|
729
|
+
try {
|
|
730
|
+
ctx.emit("evolution/review-error", { sessionId: session.id });
|
|
731
|
+
} catch (emitError) {
|
|
732
|
+
ctx.logger.warn("dsh-evolution-review: review-error emit failed: " + (emitError instanceof Error ? emitError.message : String(emitError)));
|
|
733
|
+
}
|
|
734
|
+
return false;
|
|
735
|
+
}
|
|
748
736
|
if (reviewInFlight) {
|
|
749
737
|
const existing = deferredFallbackReviews.findIndex((entry) => entry.sessionId === session.id && entry.kind === kind);
|
|
750
738
|
if (existing >= 0 || deferredFallbackReviews.length < DEFERRED_REVIEW_CAP) {
|
|
@@ -771,138 +759,139 @@ function apply(ctx, rawConfig = {}) {
|
|
|
771
759
|
const reviewText = redactSecrets(buildReviewRequest(session, kind, signal, config.reviewContextMessages, config.reviewMessageChars, dispatchDigest.get(session.id) ?? []));
|
|
772
760
|
const agentOptions = { model };
|
|
773
761
|
if (config.reviewProvider) agentOptions.provider = config.reviewProvider;
|
|
774
|
-
const registry = ctx.get("tools");
|
|
775
762
|
const requestedReviewTools = config.reviewToolAllow ?? [];
|
|
776
|
-
if (requestedReviewTools.length > 0) if (registry === void 0) ctx.logger.warn("dsh-evolution-review: tools service not mounted — the review subagent tool allow-list was not probed; passing it through unchanged");
|
|
777
|
-
else {
|
|
778
|
-
const notInGlobalLayer = requestedReviewTools.filter((name) => registry.get(name) == null);
|
|
779
|
-
if (notInGlobalLayer.length > 0) ctx.logger.warn(`dsh-evolution-review: reviewToolAllow name(s) ${notInGlobalLayer.map((name) => `"${name}"`).join(", ")} are absent from the GLOBAL tool layer — passing them through unchanged (a preset-mounted tool is invisible to a scope-less lookup)`);
|
|
780
|
-
}
|
|
781
763
|
const preRunHashes = await treeSkillHashes();
|
|
782
764
|
const sessionSeqAtPlanTime = session.seq - 1;
|
|
783
765
|
const substantiveEvidenceSeqs = sessionEvidenceIndex(ctx, session);
|
|
784
|
-
const
|
|
785
|
-
label: "dsh-evolution-review",
|
|
786
|
-
prompt: [{
|
|
787
|
-
type: "text",
|
|
788
|
-
text: reviewText
|
|
789
|
-
}],
|
|
766
|
+
const outcome = await reviewChannel.run({
|
|
790
767
|
parent: agent,
|
|
791
|
-
|
|
768
|
+
label: "dsh-evolution-review",
|
|
769
|
+
promptText: reviewText + "\n\n" + reviewPrompt(kind, "plan"),
|
|
770
|
+
kind: "reviewer",
|
|
771
|
+
allow: requestedReviewTools,
|
|
772
|
+
outputSchema: REVIEW_OUTPUT_SCHEMA,
|
|
792
773
|
maxDepth: config.reviewMaxDepth,
|
|
793
774
|
agentOptions,
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
775
|
+
timeoutMs: config.reviewTimeoutMs,
|
|
776
|
+
signal: fiberAbort.signal,
|
|
777
|
+
afterResult: (child) => {
|
|
778
|
+
const childSession = child.session;
|
|
779
|
+
return childSession === void 0 ? /* @__PURE__ */ new Set() : new Set(sessionReadNames(ctx, childSession) ?? []);
|
|
780
|
+
}
|
|
797
781
|
});
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
782
|
+
if (outcome.kind === "aborted") {
|
|
783
|
+
try {
|
|
784
|
+
ctx.emit("evolution/review-error", { sessionId: session.id });
|
|
785
|
+
} catch (emitError) {
|
|
786
|
+
ctx.logger.warn("dsh-evolution-review: review-error emit failed: " + (emitError instanceof Error ? emitError.message : String(emitError)));
|
|
787
|
+
}
|
|
788
|
+
return false;
|
|
789
|
+
}
|
|
790
|
+
if (outcome.kind === "unavailable") {
|
|
791
|
+
if (!channelUnavailableWarned) {
|
|
792
|
+
channelUnavailableWarned = true;
|
|
793
|
+
ctx.logger.warn("dsh-evolution-review: subagent channel unavailable (" + outcome.code + ": " + outcome.reason + ") — reviews fall back to the inject path until the capability returns");
|
|
810
794
|
}
|
|
811
|
-
const childReads = run.localAgent ? sessionReadNames(ctx, run.localAgent.session) ?? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set();
|
|
812
|
-
const plan = result.structured;
|
|
813
|
-
const policyFingerprint = fingerprintPolicy(snapshot);
|
|
814
|
-
const validation = validateEvolutionPlan(plan, {
|
|
815
|
-
sessionSeq: sessionSeqAtPlanTime,
|
|
816
|
-
substantiveEvidenceSeqs,
|
|
817
|
-
maxOpsPerPlan: snapshot?.maxOpsPerPlan ?? DEFAULT_MAX_OPS_PER_PLAN,
|
|
818
|
-
protectedSkillNames: new Set(snapshot?.protectedSkillNames ?? []),
|
|
819
|
-
maxMemoryChars: snapshot?.memoryChars ?? DEFAULT_MEMORY_CHAR_LIMIT,
|
|
820
|
-
maxUserChars: snapshot?.userChars ?? DEFAULT_USER_CHAR_LIMIT,
|
|
821
|
-
maxSkillContentChars: snapshot?.skillContentChars ?? DEFAULT_SKILL_CONTENT_CHARS
|
|
822
|
-
});
|
|
823
|
-
if (validation.reports.length > 0) ctx.logger.warn(`dsh-evolution-review: ${validation.reports.length} op(s) cite only bookkeeping frames as evidence: ${validation.reports.map((report) => report.reason).join("; ")}`);
|
|
824
|
-
const acceptedSkillOps = validation.accepted.skillOps ?? [];
|
|
825
|
-
const skippedUnread = filterUnreadSkillOps$1(acceptedSkillOps, new Set([...sessionReadNames(ctx, session) ?? /* @__PURE__ */ new Set(), ...childReads]));
|
|
826
|
-
const evidenceQuotes = [...validation.accepted.memoryOps ?? [], ...acceptedSkillOps].reduce((total, op) => total + (Array.isArray(op.evidence) ? op.evidence.length : 0), 0);
|
|
827
|
-
const emitApplied = (report) => {
|
|
828
|
-
try {
|
|
829
|
-
ctx.emit("evolution/plan-applied", {
|
|
830
|
-
sessionId: session.id,
|
|
831
|
-
planId: randomUUID(),
|
|
832
|
-
policyFingerprint,
|
|
833
|
-
memoryApplied: report.actions.filter((action) => action.startsWith("Memory")).length,
|
|
834
|
-
skillApplied: report.actions.filter((action) => action.startsWith("Skill ")).length,
|
|
835
|
-
rejectedOps: validation.rejected.length,
|
|
836
|
-
...skippedUnread > 0 ? { skippedUnread } : {},
|
|
837
|
-
...validation.reports.length > 0 ? { evidenceClassReports: validation.reports.length } : {},
|
|
838
|
-
executionFailures: report.failedOps?.length ?? 0,
|
|
839
|
-
...report.executionError !== void 0 ? { executionError: report.executionError } : {},
|
|
840
|
-
evidenceQuotes,
|
|
841
|
-
estimatedInputChars: reviewText.length
|
|
842
|
-
});
|
|
843
|
-
} catch (emitError) {
|
|
844
|
-
ctx.logger.warn(`dsh-evolution-review: plan-applied emit failed: ${emitError instanceof Error ? emitError.message : String(emitError)}`);
|
|
845
|
-
}
|
|
846
|
-
};
|
|
847
|
-
const landed = [];
|
|
848
|
-
let deadlineMissed = false;
|
|
849
|
-
const lateLandings = [];
|
|
850
|
-
const noteLanded = (action) => {
|
|
851
|
-
landed.push(action);
|
|
852
|
-
if (!deadlineMissed) return;
|
|
853
|
-
lateLandings.push(action);
|
|
854
|
-
const before = landed.length - lateLandings.length;
|
|
855
|
-
const earlier = before > 0 ? ` (${before} other op(s) had landed before the deadline)` : "";
|
|
856
|
-
deliverMessage(agent, `\u{1F4BE} Self-improvement review: ${action} ${lateLandings.length === 1 ? "landed AFTER the review timed out" : `landed after the timeout (${lateLandings.length} late so far)`}${earlier} \u2014 reconcile before re-requesting the same change.`, "self-improvement review late landing");
|
|
857
|
-
};
|
|
858
|
-
let executed;
|
|
859
795
|
try {
|
|
860
|
-
|
|
861
|
-
} catch (
|
|
862
|
-
|
|
863
|
-
emitApplied({
|
|
864
|
-
actions: landed,
|
|
865
|
-
executionError: `execution timed out after ${config.reviewTimeoutMs}ms`
|
|
866
|
-
});
|
|
867
|
-
throw error;
|
|
796
|
+
ctx.emit("evolution/review-error", { sessionId: session.id });
|
|
797
|
+
} catch (emitError) {
|
|
798
|
+
ctx.logger.warn("dsh-evolution-review: review-error emit failed: " + (emitError instanceof Error ? emitError.message : String(emitError)));
|
|
868
799
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
if (validation.rejected.length > 0) reasons.push(`${validation.rejected.length} op(s) rejected by validation`);
|
|
877
|
-
if (skippedUnread > 0) reasons.push(`${skippedUnread} op(s) skipped (skill not read this session)`);
|
|
878
|
-
if (executed.failedOps.length > 0) reasons.push(`${executed.failedOps.length} op(s) failed at execution: ${executed.failedOps.join("; ")}`);
|
|
879
|
-
if (executed.aborted !== void 0) reasons.push(`execution aborted: ${executed.aborted}`);
|
|
880
|
-
if (reasons.length === 0) reasons.push("the review plan contained nothing executable");
|
|
881
|
-
let text = `💾 Self-improvement review: 0 ops landed. ${reasons.join(" ")}`;
|
|
882
|
-
if (text.length > 500) text = `${text.slice(0, 497)}…`;
|
|
883
|
-
deliverMessage(agent, text, "self-improvement review");
|
|
800
|
+
return false;
|
|
801
|
+
}
|
|
802
|
+
if (outcome.kind === "no-structured") {
|
|
803
|
+
try {
|
|
804
|
+
ctx.emit("evolution/review-error", { sessionId: session.id });
|
|
805
|
+
} catch (emitError) {
|
|
806
|
+
ctx.logger.warn("dsh-evolution-review: review-error emit failed: " + (emitError instanceof Error ? emitError.message : String(emitError)));
|
|
884
807
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
808
|
+
const stopDetail = typeof outcome.stopReason === "string" ? outcome.stopReason : void 0;
|
|
809
|
+
const diagDetail = typeof outcome.diagnostic === "string" ? outcome.diagnostic : void 0;
|
|
810
|
+
ctx.logger.warn("dsh-evolution-review: review subagent returned no structured plan" + (stopDetail !== void 0 ? " (stopReason=" + stopDetail + (diagDetail !== void 0 ? "; " + diagDetail : "") + ")" : ""));
|
|
811
|
+
return false;
|
|
812
|
+
}
|
|
813
|
+
const childReads = outcome.evidence ?? /* @__PURE__ */ new Set();
|
|
814
|
+
const plan = outcome.structured;
|
|
815
|
+
const policyFingerprint = fingerprintPolicy(snapshot);
|
|
816
|
+
const validation = validateEvolutionPlan(plan, {
|
|
817
|
+
sessionSeq: sessionSeqAtPlanTime,
|
|
818
|
+
substantiveEvidenceSeqs,
|
|
819
|
+
maxOpsPerPlan: snapshot?.maxOpsPerPlan ?? DEFAULT_MAX_OPS_PER_PLAN,
|
|
820
|
+
protectedSkillNames: new Set(snapshot?.protectedSkillNames ?? []),
|
|
821
|
+
maxMemoryChars: snapshot?.memoryChars ?? DEFAULT_MEMORY_CHAR_LIMIT,
|
|
822
|
+
maxUserChars: snapshot?.userChars ?? DEFAULT_USER_CHAR_LIMIT,
|
|
823
|
+
maxSkillContentChars: snapshot?.skillContentChars ?? DEFAULT_SKILL_CONTENT_CHARS
|
|
824
|
+
});
|
|
825
|
+
if (validation.reports.length > 0) ctx.logger.warn(`dsh-evolution-review: ${validation.reports.length} op(s) cite only bookkeeping frames as evidence: ${validation.reports.map((report) => report.reason).join("; ")}`);
|
|
826
|
+
const acceptedSkillOps = validation.accepted.skillOps ?? [];
|
|
827
|
+
const skippedUnread = filterUnreadSkillOps$1(acceptedSkillOps, new Set([...sessionReadNames(ctx, session) ?? /* @__PURE__ */ new Set(), ...childReads]));
|
|
828
|
+
const evidenceQuotes = [...validation.accepted.memoryOps ?? [], ...acceptedSkillOps].reduce((total, op) => total + (Array.isArray(op.evidence) ? op.evidence.length : 0), 0);
|
|
829
|
+
const emitApplied = (report) => {
|
|
892
830
|
try {
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
831
|
+
ctx.emit("evolution/plan-applied", {
|
|
832
|
+
sessionId: session.id,
|
|
833
|
+
planId: randomUUID(),
|
|
834
|
+
policyFingerprint,
|
|
835
|
+
memoryApplied: report.actions.filter((action) => action.startsWith("Memory")).length,
|
|
836
|
+
skillApplied: report.actions.filter((action) => action.startsWith("Skill ")).length,
|
|
837
|
+
rejectedOps: validation.rejected.length,
|
|
838
|
+
...skippedUnread > 0 ? { skippedUnread } : {},
|
|
839
|
+
...validation.reports.length > 0 ? { evidenceClassReports: validation.reports.length } : {},
|
|
840
|
+
executionFailures: report.failedOps?.length ?? 0,
|
|
841
|
+
...report.executionError !== void 0 ? { executionError: report.executionError } : {},
|
|
842
|
+
evidenceQuotes,
|
|
843
|
+
estimatedInputChars: reviewText.length
|
|
844
|
+
});
|
|
845
|
+
} catch (emitError) {
|
|
846
|
+
ctx.logger.warn(`dsh-evolution-review: plan-applied emit failed: ${emitError instanceof Error ? emitError.message : String(emitError)}`);
|
|
896
847
|
}
|
|
848
|
+
};
|
|
849
|
+
const landed = [];
|
|
850
|
+
let deadlineMissed = false;
|
|
851
|
+
const lateLandings = [];
|
|
852
|
+
const noteLanded = (action) => {
|
|
853
|
+
landed.push(action);
|
|
854
|
+
if (!deadlineMissed) return;
|
|
855
|
+
lateLandings.push(action);
|
|
856
|
+
const before = landed.length - lateLandings.length;
|
|
857
|
+
const earlier = before > 0 ? ` (${before} other op(s) had landed before the deadline)` : "";
|
|
858
|
+
deliverMessage(agent, `\u{1F4BE} Self-improvement review: ${action} ${lateLandings.length === 1 ? "landed AFTER the review timed out" : `landed after the timeout (${lateLandings.length} late so far)`}${earlier} \u2014 reconcile before re-requesting the same change.`, "self-improvement review late landing");
|
|
859
|
+
};
|
|
860
|
+
let executed;
|
|
861
|
+
try {
|
|
862
|
+
executed = await withTimeout(executePlan(validation.accepted, session, noteLanded, preRunHashes, fiberAbort.signal), config.reviewTimeoutMs, "review plan execution");
|
|
863
|
+
} catch (error) {
|
|
864
|
+
deadlineMissed = true;
|
|
865
|
+
emitApplied({
|
|
866
|
+
actions: landed,
|
|
867
|
+
executionError: `execution timed out after ${config.reviewTimeoutMs}ms`
|
|
868
|
+
});
|
|
869
|
+
throw error;
|
|
870
|
+
}
|
|
871
|
+
const actions = executed.actions;
|
|
872
|
+
if (actions.length > 0) {
|
|
873
|
+
const applied = actions.join(" · ");
|
|
874
|
+
const failedNote = executed.failedOps.length > 0 ? ` 失败 ${executed.failedOps.length} 个:${executed.failedOps.join(";")}。` : "";
|
|
875
|
+
deliverMessage(agent, `💾 Self-improvement review: ${applied}${executed.ok ? "" : `\n部分操作失败。${failedNote}以上操作已应用,请勿重复执行。`}`, "self-improvement review");
|
|
876
|
+
} else {
|
|
877
|
+
const reasons = [];
|
|
878
|
+
if (validation.rejected.length > 0) reasons.push(`${validation.rejected.length} op(s) rejected by validation`);
|
|
879
|
+
if (skippedUnread > 0) reasons.push(`${skippedUnread} op(s) skipped (skill not read this session)`);
|
|
880
|
+
if (executed.failedOps.length > 0) reasons.push(`${executed.failedOps.length} op(s) failed at execution: ${executed.failedOps.join("; ")}`);
|
|
881
|
+
if (executed.aborted !== void 0) reasons.push(`execution aborted: ${executed.aborted}`);
|
|
882
|
+
if (reasons.length === 0) reasons.push("the review plan contained nothing executable");
|
|
883
|
+
let text = `💾 Self-improvement review: 0 ops landed. ${reasons.join(" ")}`;
|
|
884
|
+
if (text.length > 500) text = `${text.slice(0, 497)}…`;
|
|
885
|
+
deliverMessage(agent, text, "self-improvement review");
|
|
897
886
|
}
|
|
887
|
+
emitApplied({
|
|
888
|
+
actions,
|
|
889
|
+
failedOps: executed.failedOps,
|
|
890
|
+
...executed.aborted !== void 0 ? { executionError: executed.aborted } : executed.failedOps[0] !== void 0 ? { executionError: executed.failedOps[0] } : {}
|
|
891
|
+
});
|
|
892
|
+
return true;
|
|
898
893
|
} catch (error) {
|
|
899
894
|
ctx.logger.warn(`dsh-evolution-review: subagent review failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
900
|
-
if (error instanceof Error && error.name === REVIEW_SETTLE_TIMEOUT) try {
|
|
901
|
-
ctx.emit("evolution/review-error", { sessionId: session.id });
|
|
902
|
-
} catch (emitError) {
|
|
903
|
-
ctx.logger.warn(`dsh-evolution-review: review-error emit failed: ${emitError instanceof Error ? emitError.message : String(emitError)}`);
|
|
904
|
-
}
|
|
905
|
-
if (error instanceof Error && error.message.includes("plan execution timed out")) ctx.logger.warn("dsh-evolution-review: plan execution abandoned on timeout — late writes ARE reported to the model as they land (S2-10) but still have NO plan-applied record and race the fallback inject; inspect the skill tree and usage sidecar");
|
|
906
895
|
return false;
|
|
907
896
|
} finally {
|
|
908
897
|
reviewInFlight = false;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -15,6 +15,14 @@ declare module '@deepseek-ai/dsh-llm' {
|
|
|
15
15
|
import type { Session } from '@deepseek-ai/dsh-session';
|
|
16
16
|
import { type ReviewKind } from '@lmzhen/dsh-evolution-core';
|
|
17
17
|
export { filterUnreadSkillOps } from '@lmzhen/dsh-evolution-core';
|
|
18
|
+
/** S2-6 (FLOW1-1): the settle-grace margin a subagent handle gets beyond its
|
|
19
|
+
* own deadline before the review abandons it. As the dispose watchdog's WHOLE
|
|
20
|
+
* budget it is capped by the review timeout so a short (test) budget stays
|
|
21
|
+
* short; the result watchdog adds it AFTER the full timeout (PLAN S1.1,
|
|
22
|
+
* 2026-09-16) and caps the sum at the timer ceiling (PLAN-R2 P2-1). The
|
|
23
|
+
* `run.dispose` arm point counts this margin ALONE — its clock starts after
|
|
24
|
+
* the result settled, not at the review timeout (PLAN-R2 P2-2, 2026-09-16). */
|
|
25
|
+
/** Error name marking the S2-6 watchdog expiry (see the catch in trySubagentReview). */
|
|
18
26
|
export declare const name = "evolution-review";
|
|
19
27
|
export declare const inject: string[];
|
|
20
28
|
export interface 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.
|
|
4
|
+
"version": "0.27.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -26,32 +26,35 @@
|
|
|
26
26
|
],
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
30
|
-
"@lmzhen/dsh-evolution-approval": "^0.
|
|
31
|
-
"@lmzhen/dsh-evolution-core": "^0.
|
|
32
|
-
"@lmzhen/dsh-evolution-plan-validator": "^0.
|
|
29
|
+
"@deepseek-ai/schemastery": "^3.18.5",
|
|
30
|
+
"@lmzhen/dsh-evolution-approval": "^0.27.1",
|
|
31
|
+
"@lmzhen/dsh-evolution-core": "^0.27.1",
|
|
32
|
+
"@lmzhen/dsh-evolution-plan-validator": "^0.27.1"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
36
|
-
"@deepseek-ai/dsh-agent": "^0.2.
|
|
37
|
-
"@deepseek-ai/dsh-llm": "^0.2.
|
|
38
|
-
"@deepseek-ai/dsh-session": "^0.2.
|
|
39
|
-
"@deepseek-ai/dsh-tools": "^0.2.
|
|
40
|
-
"@lmzhen/dsh-evolution-policy": "^0.
|
|
41
|
-
"@lmzhen/dsh-evolution-state": "^0.
|
|
35
|
+
"@deepseek-ai/cordis": "^4.0.5",
|
|
36
|
+
"@deepseek-ai/dsh-agent": "^0.2.1-rc.1",
|
|
37
|
+
"@deepseek-ai/dsh-llm": "^0.2.1-rc.1",
|
|
38
|
+
"@deepseek-ai/dsh-session": "^0.2.1-rc.1",
|
|
39
|
+
"@deepseek-ai/dsh-tools": "^0.2.1-rc.1",
|
|
40
|
+
"@lmzhen/dsh-evolution-policy": "^0.27.1",
|
|
41
|
+
"@lmzhen/dsh-evolution-state": "^0.27.1"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@deepseek-ai/dsh-agent": "^0.2.
|
|
45
|
-
"@deepseek-ai/dsh-agent-loop-testkit": "^0.2.
|
|
46
|
-
"@deepseek-ai/dsh-llm": "^0.2.
|
|
47
|
-
"@deepseek-ai/dsh-session": "^0.2.
|
|
48
|
-
"@deepseek-ai/dsh-session-persistence": "^0.2.
|
|
49
|
-
"@deepseek-ai/dsh-session-persistence-jsonl": "^0.2.
|
|
50
|
-
"@deepseek-ai/dsh-tools": "^0.2.
|
|
51
|
-
"@lmzhen/dsh-evolution-approval": "^0.
|
|
52
|
-
"@lmzhen/dsh-evolution-core": "^0.
|
|
53
|
-
"@lmzhen/dsh-evolution-curator": "^0.
|
|
54
|
-
"@lmzhen/dsh-evolution-plan-validator": "^0.
|
|
55
|
-
"@lmzhen/dsh-evolution-state": "^0.
|
|
44
|
+
"@deepseek-ai/dsh-agent": "^0.2.1-rc.1",
|
|
45
|
+
"@deepseek-ai/dsh-agent-loop-testkit": "^0.2.1-rc.1",
|
|
46
|
+
"@deepseek-ai/dsh-llm": "^0.2.1-rc.1",
|
|
47
|
+
"@deepseek-ai/dsh-session": "^0.2.1-rc.1",
|
|
48
|
+
"@deepseek-ai/dsh-session-persistence": "^0.2.1-rc.1",
|
|
49
|
+
"@deepseek-ai/dsh-session-persistence-jsonl": "^0.2.1-rc.1",
|
|
50
|
+
"@deepseek-ai/dsh-tools": "^0.2.1-rc.1",
|
|
51
|
+
"@lmzhen/dsh-evolution-approval": "^0.27.1",
|
|
52
|
+
"@lmzhen/dsh-evolution-core": "^0.27.1",
|
|
53
|
+
"@lmzhen/dsh-evolution-curator": "^0.27.1",
|
|
54
|
+
"@lmzhen/dsh-evolution-plan-validator": "^0.27.1",
|
|
55
|
+
"@lmzhen/dsh-evolution-state": "^0.27.1"
|
|
56
|
+
},
|
|
57
|
+
"engines": {
|
|
58
|
+
"dsh": ">=0.2.1-rc.1 <0.2.2-0"
|
|
56
59
|
}
|
|
57
60
|
}
|