@lmzhen/dsh-evolution-review 0.30.27 → 0.30.29
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 +135 -739
- package/lib/types/index.d.ts +4 -93
- package/package.json +6 -13
package/lib/index.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
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, ensureSkillFrontmatter, 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
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
3
1
|
import z from "@deepseek-ai/schemastery";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
4
3
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
5
|
-
import { SessionId } from "@deepseek-ai/dsh-session";
|
|
6
|
-
import {
|
|
4
|
+
import { SessionId, SessionLogOffset } from "@deepseek-ai/dsh-session";
|
|
5
|
+
import { COMPLETION_SKILL_REVIEW_PROMPT, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_REVIEW_TIMEOUT_MS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS, DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS, DEFAULT_SUBSTANTIVE_MIN_USER_CHARS, MAX_TIMER_DELAY_MS, PROMPT_BUNDLE, advanceReview, assertSkillsRootAliasRetired, clampOnce, classifySessionEvent, clearReviewChannel, filterUnreadSkillOps, liveBool, markReviewChannel, paramRowId, pickWithPolicy, readDispatchSignal, reviewPrompt, sessionAudited, sessionTurnSignals, sweepReviewChannelSessions, unwrapLive, userSetKeys, verifyPromptBundle } from "@lmzhen/dsh-evolution-core";
|
|
7
6
|
/**
|
|
8
7
|
* Whether a message is a notice this plugin delivered, judged only by durable source fields.
|
|
9
8
|
*
|
|
@@ -149,17 +148,12 @@ function noticeAllowed(reviewPrompt, notice) {
|
|
|
149
148
|
}
|
|
150
149
|
const Config = z.object({
|
|
151
150
|
reviewEnabled: z.boolean().default(true).volatile(),
|
|
152
|
-
reviewMode: z.union([z.const("
|
|
151
|
+
reviewMode: z.union([z.const("inject"), z.const("branch")]).default("inject").volatile(),
|
|
153
152
|
reviewMemoryInterval: z.number().min(1).volatile(),
|
|
154
153
|
reviewSkillInterval: z.number().min(1).volatile(),
|
|
155
154
|
memoryInterval: z.number().min(1).default(DEFAULT_REVIEW_MEMORY_INTERVAL),
|
|
156
155
|
skillInterval: z.number().min(1).default(DEFAULT_REVIEW_SKILL_INTERVAL),
|
|
157
|
-
reviewToolAllow: z.array(z.string()).default(["skill"]),
|
|
158
156
|
reviewTimeoutMs: z.number().min(1).max(MAX_TIMER_DELAY_MS).default(DEFAULT_REVIEW_TIMEOUT_MS),
|
|
159
|
-
reviewContextMessages: z.number().min(1).default(DEFAULT_REVIEW_CONTEXT_MESSAGES),
|
|
160
|
-
reviewMessageChars: z.number().min(1).default(DEFAULT_REVIEW_MESSAGE_CHARS),
|
|
161
|
-
reviewMaxDepth: z.number().min(1).default(1),
|
|
162
|
-
reviewProvider: z.string(),
|
|
163
157
|
skillReviewTrigger: z.union([
|
|
164
158
|
z.const("cadence"),
|
|
165
159
|
z.const("completion"),
|
|
@@ -172,24 +166,6 @@ const Config = z.object({
|
|
|
172
166
|
root: z.string().default(""),
|
|
173
167
|
skillsRoot: z.string().default("")
|
|
174
168
|
});
|
|
175
|
-
/** V8-01 (0.3.45): the review subagent's structured-output contract. The
|
|
176
|
-
* `subagents.start` outputSchema is RAW JSON Schema validated by
|
|
177
|
-
* assertObjectJsonSchema (upstream dsh-tools json-schema.ts — SCHEMA_TYPES
|
|
178
|
-
* has no 'json'; 'json' is a defineTool-author DSL-only value). The arrays
|
|
179
|
-
* deliberately use `{ type: 'array' }` with NO items node — absent items
|
|
180
|
-
* accepts any JSON item (upstream semantics, json-schema.ts "Item schema
|
|
181
|
-
* (type: 'array' only); absent accepts any JSON item"), and plan-validator
|
|
182
|
-
* owns the per-op structure proof. The whitelist contract test (review.spec
|
|
183
|
-
* V8-01) forbids a DSL value slipping back in. */
|
|
184
|
-
const REVIEW_OUTPUT_SCHEMA = {
|
|
185
|
-
type: "object",
|
|
186
|
-
additionalProperties: false,
|
|
187
|
-
properties: {
|
|
188
|
-
memoryOps: { type: "array" },
|
|
189
|
-
skillOps: { type: "array" },
|
|
190
|
-
summary: { type: "string" }
|
|
191
|
-
}
|
|
192
|
-
};
|
|
193
169
|
/**
|
|
194
170
|
* v43 audit (P1-4 / FLOW1): the cadence summary MUST name the review kind — it
|
|
195
171
|
* is what tells the three cadence deliveries apart (their prompts differ by
|
|
@@ -211,15 +187,7 @@ function clampReviewConfig(rawConfig, ctx) {
|
|
|
211
187
|
ctx.logger.warn("dsh-evolution-review: " + message);
|
|
212
188
|
};
|
|
213
189
|
const field = (name, value, fallback, min, max) => clampOnce(warned, warn, name, value, fallback, min, max);
|
|
214
|
-
return Object.assign({}, rawConfig, {
|
|
215
|
-
reviewTimeoutMs: field("reviewTimeoutMs", rawConfig.reviewTimeoutMs, DEFAULT_REVIEW_TIMEOUT_MS, 1, MAX_TIMER_DELAY_MS),
|
|
216
|
-
reviewContextMessages: field("reviewContextMessages", rawConfig.reviewContextMessages, DEFAULT_REVIEW_CONTEXT_MESSAGES, 1),
|
|
217
|
-
reviewMessageChars: field("reviewMessageChars", rawConfig.reviewMessageChars, DEFAULT_REVIEW_MESSAGE_CHARS, 1),
|
|
218
|
-
reviewMaxDepth: field("reviewMaxDepth", rawConfig.reviewMaxDepth, 1, 1)
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
function policySnapshotOf(source) {
|
|
222
|
-
return source?.get?.();
|
|
190
|
+
return Object.assign({}, rawConfig, { reviewTimeoutMs: field("reviewTimeoutMs", rawConfig.reviewTimeoutMs, DEFAULT_REVIEW_TIMEOUT_MS, 1, MAX_TIMER_DELAY_MS) });
|
|
223
191
|
}
|
|
224
192
|
/**
|
|
225
193
|
* This row's review knobs as they stand RIGHT NOW (G1 §8.3) — the base layer of the
|
|
@@ -253,9 +221,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
253
221
|
});
|
|
254
222
|
if (!verifyPromptBundle(PROMPT_BUNDLE)) throw new Error("dsh-evolution prompt bundle integrity check failed; refusing to schedule review work");
|
|
255
223
|
const config = clampReviewConfig(rawConfig, ctx);
|
|
256
|
-
const reviewChannel = createSubagentChannel(ctx);
|
|
257
224
|
assertSkillsRootAliasRetired(rawConfig);
|
|
258
|
-
const rootConfig = resolveRootConfig(rawConfig);
|
|
259
225
|
const sessionState = new SessionScopedState();
|
|
260
226
|
const dispatchDigest = sessionState.add("dispatchDigest", /* @__PURE__ */ new Map());
|
|
261
227
|
let statelessReviewStateWarned = false;
|
|
@@ -267,9 +233,10 @@ function apply(ctx, rawConfig = {}) {
|
|
|
267
233
|
const pendingCadenceWarned = sessionState.add("pendingCadenceWarned", /* @__PURE__ */ new Set());
|
|
268
234
|
const pendingReviewNotices = sessionState.add("pendingReviewNotices", /* @__PURE__ */ new Map());
|
|
269
235
|
const cadenceResetWarned = sessionState.add("cadenceResetWarned", /* @__PURE__ */ new Set());
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
236
|
+
/** Branch mode runs at most one branch per parent session; a second trigger only sets rerun. */
|
|
237
|
+
const branchRunning = sessionState.add("branchRunning", /* @__PURE__ */ new Map());
|
|
238
|
+
/** Child Sessions this plugin owns; their turn/end must not start another review. */
|
|
239
|
+
const branchSessions = sessionState.add("branchSessions", /* @__PURE__ */ new Set());
|
|
273
240
|
const policy = () => ctx.get("evolutionPolicy")?.get();
|
|
274
241
|
/**
|
|
275
242
|
* This row's knobs as they stand right now (G1 §8.3): the live fields are resolved
|
|
@@ -435,6 +402,8 @@ function apply(ctx, rawConfig = {}) {
|
|
|
435
402
|
async function runOnTurnEnd(session, event) {
|
|
436
403
|
if (!params().reviewEnabled) return;
|
|
437
404
|
if (session.header.origin === "subagent") return;
|
|
405
|
+
if (branchSessions.has(session.id)) return;
|
|
406
|
+
if (session.header.visibility === "background") return;
|
|
438
407
|
const agent = ctx.agents.get(session.id);
|
|
439
408
|
if (!agent) return;
|
|
440
409
|
const signal = sessionTurnSignals(ctx, session);
|
|
@@ -480,7 +449,8 @@ function apply(ctx, rawConfig = {}) {
|
|
|
480
449
|
const pendingKind = kind ?? pendingCadenceReviews.get(session.id) ?? void 0;
|
|
481
450
|
if (pendingKind !== void 0) {
|
|
482
451
|
pendingCadenceReviews.delete(session.id);
|
|
483
|
-
if (params().reviewMode === "
|
|
452
|
+
if (params().reviewMode === "branch" && await tryBranchReview(session, agent, pendingKind, signal, "cadence")) return;
|
|
453
|
+
if (params().reviewMode === "inject" || params().reviewMode === "branch") {
|
|
484
454
|
if (!deliverMessage(agent, reviewPrompt(pendingKind), cadenceSummary(pendingKind), true)) {
|
|
485
455
|
pendingCadenceReviews.set(session.id, pendingKind);
|
|
486
456
|
return;
|
|
@@ -493,35 +463,6 @@ function apply(ctx, rawConfig = {}) {
|
|
|
493
463
|
assistantChars: signal.assistantChars,
|
|
494
464
|
channel: "inject"
|
|
495
465
|
});
|
|
496
|
-
} else {
|
|
497
|
-
const reviewOutcome = await trySubagentReview(session, agent, pendingKind, signal);
|
|
498
|
-
if (reviewOutcome === true) {
|
|
499
|
-
emitReviewScheduled(ctx, {
|
|
500
|
-
sessionId: session.id,
|
|
501
|
-
kind: pendingKind,
|
|
502
|
-
toolCalls: signal.toolCalls,
|
|
503
|
-
userChars: signal.userChars,
|
|
504
|
-
assistantChars: signal.assistantChars,
|
|
505
|
-
channel: "subagent"
|
|
506
|
-
});
|
|
507
|
-
await settleNotice(session.id, "subagent-flush");
|
|
508
|
-
} else if (reviewOutcome === "dropped") {
|
|
509
|
-
pendingCadenceReviews.set(session.id, pendingKind);
|
|
510
|
-
return;
|
|
511
|
-
} else if (reviewOutcome !== "deferred") {
|
|
512
|
-
if (!deliverMessage(agent, reviewPrompt(pendingKind), cadenceSummary(pendingKind), true)) {
|
|
513
|
-
pendingCadenceReviews.set(session.id, pendingKind);
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
emitReviewScheduled(ctx, {
|
|
517
|
-
sessionId: session.id,
|
|
518
|
-
kind: pendingKind,
|
|
519
|
-
toolCalls: signal.toolCalls,
|
|
520
|
-
userChars: signal.userChars,
|
|
521
|
-
assistantChars: signal.assistantChars,
|
|
522
|
-
channel: "inject"
|
|
523
|
-
});
|
|
524
|
-
}
|
|
525
466
|
}
|
|
526
467
|
return;
|
|
527
468
|
}
|
|
@@ -540,22 +481,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
540
481
|
if (completionInjected.has(session.id)) return;
|
|
541
482
|
if (!shouldCompletionReview(event.data.reason, cumulative, params().skillReviewCompletionMinToolCalls)) return;
|
|
542
483
|
completionInjected.add(session.id);
|
|
543
|
-
if (
|
|
544
|
-
if (deferredFallbackReviews.length < DEFERRED_REVIEW_CAP) deferredFallbackReviews.push({
|
|
545
|
-
agent,
|
|
546
|
-
sessionId: session.id,
|
|
547
|
-
kind: "skill",
|
|
548
|
-
prompt: COMPLETION_SKILL_REVIEW_PROMPT,
|
|
549
|
-
label: "completion review",
|
|
550
|
-
channel: "completion",
|
|
551
|
-
counts: signal
|
|
552
|
-
});
|
|
553
|
-
else {
|
|
554
|
-
completionInjected.delete(session.id);
|
|
555
|
-
ctx.logger.warn(`dsh-evolution-review: deferred-review queue at cap (${DEFERRED_REVIEW_CAP}) — dropped one deferred completion review prompt; the completion gate re-arms for the next completed turn`);
|
|
556
|
-
}
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
484
|
+
if (params().reviewMode === "branch" && await tryBranchReview(session, agent, "skill", signal, "completion")) return;
|
|
559
485
|
if (!deliverMessage(agent, COMPLETION_SKILL_REVIEW_PROMPT, "completion review", true)) {
|
|
560
486
|
completionInjected.delete(session.id);
|
|
561
487
|
return;
|
|
@@ -679,527 +605,142 @@ function apply(ctx, rawConfig = {}) {
|
|
|
679
605
|
return false;
|
|
680
606
|
}
|
|
681
607
|
};
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
608
|
+
const BRANCH_MEMORY_PROMPT = "[Auto-review — Memory] Review the conversation above and save durable user preferences, corrections, or stable facts with the memory tool when appropriate. Do not create skills here. Return a short summary of what was saved, or say Nothing to save.";
|
|
609
|
+
async function tryBranchReview(session, agent, kind, signal, reason) {
|
|
610
|
+
const state = branchRunning.get(session.id);
|
|
611
|
+
if (state !== void 0) {
|
|
612
|
+
state.rerun = kind;
|
|
613
|
+
return true;
|
|
614
|
+
}
|
|
615
|
+
const branchState = {};
|
|
616
|
+
branchRunning.set(session.id, branchState);
|
|
617
|
+
const childId = SessionId("session-" + randomUUID());
|
|
618
|
+
const startedAt = Date.now();
|
|
619
|
+
const controller = new AbortController();
|
|
620
|
+
let timedOut = false;
|
|
695
621
|
const timer = setTimeout(() => {
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
}, "dsh-evolution-review.deferred-drain");
|
|
711
|
-
/** v32 REV-06(b): content hash of every tree skill at REVIEW SCHEDULE time.
|
|
712
|
-
* The subagent's read-time hash is not plumbed through the plan, so this
|
|
713
|
-
* pre-run snapshot is what the execute-time staleness check can honestly
|
|
714
|
-
* compare against: a skill whose hash changed while the review ran was
|
|
715
|
-
* touched concurrently, and the plan (authored against the old state) is
|
|
716
|
-
* refused as stale. */
|
|
717
|
-
async function treeSkillHashes() {
|
|
718
|
-
const hashes = /* @__PURE__ */ new Map();
|
|
719
|
-
const io = ctx.get("evolutionIo");
|
|
720
|
-
if (!io) return hashes;
|
|
721
|
-
const library = newSkillLibrary({
|
|
722
|
-
authority: authorityFrom({ origin: "foreground" }),
|
|
723
|
-
config: rootConfig,
|
|
724
|
-
io: evolutionIoAdapter(() => io.provider())
|
|
725
|
-
});
|
|
726
|
-
for (const summary of await library.list({ withContent: true }).catch((error) => {
|
|
727
|
-
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`);
|
|
728
|
-
return [];
|
|
729
|
-
})) if (summary.content !== void 0) hashes.set(summary.name, contentHash(summary.content));
|
|
730
|
-
return hashes;
|
|
731
|
-
}
|
|
732
|
-
async function trySubagentReview(session, agent, kind, signal) {
|
|
733
|
-
if (params().reviewMode === "inject") return false;
|
|
734
|
-
const channelState = reviewChannel.state;
|
|
735
|
-
if (channelState.kind !== "available") {
|
|
736
|
-
if (!channelUnavailableWarned) {
|
|
737
|
-
channelUnavailableWarned = true;
|
|
738
|
-
ctx.logger.warn("dsh-evolution-review: subagent channel unavailable (" + channelState.code + ": " + channelState.reason + ") — reviews fall back to the inject path until the capability returns");
|
|
739
|
-
}
|
|
740
|
-
try {
|
|
741
|
-
ctx.emit("evolution/review-error", { sessionId: session.id });
|
|
742
|
-
} catch (emitError) {
|
|
743
|
-
ctx.logger.warn("dsh-evolution-review: review-error emit failed: " + (emitError instanceof Error ? emitError.message : String(emitError)));
|
|
622
|
+
timedOut = true;
|
|
623
|
+
controller.abort();
|
|
624
|
+
}, rawConfig.reviewTimeoutMs ?? DEFAULT_REVIEW_TIMEOUT_MS);
|
|
625
|
+
let handle;
|
|
626
|
+
try {
|
|
627
|
+
const presets = ctx.get("agentPresets");
|
|
628
|
+
let agentPreset;
|
|
629
|
+
let setup;
|
|
630
|
+
if (presets !== void 0) {
|
|
631
|
+
const resolved = await presets.resolve(session.header.agentPreset);
|
|
632
|
+
agentPreset = resolved.id;
|
|
633
|
+
setup = async (target, _child) => {
|
|
634
|
+
await presets.mount(target, resolved.id);
|
|
635
|
+
};
|
|
744
636
|
}
|
|
637
|
+
const seed = await ctx.get("sessionQuery")?.readEvents?.(session.id);
|
|
638
|
+
if (seed === void 0) throw new Error("session-query readEvents is unavailable");
|
|
639
|
+
const current = ctx.get("agentDefaultModel")?.currentSelection?.();
|
|
640
|
+
handle = await ctx.agents.create({
|
|
641
|
+
sessionId: childId,
|
|
642
|
+
parentAgent: agent,
|
|
643
|
+
seed,
|
|
644
|
+
inheritedEventCount: SessionLogOffset(seed.length),
|
|
645
|
+
meta: {
|
|
646
|
+
parentSession: session.id,
|
|
647
|
+
isSeeded: true,
|
|
648
|
+
...agentPreset === void 0 ? {} : { agentPreset }
|
|
649
|
+
},
|
|
650
|
+
...current === void 0 ? {} : { agentOptions: {
|
|
651
|
+
provider: current.provider,
|
|
652
|
+
model: current.model
|
|
653
|
+
} },
|
|
654
|
+
...setup === void 0 ? {} : { setup }
|
|
655
|
+
});
|
|
656
|
+
} catch (error) {
|
|
657
|
+
clearTimeout(timer);
|
|
658
|
+
branchRunning.delete(session.id);
|
|
659
|
+
ctx.logger.warn("dsh-evolution-review: branch Session did not start: " + (error instanceof Error ? error.message : String(error)));
|
|
745
660
|
return false;
|
|
746
661
|
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
662
|
+
branchSessions.add(childId);
|
|
663
|
+
const collected = {
|
|
664
|
+
finalText: "",
|
|
665
|
+
actions: [],
|
|
666
|
+
usage: void 0,
|
|
667
|
+
error: void 0
|
|
668
|
+
};
|
|
669
|
+
const onEvent = (owner, event) => {
|
|
670
|
+
if (owner.id !== childId) return;
|
|
671
|
+
if (event.type === "assistant/message") {
|
|
672
|
+
const text = event.data.message.content.filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
673
|
+
if (text !== "") collected.finalText = text;
|
|
674
|
+
if (event.data.usage !== void 0) collected.usage = {
|
|
675
|
+
inputTokens: event.data.usage.inputTokens ?? 0,
|
|
676
|
+
outputTokens: event.data.usage.outputTokens ?? 0,
|
|
677
|
+
cacheReadTokens: event.data.usage.cacheReadTokens ?? 0,
|
|
678
|
+
cacheWriteTokens: event.data.usage.cacheWriteTokens ?? 0
|
|
758
679
|
};
|
|
759
|
-
if (existing >= 0) deferredFallbackReviews[existing] = entry;
|
|
760
|
-
else deferredFallbackReviews.push(entry);
|
|
761
|
-
return "deferred";
|
|
762
|
-
}
|
|
763
|
-
ctx.logger.warn(`dsh-evolution-review: deferred-review queue at cap (${DEFERRED_REVIEW_CAP}) — dropping one fallback review prompt`);
|
|
764
|
-
return "dropped";
|
|
765
|
-
}
|
|
766
|
-
reviewInFlight = true;
|
|
767
|
-
try {
|
|
768
|
-
const snapshot = policy();
|
|
769
|
-
const model = kind === "memory" ? snapshot?.memoryReviewModel ?? DEFAULT_MEMORY_REVIEW_MODEL : snapshot?.skillReviewModel ?? DEFAULT_SKILL_REVIEW_MODEL;
|
|
770
|
-
const reviewText = redactSecrets(buildReviewRequest(session, kind, signal, config.reviewContextMessages, config.reviewMessageChars, dispatchDigest.get(session.id) ?? []));
|
|
771
|
-
const agentOptions = { model };
|
|
772
|
-
if (config.reviewProvider) agentOptions.provider = config.reviewProvider;
|
|
773
|
-
const requestedReviewTools = config.reviewToolAllow ?? [];
|
|
774
|
-
const preRunHashes = await treeSkillHashes();
|
|
775
|
-
const sessionSeqAtPlanTime = session.seq - 1;
|
|
776
|
-
const substantiveEvidenceSeqs = sessionEvidenceIndex(ctx, session);
|
|
777
|
-
const outcome = await reviewChannel.run({
|
|
778
|
-
parent: agent,
|
|
779
|
-
label: "dsh-evolution-review",
|
|
780
|
-
promptText: reviewText + "\n\n" + reviewPrompt(kind, "plan"),
|
|
781
|
-
kind: "reviewer",
|
|
782
|
-
allow: requestedReviewTools,
|
|
783
|
-
outputSchema: REVIEW_OUTPUT_SCHEMA,
|
|
784
|
-
maxDepth: config.reviewMaxDepth,
|
|
785
|
-
agentOptions,
|
|
786
|
-
timeoutMs: config.reviewTimeoutMs,
|
|
787
|
-
signal: fiberAbort.signal,
|
|
788
|
-
afterResult: (child) => {
|
|
789
|
-
const childSession = child.session;
|
|
790
|
-
return childSession === void 0 ? /* @__PURE__ */ new Set() : new Set(sessionReadNames(ctx, childSession) ?? []);
|
|
791
|
-
}
|
|
792
|
-
});
|
|
793
|
-
if (outcome.kind === "aborted") {
|
|
794
|
-
try {
|
|
795
|
-
ctx.emit("evolution/review-error", { sessionId: session.id });
|
|
796
|
-
} catch (emitError) {
|
|
797
|
-
ctx.logger.warn("dsh-evolution-review: review-error emit failed: " + (emitError instanceof Error ? emitError.message : String(emitError)));
|
|
798
|
-
}
|
|
799
|
-
return false;
|
|
800
|
-
}
|
|
801
|
-
if (outcome.kind === "unavailable") {
|
|
802
|
-
if (!channelUnavailableWarned) {
|
|
803
|
-
channelUnavailableWarned = true;
|
|
804
|
-
ctx.logger.warn("dsh-evolution-review: subagent channel unavailable (" + outcome.code + ": " + outcome.reason + ") — reviews fall back to the inject path until the capability returns");
|
|
805
|
-
}
|
|
806
|
-
try {
|
|
807
|
-
ctx.emit("evolution/review-error", { sessionId: session.id });
|
|
808
|
-
} catch (emitError) {
|
|
809
|
-
ctx.logger.warn("dsh-evolution-review: review-error emit failed: " + (emitError instanceof Error ? emitError.message : String(emitError)));
|
|
810
|
-
}
|
|
811
|
-
return false;
|
|
812
|
-
}
|
|
813
|
-
if (outcome.kind === "no-structured") {
|
|
814
|
-
try {
|
|
815
|
-
ctx.emit("evolution/review-error", { sessionId: session.id });
|
|
816
|
-
} catch (emitError) {
|
|
817
|
-
ctx.logger.warn("dsh-evolution-review: review-error emit failed: " + (emitError instanceof Error ? emitError.message : String(emitError)));
|
|
818
|
-
}
|
|
819
|
-
const stopDetail = typeof outcome.stopReason === "string" ? outcome.stopReason : void 0;
|
|
820
|
-
const diagDetail = typeof outcome.diagnostic === "string" ? outcome.diagnostic : void 0;
|
|
821
|
-
ctx.logger.warn("dsh-evolution-review: review subagent returned no structured plan" + (stopDetail !== void 0 ? " (stopReason=" + stopDetail + (diagDetail !== void 0 ? "; " + diagDetail : "") + ")" : ""));
|
|
822
|
-
return false;
|
|
823
680
|
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
681
|
+
};
|
|
682
|
+
const offEvent = ctx.on("session/event", onEvent);
|
|
683
|
+
emitReviewScheduled(ctx, {
|
|
684
|
+
sessionId: session.id,
|
|
685
|
+
kind,
|
|
686
|
+
toolCalls: signal.toolCalls,
|
|
687
|
+
userChars: signal.userChars,
|
|
688
|
+
assistantChars: signal.assistantChars,
|
|
689
|
+
channel: "branch"
|
|
690
|
+
});
|
|
691
|
+
(async () => {
|
|
692
|
+
try {
|
|
693
|
+
const prompt = kind === "memory" ? BRANCH_MEMORY_PROMPT : COMPLETION_SKILL_REVIEW_PROMPT;
|
|
694
|
+
handle.agent.followup(createUserMessage({
|
|
695
|
+
content: [{
|
|
696
|
+
type: "text",
|
|
697
|
+
text: prompt
|
|
698
|
+
}],
|
|
699
|
+
source: { kind: "user" }
|
|
700
|
+
}));
|
|
701
|
+
await handle.agent.whenIdle();
|
|
702
|
+
offEvent();
|
|
703
|
+
if (collected.finalText.trim() !== "") deliverMessage(agent, collected.finalText, "branch review result", false);
|
|
841
704
|
try {
|
|
842
|
-
ctx.emit("evolution/
|
|
705
|
+
ctx.emit("evolution/review-completed", {
|
|
706
|
+
version: 1,
|
|
707
|
+
reviewId: childId,
|
|
843
708
|
sessionId: session.id,
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
709
|
+
branchSessionId: childId,
|
|
710
|
+
kind,
|
|
711
|
+
status: collected.error !== void 0 ? "failed" : timedOut ? "timeout" : controller.signal.aborted ? "aborted" : "completed",
|
|
712
|
+
trigger: {
|
|
713
|
+
reason,
|
|
714
|
+
toolCalls: signal.toolCalls,
|
|
715
|
+
userChars: signal.userChars,
|
|
716
|
+
assistantChars: signal.assistantChars
|
|
717
|
+
},
|
|
718
|
+
finalText: collected.finalText,
|
|
719
|
+
actions: collected.actions,
|
|
720
|
+
...collected.usage === void 0 ? {} : { usage: collected.usage },
|
|
721
|
+
startedAt,
|
|
722
|
+
finishedAt: Date.now(),
|
|
723
|
+
...collected.error === void 0 ? {} : { error: collected.error }
|
|
855
724
|
});
|
|
856
725
|
} catch (emitError) {
|
|
857
|
-
ctx.logger.warn(
|
|
726
|
+
ctx.logger.warn("dsh-evolution-review: review-completed emit failed: " + (emitError instanceof Error ? emitError.message : String(emitError)));
|
|
858
727
|
}
|
|
859
|
-
};
|
|
860
|
-
const landed = [];
|
|
861
|
-
let deadlineMissed = false;
|
|
862
|
-
const lateLandings = [];
|
|
863
|
-
const noteLanded = (action) => {
|
|
864
|
-
landed.push(action);
|
|
865
|
-
if (!deadlineMissed) return;
|
|
866
|
-
lateLandings.push(action);
|
|
867
|
-
const before = landed.length - lateLandings.length;
|
|
868
|
-
const earlier = before > 0 ? ` (${before} other op(s) had landed before the deadline)` : "";
|
|
869
|
-
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");
|
|
870
|
-
};
|
|
871
|
-
let executed;
|
|
872
|
-
try {
|
|
873
|
-
executed = await withTimeout(executePlan(validation.accepted, session, noteLanded, preRunHashes, fiberAbort.signal), config.reviewTimeoutMs, "review plan execution");
|
|
874
728
|
} catch (error) {
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
const failedNote = executed.failedOps.length > 0 ? ` 失败 ${executed.failedOps.length} 个:${executed.failedOps.join(";")}。` : "";
|
|
886
|
-
deliverMessage(agent, `💾 Self-improvement review: ${applied}${executed.ok ? "" : `\n部分操作失败。${failedNote}以上操作已应用,请勿重复执行。`}`, "self-improvement review");
|
|
887
|
-
} else {
|
|
888
|
-
const reasons = [];
|
|
889
|
-
if (validation.rejected.length > 0) reasons.push(`${validation.rejected.length} op(s) rejected by validation`);
|
|
890
|
-
if (skippedUnread > 0) reasons.push(`${skippedUnread} op(s) skipped (skill not read this session)`);
|
|
891
|
-
if (executed.failedOps.length > 0) reasons.push(`${executed.failedOps.length} op(s) failed at execution: ${executed.failedOps.join("; ")}`);
|
|
892
|
-
if (executed.aborted !== void 0) reasons.push(`execution aborted: ${executed.aborted}`);
|
|
893
|
-
if (reasons.length === 0) reasons.push("the review plan contained nothing executable");
|
|
894
|
-
let text = `💾 Self-improvement review: 0 ops landed. ${reasons.join(" ")}`;
|
|
895
|
-
if (text.length > 500) text = `${text.slice(0, 497)}…`;
|
|
896
|
-
deliverMessage(agent, text, "self-improvement review");
|
|
897
|
-
}
|
|
898
|
-
emitApplied({
|
|
899
|
-
actions,
|
|
900
|
-
failedOps: executed.failedOps,
|
|
901
|
-
...executed.aborted !== void 0 ? { executionError: executed.aborted } : executed.failedOps[0] !== void 0 ? { executionError: executed.failedOps[0] } : {}
|
|
902
|
-
});
|
|
903
|
-
return true;
|
|
904
|
-
} catch (error) {
|
|
905
|
-
ctx.logger.warn(`dsh-evolution-review: subagent review failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
906
|
-
return false;
|
|
907
|
-
} finally {
|
|
908
|
-
reviewInFlight = false;
|
|
909
|
-
const deferred = deferredFallbackReviews.splice(0);
|
|
910
|
-
for (const { agent: waitingAgent, sessionId: entrySession, kind: waitingKind, prompt, label, channel, counts: entryCounts } of deferred) {
|
|
911
|
-
if (!deliverMessage(waitingAgent, prompt, label, true)) {
|
|
912
|
-
if (channel === "completion") completionInjected.delete(entrySession);
|
|
913
|
-
else pendingCadenceReviews.set(entrySession, waitingKind);
|
|
914
|
-
continue;
|
|
915
|
-
}
|
|
916
|
-
emitReviewScheduled(ctx, {
|
|
917
|
-
sessionId: entrySession,
|
|
918
|
-
kind: waitingKind,
|
|
919
|
-
toolCalls: entryCounts.toolCalls,
|
|
920
|
-
userChars: entryCounts.userChars,
|
|
921
|
-
assistantChars: entryCounts.assistantChars,
|
|
922
|
-
channel
|
|
923
|
-
});
|
|
729
|
+
offEvent();
|
|
730
|
+
collected.error = error instanceof Error ? error.message : String(error);
|
|
731
|
+
ctx.logger.warn("dsh-evolution-review: branch review failed: " + collected.error);
|
|
732
|
+
} finally {
|
|
733
|
+
clearTimeout(timer);
|
|
734
|
+
branchSessions.delete(childId);
|
|
735
|
+
await handle.dispose().catch(() => {});
|
|
736
|
+
const rerun = branchState.rerun;
|
|
737
|
+
branchRunning.delete(session.id);
|
|
738
|
+
if (rerun !== void 0) tryBranchReview(session, agent, rerun, signal, reason);
|
|
924
739
|
}
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
async function executePlan(plan, session, onLanded, preRunHashes, signal) {
|
|
928
|
-
const sessionId = session?.id;
|
|
929
|
-
const memory = ctx.get("memory");
|
|
930
|
-
const approval = ctx.get("evolutionApproval");
|
|
931
|
-
const hashLibrary = (() => {
|
|
932
|
-
const io = ctx.get("evolutionIo");
|
|
933
|
-
if (!io) return null;
|
|
934
|
-
return newSkillLibrary({
|
|
935
|
-
authority: authorityFrom({ origin: "foreground" }),
|
|
936
|
-
config: rootConfig,
|
|
937
|
-
io: evolutionIoAdapter(() => io.provider())
|
|
938
|
-
});
|
|
939
740
|
})();
|
|
940
|
-
|
|
941
|
-
const actions = [];
|
|
942
|
-
const failedOps = [];
|
|
943
|
-
let ok = true;
|
|
944
|
-
try {
|
|
945
|
-
for (const op of plan.memoryOps ?? []) {
|
|
946
|
-
if (ctx.fiber.uid === null || signal?.aborted === true) {
|
|
947
|
-
const reason = "the review was unloaded mid-plan";
|
|
948
|
-
ctx.logger.warn(`dsh-evolution-review: executePlan stopped at an op boundary after ${actions.length} op(s): ${reason}`);
|
|
949
|
-
return {
|
|
950
|
-
actions,
|
|
951
|
-
ok: false,
|
|
952
|
-
failedOps,
|
|
953
|
-
aborted: reason
|
|
954
|
-
};
|
|
955
|
-
}
|
|
956
|
-
if (!Array.isArray(op.evidence) || op.evidence.length === 0) {
|
|
957
|
-
ok = false;
|
|
958
|
-
failedOps.push(`memory ${op.action ?? "add"} ${op.target}: missing evidence (defense-in-depth rejection)`);
|
|
959
|
-
continue;
|
|
960
|
-
}
|
|
961
|
-
const normalized = {
|
|
962
|
-
target: op.target === "user" ? "user" : "memory",
|
|
963
|
-
action: op.action ?? "add",
|
|
964
|
-
facts: payloadText(op),
|
|
965
|
-
old_text: op.old_text
|
|
966
|
-
};
|
|
967
|
-
const result = approval ? await runApproved("memory", `memory ${normalized.target} ${normalized.action}`, normalized, normalized, session) : await memory?.applyBatch(normalized.target, [normalized]);
|
|
968
|
-
if (result?.ok) {
|
|
969
|
-
actions.push("Memory updated");
|
|
970
|
-
onLanded?.("Memory updated");
|
|
971
|
-
} else {
|
|
972
|
-
ok = false;
|
|
973
|
-
failedOps.push(`memory ${normalized.action} ${normalized.target}: ${result?.message ?? "service unavailable"}`);
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
for (const op of plan.skillOps ?? []) {
|
|
977
|
-
if (ctx.fiber.uid === null || signal?.aborted === true) {
|
|
978
|
-
const reason = "the review was unloaded mid-plan";
|
|
979
|
-
ctx.logger.warn(`dsh-evolution-review: executePlan stopped at an op boundary after ${actions.length} op(s): ${reason}`);
|
|
980
|
-
return {
|
|
981
|
-
actions,
|
|
982
|
-
ok: false,
|
|
983
|
-
failedOps,
|
|
984
|
-
aborted: reason
|
|
985
|
-
};
|
|
986
|
-
}
|
|
987
|
-
if (!Array.isArray(op.evidence) || op.evidence.length === 0 || !op.name) {
|
|
988
|
-
ok = false;
|
|
989
|
-
failedOps.push(`skill ${op.action ?? "patch"} ${op.name ?? "<unnamed>"}: missing evidence or name (defense-in-depth rejection)`);
|
|
990
|
-
continue;
|
|
991
|
-
}
|
|
992
|
-
const args = {
|
|
993
|
-
...op,
|
|
994
|
-
evidence: op.evidence
|
|
995
|
-
};
|
|
996
|
-
if ((args.action === "create" || args.action === "edit" || args.action === "update") && typeof args.content === "string" && typeof args.name === "string") {
|
|
997
|
-
const repaired = ensureSkillFrontmatter(args.content, args.name, args.description);
|
|
998
|
-
if (repaired.repaired) {
|
|
999
|
-
args.content = repaired.content;
|
|
1000
|
-
ctx.logger.warn(`dsh-evolution-review: synthesized missing SKILL.md frontmatter for ${args.name}`);
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
let stagedBytes;
|
|
1004
|
-
if ((args.action === "update" || args.action === "edit") && hashLibrary && typeof args.name === "string" && args.name !== "") {
|
|
1005
|
-
const probe = await hashLibrary.readSkillProbe(args.name);
|
|
1006
|
-
if (isUnknown(probe)) {
|
|
1007
|
-
ok = false;
|
|
1008
|
-
failedOps.push(`skill ${args.action} ${args.name}: target is unreadable (${probe.reason}); refusing to stage an unverifiable anchor`);
|
|
1009
|
-
continue;
|
|
1010
|
-
}
|
|
1011
|
-
stagedBytes = isPresent(probe) ? probe.value : null;
|
|
1012
|
-
}
|
|
1013
|
-
if ((args.action === "write_file" || args.action === "remove_file") && hashLibrary && typeof args.name === "string" && args.name !== "" && typeof args.file_path === "string" && args.file_path !== "") {
|
|
1014
|
-
const supportProbe = await hashLibrary.readSupportFileProbe(args.name, args.file_path);
|
|
1015
|
-
if (isUnknown(supportProbe)) {
|
|
1016
|
-
ok = false;
|
|
1017
|
-
failedOps.push(`file ${args.action} ${args.name}/${args.file_path}: target is unreadable (${supportProbe.reason}); refusing to stage an unverifiable anchor`);
|
|
1018
|
-
continue;
|
|
1019
|
-
}
|
|
1020
|
-
const stageFile = isPresent(supportProbe) ? supportProbe.value : null;
|
|
1021
|
-
args.staged_from_sha256 = stageFile === null ? "absent" : contentHash(stageFile);
|
|
1022
|
-
}
|
|
1023
|
-
const opName = typeof args.name === "string" ? args.name : "";
|
|
1024
|
-
const hashChecked = (args.action === "update" || args.action === "edit" || args.action === "patch") && opName !== "" && preRunHashes?.has(opName) === true;
|
|
1025
|
-
const anchored = hashChecked && typeof stagedBytes === "string" ? stagedBytes : null;
|
|
1026
|
-
let gateVerifiedBytes = null;
|
|
1027
|
-
if (hashChecked) {
|
|
1028
|
-
const live = anchored !== null ? anchored : await (async () => {
|
|
1029
|
-
if (hashLibrary === null) return null;
|
|
1030
|
-
const probed = await hashLibrary.readSkillProbe(opName);
|
|
1031
|
-
return isPresent(probed) ? probed.value : null;
|
|
1032
|
-
})();
|
|
1033
|
-
const preRun = preRunHashes.get(opName);
|
|
1034
|
-
if (live === null || preRun !== void 0 && contentHash(live) !== preRun) {
|
|
1035
|
-
ok = false;
|
|
1036
|
-
failedOps.push(`skill ${args.action} ${args.name}: the skill changed while this review ran — ${args.action === "patch" ? "patch" : "update"} refused as stale; produce a fresh plan`);
|
|
1037
|
-
continue;
|
|
1038
|
-
}
|
|
1039
|
-
if (anchored === null) gateVerifiedBytes = live;
|
|
1040
|
-
}
|
|
1041
|
-
const anchorBytes = anchored ?? gateVerifiedBytes ?? stagedBytes;
|
|
1042
|
-
if ((args.action === "update" || args.action === "edit") && anchorBytes !== void 0) args.staged_from_sha256 = anchorBytes === null ? "absent" : contentHash(anchorBytes);
|
|
1043
|
-
const runnerArgs = {
|
|
1044
|
-
operation: args,
|
|
1045
|
-
origin: origins.library
|
|
1046
|
-
};
|
|
1047
|
-
const result = approval ? await runApproved("skill", `skill ${op.action ?? "patch"} ${op.name}`, runnerArgs, runnerArgs, session) : await executeSkillDirect(args);
|
|
1048
|
-
if (result?.ok) {
|
|
1049
|
-
actions.push(`Skill ${op.name} ${op.action ?? "patch"}`);
|
|
1050
|
-
onLanded?.(`Skill ${op.name} ${op.action ?? "patch"}`);
|
|
1051
|
-
if (hashChecked) {
|
|
1052
|
-
const landedProbe = hashLibrary === null ? void 0 : await hashLibrary.readSkillProbe(opName);
|
|
1053
|
-
const landed = landedProbe !== void 0 && isPresent(landedProbe) ? landedProbe.value : null;
|
|
1054
|
-
if (landed !== null) preRunHashes.set(opName, contentHash(landed));
|
|
1055
|
-
}
|
|
1056
|
-
} else {
|
|
1057
|
-
ok = false;
|
|
1058
|
-
failedOps.push(`skill ${op.action ?? "patch"} ${op.name}: ${result?.message ?? "service unavailable"}`);
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
if (failedOps.length > 0) ctx.logger.warn(`dsh-evolution-review: executePlan ${actions.length === 0 ? "no op landed" : `${actions.length} op(s) landed, ${failedOps.length} failed`}: ${failedOps.join("; ")}`);
|
|
1062
|
-
return {
|
|
1063
|
-
actions,
|
|
1064
|
-
ok,
|
|
1065
|
-
failedOps
|
|
1066
|
-
};
|
|
1067
|
-
} catch (error) {
|
|
1068
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
1069
|
-
ctx.logger.warn(`dsh-evolution-review: executePlan aborted mid-way after ${actions.length} ops landed: ${reason}`);
|
|
1070
|
-
return {
|
|
1071
|
-
actions,
|
|
1072
|
-
ok: false,
|
|
1073
|
-
failedOps,
|
|
1074
|
-
aborted: reason
|
|
1075
|
-
};
|
|
1076
|
-
}
|
|
1077
|
-
async function runApproved(kind, summary, stored, runnerArgs, approvalSession) {
|
|
1078
|
-
if (!approval) return void 0;
|
|
1079
|
-
if (approval.isEnabled === true && !approval.hasRunner(kind)) {
|
|
1080
|
-
ctx.logger.warn(`dsh-evolution-review: approval enabled but no replay runner registered for kind "${kind}" - skipping write (${summary})`);
|
|
1081
|
-
return {
|
|
1082
|
-
ok: false,
|
|
1083
|
-
message: `已启用批准,但没有为类型「${kind}」注册回放运行器;已跳过写入(请挂载提供该能力的工具,或禁用批准)。`
|
|
1084
|
-
};
|
|
1085
|
-
}
|
|
1086
|
-
const decision = await approval.request({
|
|
1087
|
-
kind,
|
|
1088
|
-
summary,
|
|
1089
|
-
args: stored,
|
|
1090
|
-
origin: origins.approval,
|
|
1091
|
-
...sessionId ? { sessionId } : {},
|
|
1092
|
-
...approvalSession ? { session: approvalSession } : {}
|
|
1093
|
-
});
|
|
1094
|
-
if (decision.action === "staged") return {
|
|
1095
|
-
ok: false,
|
|
1096
|
-
message: decision.message
|
|
1097
|
-
};
|
|
1098
|
-
if (approval.isEnabled === false) return await runnerDirect(kind, runnerArgs);
|
|
1099
|
-
return await approval.run(kind, runnerArgs, { interface: "background_review" });
|
|
1100
|
-
}
|
|
1101
|
-
/** Direct execution for the approval-disabled case (parallel to executeSkillDirect). */
|
|
1102
|
-
async function runnerDirect(kind, args) {
|
|
1103
|
-
if (kind === "memory") {
|
|
1104
|
-
const memory = ctx.get("memory");
|
|
1105
|
-
const op = args;
|
|
1106
|
-
if (!memory?.applyBatch) return void 0;
|
|
1107
|
-
return await memory.applyBatch(op.target === "user" ? "user" : "memory", [{
|
|
1108
|
-
action: op.action ?? "add",
|
|
1109
|
-
facts: payloadText(op),
|
|
1110
|
-
old_text: op.old_text
|
|
1111
|
-
}]);
|
|
1112
|
-
}
|
|
1113
|
-
const wrapped = args ?? {};
|
|
1114
|
-
if (!wrapped.operation) return void 0;
|
|
1115
|
-
return await executeSkillDirect(wrapped.operation);
|
|
1116
|
-
}
|
|
1117
|
-
/**
|
|
1118
|
-
* Approval-disabled path: execute the skill op through SkillLibrary with an
|
|
1119
|
-
* EXPLICIT background_review origin. Going through ctx.tools.execute would
|
|
1120
|
-
* make tool-skill-manage infer origin from the parent agent's header
|
|
1121
|
-
* (not 'subagent'), silently escaping the .hermes-managed marker and the
|
|
1122
|
-
* pinned write guard.
|
|
1123
|
-
*/
|
|
1124
|
-
async function executeSkillDirect(skillArgs) {
|
|
1125
|
-
const io = ctx.get("evolutionIo");
|
|
1126
|
-
if (!io) return {
|
|
1127
|
-
ok: false,
|
|
1128
|
-
message: "evolution-io 服务未挂载"
|
|
1129
|
-
};
|
|
1130
|
-
const policySnapshot = policySnapshotOf(ctx.get("evolutionPolicy"));
|
|
1131
|
-
const library = newSkillLibrary({
|
|
1132
|
-
authority: authorityFrom({ origin: resolveOrigins(void 0, true).library }),
|
|
1133
|
-
config: rootConfig,
|
|
1134
|
-
io: evolutionIoAdapter(() => io.provider()),
|
|
1135
|
-
limits: {
|
|
1136
|
-
...DEFAULT_SKILL_LIMITS,
|
|
1137
|
-
...policyStageLimits(policySnapshot),
|
|
1138
|
-
maxSkillContentChars: policySnapshot?.skillContentChars ?? DEFAULT_SKILL_LIMITS.maxSkillContentChars
|
|
1139
|
-
},
|
|
1140
|
-
ctx
|
|
1141
|
-
});
|
|
1142
|
-
const op = skillArgs;
|
|
1143
|
-
const name = op.name ?? "";
|
|
1144
|
-
const origin = origins.library;
|
|
1145
|
-
const protectedNames = policySnapshot?.protectedSkillNames;
|
|
1146
|
-
if (origin !== "foreground" && protectedNames?.includes(name) && op.action !== "create") return {
|
|
1147
|
-
ok: false,
|
|
1148
|
-
message: `技能「${name}」受当前策略保护(protectedSkillNames):拒绝自主写入。`
|
|
1149
|
-
};
|
|
1150
|
-
if (op.action === "create") {
|
|
1151
|
-
const created = await library.create(name, op.content ?? "", origin);
|
|
1152
|
-
if (created.ok) await ctx.get("skillUsage")?.markAgentCreated?.(name);
|
|
1153
|
-
return created;
|
|
1154
|
-
}
|
|
1155
|
-
if (op.action === "edit" || op.action === "update") {
|
|
1156
|
-
const updated = await library.update(name, op.content ?? "", origin, anchorOf(op));
|
|
1157
|
-
if (updated.stale === true) return staleRefusal(updated, name, op.file_path);
|
|
1158
|
-
if (updated.ok && updated.noop !== true) await ctx.get("skillUsage")?.record?.(name, "patch").catch(() => {});
|
|
1159
|
-
return updated;
|
|
1160
|
-
}
|
|
1161
|
-
if (op.action === "patch") {
|
|
1162
|
-
const patched = await library.patch(name, op.old_string ?? "", op.new_string ?? "", op.file_path ?? "", op.replace_all === true, origin);
|
|
1163
|
-
if (patched.ok && patched.noop !== true) await ctx.get("skillUsage")?.record?.(name, "patch").catch(() => {});
|
|
1164
|
-
return patched;
|
|
1165
|
-
}
|
|
1166
|
-
if (op.action === "delete") {
|
|
1167
|
-
const into = (op.absorbed_into ?? "").trim();
|
|
1168
|
-
if (!into || !await library.read(into)) return {
|
|
1169
|
-
ok: false,
|
|
1170
|
-
message: "删除需要一个已存在的 absorbed_into 目标"
|
|
1171
|
-
};
|
|
1172
|
-
const archived = await library.archive(name, { absorbedInto: into });
|
|
1173
|
-
if (archived.ok) await ctx.get("skillUsage")?.markArchived?.(name);
|
|
1174
|
-
return archived;
|
|
1175
|
-
}
|
|
1176
|
-
if (op.action === "write_file" || op.action === "remove_file") {
|
|
1177
|
-
const anchor = anchorOf(op);
|
|
1178
|
-
if (op.action === "write_file") {
|
|
1179
|
-
const written = await library.writeSupportFile(name, op.file_path ?? "", op.file_content ?? "", origin, anchor);
|
|
1180
|
-
if (written.stale === true) return staleRefusal(written, name, op.file_path);
|
|
1181
|
-
return written;
|
|
1182
|
-
}
|
|
1183
|
-
const removedSupport = await library.removeSupportFile(name, op.file_path ?? "", origin, anchor);
|
|
1184
|
-
if (removedSupport.stale === true) return staleRefusal(removedSupport, name, op.file_path);
|
|
1185
|
-
if (removedSupport.ok && removedSupport.noop !== true) await ctx.get("skillUsage")?.record?.(name, "patch").catch(() => {});
|
|
1186
|
-
return removedSupport;
|
|
1187
|
-
}
|
|
1188
|
-
if (op.action === "restructure") {
|
|
1189
|
-
const moves = (op.restructure ?? []).filter((move) => move !== null).map((move) => ({
|
|
1190
|
-
heading: move.heading ?? "",
|
|
1191
|
-
toFile: move.to_file ?? ""
|
|
1192
|
-
}));
|
|
1193
|
-
return await library.restructure(name, moves, origin);
|
|
1194
|
-
}
|
|
1195
|
-
return {
|
|
1196
|
-
ok: false,
|
|
1197
|
-
message: `未知的技能动作「${op.action ?? ""}」`
|
|
1198
|
-
};
|
|
1199
|
-
}
|
|
741
|
+
return true;
|
|
1200
742
|
}
|
|
1201
743
|
ctx.effect(() => () => {
|
|
1202
|
-
fiberAbort.abort();
|
|
1203
744
|
sessionState.dispose();
|
|
1204
745
|
}, "dsh-evolution-review.cleanup");
|
|
1205
746
|
}
|
|
@@ -1222,11 +763,6 @@ function shouldCompletionReview(reason, sessionToolCalls, minToolCalls) {
|
|
|
1222
763
|
* @param op - one accepted plan op.
|
|
1223
764
|
* @returns the anchor, or `undefined` when the op carries none.
|
|
1224
765
|
*/
|
|
1225
|
-
function anchorOf(op) {
|
|
1226
|
-
const expected = op.staged_from_sha256;
|
|
1227
|
-
if (typeof expected !== "string" || expected === "") return void 0;
|
|
1228
|
-
return expected === "absent" ? { absent: true } : { sha256: expected };
|
|
1229
|
-
}
|
|
1230
766
|
/**
|
|
1231
767
|
* Translate the library's anchor refusal into this channel's staging wording.
|
|
1232
768
|
* The library reports WHICH drift it saw (drift / missing / unverifiable); the
|
|
@@ -1239,16 +775,6 @@ function anchorOf(op) {
|
|
|
1239
775
|
* @param filePath - the support-file path for file ops; omitted for body ops.
|
|
1240
776
|
* @returns the refusal to return from the direct path.
|
|
1241
777
|
*/
|
|
1242
|
-
function staleRefusal(result, name, filePath) {
|
|
1243
|
-
if (filePath === void 0 && result.anchor === "missing") return {
|
|
1244
|
-
ok: false,
|
|
1245
|
-
message: `技能「${name}」不存在。`
|
|
1246
|
-
};
|
|
1247
|
-
return {
|
|
1248
|
-
ok: false,
|
|
1249
|
-
message: filePath === void 0 ? `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.` : `Support file "${filePath}" 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.`
|
|
1250
|
-
};
|
|
1251
|
-
}
|
|
1252
778
|
/** Map/set size that triggers a dead-session counter sweep (bounded, not a hard cap). */
|
|
1253
779
|
const COUNTER_SWEEP_THRESHOLD = 128;
|
|
1254
780
|
/**
|
|
@@ -1295,13 +821,6 @@ function sweepDeadSessionEntries(entries, isAlive) {
|
|
|
1295
821
|
}
|
|
1296
822
|
return removed;
|
|
1297
823
|
}
|
|
1298
|
-
function fingerprintPolicy(snapshot) {
|
|
1299
|
-
try {
|
|
1300
|
-
return createHash("sha256").update(JSON.stringify(snapshot)).digest("hex").slice(0, 12);
|
|
1301
|
-
} catch {
|
|
1302
|
-
return;
|
|
1303
|
-
}
|
|
1304
|
-
}
|
|
1305
824
|
/**
|
|
1306
825
|
* The call one log event ANSWERS, in either platform vocabulary.
|
|
1307
826
|
*
|
|
@@ -1321,128 +840,5 @@ function resultCallIdOf(event) {
|
|
|
1321
840
|
if (typeof data.subCallId === "string" && typeof data.isError === "boolean") return data.subCallId;
|
|
1322
841
|
return null;
|
|
1323
842
|
}
|
|
1324
|
-
/**
|
|
1325
|
-
* V10-10 (P2-11) / A1 (audit P1-1): render one `[result]` evidence line from
|
|
1326
|
-
* a tool-result event payload. The former read (`data.output`) targeted a
|
|
1327
|
-
* field that does not exist on the rc.2 payload; the current contract covers
|
|
1328
|
-
* BOTH rc.2 result shapes:
|
|
1329
|
-
* - native `tool/result`: the outcome lives in `message.content`
|
|
1330
|
-
* tool-result blocks (inner text blocks joined); a failure is marked by
|
|
1331
|
-
* the payload-level `error` OR a block-level `isError`;
|
|
1332
|
-
* - PTC `tool/ptc-dispatch` settle: the outcome lives at the TOP level —
|
|
1333
|
-
* `content` is the logged ContentBlock list and `isError` the flag; there
|
|
1334
|
-
* is no `message` wrapper. (Before A1 this shape rendered an empty line,
|
|
1335
|
-
* so every PTC session's evidence block starved while its plan prompt
|
|
1336
|
-
* still demanded evidence.)
|
|
1337
|
-
* The legacy pre-rc.2 shape (neither `message` nor a PTC settle marker) is
|
|
1338
|
-
* tolerated as an empty payload — it never throws. `identity`, when given, is
|
|
1339
|
-
* the dispatched tool's name + raw arguments, prepended so a settled dispatch
|
|
1340
|
-
* keeps the call identity its `[call]` line would have had. Budget: 500 chars
|
|
1341
|
-
* per line (the 12-line cap lives in buildReviewRequest and is unchanged).
|
|
1342
|
-
*/
|
|
1343
|
-
function renderToolResultLine(data, identity, seq) {
|
|
1344
|
-
const shape = data;
|
|
1345
|
-
const nativeContent = shape?.message?.content;
|
|
1346
|
-
const resultBlocks = Array.isArray(nativeContent) ? nativeContent.filter((block) => block.type === "tool-result") : [];
|
|
1347
|
-
let output;
|
|
1348
|
-
let failed;
|
|
1349
|
-
if (shape?.message !== void 0) {
|
|
1350
|
-
output = resultBlocks.map((block) => Array.isArray(block.content) ? block.content.map((inner) => inner.type === "text" && typeof inner.text === "string" ? inner.text : "").join(" ") : typeof block.text === "string" ? block.text : "").join(" ").trim();
|
|
1351
|
-
failed = Boolean(shape.error) || resultBlocks.some((block) => block.isError === true);
|
|
1352
|
-
} else if (typeof shape?.subCallId === "string" && typeof shape.isError === "boolean") {
|
|
1353
|
-
output = textOfLoggedContent(shape.content);
|
|
1354
|
-
failed = shape.isError;
|
|
1355
|
-
} else {
|
|
1356
|
-
output = "";
|
|
1357
|
-
failed = false;
|
|
1358
|
-
}
|
|
1359
|
-
const head = identity === void 0 ? "" : `${foldUntrustedLines(identity.name, 0)} ${foldUntrustedLines(identity.argsRaw, 200)} → `;
|
|
1360
|
-
const failure = failed ? " [ERROR]" : "";
|
|
1361
|
-
return (seq === void 0 ? "" : "seq=" + String(seq) + " ") + "[result]" + failure + " " + head + foldUntrustedLines(output, 500);
|
|
1362
|
-
}
|
|
1363
|
-
/** Text of a PTC settle `content` payload (the logged ContentBlock list):
|
|
1364
|
-
* text blocks and plain strings joined, anything else skipped. */
|
|
1365
|
-
function textOfLoggedContent(content) {
|
|
1366
|
-
if (typeof content === "string") return content.trim();
|
|
1367
|
-
if (!Array.isArray(content)) return "";
|
|
1368
|
-
return content.map((block) => {
|
|
1369
|
-
if (typeof block === "string") return block;
|
|
1370
|
-
if (block !== null && typeof block === "object") {
|
|
1371
|
-
const candidate = block;
|
|
1372
|
-
if (candidate.type === "text" && typeof candidate.text === "string") return candidate.text;
|
|
1373
|
-
}
|
|
1374
|
-
return "";
|
|
1375
|
-
}).join(" ").trim();
|
|
1376
|
-
}
|
|
1377
|
-
/**
|
|
1378
|
-
* PLAN S4.1 (2026-09-16, audit P2-12): text of one persisted content block,
|
|
1379
|
-
* or `''` for any other shape. Content blocks cross the durable session-log
|
|
1380
|
-
* boundary, so their runtime shape is `unknown` even where the static type
|
|
1381
|
-
* promises `{ type, text }` — a persisted `content: [null]` (the A2-7 shape)
|
|
1382
|
-
* used to TypeError in buildReviewRequest and the caller's catch dropped the
|
|
1383
|
-
* whole subagent review leg. This mirrors evolution-core signals.ts's private
|
|
1384
|
-
* `textOfBlock` (same guard, same rationale); it is not imported because core
|
|
1385
|
-
* keeps that helper module-private, and this file's other block renderer
|
|
1386
|
-
* (renderToolResultLine) guards its own inner-block shapes inline.
|
|
1387
|
-
*/
|
|
1388
|
-
function textOfPersistedBlock(block) {
|
|
1389
|
-
if (block === null || typeof block !== "object") return "";
|
|
1390
|
-
const candidate = block;
|
|
1391
|
-
return candidate.type === "text" && typeof candidate.text === "string" ? candidate.text : "";
|
|
1392
|
-
}
|
|
1393
|
-
function buildReviewRequest(session, kind, signal, maxMessages, maxMessageChars, dispatchTail = []) {
|
|
1394
|
-
const messages = [];
|
|
1395
|
-
const eligible = session.deriveMessages().filter((message) => message.role === "user" || message.role === "assistant").slice(-maxMessages);
|
|
1396
|
-
for (const message of eligible) {
|
|
1397
|
-
const text = message.content.map(textOfPersistedBlock).join(" ").trim();
|
|
1398
|
-
if (text) messages.push(`${message.role.toUpperCase()}: ${foldUntrustedLines(text, maxMessageChars)}`);
|
|
1399
|
-
}
|
|
1400
|
-
const toolLines = [];
|
|
1401
|
-
const events = dispatchTail;
|
|
1402
|
-
const callIdentity = /* @__PURE__ */ new Map();
|
|
1403
|
-
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
1404
|
-
const event = events[index];
|
|
1405
|
-
const opened = readDispatchSignal(event);
|
|
1406
|
-
if (opened === null || callIdentity.has(opened.callId)) continue;
|
|
1407
|
-
callIdentity.set(opened.callId, {
|
|
1408
|
-
name: opened.name,
|
|
1409
|
-
argsRaw: typeof opened.arguments === "string" ? opened.arguments : JSON.stringify(opened.arguments ?? {})
|
|
1410
|
-
});
|
|
1411
|
-
}
|
|
1412
|
-
const openedCallIds = /* @__PURE__ */ new Set();
|
|
1413
|
-
for (let index = events.length - 1; index >= 0 && toolLines.length < 12; index -= 1) {
|
|
1414
|
-
const event = events[index];
|
|
1415
|
-
const answeredCallId = resultCallIdOf(event);
|
|
1416
|
-
if (answeredCallId !== null) {
|
|
1417
|
-
if (openedCallIds.has(answeredCallId)) continue;
|
|
1418
|
-
openedCallIds.add(answeredCallId);
|
|
1419
|
-
const rawSettle = event?.data;
|
|
1420
|
-
const settleData = rawSettle !== null && typeof rawSettle === "object" ? rawSettle : void 0;
|
|
1421
|
-
const identity = settleData !== void 0 && typeof settleData.name === "string" && settleData.name !== "" ? {
|
|
1422
|
-
name: settleData.name,
|
|
1423
|
-
argsRaw: typeof settleData.arguments === "string" ? settleData.arguments : JSON.stringify(settleData.arguments ?? {})
|
|
1424
|
-
} : callIdentity.get(answeredCallId);
|
|
1425
|
-
toolLines.push(renderToolResultLine(event?.data, identity, event?.seq));
|
|
1426
|
-
continue;
|
|
1427
|
-
}
|
|
1428
|
-
const opened = readDispatchSignal(event);
|
|
1429
|
-
if (opened === null || openedCallIds.has(opened.callId)) continue;
|
|
1430
|
-
openedCallIds.add(opened.callId);
|
|
1431
|
-
const argsRaw = typeof opened.arguments === "string" ? opened.arguments : JSON.stringify(opened.arguments ?? {});
|
|
1432
|
-
const seq = typeof event?.seq === "number" ? "seq=" + String(event.seq) + " " : "";
|
|
1433
|
-
toolLines.push(seq + "[call] " + foldUntrustedLines(opened.name, 0) + " " + foldUntrustedLines(argsRaw, 500));
|
|
1434
|
-
}
|
|
1435
|
-
toolLines.reverse();
|
|
1436
|
-
return [
|
|
1437
|
-
`Review kind: ${kind}`,
|
|
1438
|
-
`Signals: ${signal.toolCalls} tool calls, ${signal.userChars} user chars, ${signal.assistantChars} assistant chars.`,
|
|
1439
|
-
`Recent tool activity (${toolLines.length}):`,
|
|
1440
|
-
...toolLines,
|
|
1441
|
-
"Return ONLY the structured JSON plan. Evidence is mandatory for every op.",
|
|
1442
|
-
planContractPrompt(),
|
|
1443
|
-
"",
|
|
1444
|
-
...messages
|
|
1445
|
-
].join("\n");
|
|
1446
|
-
}
|
|
1447
843
|
//#endregion
|
|
1448
|
-
export { Config,
|
|
844
|
+
export { Config, apply, clampReviewConfig, filterUnreadSkillOps, inject, name, noticeAllowed, resolveReviewRowSettings, shouldCompletionReview, sweepDeadSessionEntries };
|
package/lib/types/index.d.ts
CHANGED
|
@@ -12,8 +12,6 @@ declare module '@deepseek-ai/dsh-llm' {
|
|
|
12
12
|
} & ContextFormed;
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
-
import type { Session } from '@deepseek-ai/dsh-session';
|
|
16
|
-
import { type ReviewKind } from '@lmzhen/dsh-evolution-core';
|
|
17
15
|
export { filterUnreadSkillOps } from '@lmzhen/dsh-evolution-core';
|
|
18
16
|
/** S2-6 (FLOW1-1): the settle-grace margin a subagent handle gets beyond its
|
|
19
17
|
* own deadline before the review abandons it. As the dispose watchdog's WHOLE
|
|
@@ -41,7 +39,7 @@ export interface Config {
|
|
|
41
39
|
* at full price. Deployments that prefer the clean-context split switch the
|
|
42
40
|
* POLICY row (`evolution-policy`): the mounted policy snapshot shadows this
|
|
43
41
|
* row's value, which the plugin reports once at load (v37 P2-24). */
|
|
44
|
-
reviewMode?: Volatile<'
|
|
42
|
+
reviewMode?: Volatile<'inject' | 'branch'>;
|
|
45
43
|
/** Review cadence in TOOL CALLS — the CANONICAL registry id (G1, signals.ts:
|
|
46
44
|
* a turn advances by its tool-call count, minimum 1, or by 1 when the turn
|
|
47
45
|
* itself carried the memory signal). Shadowed by the policy snapshot in every
|
|
@@ -57,22 +55,7 @@ export interface Config {
|
|
|
57
55
|
/** Deprecated alias of `reviewSkillInterval` (G0/S0.3): still readable,
|
|
58
56
|
* refused by writes; removal was planned for 0.7.0 and is deferred (the alias still ships — see the package README). */
|
|
59
57
|
skillInterval?: number;
|
|
60
|
-
/**
|
|
61
|
-
* Tools the one-shot review subagent may use. Only actually-existing tools
|
|
62
|
-
* may be listed (the DSH tool catalog has `skill`; the `skill_search` /
|
|
63
|
-
* `skill_load` discovery pair does not exist on this platform).
|
|
64
|
-
*/
|
|
65
|
-
reviewToolAllow?: string[];
|
|
66
58
|
reviewTimeoutMs?: number;
|
|
67
|
-
reviewContextMessages?: number;
|
|
68
|
-
reviewMessageChars?: number;
|
|
69
|
-
/** ABSOLUTE cap of the review subagent's own delegation depth (platform
|
|
70
|
-
* resolveChildDepth: childDepth = parentDepth+1 must be <= maxDepth).
|
|
71
|
-
* 1 permits the subagent itself and denies nesting (2 > 1); 0 rejects the
|
|
72
|
-
* spawn outright (SubagentDepthError on any real run — the 0.3.1 defect). */
|
|
73
|
-
reviewMaxDepth?: number;
|
|
74
|
-
/** LLM provider for review subagents. Omit to inherit the deployment default route. */
|
|
75
|
-
reviewProvider?: string;
|
|
76
59
|
/** E3/P1-8 (v11) — TRUE semantics: this flag gates ONLY the completion
|
|
77
60
|
* channel. The cadence latch stays active regardless of the value (a
|
|
78
61
|
* completion-gated deployment still fires at the policy cadence), so
|
|
@@ -112,17 +95,12 @@ export interface Config {
|
|
|
112
95
|
}
|
|
113
96
|
export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
114
97
|
reviewEnabled: z<boolean, boolean, "volatile-defined">;
|
|
115
|
-
reviewMode: z<"
|
|
98
|
+
reviewMode: z<"inject" | "branch", "inject" | "branch", "volatile-defined">;
|
|
116
99
|
reviewMemoryInterval: z<number, number, "volatile">;
|
|
117
100
|
reviewSkillInterval: z<number, number, "volatile">;
|
|
118
101
|
memoryInterval: z<number, number, "defined">;
|
|
119
102
|
skillInterval: z<number, number, "defined">;
|
|
120
|
-
reviewToolAllow: z<string[], string[], "defined">;
|
|
121
103
|
reviewTimeoutMs: z<number, number, "defined">;
|
|
122
|
-
reviewContextMessages: z<number, number, "defined">;
|
|
123
|
-
reviewMessageChars: z<number, number, "defined">;
|
|
124
|
-
reviewMaxDepth: z<number, number, "defined">;
|
|
125
|
-
reviewProvider: z<string, string, "plain">;
|
|
126
104
|
skillReviewTrigger: z<"cadence" | "completion" | "both", "cadence" | "completion" | "both", "volatile-defined">;
|
|
127
105
|
reviewWakeInject: z<boolean, boolean, "volatile-defined">;
|
|
128
106
|
reviewNotice: z<boolean, boolean, "volatile-defined">;
|
|
@@ -132,17 +110,12 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
132
110
|
skillsRoot: z<string, string, "defined">;
|
|
133
111
|
}>>, Schemastery.ObjectT<NoInfer<{
|
|
134
112
|
reviewEnabled: z<boolean, boolean, "volatile-defined">;
|
|
135
|
-
reviewMode: z<"
|
|
113
|
+
reviewMode: z<"inject" | "branch", "inject" | "branch", "volatile-defined">;
|
|
136
114
|
reviewMemoryInterval: z<number, number, "volatile">;
|
|
137
115
|
reviewSkillInterval: z<number, number, "volatile">;
|
|
138
116
|
memoryInterval: z<number, number, "defined">;
|
|
139
117
|
skillInterval: z<number, number, "defined">;
|
|
140
|
-
reviewToolAllow: z<string[], string[], "defined">;
|
|
141
118
|
reviewTimeoutMs: z<number, number, "defined">;
|
|
142
|
-
reviewContextMessages: z<number, number, "defined">;
|
|
143
|
-
reviewMessageChars: z<number, number, "defined">;
|
|
144
|
-
reviewMaxDepth: z<number, number, "defined">;
|
|
145
|
-
reviewProvider: z<string, string, "plain">;
|
|
146
119
|
skillReviewTrigger: z<"cadence" | "completion" | "both", "cadence" | "completion" | "both", "volatile-defined">;
|
|
147
120
|
reviewWakeInject: z<boolean, boolean, "volatile-defined">;
|
|
148
121
|
reviewNotice: z<boolean, boolean, "volatile-defined">;
|
|
@@ -151,30 +124,6 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
151
124
|
root: z<string, string, "defined">;
|
|
152
125
|
skillsRoot: z<string, string, "defined">;
|
|
153
126
|
}>>, "plain">;
|
|
154
|
-
/** V8-01 (0.3.45): the review subagent's structured-output contract. The
|
|
155
|
-
* `subagents.start` outputSchema is RAW JSON Schema validated by
|
|
156
|
-
* assertObjectJsonSchema (upstream dsh-tools json-schema.ts — SCHEMA_TYPES
|
|
157
|
-
* has no 'json'; 'json' is a defineTool-author DSL-only value). The arrays
|
|
158
|
-
* deliberately use `{ type: 'array' }` with NO items node — absent items
|
|
159
|
-
* accepts any JSON item (upstream semantics, json-schema.ts "Item schema
|
|
160
|
-
* (type: 'array' only); absent accepts any JSON item"), and plan-validator
|
|
161
|
-
* owns the per-op structure proof. The whitelist contract test (review.spec
|
|
162
|
-
* V8-01) forbids a DSL value slipping back in. */
|
|
163
|
-
export declare const REVIEW_OUTPUT_SCHEMA: {
|
|
164
|
-
readonly type: "object";
|
|
165
|
-
readonly additionalProperties: false;
|
|
166
|
-
readonly properties: {
|
|
167
|
-
readonly memoryOps: {
|
|
168
|
-
readonly type: "array";
|
|
169
|
-
};
|
|
170
|
-
readonly skillOps: {
|
|
171
|
-
readonly type: "array";
|
|
172
|
-
};
|
|
173
|
-
readonly summary: {
|
|
174
|
-
readonly type: "string";
|
|
175
|
-
};
|
|
176
|
-
};
|
|
177
|
-
};
|
|
178
127
|
/** F5 (P2-16, v11): the clamp sets exactly these four numeric fields — the
|
|
179
128
|
* old `as Required<Config>` lied about `reviewToolAllow` etc. being populated
|
|
180
129
|
* (`[...undefined]` TypeErrors under a direct `apply(ctx, {})`).
|
|
@@ -182,9 +131,6 @@ export declare const REVIEW_OUTPUT_SCHEMA: {
|
|
|
182
131
|
* {@link resolveReviewRowSettings}, which clamps its own numerics. */
|
|
183
132
|
type ClampedReviewConfig = Config & {
|
|
184
133
|
reviewTimeoutMs: number;
|
|
185
|
-
reviewContextMessages: number;
|
|
186
|
-
reviewMessageChars: number;
|
|
187
|
-
reviewMaxDepth: number;
|
|
188
134
|
};
|
|
189
135
|
/**
|
|
190
136
|
* The DEPLOYMENT layer of this row, clamped once at assembly: the restart-only
|
|
@@ -213,7 +159,7 @@ export interface ReviewSettings {
|
|
|
213
159
|
/** Master switch for the review plugin. */
|
|
214
160
|
reviewEnabled: boolean;
|
|
215
161
|
/** Run the review in the parent session (inject) or on a subagent. */
|
|
216
|
-
reviewMode: '
|
|
162
|
+
reviewMode: 'inject' | 'branch';
|
|
217
163
|
/** Deliver the deferred review as a waking follow-up message. */
|
|
218
164
|
reviewWakeInject: boolean;
|
|
219
165
|
/** Deliver result notices (not review prompts). */
|
|
@@ -235,13 +181,6 @@ export declare function apply(ctx: Context, rawConfig?: Config): void;
|
|
|
235
181
|
export declare function shouldCompletionReview(reason: {
|
|
236
182
|
kind?: string;
|
|
237
183
|
} | undefined, sessionToolCalls: number, minToolCalls: number): boolean;
|
|
238
|
-
/** One dispatch frame the review digest can render (a native call/result or a PTC sub-dispatch). */
|
|
239
|
-
interface ReviewDispatchFrame {
|
|
240
|
-
readonly type: string;
|
|
241
|
-
/** Session seq of the frame, so a plan can cite a real `event_seq`. */
|
|
242
|
-
readonly seq?: number;
|
|
243
|
-
readonly data?: unknown;
|
|
244
|
-
}
|
|
245
184
|
/**
|
|
246
185
|
* Remove every entry whose session is no longer live (rc.42 audit P1-10):
|
|
247
186
|
* `cumulativeToolCalls` / `completionInjected` / `pendingCadenceReviews` are keyed by
|
|
@@ -250,32 +189,4 @@ interface ReviewDispatchFrame {
|
|
|
250
189
|
* number of removed entries.
|
|
251
190
|
*/
|
|
252
191
|
export declare function sweepDeadSessionEntries<K>(entries: Map<K, unknown> | Set<K>, isAlive: (id: K) => boolean): number;
|
|
253
|
-
/**
|
|
254
|
-
* V10-10 (P2-11) / A1 (audit P1-1): render one `[result]` evidence line from
|
|
255
|
-
* a tool-result event payload. The former read (`data.output`) targeted a
|
|
256
|
-
* field that does not exist on the rc.2 payload; the current contract covers
|
|
257
|
-
* BOTH rc.2 result shapes:
|
|
258
|
-
* - native `tool/result`: the outcome lives in `message.content`
|
|
259
|
-
* tool-result blocks (inner text blocks joined); a failure is marked by
|
|
260
|
-
* the payload-level `error` OR a block-level `isError`;
|
|
261
|
-
* - PTC `tool/ptc-dispatch` settle: the outcome lives at the TOP level —
|
|
262
|
-
* `content` is the logged ContentBlock list and `isError` the flag; there
|
|
263
|
-
* is no `message` wrapper. (Before A1 this shape rendered an empty line,
|
|
264
|
-
* so every PTC session's evidence block starved while its plan prompt
|
|
265
|
-
* still demanded evidence.)
|
|
266
|
-
* The legacy pre-rc.2 shape (neither `message` nor a PTC settle marker) is
|
|
267
|
-
* tolerated as an empty payload — it never throws. `identity`, when given, is
|
|
268
|
-
* the dispatched tool's name + raw arguments, prepended so a settled dispatch
|
|
269
|
-
* keeps the call identity its `[call]` line would have had. Budget: 500 chars
|
|
270
|
-
* per line (the 12-line cap lives in buildReviewRequest and is unchanged).
|
|
271
|
-
*/
|
|
272
|
-
export declare function renderToolResultLine(data: unknown, identity?: {
|
|
273
|
-
name: string;
|
|
274
|
-
argsRaw: string;
|
|
275
|
-
}, seq?: number): string;
|
|
276
|
-
export declare function buildReviewRequest(session: Session, kind: ReviewKind, signal: {
|
|
277
|
-
toolCalls: number;
|
|
278
|
-
userChars: number;
|
|
279
|
-
assistantChars: number;
|
|
280
|
-
}, maxMessages: number, maxMessageChars: number, dispatchTail?: readonly ReviewDispatchFrame[]): string;
|
|
281
192
|
//# sourceMappingURL=index.d.ts.map
|
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.30.
|
|
4
|
+
"version": "0.30.29",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -27,9 +27,7 @@
|
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@deepseek-ai/schemastery": "^3.18.5",
|
|
30
|
-
"@lmzhen/dsh-evolution-
|
|
31
|
-
"@lmzhen/dsh-evolution-core": "^0.30.27",
|
|
32
|
-
"@lmzhen/dsh-evolution-plan-validator": "^0.30.27"
|
|
30
|
+
"@lmzhen/dsh-evolution-core": "^0.30.29"
|
|
33
31
|
},
|
|
34
32
|
"peerDependencies": {
|
|
35
33
|
"@deepseek-ai/cordis": "^4.0.5",
|
|
@@ -37,22 +35,17 @@
|
|
|
37
35
|
"@deepseek-ai/dsh-llm": "^0.2.1-rc.2",
|
|
38
36
|
"@deepseek-ai/dsh-session": "^0.2.1-rc.2",
|
|
39
37
|
"@deepseek-ai/dsh-tools": "^0.2.1-rc.2",
|
|
40
|
-
"@lmzhen/dsh-evolution-policy": "^0.30.
|
|
41
|
-
"@lmzhen/dsh-evolution-state": "^0.30.
|
|
38
|
+
"@lmzhen/dsh-evolution-policy": "^0.30.29",
|
|
39
|
+
"@lmzhen/dsh-evolution-state": "^0.30.29"
|
|
42
40
|
},
|
|
43
41
|
"devDependencies": {
|
|
44
42
|
"@deepseek-ai/dsh-agent": "^0.2.1-rc.2",
|
|
45
43
|
"@deepseek-ai/dsh-agent-loop-testkit": "^0.2.1-rc.2",
|
|
46
44
|
"@deepseek-ai/dsh-llm": "^0.2.1-rc.2",
|
|
47
45
|
"@deepseek-ai/dsh-session": "^0.2.1-rc.2",
|
|
48
|
-
"@deepseek-ai/dsh-session-persistence": "^0.2.1-rc.2",
|
|
49
|
-
"@deepseek-ai/dsh-session-persistence-jsonl": "^0.2.1-rc.2",
|
|
50
46
|
"@deepseek-ai/dsh-tools": "^0.2.1-rc.2",
|
|
51
|
-
"@lmzhen/dsh-evolution-
|
|
52
|
-
"@lmzhen/dsh-evolution-
|
|
53
|
-
"@lmzhen/dsh-evolution-curator": "^0.30.27",
|
|
54
|
-
"@lmzhen/dsh-evolution-plan-validator": "^0.30.27",
|
|
55
|
-
"@lmzhen/dsh-evolution-state": "^0.30.27"
|
|
47
|
+
"@lmzhen/dsh-evolution-core": "^0.30.29",
|
|
48
|
+
"@lmzhen/dsh-evolution-state": "^0.30.29"
|
|
56
49
|
},
|
|
57
50
|
"engines": {
|
|
58
51
|
"dsh": ">=0.2.1-rc.2 <0.2.2-0"
|