dsh-continual-evolve 0.10.1 → 0.10.3
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 +3 -3
- package/README.zh.md +3 -3
- package/lib/approval.d.ts +7 -7
- package/lib/approval.js +14 -21
- package/lib/auto.d.ts +9 -1
- package/lib/auto.js +17 -12
- package/lib/copy.d.ts +74 -0
- package/lib/copy.js +160 -0
- package/lib/fate.d.ts +2 -1
- package/lib/fate.js +27 -30
- package/lib/index.d.ts +14 -0
- package/lib/index.js +8 -0
- package/lib/memory-agent.d.ts +7 -0
- package/lib/memory-agent.js +4 -1
- package/lib/planner.d.ts +7 -0
- package/lib/planner.js +3 -1
- package/lib/record-language.d.ts +77 -0
- package/lib/record-language.js +135 -0
- package/lib/review.d.ts +7 -0
- package/lib/review.js +2 -1
- package/lib/wrapup-command.js +6 -6
- package/lib/wrapup.d.ts +7 -0
- package/lib/wrapup.js +2 -1
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://github.com/ZK-Andy/dsh-continual-evolve/actions/workflows/ci.yml)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](package.json)
|
|
10
|
-
[]()
|
|
11
11
|
[]()
|
|
12
12
|
[]()
|
|
13
13
|
[]()
|
|
@@ -142,7 +142,7 @@ before aborting.
|
|
|
142
142
|
|
|
143
143
|
```bash
|
|
144
144
|
pnpm install && pnpm build # deps + tsc -> lib/
|
|
145
|
-
pnpm test # vitest (
|
|
145
|
+
pnpm test # vitest (1075 tests)
|
|
146
146
|
pnpm test:coverage # v8 coverage, thresholds enforced in CI
|
|
147
147
|
pnpm coverage:gaps # locate uncovered lines per file (read-only)
|
|
148
148
|
pnpm lint # oxlint src test
|
|
@@ -152,7 +152,7 @@ Project layout:
|
|
|
152
152
|
|
|
153
153
|
```
|
|
154
154
|
├── src/ # engine, tools, commands, memory agent, recall, projection, gate, fate, benchmark, injection + token usage…
|
|
155
|
-
├── test/ # vitest suites (
|
|
155
|
+
├── test/ # vitest suites (58 files)
|
|
156
156
|
├── lib/ # build output (tsc)
|
|
157
157
|
├── docs/
|
|
158
158
|
│ ├── design.md # full design doc (hardening matrix)
|
package/README.zh.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://github.com/ZK-Andy/dsh-continual-evolve/actions/workflows/ci.yml)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](package.json)
|
|
10
|
-
[]()
|
|
11
11
|
[]()
|
|
12
12
|
[]()
|
|
13
13
|
[]()
|
|
@@ -126,7 +126,7 @@ profile patch 示例:
|
|
|
126
126
|
|
|
127
127
|
```bash
|
|
128
128
|
pnpm install && pnpm build # 依赖 + tsc -> lib/
|
|
129
|
-
pnpm test # vitest(
|
|
129
|
+
pnpm test # vitest(1075 例)
|
|
130
130
|
pnpm test:coverage # v8 覆盖率,CI 强制阈值
|
|
131
131
|
pnpm coverage:gaps # 定位各文件未覆盖行(只读)
|
|
132
132
|
pnpm lint # oxlint src test
|
|
@@ -136,7 +136,7 @@ pnpm lint # oxlint src test
|
|
|
136
136
|
|
|
137
137
|
```
|
|
138
138
|
├── src/ # 引擎、工具、命令、memory Agent、召回、投影、门禁、fate、benchmark、注入 + token 用量…
|
|
139
|
-
├── test/ # vitest 测试套件(
|
|
139
|
+
├── test/ # vitest 测试套件(58 个文件)
|
|
140
140
|
├── lib/ # 构建产物(tsc)
|
|
141
141
|
├── docs/
|
|
142
142
|
│ ├── design.md # 完整设计文档(硬化矩阵)
|
package/lib/approval.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { Context } from "@deepseek-ai/cordis";
|
|
8
8
|
import type { Agent } from "@deepseek-ai/dsh-agent";
|
|
9
|
+
import type { RecordLanguage } from "./record-language.js";
|
|
9
10
|
export type ScopeApprovalDecision = "approved" | "declined";
|
|
10
11
|
export interface QuestionService {
|
|
11
12
|
ask(request: {
|
|
@@ -37,21 +38,20 @@ export declare function questionServiceOf(ctx: Context): QuestionService | undef
|
|
|
37
38
|
* choice is accepted. A missing/ambiguous/unknown answer throws so callers
|
|
38
39
|
* never interpret a lost or malformed dialog as a durable rejection.
|
|
39
40
|
*
|
|
40
|
-
* Copy follows the headline → details → impact shape
|
|
41
|
-
*
|
|
42
|
-
* last line states visibility/reversibility. Option labels stay literal
|
|
43
|
-
* ("批准"/"拒绝") for parser compatibility; human guidance lives in
|
|
44
|
-
* `description`.
|
|
41
|
+
* Copy follows the headline → details → impact shape in the resolved dialog
|
|
42
|
+
* language; labels stay literal per language for parser compatibility.
|
|
45
43
|
*
|
|
46
44
|
* @param ctx Cordis context carrying the userQuestions service.
|
|
47
45
|
* @param agent Agent prompting the question, if any.
|
|
48
46
|
* @param signal Abort signal for the question, if any.
|
|
49
47
|
* @param scope Target persistent scope.
|
|
50
48
|
* @param what Edit summary; truncated to 300 chars with "…" when longer.
|
|
51
|
-
* @
|
|
49
|
+
* @param lang Dialog language; absent → resolved per call (durable client
|
|
50
|
+
* preference, else `en`).
|
|
51
|
+
* @returns "approved" when the user picks the approval label, otherwise "declined".
|
|
52
52
|
* @throws When the service is missing or the answer is not unique/explicit.
|
|
53
53
|
*/
|
|
54
|
-
export declare function requestScopeApproval(ctx: Context, agent: Agent | undefined, signal: AbortSignal | undefined, scope: "project" | "global", what: string): Promise<ScopeApprovalDecision>;
|
|
54
|
+
export declare function requestScopeApproval(ctx: Context, agent: Agent | undefined, signal: AbortSignal | undefined, scope: "project" | "global", what: string, lang?: RecordLanguage): Promise<ScopeApprovalDecision>;
|
|
55
55
|
/**
|
|
56
56
|
* Ask the user to approve a global edit. Throws when the service is missing,
|
|
57
57
|
* the user declines, or the question cannot be answered.
|
package/lib/approval.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { approvalCopy, resolveDialogLanguage } from "./copy.js";
|
|
1
2
|
/**
|
|
2
3
|
* Lazily resolve the userQuestions service from the context.
|
|
3
4
|
* Returns undefined when the service is not loaded — callers decide
|
|
@@ -11,40 +12,31 @@ export function questionServiceOf(ctx) {
|
|
|
11
12
|
* choice is accepted. A missing/ambiguous/unknown answer throws so callers
|
|
12
13
|
* never interpret a lost or malformed dialog as a durable rejection.
|
|
13
14
|
*
|
|
14
|
-
* Copy follows the headline → details → impact shape
|
|
15
|
-
*
|
|
16
|
-
* last line states visibility/reversibility. Option labels stay literal
|
|
17
|
-
* ("批准"/"拒绝") for parser compatibility; human guidance lives in
|
|
18
|
-
* `description`.
|
|
15
|
+
* Copy follows the headline → details → impact shape in the resolved dialog
|
|
16
|
+
* language; labels stay literal per language for parser compatibility.
|
|
19
17
|
*
|
|
20
18
|
* @param ctx Cordis context carrying the userQuestions service.
|
|
21
19
|
* @param agent Agent prompting the question, if any.
|
|
22
20
|
* @param signal Abort signal for the question, if any.
|
|
23
21
|
* @param scope Target persistent scope.
|
|
24
22
|
* @param what Edit summary; truncated to 300 chars with "…" when longer.
|
|
25
|
-
* @
|
|
23
|
+
* @param lang Dialog language; absent → resolved per call (durable client
|
|
24
|
+
* preference, else `en`).
|
|
25
|
+
* @returns "approved" when the user picks the approval label, otherwise "declined".
|
|
26
26
|
* @throws When the service is missing or the answer is not unique/explicit.
|
|
27
27
|
*/
|
|
28
|
-
export async function requestScopeApproval(ctx, agent, signal, scope, what) {
|
|
28
|
+
export async function requestScopeApproval(ctx, agent, signal, scope, what, lang) {
|
|
29
29
|
const userQuestions = questionServiceOf(ctx);
|
|
30
30
|
if (!userQuestions) {
|
|
31
31
|
throw new Error("global evolution edits require the userQuestions service (load @deepseek-ai/dsh-user-questions)");
|
|
32
32
|
}
|
|
33
|
-
const
|
|
34
|
-
const compactWhat = what.length > 300 ? `${what.slice(0, 300)}…` : what;
|
|
35
|
-
const impact = scope === "project" ? "影响:仅本项目会话可见,可回滚。" : "影响:所有会话可见,可回滚。";
|
|
33
|
+
const copy = approvalCopy(scope, what, lang ?? resolveDialogLanguage(ctx));
|
|
36
34
|
const answer = await userQuestions.ask({
|
|
37
35
|
questions: [
|
|
38
36
|
{
|
|
39
37
|
id: "approve-global-evolve",
|
|
40
|
-
question:
|
|
41
|
-
options:
|
|
42
|
-
{
|
|
43
|
-
label: "批准",
|
|
44
|
-
description: scope === "project" ? "写入,仅本项目会话可见" : "写入,所有会话可见",
|
|
45
|
-
},
|
|
46
|
-
{ label: "拒绝", description: "不写入,本次跳过" },
|
|
47
|
-
],
|
|
38
|
+
question: copy.question,
|
|
39
|
+
options: copy.options,
|
|
48
40
|
},
|
|
49
41
|
],
|
|
50
42
|
...(agent ? { agent } : {}),
|
|
@@ -55,11 +47,12 @@ export async function requestScopeApproval(ctx, agent, signal, scope, what) {
|
|
|
55
47
|
throw new Error("evolution approval returned no unique explicit decision");
|
|
56
48
|
}
|
|
57
49
|
const selected = [...new Set(matching[0]?.selected ?? [])];
|
|
58
|
-
const
|
|
59
|
-
|
|
50
|
+
const approvals = selected.filter((label) => label === "批准" || label === "Approve");
|
|
51
|
+
const declines = selected.filter((label) => label === "拒绝" || label === "Decline");
|
|
52
|
+
if (approvals.length + declines.length !== 1 || selected.length !== 1) {
|
|
60
53
|
throw new Error(`evolution approval returned an invalid decision: ${JSON.stringify(selected)}`);
|
|
61
54
|
}
|
|
62
|
-
return
|
|
55
|
+
return approvals.length === 1 ? "approved" : "declined";
|
|
63
56
|
}
|
|
64
57
|
/**
|
|
65
58
|
* Ask the user to approve a global edit. Throws when the service is missing,
|
package/lib/auto.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ import { type ReviewScheduler } from "./review-scheduler.js";
|
|
|
8
8
|
import { type TurnSnapshot } from "./turn-snapshot.js";
|
|
9
9
|
import type { PromotionPolicy } from "./promotion.js";
|
|
10
10
|
import type { PlannerPrefixCacheMode } from "./prefix-cache.js";
|
|
11
|
+
import { type RecordLanguagePreference } from "./record-language.js";
|
|
12
|
+
import type { RecordLanguage } from "./record-language.js";
|
|
11
13
|
export interface AutoReviewConfig {
|
|
12
14
|
/** Legacy fate cadence; successful turns are no longer gated by this value. */
|
|
13
15
|
intervalTurns: number;
|
|
@@ -44,6 +46,12 @@ export interface AutoReviewConfig {
|
|
|
44
46
|
reviewModel?: string;
|
|
45
47
|
/** Dedicated memory-agent writes to project/global require the normal human approval boundary. */
|
|
46
48
|
requireGlobalApproval?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Authoring language for this run's records. Absent/`auto` resolves per
|
|
51
|
+
* call through the record-language chain (durable client preference,
|
|
52
|
+
* then trajectory detection, then `en`); `zh`/`en` pin it.
|
|
53
|
+
*/
|
|
54
|
+
recordLanguage?: RecordLanguagePreference;
|
|
47
55
|
/**
|
|
48
56
|
* Prefix-cache routing for the gate input (see reviewAutoRefine).
|
|
49
57
|
* Absent mode → auto-detect; absent budget → the default prefix budget.
|
|
@@ -224,5 +232,5 @@ export declare function splitSkillEdits(proposal: RefinementProposal): {
|
|
|
224
232
|
* - the user declines → false and the rejection is recorded for cooldown;
|
|
225
233
|
* - the question call fails/aborts → false (conservative).
|
|
226
234
|
*/
|
|
227
|
-
export declare function consultSkillEdits(ctx: Context, agent: Agent, skillEdits: RefinementEdit[], gate: GateState): Promise<boolean>;
|
|
235
|
+
export declare function consultSkillEdits(ctx: Context, agent: Agent, skillEdits: RefinementEdit[], gate: GateState, language?: RecordLanguage): Promise<boolean>;
|
|
228
236
|
//# sourceMappingURL=auto.d.ts.map
|
package/lib/auto.js
CHANGED
|
@@ -38,6 +38,8 @@ import { loadDeclinedMemory, matchDeclinedCheckpoint } from "./declines.js";
|
|
|
38
38
|
import { tokenize } from "./search.js";
|
|
39
39
|
import { applyMemoryExtractionProposal, buildMemoryManifest, runMemoryAgent, } from "./memory-agent.js";
|
|
40
40
|
import { asLlmSessionId } from "./llm-text.js";
|
|
41
|
+
import { resolveRecordLanguage } from "./record-language.js";
|
|
42
|
+
import { resolveDialogLanguage, skillConsultCopy, skillEditLine } from "./copy.js";
|
|
41
43
|
/** Turns a rejected skill candidate stays silent before being offered again. */
|
|
42
44
|
export const SKILL_CONSULT_COOLDOWN_TURNS = 10;
|
|
43
45
|
/** Default session-close drain budget: an in-flight extraction gets 15s to settle. */
|
|
@@ -466,6 +468,7 @@ export async function runMemoryExtractionPhase(ctx, engine, agent, config, state
|
|
|
466
468
|
maxOutputTokens: config.budgetTokens,
|
|
467
469
|
...(signal ? { signal } : {}),
|
|
468
470
|
tokenUsage,
|
|
471
|
+
language: resolveRecordLanguage({ configured: config.recordLanguage, ctx, trajectoryText: memorySnapshot.trajectory }),
|
|
469
472
|
...((config.prefixCacheMode !== undefined || config.prefixMaxChars !== undefined
|
|
470
473
|
? {
|
|
471
474
|
prefixCache: {
|
|
@@ -576,6 +579,9 @@ async function runReviewPhase(ctx, engine, agent, config, state, snapshot, recor
|
|
|
576
579
|
const history = engine.history("local", sessionId);
|
|
577
580
|
// Gap C1: resolve optional review model override.
|
|
578
581
|
const reviewRoute = parseReviewModel(config.reviewModel, agent.options.provider);
|
|
582
|
+
// Records follow the client language: explicit config first, else the
|
|
583
|
+
// durable DSH preference, else this run's own trajectory.
|
|
584
|
+
const recordLanguage = resolveRecordLanguage({ configured: config.recordLanguage, ctx, trajectoryText: trajectory });
|
|
579
585
|
const review = await reviewAutoRefine(ctx, {
|
|
580
586
|
agent,
|
|
581
587
|
state: harnessState,
|
|
@@ -586,6 +592,7 @@ async function runReviewPhase(ctx, engine, agent, config, state, snapshot, recor
|
|
|
586
592
|
context: { reason, turnsSinceLastReview },
|
|
587
593
|
budgetTokens: config.budgetTokens,
|
|
588
594
|
tokenUsage,
|
|
595
|
+
language: recordLanguage,
|
|
589
596
|
...(reviewRoute ? { overrideProvider: reviewRoute.provider, overrideModel: reviewRoute.model } : {}),
|
|
590
597
|
...((config.prefixCacheMode !== undefined || config.prefixMaxChars !== undefined
|
|
591
598
|
? {
|
|
@@ -615,6 +622,7 @@ async function runReviewPhase(ctx, engine, agent, config, state, snapshot, recor
|
|
|
615
622
|
// guide) so skill proposals follow the standard.
|
|
616
623
|
skillsRoot: join(engine.baseDir, "skills"),
|
|
617
624
|
tokenUsage,
|
|
625
|
+
language: recordLanguage,
|
|
618
626
|
...((config.prefixCacheMode !== undefined || config.prefixMaxChars !== undefined
|
|
619
627
|
? {
|
|
620
628
|
prefixCache: {
|
|
@@ -631,7 +639,7 @@ async function runReviewPhase(ctx, engine, agent, config, state, snapshot, recor
|
|
|
631
639
|
// writes a skill silently. Without consent the skill edits are withheld
|
|
632
640
|
// and the rest of the proposal proceeds as usual.
|
|
633
641
|
const { skillEdits, otherEdits } = splitSkillEdits(proposal);
|
|
634
|
-
const skillConsented = await consultSkillEdits(ctx, agent, skillEdits, state);
|
|
642
|
+
const skillConsented = await consultSkillEdits(ctx, agent, skillEdits, state, recordLanguage);
|
|
635
643
|
const finalProposal = skillConsented
|
|
636
644
|
? proposal
|
|
637
645
|
: {
|
|
@@ -721,7 +729,7 @@ export function splitSkillEdits(proposal) {
|
|
|
721
729
|
* - the user declines → false and the rejection is recorded for cooldown;
|
|
722
730
|
* - the question call fails/aborts → false (conservative).
|
|
723
731
|
*/
|
|
724
|
-
export async function consultSkillEdits(ctx, agent, skillEdits, gate) {
|
|
732
|
+
export async function consultSkillEdits(ctx, agent, skillEdits, gate, language) {
|
|
725
733
|
if (skillEdits.length === 0)
|
|
726
734
|
return true;
|
|
727
735
|
const key = skillEdits.map((edit) => edit.id ?? slug(edit.title ?? edit.kind, edit.kind)).join("|");
|
|
@@ -733,28 +741,25 @@ export async function consultSkillEdits(ctx, agent, skillEdits, gate) {
|
|
|
733
741
|
if (!userQuestions) {
|
|
734
742
|
return false;
|
|
735
743
|
}
|
|
744
|
+
const lang = language ?? resolveDialogLanguage(ctx);
|
|
736
745
|
const description = skillEdits
|
|
737
|
-
.map((edit) =>
|
|
738
|
-
const form = edit.skill_kind === "guidance" ? "guidance 技能(SKILL.md 文档)" : "可执行技能";
|
|
739
|
-
return `- ${edit.action}「${edit.title ?? edit.id}」(${form})`;
|
|
740
|
-
})
|
|
746
|
+
.map((edit) => skillEditLine(edit.action, edit.title ?? edit.id ?? edit.kind, edit.skill_kind, lang))
|
|
741
747
|
.join("\n");
|
|
742
748
|
try {
|
|
749
|
+
const copy = skillConsultCopy(description, lang);
|
|
743
750
|
const answer = await userQuestions.ask({
|
|
744
751
|
questions: [
|
|
745
752
|
{
|
|
746
753
|
id: "evolve-skill-consult",
|
|
747
|
-
question:
|
|
748
|
-
options:
|
|
749
|
-
{ label: "固化", description: "生成技能,以后复用" },
|
|
750
|
-
{ label: "不固化", description: "本次跳过,10 回合内不再打扰" },
|
|
751
|
-
],
|
|
754
|
+
question: copy.question,
|
|
755
|
+
options: copy.options,
|
|
752
756
|
},
|
|
753
757
|
],
|
|
754
758
|
agent,
|
|
755
759
|
});
|
|
756
760
|
const item = answer.answers?.find((entry) => entry.id === "evolve-skill-consult");
|
|
757
|
-
const
|
|
761
|
+
const selected = item?.selected ?? [];
|
|
762
|
+
const consented = selected.includes("固化") || selected.includes("Solidify");
|
|
758
763
|
if (!consented) {
|
|
759
764
|
gate.skillRejects.set(key, gate.turns);
|
|
760
765
|
}
|
package/lib/copy.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend user-visible copy for the four evolution confirmation dialogs,
|
|
3
|
+
* in both shipped languages. Labels stay literal per language (the parsers
|
|
4
|
+
* match them); guidance lives in `description`.
|
|
5
|
+
*
|
|
6
|
+
* Language is never hardcoded at a call site: every builder takes an
|
|
7
|
+
* explicit `lang`, and dialog entry points resolve it per call
|
|
8
|
+
* (explicit config upstream, else the durable DSH client preference,
|
|
9
|
+
* else `en`) when the caller does not supply one.
|
|
10
|
+
*/
|
|
11
|
+
import { type RecordLanguage } from "./record-language.js";
|
|
12
|
+
export interface ConfirmOption {
|
|
13
|
+
label: string;
|
|
14
|
+
description: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ConfirmCopy {
|
|
17
|
+
question: string;
|
|
18
|
+
options: ConfirmOption[];
|
|
19
|
+
}
|
|
20
|
+
/** Maximum edit-summary chars carried into a dialog; longer text truncates. */
|
|
21
|
+
export declare const COPY_WHAT_MAX_CHARS = 300;
|
|
22
|
+
/**
|
|
23
|
+
* Persistent-scope write approval (`approve-global-evolve`).
|
|
24
|
+
*
|
|
25
|
+
* @param scope Target persistent scope.
|
|
26
|
+
* @param what Edit summary shown in the dialog body.
|
|
27
|
+
* @param lang Dialog language.
|
|
28
|
+
*/
|
|
29
|
+
export declare function approvalCopy(scope: "project" | "global", what: string, lang: RecordLanguage): ConfirmCopy;
|
|
30
|
+
export interface FateConsultSections {
|
|
31
|
+
promotable: string[];
|
|
32
|
+
splits: string[];
|
|
33
|
+
reviewArchives: string[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Local-fate homing consult (`evolve-fate-consult`).
|
|
37
|
+
*
|
|
38
|
+
* @param sections Pre-rendered per-item lines per group.
|
|
39
|
+
* @param lang Dialog language.
|
|
40
|
+
*/
|
|
41
|
+
export declare function fateConsultCopy(sections: FateConsultSections, lang: RecordLanguage): ConfirmCopy;
|
|
42
|
+
/**
|
|
43
|
+
* Single-entry wrap-up archive confirm (`evolve-wrapup-archive-review`).
|
|
44
|
+
*
|
|
45
|
+
* @param title Entry title shown in the headline.
|
|
46
|
+
* @param lang Dialog language.
|
|
47
|
+
*/
|
|
48
|
+
export declare function wrapupArchiveCopy(title: string, lang: RecordLanguage): ConfirmCopy;
|
|
49
|
+
/**
|
|
50
|
+
* Skill solidification consult (`evolve-skill-consult`).
|
|
51
|
+
*
|
|
52
|
+
* @param description Pre-rendered per-edit lines.
|
|
53
|
+
* @param lang Dialog language.
|
|
54
|
+
*/
|
|
55
|
+
export declare function skillConsultCopy(description: string, lang: RecordLanguage): ConfirmCopy;
|
|
56
|
+
/**
|
|
57
|
+
* One skill-edit line inside the solidification consult.
|
|
58
|
+
*
|
|
59
|
+
* @param action Edit action (`create` etc.).
|
|
60
|
+
* @param title Entry title or id fallback.
|
|
61
|
+
* @param skillKind `guidance` skills are SKILL.md documents.
|
|
62
|
+
* @param lang Dialog language.
|
|
63
|
+
*/
|
|
64
|
+
export declare function skillEditLine(action: string, title: string, skillKind: string | undefined, lang: RecordLanguage): string;
|
|
65
|
+
/**
|
|
66
|
+
* Resolve the dialog language for a call site that holds no explicit
|
|
67
|
+
* choice: durable client preference first, `en` otherwise. (Dialogs carry
|
|
68
|
+
* no trajectory text, so there is no detection tier here.)
|
|
69
|
+
*
|
|
70
|
+
* @param ctx Host context for the durable-preference read.
|
|
71
|
+
* @returns The dialog language.
|
|
72
|
+
*/
|
|
73
|
+
export declare function resolveDialogLanguage(ctx: unknown): RecordLanguage;
|
|
74
|
+
//# sourceMappingURL=copy.d.ts.map
|
package/lib/copy.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend user-visible copy for the four evolution confirmation dialogs,
|
|
3
|
+
* in both shipped languages. Labels stay literal per language (the parsers
|
|
4
|
+
* match them); guidance lives in `description`.
|
|
5
|
+
*
|
|
6
|
+
* Language is never hardcoded at a call site: every builder takes an
|
|
7
|
+
* explicit `lang`, and dialog entry points resolve it per call
|
|
8
|
+
* (explicit config upstream, else the durable DSH client preference,
|
|
9
|
+
* else `en`) when the caller does not supply one.
|
|
10
|
+
*/
|
|
11
|
+
import { resolveRecordLanguage } from "./record-language.js";
|
|
12
|
+
/** Maximum edit-summary chars carried into a dialog; longer text truncates. */
|
|
13
|
+
export const COPY_WHAT_MAX_CHARS = 300;
|
|
14
|
+
function compactWhat(what) {
|
|
15
|
+
return what.length > COPY_WHAT_MAX_CHARS ? `${what.slice(0, COPY_WHAT_MAX_CHARS)}…` : what;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Persistent-scope write approval (`approve-global-evolve`).
|
|
19
|
+
*
|
|
20
|
+
* @param scope Target persistent scope.
|
|
21
|
+
* @param what Edit summary shown in the dialog body.
|
|
22
|
+
* @param lang Dialog language.
|
|
23
|
+
*/
|
|
24
|
+
export function approvalCopy(scope, what, lang) {
|
|
25
|
+
const body = compactWhat(what);
|
|
26
|
+
if (lang === "zh") {
|
|
27
|
+
const storeLabel = scope === "project" ? "本项目跨会话 store" : "跨会话全局 store";
|
|
28
|
+
return {
|
|
29
|
+
question: `写入${storeLabel}?\n${body}\n${scope === "project" ? "影响:仅本项目会话可见,可回滚。" : "影响:所有会话可见,可回滚。"}`,
|
|
30
|
+
options: [
|
|
31
|
+
{ label: "批准", description: scope === "project" ? "写入,仅本项目会话可见" : "写入,所有会话可见" },
|
|
32
|
+
{ label: "拒绝", description: "不写入,本次跳过" },
|
|
33
|
+
],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const storeLabel = scope === "project" ? "project cross-session store" : "global cross-session store";
|
|
37
|
+
return {
|
|
38
|
+
question: `Write to the ${storeLabel}?\n${body}\n${scope === "project" ? "Impact: visible to this project's sessions only, reversible." : "Impact: visible to all sessions, reversible."}`,
|
|
39
|
+
options: [
|
|
40
|
+
{ label: "Approve", description: scope === "project" ? "Write, visible to this project only" : "Write, visible to all sessions" },
|
|
41
|
+
{ label: "Decline", description: "Skip this write" },
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Local-fate homing consult (`evolve-fate-consult`).
|
|
47
|
+
*
|
|
48
|
+
* @param sections Pre-rendered per-item lines per group.
|
|
49
|
+
* @param lang Dialog language.
|
|
50
|
+
*/
|
|
51
|
+
export function fateConsultCopy(sections, lang) {
|
|
52
|
+
if (lang === "zh") {
|
|
53
|
+
const lines = ["自进化门禁:本会话 local 条目需要归宿处理"];
|
|
54
|
+
if (sections.promotable.length > 0 || sections.splits.length > 0) {
|
|
55
|
+
lines.push("【提升到全局(写入全局 store)】", ...sections.promotable, ...sections.splits);
|
|
56
|
+
}
|
|
57
|
+
if (sections.reviewArchives.length > 0) {
|
|
58
|
+
lines.push("【归档(本地隐藏,可恢复)】", ...sections.reviewArchives);
|
|
59
|
+
}
|
|
60
|
+
lines.push("提升写入后所有会话可见,归档隐藏但可恢复。是否执行?");
|
|
61
|
+
return {
|
|
62
|
+
question: lines.join("\n"),
|
|
63
|
+
options: [
|
|
64
|
+
{ label: "执行", description: "提升写全局,归档隐藏本地(均可恢复)" },
|
|
65
|
+
{ label: "不执行", description: "全部保留,10 回合内不再打扰" },
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const lines = ["Evolution gate: this session's local entries need homing"];
|
|
70
|
+
if (sections.promotable.length > 0 || sections.splits.length > 0) {
|
|
71
|
+
lines.push("[Promote to global (write to the global store)]", ...sections.promotable, ...sections.splits);
|
|
72
|
+
}
|
|
73
|
+
if (sections.reviewArchives.length > 0) {
|
|
74
|
+
lines.push("[Archive (hidden locally, restorable)]", ...sections.reviewArchives);
|
|
75
|
+
}
|
|
76
|
+
lines.push("Promotions become visible to all sessions; archives hide but stay restorable. Proceed?");
|
|
77
|
+
return {
|
|
78
|
+
question: lines.join("\n"),
|
|
79
|
+
options: [
|
|
80
|
+
{ label: "Proceed", description: "Promote to global, archive locally (all reversible)" },
|
|
81
|
+
{ label: "Skip", description: "Keep everything, cooldown 10 turns" },
|
|
82
|
+
],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Single-entry wrap-up archive confirm (`evolve-wrapup-archive-review`).
|
|
87
|
+
*
|
|
88
|
+
* @param title Entry title shown in the headline.
|
|
89
|
+
* @param lang Dialog language.
|
|
90
|
+
*/
|
|
91
|
+
export function wrapupArchiveCopy(title, lang) {
|
|
92
|
+
if (lang === "zh") {
|
|
93
|
+
return {
|
|
94
|
+
question: `wrapup 确认归档:条目「${title}」\n未被全局覆盖且源自真实对话。归档后不再注入,数据保留、可恢复。`,
|
|
95
|
+
options: [
|
|
96
|
+
{ label: "归档", description: "隐藏但可恢复" },
|
|
97
|
+
{ label: "保留", description: "继续注入" },
|
|
98
|
+
],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
question: `Wrap-up archive confirm: entry "${title}"\nNot covered globally and distilled from real conversation. Archiving stops injection; data stays restorable.`,
|
|
103
|
+
options: [
|
|
104
|
+
{ label: "Archive", description: "Hide but restorable" },
|
|
105
|
+
{ label: "Keep", description: "Keep injecting" },
|
|
106
|
+
],
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Skill solidification consult (`evolve-skill-consult`).
|
|
111
|
+
*
|
|
112
|
+
* @param description Pre-rendered per-edit lines.
|
|
113
|
+
* @param lang Dialog language.
|
|
114
|
+
*/
|
|
115
|
+
export function skillConsultCopy(description, lang) {
|
|
116
|
+
if (lang === "zh") {
|
|
117
|
+
return {
|
|
118
|
+
question: `发现可复用的流程,建议固化为技能\n${description}\n\n固化后以后同类任务自动复用,可回滚。是否固化?`,
|
|
119
|
+
options: [
|
|
120
|
+
{ label: "固化", description: "生成技能,以后复用" },
|
|
121
|
+
{ label: "不固化", description: "本次跳过,10 回合内不再打扰" },
|
|
122
|
+
],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
question: `Reusable workflow found, suggest solidifying as a skill\n${description}\n\nSolidified skills are reused by similar tasks later, reversible. Solidify?`,
|
|
127
|
+
options: [
|
|
128
|
+
{ label: "Solidify", description: "Create the skill for reuse" },
|
|
129
|
+
{ label: "Skip", description: "Skip this time, cooldown 10 turns" },
|
|
130
|
+
],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* One skill-edit line inside the solidification consult.
|
|
135
|
+
*
|
|
136
|
+
* @param action Edit action (`create` etc.).
|
|
137
|
+
* @param title Entry title or id fallback.
|
|
138
|
+
* @param skillKind `guidance` skills are SKILL.md documents.
|
|
139
|
+
* @param lang Dialog language.
|
|
140
|
+
*/
|
|
141
|
+
export function skillEditLine(action, title, skillKind, lang) {
|
|
142
|
+
if (lang === "zh") {
|
|
143
|
+
const form = skillKind === "guidance" ? "guidance 技能(SKILL.md 文档)" : "可执行技能";
|
|
144
|
+
return `- ${action}「${title}」(${form})`;
|
|
145
|
+
}
|
|
146
|
+
const form = skillKind === "guidance" ? "guidance skill (SKILL.md document)" : "executable skill";
|
|
147
|
+
return `- ${action} "${title}" (${form})`;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Resolve the dialog language for a call site that holds no explicit
|
|
151
|
+
* choice: durable client preference first, `en` otherwise. (Dialogs carry
|
|
152
|
+
* no trajectory text, so there is no detection tier here.)
|
|
153
|
+
*
|
|
154
|
+
* @param ctx Host context for the durable-preference read.
|
|
155
|
+
* @returns The dialog language.
|
|
156
|
+
*/
|
|
157
|
+
export function resolveDialogLanguage(ctx) {
|
|
158
|
+
return resolveRecordLanguage({ ctx });
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=copy.js.map
|
package/lib/fate.d.ts
CHANGED
|
@@ -35,6 +35,7 @@ import type { EvolutionEngine } from "./service.js";
|
|
|
35
35
|
import type { AutoRefineReason } from "./review.js";
|
|
36
36
|
import type { AutoReviewConfig, GateState, ReviewRecord } from "./auto.js";
|
|
37
37
|
import { type PromotionPolicy } from "./promotion.js";
|
|
38
|
+
import { type RecordLanguage } from "./record-language.js";
|
|
38
39
|
import { type WrapupCandidate, type WrapupItem } from "./wrapup.js";
|
|
39
40
|
/** Turns a declined local-fate proposal stays silent before being offered again. */
|
|
40
41
|
export declare const FATE_CONSULT_COOLDOWN_TURNS = 10;
|
|
@@ -100,7 +101,7 @@ export interface FateConsultResult {
|
|
|
100
101
|
* the same candidate set was declined within the cooldown → not asked again.
|
|
101
102
|
* A decline records the cooldown (the consultSkillEdits pattern).
|
|
102
103
|
*/
|
|
103
|
-
export declare function consultLocalFates(ctx: Context, agent: Agent, plan: FatePlan, gate: GateState): Promise<FateConsultResult>;
|
|
104
|
+
export declare function consultLocalFates(ctx: Context, agent: Agent, plan: FatePlan, gate: GateState, language?: RecordLanguage): Promise<FateConsultResult>;
|
|
104
105
|
export type FateApplyMode = "full" | "silent-only";
|
|
105
106
|
export interface FateApplyResult {
|
|
106
107
|
/** Human-readable lines of what was applied (for the notice and the audit record). */
|
package/lib/fate.js
CHANGED
|
@@ -2,6 +2,8 @@ import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
|
2
2
|
import { EVOLVE_MESSAGE_SOURCE } from "./message-source.js";
|
|
3
3
|
import { DEFAULT_PROMOTION_POLICY } from "./promotion.js";
|
|
4
4
|
import { questionServiceOf } from "./approval.js";
|
|
5
|
+
import { fateConsultCopy, resolveDialogLanguage } from "./copy.js";
|
|
6
|
+
import { resolveRecordLanguage } from "./record-language.js";
|
|
5
7
|
import { assessLocalEntries, candidateKey, filterPromotable, listLocalCandidates, splitArchiveGuards, splitPromoteBlocked, splitPromoteProposals, wholePromoteProposals, } from "./wrapup.js";
|
|
6
8
|
/** Turns a declined local-fate proposal stays silent before being offered again. */
|
|
7
9
|
export const FATE_CONSULT_COOLDOWN_TURNS = 10;
|
|
@@ -72,7 +74,7 @@ export function fateCadenceDue(state, reason, intervalTurns) {
|
|
|
72
74
|
* the same candidate set was declined within the cooldown → not asked again.
|
|
73
75
|
* A decline records the cooldown (the consultSkillEdits pattern).
|
|
74
76
|
*/
|
|
75
|
-
export async function consultLocalFates(ctx, agent, plan, gate) {
|
|
77
|
+
export async function consultLocalFates(ctx, agent, plan, gate, language) {
|
|
76
78
|
const needsDialog = plan.promotable.length + plan.splits.length + plan.reviewArchives.length > 0;
|
|
77
79
|
if (!needsDialog) {
|
|
78
80
|
return { approved: true, asked: false, reason: "nothing-to-ask" };
|
|
@@ -86,21 +88,21 @@ export async function consultLocalFates(ctx, agent, plan, gate) {
|
|
|
86
88
|
if (!userQuestions) {
|
|
87
89
|
return { approved: false, asked: false, reason: "unavailable" };
|
|
88
90
|
}
|
|
91
|
+
const lang = language ?? resolveDialogLanguage(ctx);
|
|
89
92
|
try {
|
|
93
|
+
const copy = fateConsultCopy(sectionsOf(plan, lang), lang);
|
|
90
94
|
const answer = await userQuestions.ask({
|
|
91
95
|
questions: [
|
|
92
96
|
{
|
|
93
97
|
id: "evolve-fate-consult",
|
|
94
|
-
question:
|
|
95
|
-
options:
|
|
96
|
-
{ label: "执行", description: "提升写全局,归档隐藏本地(均可恢复)" },
|
|
97
|
-
{ label: "不执行", description: "全部保留,10 回合内不再打扰" },
|
|
98
|
-
],
|
|
98
|
+
question: copy.question,
|
|
99
|
+
options: copy.options,
|
|
99
100
|
},
|
|
100
101
|
],
|
|
101
102
|
agent,
|
|
102
103
|
});
|
|
103
|
-
const
|
|
104
|
+
const selected = answer.answers?.find((entry) => entry.id === "evolve-fate-consult")?.selected ?? [];
|
|
105
|
+
const approved = selected.includes("执行") || selected.includes("Proceed");
|
|
104
106
|
if (!approved) {
|
|
105
107
|
gate.fateRejects.set(setKey, gate.turns);
|
|
106
108
|
}
|
|
@@ -110,30 +112,24 @@ export async function consultLocalFates(ctx, agent, plan, gate) {
|
|
|
110
112
|
return { approved: false, asked: true, reason: "error" };
|
|
111
113
|
}
|
|
112
114
|
}
|
|
113
|
-
/**
|
|
114
|
-
function
|
|
115
|
+
/** Split a fate plan into pre-rendered per-group lines for the dialog copy. */
|
|
116
|
+
function sectionsOf(plan, lang) {
|
|
115
117
|
const byKey = new Map(plan.candidates.map((candidate) => [candidateKey(candidate.kind, candidate.id), candidate]));
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
for (const { item } of plan.splits) {
|
|
123
|
-
lines.push(`- ${item.key} → 拆出提升「${item.promote?.title}」(原条目随之归档)`);
|
|
124
|
-
}
|
|
118
|
+
const promotable = [];
|
|
119
|
+
const splits = [];
|
|
120
|
+
const reviewArchives = [];
|
|
121
|
+
for (const item of plan.promotable) {
|
|
122
|
+
promotable.push(`- ${item.key}「${byKey.get(item.key)?.title ?? item.key}」 — ${item.reason}`);
|
|
125
123
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
124
|
+
for (const { item } of plan.splits) {
|
|
125
|
+
splits.push(lang === "zh"
|
|
126
|
+
? `- ${item.key} → 拆出提升「${item.promote?.title}」(原条目随之归档)`
|
|
127
|
+
: `- ${item.key} → split-promote "${item.promote?.title}" (original archived alongside)`);
|
|
131
128
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
].join("\n");
|
|
129
|
+
for (const item of plan.reviewArchives) {
|
|
130
|
+
reviewArchives.push(`- ${item.key}「${byKey.get(item.key)?.title ?? item.key}」 — ${item.reason}`);
|
|
131
|
+
}
|
|
132
|
+
return { promotable, splits, reviewArchives };
|
|
137
133
|
}
|
|
138
134
|
/**
|
|
139
135
|
* Deterministically apply the fate plan. `"full"` applies everything
|
|
@@ -226,8 +222,9 @@ export async function runLocalFatePhase(ctx, engine, agent, config, state, reaso
|
|
|
226
222
|
const turnsSinceFate = state.turns - state.lastFateAt;
|
|
227
223
|
state.lastFateAt = state.turns;
|
|
228
224
|
let assessment;
|
|
225
|
+
const fateLanguage = resolveRecordLanguage({ configured: config.recordLanguage, ctx });
|
|
229
226
|
try {
|
|
230
|
-
assessment = await assessLocalEntries(ctx, agent, candidates, { tokenUsage, tokenUsagePhase: "fate" });
|
|
227
|
+
assessment = await assessLocalEntries(ctx, agent, candidates, { tokenUsage, tokenUsagePhase: "fate", language: fateLanguage });
|
|
231
228
|
}
|
|
232
229
|
catch (cause) {
|
|
233
230
|
const message = cause instanceof Error ? cause.message : String(cause);
|
|
@@ -248,7 +245,7 @@ export async function runLocalFatePhase(ctx, engine, agent, config, state, reaso
|
|
|
248
245
|
// a dialog — governed actions are deferred to `/evolve wrapup`. Only an
|
|
249
246
|
// explicit goal_blocked signal (own streak counter) may consult.
|
|
250
247
|
if (reason === "goal_blocked" && needsDialog) {
|
|
251
|
-
consent = await consultLocalFates(ctx, agent, plan, state);
|
|
248
|
+
consent = await consultLocalFates(ctx, agent, plan, state, fateLanguage);
|
|
252
249
|
}
|
|
253
250
|
if (consent.approved) {
|
|
254
251
|
const { applied, results } = applyLocalFates(engine, sessionId, plan, localState, "full");
|
package/lib/index.d.ts
CHANGED
|
@@ -38,6 +38,13 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
38
38
|
notifyOnAutoReview: z<boolean, boolean, "defined">;
|
|
39
39
|
/** Cross-session (global) edits require an explicit human approval. */
|
|
40
40
|
requireGlobalApproval: z<boolean, boolean, "defined">;
|
|
41
|
+
/**
|
|
42
|
+
* Authoring language for evolution records (refinement summaries,
|
|
43
|
+
* rationales, entry titles/content, review and wrap-up verdicts).
|
|
44
|
+
* `auto` follows the DSH client language (durable `locale.preference`,
|
|
45
|
+
* then trajectory detection, then `en`); `zh`/`en` pin it.
|
|
46
|
+
*/
|
|
47
|
+
recordLanguage: z<"zh" | "en" | "auto", "zh" | "en" | "auto", "defined">;
|
|
41
48
|
/** Skills root for materialized skill entries; defaults to <dshHome>/skills. */
|
|
42
49
|
skillsDir: z<string, string, "plain">;
|
|
43
50
|
/** Passphrase for rubric encryption; falls back to DSH_EVOLVE_RUBRIC_KEY, then a local key file. */
|
|
@@ -143,6 +150,13 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
143
150
|
notifyOnAutoReview: z<boolean, boolean, "defined">;
|
|
144
151
|
/** Cross-session (global) edits require an explicit human approval. */
|
|
145
152
|
requireGlobalApproval: z<boolean, boolean, "defined">;
|
|
153
|
+
/**
|
|
154
|
+
* Authoring language for evolution records (refinement summaries,
|
|
155
|
+
* rationales, entry titles/content, review and wrap-up verdicts).
|
|
156
|
+
* `auto` follows the DSH client language (durable `locale.preference`,
|
|
157
|
+
* then trajectory detection, then `en`); `zh`/`en` pin it.
|
|
158
|
+
*/
|
|
159
|
+
recordLanguage: z<"zh" | "en" | "auto", "zh" | "en" | "auto", "defined">;
|
|
146
160
|
/** Skills root for materialized skill entries; defaults to <dshHome>/skills. */
|
|
147
161
|
skillsDir: z<string, string, "plain">;
|
|
148
162
|
/** Passphrase for rubric encryption; falls back to DSH_EVOLVE_RUBRIC_KEY, then a local key file. */
|
package/lib/index.js
CHANGED
|
@@ -59,6 +59,13 @@ export const Config = z.object({
|
|
|
59
59
|
notifyOnAutoReview: z.boolean().default(true),
|
|
60
60
|
/** Cross-session (global) edits require an explicit human approval. */
|
|
61
61
|
requireGlobalApproval: z.boolean().default(true),
|
|
62
|
+
/**
|
|
63
|
+
* Authoring language for evolution records (refinement summaries,
|
|
64
|
+
* rationales, entry titles/content, review and wrap-up verdicts).
|
|
65
|
+
* `auto` follows the DSH client language (durable `locale.preference`,
|
|
66
|
+
* then trajectory detection, then `en`); `zh`/`en` pin it.
|
|
67
|
+
*/
|
|
68
|
+
recordLanguage: z.union([z.const("auto"), z.const("zh"), z.const("en")]).default("auto"),
|
|
62
69
|
/** Skills root for materialized skill entries; defaults to <dshHome>/skills. */
|
|
63
70
|
skillsDir: z.string(),
|
|
64
71
|
/** Passphrase for rubric encryption; falls back to DSH_EVOLVE_RUBRIC_KEY, then a local key file. */
|
|
@@ -212,6 +219,7 @@ export function apply(ctx, config) {
|
|
|
212
219
|
requireGlobalApproval: config.requireGlobalApproval ?? true,
|
|
213
220
|
...(config.plannerPrefixCache ? { prefixCacheMode: config.plannerPrefixCache } : {}),
|
|
214
221
|
...(config.plannerPrefixMaxChars !== undefined ? { prefixMaxChars: config.plannerPrefixMaxChars } : {}),
|
|
222
|
+
...(config.recordLanguage !== undefined ? { recordLanguage: config.recordLanguage } : {}),
|
|
215
223
|
});
|
|
216
224
|
ctx.logger("continual-evolve").info("continual-evolve automatic listener registered (Memory Agent only; generic review/planner/fate disconnected)");
|
|
217
225
|
ctx.logger("continual-evolve").info(`continual-evolve mounted (baseDir=${baseDir})`);
|
package/lib/memory-agent.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { type LlmSessionId } from "./llm-text.js";
|
|
|
15
15
|
import { type PrefixCacheOptions } from "./prefix-cache.js";
|
|
16
16
|
import { type EvolutionEngine } from "./service.js";
|
|
17
17
|
import { type TokenUsageTarget } from "./token-usage.js";
|
|
18
|
+
import { type RecordLanguage } from "./record-language.js";
|
|
18
19
|
import type { EntrySource, HarnessEntry, HarnessScope, HarnessState, RefinementEdit, RefinementProposal, RefinementResult } from "./types.js";
|
|
19
20
|
/** ZCode parity: the extractor may inspect memory and propose at most five times. */
|
|
20
21
|
export declare const MEMORY_AGENT_MAX_TURNS = 5;
|
|
@@ -80,6 +81,12 @@ export interface MemoryAgentOptions {
|
|
|
80
81
|
maxOutputTokens?: number;
|
|
81
82
|
signal?: AbortSignal;
|
|
82
83
|
tokenUsage?: TokenUsageTarget;
|
|
84
|
+
/**
|
|
85
|
+
* Authoring language for the proposal. Absent → resolved per call
|
|
86
|
+
* (explicit config upstream, else durable client preference, else
|
|
87
|
+
* trajectory detection, else `en`).
|
|
88
|
+
*/
|
|
89
|
+
language?: RecordLanguage;
|
|
83
90
|
}
|
|
84
91
|
/**
|
|
85
92
|
* Run the dedicated loop against a frozen manifest.
|
package/lib/memory-agent.js
CHANGED
|
@@ -10,6 +10,7 @@ import { buildConflictNotice, CONFLICT_WARN_SCORE, mostSimilarEntry } from "./pr
|
|
|
10
10
|
import { EvolutionApplyPostCommitError } from "./service.js";
|
|
11
11
|
import { tokenize } from "./search.js";
|
|
12
12
|
import { createTokenUsageObserver } from "./token-usage.js";
|
|
13
|
+
import { recordLanguageInstruction, resolveRecordLanguage } from "./record-language.js";
|
|
13
14
|
import { isArchived, isMemoryType, MEMORY_TYPE_KEY, slug } from "./types.js";
|
|
14
15
|
import { validateBlastRadiusScope, validateEdit } from "./validate.js";
|
|
15
16
|
/** ZCode parity: the extractor may inspect memory and propose at most five times. */
|
|
@@ -182,13 +183,15 @@ export async function runMemoryAgent(ctx, options) {
|
|
|
182
183
|
}),
|
|
183
184
|
];
|
|
184
185
|
let searches = 0;
|
|
186
|
+
const language = options.language ?? resolveRecordLanguage({ ctx, trajectoryText: options.trajectory });
|
|
187
|
+
const systemPrompt = `${MEMORY_AGENT_SYSTEM_PROMPT}\n\n${recordLanguageInstruction(language)}`;
|
|
185
188
|
for (let turn = 1; turn <= MEMORY_AGENT_MAX_TURNS; turn += 1) {
|
|
186
189
|
options.signal?.throwIfAborted();
|
|
187
190
|
const blocks = await streamModelTurn(ctx, {
|
|
188
191
|
provider: options.provider,
|
|
189
192
|
model: options.model,
|
|
190
193
|
...(options.sessionId ? { sessionId: options.sessionId } : {}),
|
|
191
|
-
system:
|
|
194
|
+
system: systemPrompt,
|
|
192
195
|
messages,
|
|
193
196
|
tools: MEMORY_AGENT_TOOL_SCHEMAS,
|
|
194
197
|
requireTextOrToolCall: true,
|
package/lib/planner.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type { Agent } from "@deepseek-ai/dsh-agent";
|
|
|
11
11
|
import type { HarnessState, RefinementProposal, RefinementResult } from "./types.js";
|
|
12
12
|
import { type PrefixCacheOptions } from "./prefix-cache.js";
|
|
13
13
|
import { type TokenUsageTarget } from "./token-usage.js";
|
|
14
|
+
import { type RecordLanguage } from "./record-language.js";
|
|
14
15
|
export declare const PLANNER_SYSTEM_PROMPT = "You are the /evolve continual harness subsystem.\n\nYour job is to improve the editable continual harness state. Instead of\nsummarizing the conversation you emit precise Create, Update, or Delete edits\nto reusable state: prompt notes, memories, skills, and subagent specs.\n\nRules:\n- The base system prompt is immutable and MUST NOT be rewritten (never edit id \"base_system_prompt\").\n- Prefer small evidence-backed edits. If no useful edit is justified, return an empty edits array.\n- Repeated evidence is mandatory for prompt/skill/subagent proposals: the same\n pattern must recur at least twice in the trajectory. A single one-off\n interaction never justifies a prompt, skill, or subagent edit \u2014 return an\n empty edits array with a rationale instead. Session-scoped local prompt\n landings converge on /evolve wrapup human review; the turn pipeline must\n not silently persist one-off abstractions.\n- prompt = narrow behavioral policy addendums; memory = ONE FACT per entry, typed via metadata.memoryType\n- (user = who the user is; feedback = a pitfall/correction or confirmed approach, MUST carry Why + How to apply;\n- project = work goal/constraint not derivable from the repo; reference = URL/dashboard/ticket pointer).\n- Memory rules: one fact per entry (title = one-line retrieval hook, content = the fact alone);\n- never save what the repo already records (code structure, past fixes, git history, AGENTS.md) or what only\n- matters to this conversation \u2014 save the non-obvious point instead; before creating, update the existing\n- entry when one already covers the fact (creates carry metadata {memoryType} or they are rejected);\n- skill = repeatable procedures (must carry a python reference {type:\"python\", import, callable}\n and an arguments object); subagent = reusable delegation roles.\n- Skill entries are authored to the DSH skill quality standard\n (skill-creator, distilled from the official deepseek-harness 11 skills;\n the full facts are provided in the <skill_quality_standard> block below):\n only for a REAL trigger scenario grounded in the trajectory\n (who, in what real task, what signal) \u2014 never invent one to pad the store;\n never duplicate the official 11 skills or existing entries; content is a\n SKILL.md document (frontmatter routing with \"use when / do not use when\"\n description, boundary declaration, prerequisites and exclusions, layered\n information, verifiable completion criteria). Self-check every proposed\n skill against the 7 structural features and state the result in its\n reason field.\n- Repeated multi-step workflows (session start/end routines, recurring\n wrap-up or handoff procedures) may be proposed as guidance skills:\n kind=skill, skill_kind=\"guidance\", content = a SKILL.md document (no\n python reference \u2014 executable skills keep requiring reference +\n arguments). Only propose with repeated evidence in the trajectory, never\n for one-off flows. Guidance skills materialize as discoverable SKILL.md\n files under <skillsRoot>/<kebab-name>/SKILL.md and are always offered to\n the user for a decision before they land.\n- Local edits are session-scoped; global edits persist across sessions.\n- Gap C2: EVERY edit MUST include a \"blastRadius\" field indicating how broadly\n the edit applies: \"general\" (cross-project tactical rule, applies everywhere),\n \"project\" (valid for the current project/repo), or \"session\" (one-off,\n specific to this session's context). The review gate validates that\n local-scope edits use \"session\" or \"project\", and global-scope edits use\n \"general\" or \"project\". When in doubt, prefer narrower blast radius.\n- Ground every edit in evidence: the session trajectory (recent direct user\n messages) is provided when available; prefer edits backed by it over\n speculation, and never invent preferences the user did not express.\n- Stale entries (superseded by newer ones, never referenced in recent\n trajectories, obsolete facts): propose action \"archive\" instead of\n \"delete\" \u2014 archive hides the entry from injection while keeping its data\n restorable; it requires only kind + id.\n- Output JSON only, exactly this shape:\n{\n \"summary\": \"one sentence\",\n \"rationale\": \"why these edits are justified by the evidence\",\n \"expectedOutcome\": \"what should improve and how to validate it\",\n \"edits\": [\n {\n \"action\": \"create|update|delete\",\n \"kind\": \"prompt|memory|skill|subagent\",\n \"id\": \"stable id for update/delete, optional for create\",\n \"title\": \"required for create/update except delete\",\n \"content\": \"required for create/update except delete\",\n \"path\": \"optional grouping path\",\n \"reference\": {\"type\":\"python\",\"import\":\"pkg.mod\",\"callable\":\"fn\"} ,\n \"arguments\": {\"name\": {\"type\":\"string\",\"required\":true,\"description\":\"...\"}},\n \"skill_kind\": \"executable|guidance (optional; skill kind only \u2014 guidance = SKILL.md document without reference)\",\n \"metadata\": {},\n \"reason\": \"why this edit is useful\",\n \"blastRadius\": \"general|project|session\"\n }\n ]\n}";
|
|
15
16
|
export interface PlanOptions {
|
|
16
17
|
agent: Agent;
|
|
@@ -49,6 +50,12 @@ export interface PlanOptions {
|
|
|
49
50
|
prefixCache?: PrefixCacheOptions;
|
|
50
51
|
/** Direct-call token ledger destination; the planner phase is fixed by this module. */
|
|
51
52
|
tokenUsage?: TokenUsageTarget;
|
|
53
|
+
/**
|
|
54
|
+
* Authoring language for the proposal. Absent → resolved per call
|
|
55
|
+
* (explicit config upstream, else durable client preference, else
|
|
56
|
+
* trajectory detection, else `en`).
|
|
57
|
+
*/
|
|
58
|
+
language?: RecordLanguage;
|
|
52
59
|
}
|
|
53
60
|
export declare function planWithLlm(ctx: Context, options: PlanOptions): Promise<RefinementProposal>;
|
|
54
61
|
//# sourceMappingURL=planner.d.ts.map
|
package/lib/planner.js
CHANGED
|
@@ -5,6 +5,7 @@ import { buildPrefixMessages, detectPlannerRoute, resolvePrefixCache } from "./p
|
|
|
5
5
|
import { skillQualityGuide } from "./skillquality.js";
|
|
6
6
|
import { asLlmSessionId, streamText } from "./llm-text.js";
|
|
7
7
|
import { createTokenUsageObserver } from "./token-usage.js";
|
|
8
|
+
import { recordLanguageInstruction, resolveRecordLanguage } from "./record-language.js";
|
|
8
9
|
export const PLANNER_SYSTEM_PROMPT = `You are the /evolve continual harness subsystem.
|
|
9
10
|
|
|
10
11
|
Your job is to improve the editable continual harness state. Instead of
|
|
@@ -108,6 +109,7 @@ export async function planWithLlm(ctx, options) {
|
|
|
108
109
|
});
|
|
109
110
|
}
|
|
110
111
|
const trajectory = prefixMessages.length > 0 ? (options.trajectory ?? "") : (options.trajectory ?? recentUserText(agent));
|
|
112
|
+
const language = options.language ?? resolveRecordLanguage({ ctx, trajectoryText: trajectory });
|
|
111
113
|
// The skill quality standard is always present: the skill-creator
|
|
112
114
|
// template facts when installed, the builtin distilled guide otherwise
|
|
113
115
|
// (~1KB — planning is low-frequency, and the standard keeps skill
|
|
@@ -128,7 +130,7 @@ export async function planWithLlm(ctx, options) {
|
|
|
128
130
|
provider: agent.options.provider,
|
|
129
131
|
model: agent.options.model,
|
|
130
132
|
sessionId: asLlmSessionId(agent.id),
|
|
131
|
-
system: PLANNER_SYSTEM_PROMPT
|
|
133
|
+
system: `${PLANNER_SYSTEM_PROMPT}\n\n${recordLanguageInstruction(language)}`,
|
|
132
134
|
prompt: userPrompt,
|
|
133
135
|
maxTokens: options.maxOutputTokens ?? 8000,
|
|
134
136
|
signal: options.signal,
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Record language resolution for evolution writes.
|
|
3
|
+
*
|
|
4
|
+
* Every durable record (refinement summary/rationale, entry title/content,
|
|
5
|
+
* review and wrap-up verdicts) is generated prose. Its language is resolved
|
|
6
|
+
* per call through one chain — never hardcoded at any single site:
|
|
7
|
+
*
|
|
8
|
+
* 1. explicit `recordLanguage` plugin config (`zh`|`en`; `auto` defers);
|
|
9
|
+
* 2. the durable DSH client preference (`locale` namespace, `preference`
|
|
10
|
+
* field — the same source the desktop shell and the web GUI follow);
|
|
11
|
+
* 3. detection over recent direct user text (Han-character signal);
|
|
12
|
+
* 4. `en`, matching the DSH `FALLBACK_LOCALE`.
|
|
13
|
+
*
|
|
14
|
+
* The durable-preference read is intentionally fail-open: the settings
|
|
15
|
+
* service is host-owned and its shape may drift, so any mismatch yields
|
|
16
|
+
* `undefined` and the chain moves on instead of breaking the evolution run.
|
|
17
|
+
*/
|
|
18
|
+
/** Natural languages evolution records are authored in. */
|
|
19
|
+
export type RecordLanguage = "zh" | "en";
|
|
20
|
+
/** Plugin-facing preference: an explicit language or deferral to detection. */
|
|
21
|
+
export type RecordLanguagePreference = "auto" | RecordLanguage;
|
|
22
|
+
/**
|
|
23
|
+
* Upstream durable-settings address reused verbatim
|
|
24
|
+
* (`@deepseek-ai/dsh-client-locale`: `LOCALE_SETTINGS_NAMESPACE`,
|
|
25
|
+
* `LOCALE_PREFERENCE_FIELD`). New keys are never invented here.
|
|
26
|
+
*/
|
|
27
|
+
export declare const DSH_LOCALE_NAMESPACE = "locale";
|
|
28
|
+
export declare const DSH_LOCALE_PREFERENCE_FIELD = "preference";
|
|
29
|
+
/**
|
|
30
|
+
* Detect Chinese from user text by Han-character density.
|
|
31
|
+
*
|
|
32
|
+
* @param text Arbitrary user-supplied text.
|
|
33
|
+
* @returns `"zh"` on sufficient Han signal, otherwise `undefined` (no
|
|
34
|
+
* signal — never a default; the resolver owns the fallback).
|
|
35
|
+
*/
|
|
36
|
+
export declare function detectLanguageFromText(text: string): RecordLanguage | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Read the durable DSH client language preference through the host settings
|
|
39
|
+
* service. Fail-open by contract: a missing service, a throwing read, an
|
|
40
|
+
* async-shaped reply, or an unknown value all yield `undefined`.
|
|
41
|
+
*
|
|
42
|
+
* @param ctx Cordis context that may carry the host settings service.
|
|
43
|
+
* @returns The stored preference (`zh`/`en`, subtag-tolerant) or `undefined`.
|
|
44
|
+
*/
|
|
45
|
+
export declare function durableLocalePreference(ctx: unknown): string | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Normalize an explicit preference or durable value to a record language.
|
|
48
|
+
*
|
|
49
|
+
* @param value Candidate language id (config or durable preference).
|
|
50
|
+
* @returns `"zh"`/`"en"` on a recognized id, otherwise `undefined`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function normalizeRecordLanguage(value: string | undefined): RecordLanguage | undefined;
|
|
53
|
+
export interface ResolveRecordLanguageInput {
|
|
54
|
+
/** Explicit plugin config; absent/`auto` defers to the chain below. */
|
|
55
|
+
configured?: RecordLanguagePreference | undefined;
|
|
56
|
+
/** Host context for the durable-preference read; absent skips that tier. */
|
|
57
|
+
ctx?: unknown;
|
|
58
|
+
/** Recent direct user text for the detection tier; absent skips it. */
|
|
59
|
+
trajectoryText?: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Resolve the authoring language for one evolution write.
|
|
63
|
+
*
|
|
64
|
+
* @param input Configured preference, host context, and trajectory sample.
|
|
65
|
+
* @returns The winning language; always defined (`en` is the final fallback).
|
|
66
|
+
*/
|
|
67
|
+
export declare function resolveRecordLanguage(input: ResolveRecordLanguageInput): RecordLanguage;
|
|
68
|
+
/**
|
|
69
|
+
* The one-line prompt addendum appended to every record-authoring system
|
|
70
|
+
* prompt. Machine contracts (JSON keys, ids, paths, code) stay untouched —
|
|
71
|
+
* only natural-language values follow the resolved language.
|
|
72
|
+
*
|
|
73
|
+
* @param lang Resolved record language.
|
|
74
|
+
* @returns The instruction line for `lang`.
|
|
75
|
+
*/
|
|
76
|
+
export declare function recordLanguageInstruction(lang: RecordLanguage): string;
|
|
77
|
+
//# sourceMappingURL=record-language.d.ts.map
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Record language resolution for evolution writes.
|
|
3
|
+
*
|
|
4
|
+
* Every durable record (refinement summary/rationale, entry title/content,
|
|
5
|
+
* review and wrap-up verdicts) is generated prose. Its language is resolved
|
|
6
|
+
* per call through one chain — never hardcoded at any single site:
|
|
7
|
+
*
|
|
8
|
+
* 1. explicit `recordLanguage` plugin config (`zh`|`en`; `auto` defers);
|
|
9
|
+
* 2. the durable DSH client preference (`locale` namespace, `preference`
|
|
10
|
+
* field — the same source the desktop shell and the web GUI follow);
|
|
11
|
+
* 3. detection over recent direct user text (Han-character signal);
|
|
12
|
+
* 4. `en`, matching the DSH `FALLBACK_LOCALE`.
|
|
13
|
+
*
|
|
14
|
+
* The durable-preference read is intentionally fail-open: the settings
|
|
15
|
+
* service is host-owned and its shape may drift, so any mismatch yields
|
|
16
|
+
* `undefined` and the chain moves on instead of breaking the evolution run.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Upstream durable-settings address reused verbatim
|
|
20
|
+
* (`@deepseek-ai/dsh-client-locale`: `LOCALE_SETTINGS_NAMESPACE`,
|
|
21
|
+
* `LOCALE_PREFERENCE_FIELD`). New keys are never invented here.
|
|
22
|
+
*/
|
|
23
|
+
export const DSH_LOCALE_NAMESPACE = "locale";
|
|
24
|
+
export const DSH_LOCALE_PREFERENCE_FIELD = "preference";
|
|
25
|
+
/** Han ideographs — the detection signal for Chinese user text. */
|
|
26
|
+
const HAN_PATTERN = /[\u4e00-\u9fff]/g;
|
|
27
|
+
/** Minimum Han hits to count as signal (stray characters must not flip). */
|
|
28
|
+
const HAN_SIGNAL_MIN = 5;
|
|
29
|
+
/** Detection scans a bounded prefix; trajectories are long and the signal is dense. */
|
|
30
|
+
const DETECT_SCAN_CHARS = 4000;
|
|
31
|
+
/**
|
|
32
|
+
* Detect Chinese from user text by Han-character density.
|
|
33
|
+
*
|
|
34
|
+
* @param text Arbitrary user-supplied text.
|
|
35
|
+
* @returns `"zh"` on sufficient Han signal, otherwise `undefined` (no
|
|
36
|
+
* signal — never a default; the resolver owns the fallback).
|
|
37
|
+
*/
|
|
38
|
+
export function detectLanguageFromText(text) {
|
|
39
|
+
const hits = (text.slice(0, DETECT_SCAN_CHARS).match(HAN_PATTERN) ?? []).length;
|
|
40
|
+
return hits >= HAN_SIGNAL_MIN ? "zh" : undefined;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Read the durable DSH client language preference through the host settings
|
|
44
|
+
* service. Fail-open by contract: a missing service, a throwing read, an
|
|
45
|
+
* async-shaped reply, or an unknown value all yield `undefined`.
|
|
46
|
+
*
|
|
47
|
+
* @param ctx Cordis context that may carry the host settings service.
|
|
48
|
+
* @returns The stored preference (`zh`/`en`, subtag-tolerant) or `undefined`.
|
|
49
|
+
*/
|
|
50
|
+
export function durableLocalePreference(ctx) {
|
|
51
|
+
if (typeof ctx !== "object" || ctx === null)
|
|
52
|
+
return undefined;
|
|
53
|
+
const settings = ctx.settings;
|
|
54
|
+
if (typeof settings !== "object" || settings === null)
|
|
55
|
+
return undefined;
|
|
56
|
+
const describe = settings.describe;
|
|
57
|
+
if (typeof describe !== "function")
|
|
58
|
+
return undefined;
|
|
59
|
+
let rows;
|
|
60
|
+
try {
|
|
61
|
+
rows = describe({ redactSecrets: true });
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
if (!Array.isArray(rows))
|
|
67
|
+
return undefined;
|
|
68
|
+
for (const row of rows) {
|
|
69
|
+
if (typeof row !== "object" || row === null)
|
|
70
|
+
continue;
|
|
71
|
+
const record = row;
|
|
72
|
+
if (record["ns"] !== DSH_LOCALE_NAMESPACE)
|
|
73
|
+
continue;
|
|
74
|
+
const value = record["value"];
|
|
75
|
+
if (typeof value !== "object" || value === null)
|
|
76
|
+
return undefined;
|
|
77
|
+
const preference = value[DSH_LOCALE_PREFERENCE_FIELD];
|
|
78
|
+
if (typeof preference !== "string")
|
|
79
|
+
return undefined;
|
|
80
|
+
const id = preference.toLowerCase();
|
|
81
|
+
if (id === "zh" || id.startsWith("zh-"))
|
|
82
|
+
return "zh";
|
|
83
|
+
if (id === "en" || id.startsWith("en-"))
|
|
84
|
+
return "en";
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Normalize an explicit preference or durable value to a record language.
|
|
91
|
+
*
|
|
92
|
+
* @param value Candidate language id (config or durable preference).
|
|
93
|
+
* @returns `"zh"`/`"en"` on a recognized id, otherwise `undefined`.
|
|
94
|
+
*/
|
|
95
|
+
export function normalizeRecordLanguage(value) {
|
|
96
|
+
if (value === "zh" || value === "en")
|
|
97
|
+
return value;
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Resolve the authoring language for one evolution write.
|
|
102
|
+
*
|
|
103
|
+
* @param input Configured preference, host context, and trajectory sample.
|
|
104
|
+
* @returns The winning language; always defined (`en` is the final fallback).
|
|
105
|
+
*/
|
|
106
|
+
export function resolveRecordLanguage(input) {
|
|
107
|
+
const configured = normalizeRecordLanguage(input.configured === "auto" ? undefined : input.configured);
|
|
108
|
+
if (configured !== undefined)
|
|
109
|
+
return configured;
|
|
110
|
+
if (input.ctx !== undefined) {
|
|
111
|
+
const durable = normalizeRecordLanguage(durableLocalePreference(input.ctx));
|
|
112
|
+
if (durable !== undefined)
|
|
113
|
+
return durable;
|
|
114
|
+
}
|
|
115
|
+
if (input.trajectoryText !== undefined) {
|
|
116
|
+
const detected = detectLanguageFromText(input.trajectoryText);
|
|
117
|
+
if (detected !== undefined)
|
|
118
|
+
return detected;
|
|
119
|
+
}
|
|
120
|
+
return "en";
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* The one-line prompt addendum appended to every record-authoring system
|
|
124
|
+
* prompt. Machine contracts (JSON keys, ids, paths, code) stay untouched —
|
|
125
|
+
* only natural-language values follow the resolved language.
|
|
126
|
+
*
|
|
127
|
+
* @param lang Resolved record language.
|
|
128
|
+
* @returns The instruction line for `lang`.
|
|
129
|
+
*/
|
|
130
|
+
export function recordLanguageInstruction(lang) {
|
|
131
|
+
return lang === "zh"
|
|
132
|
+
? "记录语言:用简体中文写所有的自然语言值(title、content、summary、rationale、reason、expectedOutcome)。JSON 键名、id、path 与代码片段保持原样。"
|
|
133
|
+
: "Record language: write all natural-language values (title, content, summary, rationale, reason, expectedOutcome) in English. Keep JSON keys, ids, paths, and code snippets unchanged.";
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=record-language.js.map
|
package/lib/review.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type { Agent } from "@deepseek-ai/dsh-agent";
|
|
|
10
10
|
import type { HarnessState, RefinementResult } from "./types.js";
|
|
11
11
|
import { type PrefixCacheOptions } from "./prefix-cache.js";
|
|
12
12
|
import { type TokenUsageTarget } from "./token-usage.js";
|
|
13
|
+
import { type RecordLanguage } from "./record-language.js";
|
|
13
14
|
export interface AutoRefineReview {
|
|
14
15
|
shouldRefine: boolean;
|
|
15
16
|
rationale: string;
|
|
@@ -43,6 +44,12 @@ export interface ReviewOptions {
|
|
|
43
44
|
prefixCache?: PrefixCacheOptions;
|
|
44
45
|
/** Direct-call token ledger destination; the review phase is fixed by this module. */
|
|
45
46
|
tokenUsage?: TokenUsageTarget;
|
|
47
|
+
/**
|
|
48
|
+
* Authoring language for the rationale. Absent → resolved per call
|
|
49
|
+
* (explicit config upstream, else durable client preference, else
|
|
50
|
+
* trajectory detection, else `en`).
|
|
51
|
+
*/
|
|
52
|
+
language?: RecordLanguage;
|
|
46
53
|
}
|
|
47
54
|
export declare const AUTO_REVIEW_SYSTEM_PROMPT = "You are the automatic /evolve review gate.\n\nDecide whether this checkpoint should continue the non-memory /evolve path.\nA dedicated memory extraction agent has already processed the same snapshot\nwith a frozen memory manifest, so do NOT approve solely to create, update,\narchive, or delete a memory entry. Decline with a rationale that the memory\nphase owns that evidence. For the remaining kinds, approve ONLY when the\ntrajectory shows repeated evidence: the same failure, tactic, delegation\nrole, or behavioral policy recurs at least twice across turns. A single\none-off interaction defaults to decline no matter how useful it looks for\nfuture turns \u2014 one data point is never enough to generalize. When approving,\ncite the repeated spans in the instructions so the planner can ground its\nedits.\n\nThe current harness state below includes GLOBAL entries (scope=global) plus\nthis session's local entries (scope=local). When a topic is already covered\nby a global entry, do NOT approve a local duplicate of it \u2014 decline and say\nin the rationale that the topic is already covered globally.\n\nReject one-off noise, unsupported hypotheses, transient tool outputs, and\nrequests that carry no reusable content.\n\nStale local entries (superseded, long-unused, obsolete facts) are a valid\nrefine target: approve with instructions naming the entry ids, and tell the\nplanner to archive them (archive hides from injection, data stays restorable)\nrather than delete.\n\nSkill-related trajectories (the evidence concerns creating or improving a\nskill entry) are judged against the DSH skill quality standard (skill-audit\ndimensions: frontmatter routing, the 7 structural features, paragraph\nskeleton, no duplication of the official 11 skills or covered skills).\nApprove only when the trajectory shows a REAL trigger scenario and the\nresulting skill would meet the standard; otherwise decline and say in the\nrationale what must improve \u2014 drafting follows skill-creator, and the\nplanner receives the standard as its <skill_quality_standard> block.\n\nRepeated multi-step workflows (session start/end routines, recurring\nwrap-up or handoff procedures) are a valid refine target: approve with\ninstructions telling the planner to propose a guidance skill (kind=skill,\nskill_kind=guidance \u2014 a SKILL.md document, no python reference). Only\npropose when the same workflow recurs in the trajectory \u2014 never for\none-off flows. Auto-created skills are always offered to the user for a\ndecision before they land; the gate never writes a skill silently.\n\nReturn JSON only:\n{\n \"shouldRefine\": true|false,\n \"rationale\": \"short reason\",\n \"instructions\": \"optional concise instructions for /evolve if shouldRefine is true\"\n}";
|
|
48
55
|
/** Parse the gate's JSON reply. */
|
package/lib/review.js
CHANGED
|
@@ -4,6 +4,7 @@ import { sessionEventsOf } from "./inject.js";
|
|
|
4
4
|
import { buildPrefixMessages, detectPlannerRoute, resolvePrefixCache } from "./prefix-cache.js";
|
|
5
5
|
import { asLlmSessionId, streamText } from "./llm-text.js";
|
|
6
6
|
import { createTokenUsageObserver } from "./token-usage.js";
|
|
7
|
+
import { recordLanguageInstruction, resolveRecordLanguage } from "./record-language.js";
|
|
7
8
|
export const AUTO_REVIEW_SYSTEM_PROMPT = `You are the automatic /evolve review gate.
|
|
8
9
|
|
|
9
10
|
Decide whether this checkpoint should continue the non-memory /evolve path.
|
|
@@ -132,7 +133,7 @@ export async function reviewAutoRefine(ctx, options) {
|
|
|
132
133
|
provider,
|
|
133
134
|
model,
|
|
134
135
|
sessionId: asLlmSessionId(agent.id),
|
|
135
|
-
system: AUTO_REVIEW_SYSTEM_PROMPT,
|
|
136
|
+
system: `${AUTO_REVIEW_SYSTEM_PROMPT}\n\n${recordLanguageInstruction(options.language ?? resolveRecordLanguage({ ctx, trajectoryText: options.trajectory }))}`,
|
|
136
137
|
prompt: userPrompt,
|
|
137
138
|
maxTokens: options.budgetTokens ?? 8000,
|
|
138
139
|
signal: options.signal,
|
package/lib/wrapup-command.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { questionServiceOf, requireGlobalApproval } from "./approval.js";
|
|
2
|
+
import { resolveDialogLanguage, wrapupArchiveCopy } from "./copy.js";
|
|
2
3
|
import { assessLocalEntries, candidateKey, filterPromotable, listLocalCandidates, splitArchiveGuards, splitPromoteBlocked, splitPromoteProposals, valenceStampProposal, wholePromoteProposals } from "./wrapup.js";
|
|
3
4
|
import { DEFAULT_PROMOTION_POLICY } from "./promotion.js";
|
|
4
5
|
function success(text) {
|
|
@@ -186,21 +187,20 @@ export async function executeWrapupCommand(ctx, engine, invocation, policy = DEF
|
|
|
186
187
|
const questionId = "evolve-wrapup-archive-review";
|
|
187
188
|
let archiveConfirmed = false;
|
|
188
189
|
try {
|
|
190
|
+
const copy = wrapupArchiveCopy(candidate.title, resolveDialogLanguage(ctx));
|
|
189
191
|
const answer = await userQuestions.ask({
|
|
190
192
|
questions: [
|
|
191
193
|
{
|
|
192
194
|
id: questionId,
|
|
193
|
-
question:
|
|
194
|
-
options:
|
|
195
|
-
{ label: "归档", description: "隐藏但可恢复" },
|
|
196
|
-
{ label: "保留", description: "继续注入" },
|
|
197
|
-
],
|
|
195
|
+
question: copy.question,
|
|
196
|
+
options: copy.options,
|
|
198
197
|
},
|
|
199
198
|
],
|
|
200
199
|
agent: invocation.agent,
|
|
201
200
|
signal: invocation.signal,
|
|
202
201
|
});
|
|
203
|
-
|
|
202
|
+
const selected = answer.answers?.find((entry) => entry.id === questionId)?.selected ?? [];
|
|
203
|
+
archiveConfirmed = selected.includes("归档") || selected.includes("Archive");
|
|
204
204
|
}
|
|
205
205
|
catch {
|
|
206
206
|
archiveConfirmed = false;
|
package/lib/wrapup.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ import type { Context } from "@deepseek-ai/cordis";
|
|
|
22
22
|
import type { Agent } from "@deepseek-ai/dsh-agent";
|
|
23
23
|
import type { HarnessEntry, HarnessState, RefinementKind, RefinementProposal } from "./types.js";
|
|
24
24
|
import { type TokenUsageTarget } from "./token-usage.js";
|
|
25
|
+
import { type RecordLanguage } from "./record-language.js";
|
|
25
26
|
import { type PromotionPolicy } from "./promotion.js";
|
|
26
27
|
/** What should happen to one local entry at session end. */
|
|
27
28
|
export type WrapupVerdict = "promote" | "archive" | "keep";
|
|
@@ -243,6 +244,12 @@ export interface AssessOptions {
|
|
|
243
244
|
tokenUsage?: TokenUsageTarget;
|
|
244
245
|
/** Ledger phase for this classifier call (manual wrapup or automatic fate). */
|
|
245
246
|
tokenUsagePhase?: "wrapup" | "fate";
|
|
247
|
+
/**
|
|
248
|
+
* Authoring language for verdict reasons. Absent → resolved per call
|
|
249
|
+
* (durable client preference, else `en`; the assessor sees candidate
|
|
250
|
+
* snapshots, not user text, so there is no detection tier here).
|
|
251
|
+
*/
|
|
252
|
+
language?: RecordLanguage;
|
|
246
253
|
}
|
|
247
254
|
/**
|
|
248
255
|
* Ask the model to classify the audited local candidates. Routes through the
|
package/lib/wrapup.js
CHANGED
|
@@ -5,6 +5,7 @@ import { compactText } from "./render.js";
|
|
|
5
5
|
import { asLlmSessionId, streamText } from "./llm-text.js";
|
|
6
6
|
import { getUsageCount, loadUsage } from "./usage.js";
|
|
7
7
|
import { createTokenUsageObserver } from "./token-usage.js";
|
|
8
|
+
import { recordLanguageInstruction, resolveRecordLanguage } from "./record-language.js";
|
|
8
9
|
import { recencyScore } from "./inject.js";
|
|
9
10
|
import { DEFAULT_PROMOTION_POLICY, mostSimilarGlobalEntry, projectScopedReason, secretLeakReason } from "./promotion.js";
|
|
10
11
|
export function candidateKey(kind, id) {
|
|
@@ -523,7 +524,7 @@ export async function assessLocalEntries(ctx, agent, candidates, options = {}) {
|
|
|
523
524
|
provider: agent.options.provider,
|
|
524
525
|
model: agent.options.model,
|
|
525
526
|
sessionId: asLlmSessionId(agent.id),
|
|
526
|
-
system: WRAPUP_ASSESS_SYSTEM_PROMPT
|
|
527
|
+
system: `${WRAPUP_ASSESS_SYSTEM_PROMPT}\n\n${recordLanguageInstruction(options.language ?? resolveRecordLanguage({ ctx }))}`,
|
|
527
528
|
prompt: userPrompt,
|
|
528
529
|
maxTokens: options.maxOutputTokens ?? 4096,
|
|
529
530
|
signal: options.signal,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-continual-evolve",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.3",
|
|
4
4
|
"description": "Continual self-evolution plugin for DeepSeek Harness: versioned, auditable, rollback-safe harness state (prompt notes, memories, skills, subagent specs) refined from session trajectories.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -60,19 +60,19 @@
|
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
62
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
63
|
-
"@deepseek-ai/dsh-home-paths": "^0.1.0-rc.6 || ^0.1.7-rc.1",
|
|
64
|
-
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.1.7-rc.1",
|
|
65
|
-
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.1.7-rc.1",
|
|
63
|
+
"@deepseek-ai/dsh-home-paths": "^0.1.0-rc.6 || ^0.1.7-rc.1 || ^0.2.0-rc.1",
|
|
64
|
+
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.1.7-rc.1 || ^0.2.0-rc.1",
|
|
65
|
+
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.1.7-rc.1 || ^0.2.0-rc.1",
|
|
66
66
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@deepseek-ai/cordis": "4.0.4",
|
|
70
|
-
"@deepseek-ai/dsh-agent": "0.
|
|
71
|
-
"@deepseek-ai/dsh-commands": "0.
|
|
72
|
-
"@deepseek-ai/dsh-home-paths": "0.
|
|
73
|
-
"@deepseek-ai/dsh-llm": "0.
|
|
74
|
-
"@deepseek-ai/dsh-tools": "0.
|
|
75
|
-
"@deepseek-ai/schemastery": "3.18.
|
|
70
|
+
"@deepseek-ai/dsh-agent": "0.2.0-rc.2",
|
|
71
|
+
"@deepseek-ai/dsh-commands": "0.2.0-rc.2",
|
|
72
|
+
"@deepseek-ai/dsh-home-paths": "0.2.0-rc.2",
|
|
73
|
+
"@deepseek-ai/dsh-llm": "0.2.0-rc.2",
|
|
74
|
+
"@deepseek-ai/dsh-tools": "0.2.0-rc.2",
|
|
75
|
+
"@deepseek-ai/schemastery": "3.18.4",
|
|
76
76
|
"@types/node": "^22.10.0",
|
|
77
77
|
"@vitest/coverage-v8": "^3.2.0",
|
|
78
78
|
"oxlint": "^0.16.0",
|