dsh-continual-evolve 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +172 -17
- package/README.zh.md +87 -10
- package/lib/apply.js +3 -1
- package/lib/approval.d.ts +25 -0
- package/lib/approval.js +9 -1
- package/lib/auto.d.ts +99 -5
- package/lib/auto.js +165 -6
- package/lib/benchmark-command.d.ts +9 -0
- package/lib/benchmark-command.js +331 -0
- package/lib/benchmark.d.ts +84 -0
- package/lib/benchmark.js +107 -1
- package/lib/command.js +33 -221
- package/lib/evaluate.d.ts +43 -7
- package/lib/evaluate.js +172 -43
- package/lib/evolve-event.d.ts +38 -0
- package/lib/evolve-event.js +49 -0
- package/lib/failures.d.ts +39 -0
- package/lib/failures.js +170 -0
- package/lib/fate.d.ts +128 -0
- package/lib/fate.js +342 -0
- package/lib/goal-command.d.ts +7 -0
- package/lib/goal-command.js +37 -0
- package/lib/index.d.ts +51 -21
- package/lib/index.js +32 -2
- package/lib/inject.d.ts +8 -0
- package/lib/inject.js +51 -4
- package/lib/llm-text.d.ts +30 -0
- package/lib/llm-text.js +49 -0
- package/lib/mount-command.d.ts +10 -0
- package/lib/mount-command.js +48 -0
- package/lib/mount.js +5 -0
- package/lib/plan.js +5 -0
- package/lib/planner.d.ts +8 -1
- package/lib/planner.js +40 -39
- package/lib/render.d.ts +1 -3
- package/lib/render.js +2 -5
- package/lib/review.d.ts +5 -2
- package/lib/review.js +27 -38
- package/lib/rollback.d.ts +1 -3
- package/lib/rollback.js +0 -8
- package/lib/score.d.ts +37 -4
- package/lib/score.js +120 -10
- package/lib/service.d.ts +2 -2
- package/lib/service.js +5 -2
- package/lib/skill-render.d.ts +15 -0
- package/lib/skill-render.js +30 -0
- package/lib/skill.d.ts +12 -7
- package/lib/skill.js +36 -31
- package/lib/skillquality.d.ts +80 -0
- package/lib/skillquality.js +311 -0
- package/lib/store.d.ts +1 -3
- package/lib/store.js +0 -7
- package/lib/tool.js +28 -4
- package/lib/types.d.ts +39 -0
- package/lib/types.js +19 -0
- package/lib/usage.d.ts +32 -0
- package/lib/usage.js +84 -0
- package/lib/validate.d.ts +12 -2
- package/lib/validate.js +51 -2
- package/lib/wrapup-command.d.ts +8 -0
- package/lib/wrapup-command.js +211 -0
- package/lib/wrapup.d.ts +215 -0
- package/lib/wrapup.js +427 -0
- package/package.json +8 -8
package/lib/auto.d.ts
CHANGED
|
@@ -1,23 +1,84 @@
|
|
|
1
1
|
import type { Context } from "@deepseek-ai/cordis";
|
|
2
|
-
import type {
|
|
2
|
+
import type { Agent } from "@deepseek-ai/dsh-agent";
|
|
3
|
+
import type { HarnessState, RefinementEdit, RefinementProposal } from "./types.js";
|
|
3
4
|
import type { EvolutionEngine } from "./service.js";
|
|
5
|
+
import { type AutoRefineReason } from "./review.js";
|
|
4
6
|
export interface AutoReviewConfig {
|
|
5
7
|
intervalTurns: number;
|
|
6
8
|
maxInputChars: number;
|
|
7
9
|
budgetTokens: number;
|
|
8
10
|
/** Queue a visible follow-up notice after an approved, applied gate run. */
|
|
9
11
|
notifyOnAutoReview: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Local-fate dimension (#11 P2): the gate audits the session's local
|
|
14
|
+
* entries on its own cadence and proposes promote/archive (consulted
|
|
15
|
+
* first — never written silently). Off disables the whole dimension.
|
|
16
|
+
*/
|
|
17
|
+
localFate: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Minimum turns between local-fate assessments on the turn-interval path
|
|
20
|
+
* (compaction is unconditional). Independent of the review cadence so
|
|
21
|
+
* goal-driven sessions (gate every round) do not pay an assessment per
|
|
22
|
+
* round.
|
|
23
|
+
*/
|
|
24
|
+
fateIntervalTurns: number;
|
|
25
|
+
/**
|
|
26
|
+
* Gap C1: optional model override for the review gate (cheaper model).
|
|
27
|
+
* Format: "provider/model" or just "model" (same provider as the agent).
|
|
28
|
+
* When absent, the review gate uses the agent's own provider/model.
|
|
29
|
+
*/
|
|
30
|
+
reviewModel?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Goal-blocked trigger (D3): after this many CONSECUTIVE gate runs that
|
|
33
|
+
* observe the session goal in phase "blocked", run one local-fate
|
|
34
|
+
* assessment (the same audit → classify → consult → apply pipeline as the
|
|
35
|
+
* gate's normal fate dimension) so the blocked encounter is distilled
|
|
36
|
+
* before the session moves on. 0 disables. The streak resets on any
|
|
37
|
+
* non-blocked run and after each triggered assessment; a declined
|
|
38
|
+
* proposal then follows the normal fate cooldown.
|
|
39
|
+
*/
|
|
40
|
+
goalBlockedWrapupTurns: number;
|
|
10
41
|
}
|
|
11
42
|
export interface GateState {
|
|
12
43
|
turns: number;
|
|
13
44
|
lastReviewAt: number;
|
|
14
45
|
running: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Per-candidate turn at which the user last rejected a skill proposal;
|
|
48
|
+
* consulted skill proposals are not offered again within the cooldown
|
|
49
|
+
* window (skills are governed resources — no nagging).
|
|
50
|
+
*/
|
|
51
|
+
skillRejects: Map<string, number>;
|
|
52
|
+
/** Turn at which the local-fate dimension last assessed (cadence). */
|
|
53
|
+
lastFateAt: number;
|
|
54
|
+
/**
|
|
55
|
+
* Per-candidate-set turn at which the user last declined a local-fate
|
|
56
|
+
* proposal; declined sets are not offered again within the cooldown
|
|
57
|
+
* window (the consultSkillEdits pattern — no nagging).
|
|
58
|
+
*/
|
|
59
|
+
fateRejects: Map<string, number>;
|
|
60
|
+
/**
|
|
61
|
+
* Consecutive gate runs that observed the goal phase "blocked" (D3).
|
|
62
|
+
* Reset to 0 by any non-blocked run and after a triggered assessment —
|
|
63
|
+
* see runGoalBlockedFate.
|
|
64
|
+
*/
|
|
65
|
+
goalBlockStreak: number;
|
|
66
|
+
}
|
|
67
|
+
/** Turns a rejected skill candidate stays silent before being offered again. */
|
|
68
|
+
export declare const SKILL_CONSULT_COOLDOWN_TURNS = 10;
|
|
69
|
+
export interface ReviewRecord {
|
|
70
|
+
timestamp: string;
|
|
71
|
+
sessionId: string;
|
|
72
|
+
reason: AutoRefineReason;
|
|
73
|
+
turnsSinceLastReview: number;
|
|
74
|
+
outcome: "approved" | "declined" | "failed" | "assessed" | "deferred";
|
|
75
|
+
rationale?: string;
|
|
76
|
+
refinementId?: string;
|
|
15
77
|
}
|
|
16
78
|
/**
|
|
17
|
-
* Count completed turns from agent/status transitions
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* not reliably carry the agent, so it is NOT used for counting.
|
|
79
|
+
* Count completed turns from agent/status transitions (running → idle).
|
|
80
|
+
* Exported for unit testing; production counting uses agent/turn-stopping
|
|
81
|
+
* (see registerAutoReview) which empirically carries the agent subject.
|
|
21
82
|
*/
|
|
22
83
|
export declare function advanceGateState(state: GateState, status: string): boolean;
|
|
23
84
|
export declare function registerAutoReview(ctx: Context, engine: EvolutionEngine, config: AutoReviewConfig): void;
|
|
@@ -31,4 +92,37 @@ export declare function registerAutoReview(ctx: Context, engine: EvolutionEngine
|
|
|
31
92
|
* local state (baseline checks compare local entries only).
|
|
32
93
|
*/
|
|
33
94
|
export declare function loadGateHarnessView(engine: EvolutionEngine, sessionId: string): HarnessState;
|
|
95
|
+
/**
|
|
96
|
+
* D3 (goal blocked → wrap-up coupling, reverse direction): count consecutive
|
|
97
|
+
* gate runs whose goal is in phase "blocked"; when the streak reaches
|
|
98
|
+
* `goalBlockedWrapupTurns`, run ONE local-fate assessment (same pipeline as
|
|
99
|
+
* the normal fate dimension — audit, classify, consult, apply deterministically).
|
|
100
|
+
* The streak resets on any non-blocked run and after a triggered assessment;
|
|
101
|
+
* a declined proposal is then protected by the normal fate cooldown, so a
|
|
102
|
+
* blocked session can never be nagged into another dialog.
|
|
103
|
+
*
|
|
104
|
+
* Exported for unit testing (the advanceGateState precedent); production runs
|
|
105
|
+
* it from runGate.
|
|
106
|
+
*/
|
|
107
|
+
export declare function runGoalBlockedFate(ctx: Context, engine: EvolutionEngine, agent: Agent, config: AutoReviewConfig, state: GateState, _reason: AutoRefineReason, record: (entry: Omit<ReviewRecord, "timestamp">) => void): Promise<void>;
|
|
108
|
+
/**
|
|
109
|
+
* Split a proposal into skill edits and everything else. Skill edits are the
|
|
110
|
+
* governed part: they need explicit user consent before the gate applies
|
|
111
|
+
* them, while the remaining edits flow through the normal auto path.
|
|
112
|
+
*/
|
|
113
|
+
export declare function splitSkillEdits(proposal: RefinementProposal): {
|
|
114
|
+
skillEdits: RefinementEdit[];
|
|
115
|
+
otherEdits: RefinementEdit[];
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Ask the user whether to solidify proposed skill edits (guidance or
|
|
119
|
+
* executable) into the harness. Returns true when every skill edit is
|
|
120
|
+
* consented. Never writes a skill silently:
|
|
121
|
+
* - no question service available → false (conservative);
|
|
122
|
+
* - the same candidate was rejected within the cooldown window → false
|
|
123
|
+
* without asking again (no nagging);
|
|
124
|
+
* - the user declines → false and the rejection is recorded for cooldown;
|
|
125
|
+
* - the question call fails/aborts → false (conservative).
|
|
126
|
+
*/
|
|
127
|
+
export declare function consultSkillEdits(ctx: Context, agent: Agent, skillEdits: RefinementEdit[], gate: GateState): Promise<boolean>;
|
|
34
128
|
//# sourceMappingURL=auto.d.ts.map
|
package/lib/auto.js
CHANGED
|
@@ -17,17 +17,22 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { appendFileSync, mkdirSync } from "node:fs";
|
|
19
19
|
import { join } from "node:path";
|
|
20
|
+
import { slug } from "./types.js";
|
|
20
21
|
import { planWithLlm } from "./planner.js";
|
|
21
22
|
import { reviewAutoRefine, serializeSurface } from "./review.js";
|
|
22
23
|
import { goalServiceOf } from "./goal.js";
|
|
23
24
|
import { notifyAutoReview } from "./notify.js";
|
|
25
|
+
import { runLocalFatePhase } from "./fate.js";
|
|
24
26
|
import { entrySourceOf } from "./source.js";
|
|
25
27
|
import { mergeHarnessStates } from "./state.js";
|
|
28
|
+
import { questionServiceOf } from "./approval.js";
|
|
29
|
+
import { buildEvolveCompleteEvent, emitEvolveComplete } from "./evolve-event.js";
|
|
30
|
+
/** Turns a rejected skill candidate stays silent before being offered again. */
|
|
31
|
+
export const SKILL_CONSULT_COOLDOWN_TURNS = 10;
|
|
26
32
|
/**
|
|
27
|
-
* Count completed turns from agent/status transitions
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* not reliably carry the agent, so it is NOT used for counting.
|
|
33
|
+
* Count completed turns from agent/status transitions (running → idle).
|
|
34
|
+
* Exported for unit testing; production counting uses agent/turn-stopping
|
|
35
|
+
* (see registerAutoReview) which empirically carries the agent subject.
|
|
31
36
|
*/
|
|
32
37
|
export function advanceGateState(state, status) {
|
|
33
38
|
if (status === "running") {
|
|
@@ -130,10 +135,31 @@ export function registerAutoReview(ctx, engine, config) {
|
|
|
130
135
|
});
|
|
131
136
|
});
|
|
132
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Gap C1: parse a "provider/model" or "model" string into its components.
|
|
140
|
+
* Returns undefined when the input is empty (no override).
|
|
141
|
+
*/
|
|
142
|
+
function parseReviewModel(reviewModel, fallbackProvider) {
|
|
143
|
+
if (!reviewModel || reviewModel.trim().length === 0)
|
|
144
|
+
return undefined;
|
|
145
|
+
const slash = reviewModel.indexOf("/");
|
|
146
|
+
if (slash > 0) {
|
|
147
|
+
return { provider: reviewModel.slice(0, slash), model: reviewModel.slice(slash + 1) };
|
|
148
|
+
}
|
|
149
|
+
return { provider: fallbackProvider ?? "deepseek", model: reviewModel };
|
|
150
|
+
}
|
|
133
151
|
function stateFor(map, sessionId) {
|
|
134
152
|
let state = map.get(sessionId);
|
|
135
153
|
if (!state) {
|
|
136
|
-
state = {
|
|
154
|
+
state = {
|
|
155
|
+
turns: 0,
|
|
156
|
+
lastReviewAt: 0,
|
|
157
|
+
running: false,
|
|
158
|
+
skillRejects: new Map(),
|
|
159
|
+
lastFateAt: 0,
|
|
160
|
+
fateRejects: new Map(),
|
|
161
|
+
goalBlockStreak: 0,
|
|
162
|
+
};
|
|
137
163
|
map.set(sessionId, state);
|
|
138
164
|
}
|
|
139
165
|
return state;
|
|
@@ -150,7 +176,52 @@ function stateFor(map, sessionId) {
|
|
|
150
176
|
export function loadGateHarnessView(engine, sessionId) {
|
|
151
177
|
return mergeHarnessStates(engine.load("global", undefined), engine.load("local", sessionId));
|
|
152
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* One gate run = review phase + local-fate phase (#11 P2). The review phase
|
|
181
|
+
* judges and applies local refinements; the local-fate phase then gives the
|
|
182
|
+
* session's existing local entries a running exit (promote/archive proposals,
|
|
183
|
+
* consulted before they land). Running fate AFTER the review keeps the
|
|
184
|
+
* review's baseline fresh — fate re-loads the store and never races the
|
|
185
|
+
* review's optimistic-concurrency checks.
|
|
186
|
+
*/
|
|
153
187
|
async function runGate(ctx, engine, agent, config, state, reason, record) {
|
|
188
|
+
await runReviewPhase(ctx, engine, agent, config, state, reason, record);
|
|
189
|
+
// D3: a goal stuck in "blocked" for consecutive gate runs gets one
|
|
190
|
+
// local-fate assessment (the pipeline below), so whatever led the goal
|
|
191
|
+
// astray is distilled before the session moves on.
|
|
192
|
+
await runGoalBlockedFate(ctx, engine, agent, config, state, reason, record);
|
|
193
|
+
await runLocalFatePhase(ctx, engine, agent, config, state, reason, record);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* D3 (goal blocked → wrap-up coupling, reverse direction): count consecutive
|
|
197
|
+
* gate runs whose goal is in phase "blocked"; when the streak reaches
|
|
198
|
+
* `goalBlockedWrapupTurns`, run ONE local-fate assessment (same pipeline as
|
|
199
|
+
* the normal fate dimension — audit, classify, consult, apply deterministically).
|
|
200
|
+
* The streak resets on any non-blocked run and after a triggered assessment;
|
|
201
|
+
* a declined proposal is then protected by the normal fate cooldown, so a
|
|
202
|
+
* blocked session can never be nagged into another dialog.
|
|
203
|
+
*
|
|
204
|
+
* Exported for unit testing (the advanceGateState precedent); production runs
|
|
205
|
+
* it from runGate.
|
|
206
|
+
*/
|
|
207
|
+
export async function runGoalBlockedFate(ctx, engine, agent, config, state, _reason, record) {
|
|
208
|
+
if (config.goalBlockedWrapupTurns <= 0)
|
|
209
|
+
return;
|
|
210
|
+
const goal = goalServiceOf(ctx)?.get(agent);
|
|
211
|
+
if (goal?.phase !== "blocked") {
|
|
212
|
+
state.goalBlockStreak = 0;
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
state.goalBlockStreak += 1;
|
|
216
|
+
if (state.goalBlockStreak < config.goalBlockedWrapupTurns) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
state.goalBlockStreak = 0; // one assessment per streak; declines follow the fate cooldown
|
|
220
|
+
const logger = ctx.logger("continual-evolve");
|
|
221
|
+
logger.info(`auto-review goal-blocked trigger [${agent.id}]: ${config.goalBlockedWrapupTurns} consecutive blocked gate runs → local-fate assessment`);
|
|
222
|
+
await runLocalFatePhase(ctx, engine, agent, config, state, "goal_blocked", record);
|
|
223
|
+
}
|
|
224
|
+
async function runReviewPhase(ctx, engine, agent, config, state, reason, record) {
|
|
154
225
|
const sessionId = agent.id;
|
|
155
226
|
const turnsSinceLastReview = state.turns - state.lastReviewAt;
|
|
156
227
|
const logger = ctx.logger("continual-evolve");
|
|
@@ -169,6 +240,8 @@ async function runGate(ctx, engine, agent, config, state, reason, record) {
|
|
|
169
240
|
const localState = engine.load("local", sessionId);
|
|
170
241
|
const harnessState = loadGateHarnessView(engine, sessionId);
|
|
171
242
|
const history = engine.history("local", sessionId);
|
|
243
|
+
// Gap C1: resolve optional review model override.
|
|
244
|
+
const reviewRoute = parseReviewModel(config.reviewModel, agent.options.provider);
|
|
172
245
|
const review = await reviewAutoRefine(ctx, {
|
|
173
246
|
agent,
|
|
174
247
|
state: harnessState,
|
|
@@ -176,6 +249,7 @@ async function runGate(ctx, engine, agent, config, state, reason, record) {
|
|
|
176
249
|
trajectory,
|
|
177
250
|
context: { reason, turnsSinceLastReview },
|
|
178
251
|
budgetTokens: config.budgetTokens,
|
|
252
|
+
...(reviewRoute ? { overrideProvider: reviewRoute.provider, overrideModel: reviewRoute.model } : {}),
|
|
179
253
|
});
|
|
180
254
|
state.lastReviewAt = state.turns;
|
|
181
255
|
if (!review.shouldRefine) {
|
|
@@ -189,15 +263,39 @@ async function runGate(ctx, engine, agent, config, state, reason, record) {
|
|
|
189
263
|
history,
|
|
190
264
|
...(review.instructions ? { instructions: review.instructions } : {}),
|
|
191
265
|
global: false,
|
|
266
|
+
// Read the skill-creator template facts (fallback: builtin distilled
|
|
267
|
+
// guide) so skill proposals follow the standard.
|
|
268
|
+
skillsRoot: join(engine.baseDir, "skills"),
|
|
192
269
|
});
|
|
270
|
+
// Skills are governed resources: an auto-created skill is OFFERED to the
|
|
271
|
+
// user for a decision (固化/不固化) before it lands — the gate never
|
|
272
|
+
// writes a skill silently. Without consent the skill edits are withheld
|
|
273
|
+
// and the rest of the proposal proceeds as usual.
|
|
274
|
+
const { skillEdits, otherEdits } = splitSkillEdits(proposal);
|
|
275
|
+
const skillConsented = await consultSkillEdits(ctx, agent, skillEdits, state);
|
|
276
|
+
const finalProposal = skillConsented
|
|
277
|
+
? proposal
|
|
278
|
+
: {
|
|
279
|
+
...proposal,
|
|
280
|
+
edits: otherEdits,
|
|
281
|
+
summary: skillEdits.length > 0 ? `${proposal.summary} (skill edits withheld — pending user decision)` : proposal.summary,
|
|
282
|
+
};
|
|
283
|
+
if (finalProposal.edits.length === 0) {
|
|
284
|
+
const withheld = skillEdits.length > 0 ? " (skill proposal withheld — user not consulted or declined)" : "";
|
|
285
|
+
logger.info(`auto-review declined (${reason}) [${sessionId}] after ${turnsSinceLastReview} turns: no consented edits${withheld} — ${review.rationale}`);
|
|
286
|
+
record({ sessionId, reason, turnsSinceLastReview, outcome: "declined", rationale: `${review.rationale}${withheld}` });
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
193
289
|
const source = entrySourceOf(agent, sessionId);
|
|
194
|
-
const result = engine.apply("local", sessionId,
|
|
290
|
+
const result = engine.apply("local", sessionId, finalProposal, {
|
|
195
291
|
scope: "local",
|
|
196
292
|
baselineState: localState,
|
|
197
293
|
...(source ? { source } : {}),
|
|
198
294
|
});
|
|
199
295
|
logger.info(`auto-review approved (${reason}) [${sessionId}] after ${turnsSinceLastReview} turns; auto-refine ${result.id}: ${result.appliedEdits.filter((e) => e.applied).length} applied, ${result.appliedEdits.filter((e) => !e.applied).length} failed — ${review.rationale}`);
|
|
200
296
|
record({ sessionId, reason, turnsSinceLastReview, outcome: "approved", rationale: review.rationale, refinementId: result.id });
|
|
297
|
+
// Gap C4: emit structured evolve_complete event for third-party consumers.
|
|
298
|
+
emitEvolveComplete(engine.baseDir, buildEvolveCompleteEvent(result, `auto_review:${reason}`, sessionId));
|
|
201
299
|
// Visibility: tell the user what the gate just persisted. Only the
|
|
202
300
|
// turn-interval path notifies — a compaction-triggered gate must not wake
|
|
203
301
|
// the agent mid-compaction — and only when something was actually applied
|
|
@@ -214,4 +312,65 @@ async function readTrajectory(ctx, agent, maxChars) {
|
|
|
214
312
|
const snapshot = await sessionQuery.readSurface(agent.id);
|
|
215
313
|
return serializeSurface(snapshot.events, maxChars);
|
|
216
314
|
}
|
|
315
|
+
/**
|
|
316
|
+
* Split a proposal into skill edits and everything else. Skill edits are the
|
|
317
|
+
* governed part: they need explicit user consent before the gate applies
|
|
318
|
+
* them, while the remaining edits flow through the normal auto path.
|
|
319
|
+
*/
|
|
320
|
+
export function splitSkillEdits(proposal) {
|
|
321
|
+
return {
|
|
322
|
+
skillEdits: proposal.edits.filter((edit) => edit.kind === "skill"),
|
|
323
|
+
otherEdits: proposal.edits.filter((edit) => edit.kind !== "skill"),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Ask the user whether to solidify proposed skill edits (guidance or
|
|
328
|
+
* executable) into the harness. Returns true when every skill edit is
|
|
329
|
+
* consented. Never writes a skill silently:
|
|
330
|
+
* - no question service available → false (conservative);
|
|
331
|
+
* - the same candidate was rejected within the cooldown window → false
|
|
332
|
+
* without asking again (no nagging);
|
|
333
|
+
* - the user declines → false and the rejection is recorded for cooldown;
|
|
334
|
+
* - the question call fails/aborts → false (conservative).
|
|
335
|
+
*/
|
|
336
|
+
export async function consultSkillEdits(ctx, agent, skillEdits, gate) {
|
|
337
|
+
if (skillEdits.length === 0)
|
|
338
|
+
return true;
|
|
339
|
+
const key = skillEdits.map((edit) => edit.id ?? slug(edit.title ?? edit.kind, edit.kind)).join("|");
|
|
340
|
+
const lastReject = gate.skillRejects.get(key);
|
|
341
|
+
if (lastReject !== undefined && gate.turns - lastReject < SKILL_CONSULT_COOLDOWN_TURNS) {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
const userQuestions = questionServiceOf(ctx);
|
|
345
|
+
if (!userQuestions) {
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
const description = skillEdits
|
|
349
|
+
.map((edit) => {
|
|
350
|
+
const form = edit.skill_kind === "guidance" ? "guidance 技能(SKILL.md 文档)" : "可执行技能";
|
|
351
|
+
return `- ${edit.action}「${edit.title ?? edit.id}」(${form})`;
|
|
352
|
+
})
|
|
353
|
+
.join("\n");
|
|
354
|
+
try {
|
|
355
|
+
const answer = await userQuestions.ask({
|
|
356
|
+
questions: [
|
|
357
|
+
{
|
|
358
|
+
id: "evolve-skill-consult",
|
|
359
|
+
question: `自进化检测到反复出现的流程/技能候选,建议沉淀:\n\n${description}\n\n是否固化?`,
|
|
360
|
+
options: [{ label: "固化" }, { label: "不固化" }],
|
|
361
|
+
},
|
|
362
|
+
],
|
|
363
|
+
agent,
|
|
364
|
+
});
|
|
365
|
+
const item = answer.answers?.find((entry) => entry.id === "evolve-skill-consult");
|
|
366
|
+
const consented = item?.selected?.includes("固化") ?? false;
|
|
367
|
+
if (!consented) {
|
|
368
|
+
gate.skillRejects.set(key, gate.turns);
|
|
369
|
+
}
|
|
370
|
+
return consented;
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
217
376
|
//# sourceMappingURL=auto.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `/evolve benchmark` subcommand handler. Extracted from command.ts (P2-2).
|
|
3
|
+
*/
|
|
4
|
+
import type { Context } from "@deepseek-ai/cordis";
|
|
5
|
+
import type { CommandInvocation, CommandResult } from "@deepseek-ai/dsh-commands";
|
|
6
|
+
import type { EvolutionEngine } from "./service.js";
|
|
7
|
+
import type { CommandRuntimeOptions } from "./command.js";
|
|
8
|
+
export declare function executeBenchmarkCommand(ctx: Context, engine: EvolutionEngine, invocation: CommandInvocation, rest: string[], runtime: CommandRuntimeOptions): Promise<CommandResult>;
|
|
9
|
+
//# sourceMappingURL=benchmark-command.d.ts.map
|