dsh-continual-evolve 0.10.4 → 0.10.6
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 +14 -12
- package/README.zh.md +14 -12
- package/lib/approval.d.ts +17 -5
- package/lib/approval.js +73 -12
- package/lib/auto.js +1 -0
- package/lib/command.d.ts +3 -0
- package/lib/command.js +20 -148
- package/lib/index.d.ts +36 -0
- package/lib/index.js +48 -2
- package/lib/inject.d.ts +14 -0
- package/lib/inject.js +21 -6
- package/lib/memory-agent.d.ts +3 -1
- package/lib/memory-agent.js +1 -1
- package/lib/memory-guide.d.ts +19 -0
- package/lib/memory-guide.js +73 -0
- package/lib/memory-index.d.ts +114 -0
- package/lib/memory-index.js +197 -0
- package/lib/tool.d.ts +7 -0
- package/lib/tool.js +30 -30
- package/lib/wrapup-command.d.ts +2 -1
- package/lib/wrapup-command.js +7 -4
- package/package.json +2 -1
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* evolve v2 — the passive read path: the `evolve:memory-index` system-prompt
|
|
3
|
+
* section that puts memory CONTENT in front of the model at session start.
|
|
4
|
+
*
|
|
5
|
+
* Why not just hand out a path: the memory store lives in `<dshHome>/evolve/`,
|
|
6
|
+
* outside the workspace sandbox, so the model's read tool cannot open it.
|
|
7
|
+
* The section therefore carries the entries themselves — full body while the
|
|
8
|
+
* character budget allows, then one-line index rows, then a `evolve_recall`
|
|
9
|
+
* pointer for the rest. Budget is hard: a bounded prompt beats an unbounded one.
|
|
10
|
+
*
|
|
11
|
+
* Session-freeze contract (the prompt-cache guard): the section is computed
|
|
12
|
+
* once per session and reused byte-for-byte afterwards, so the system prompt
|
|
13
|
+
* stays stable within a session and cache reads keep hitting. A memory written
|
|
14
|
+
* mid-session becomes visible in the NEXT session; `evolve_recall` covers the
|
|
15
|
+
* immediate need. See `.agents/notes/proposed/feature/2026-10-01-evolve-v2-passive-read-write.md`.
|
|
16
|
+
*/
|
|
17
|
+
import type { HarnessEntry } from "./types.js";
|
|
18
|
+
import { type MemoryRecallType } from "./types.js";
|
|
19
|
+
import type { EvolutionEngine } from "./service.js";
|
|
20
|
+
import { type AgentLike } from "./inject.js";
|
|
21
|
+
/** Hard character budget for the `<memories>` block. */
|
|
22
|
+
export declare const DEFAULT_MEMORY_INDEX_MAX_CHARS = 6000;
|
|
23
|
+
/** Prompt-section order of the memory index (empty 1–499 slot, see ADR). */
|
|
24
|
+
export declare const DEFAULT_MEMORY_SECTION_ORDER = 400;
|
|
25
|
+
/** Sessions whose frozen section text is kept before LRU eviction. */
|
|
26
|
+
export declare const DEFAULT_FROZEN_SESSIONS = 32;
|
|
27
|
+
/** Name of the injected section (unique — duplicate names throw upstream). */
|
|
28
|
+
export declare const MEMORY_SECTION_NAME = "evolve:memory-index";
|
|
29
|
+
/**
|
|
30
|
+
* Recall-type priority for the budget: durable, behavior-shaping types first.
|
|
31
|
+
* `project` (current work context) > `feedback` (how to behave) > `user` (who
|
|
32
|
+
* the user is) > `reference` (external pointers). Entries without a valid
|
|
33
|
+
* memoryType sink below every typed entry.
|
|
34
|
+
*/
|
|
35
|
+
export declare const MEMORY_TYPE_PRIORITY: Record<MemoryRecallType, number>;
|
|
36
|
+
/** The entry's recall type, or undefined when the metadata key is absent/invalid. */
|
|
37
|
+
export declare function memoryTypeOf(entry: HarnessEntry): MemoryRecallType | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Order memories for injection: recall-type priority first
|
|
40
|
+
* ({@link MEMORY_TYPE_PRIORITY}), then recency (newest first), then the stable
|
|
41
|
+
* dictionary order — deterministic for equal inputs, so a frozen section is
|
|
42
|
+
* reproducible across builds. The input is never mutated.
|
|
43
|
+
*/
|
|
44
|
+
export declare function rankMemories(entries: readonly HarnessEntry[], now?: number): HarnessEntry[];
|
|
45
|
+
/** One memory rendered in full, as a list item with the body indented. */
|
|
46
|
+
export declare function memoryFullLine(entry: HarnessEntry): string;
|
|
47
|
+
/** What a budget-bounded memory block shows. */
|
|
48
|
+
export interface MemorySelection {
|
|
49
|
+
/** Entries rendered with their full body, in display order. */
|
|
50
|
+
full: HarnessEntry[];
|
|
51
|
+
/** Entries degraded to a one-line index row (`id` + title hook). */
|
|
52
|
+
indexed: HarnessEntry[];
|
|
53
|
+
/** Entries that fit neither and are only counted. */
|
|
54
|
+
dropped: number;
|
|
55
|
+
/** Entries actually shown (full + indexed), in display order. */
|
|
56
|
+
shown: HarnessEntry[];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Greedy budget fill over {@link rankMemories}: each entry takes the full body
|
|
60
|
+
* while it fits, otherwise degrades to an index row, otherwise is dropped —
|
|
61
|
+
* and because the ranking is fixed, the first entry that fits neither means
|
|
62
|
+
* every later one is dropped too (their rows are no smaller).
|
|
63
|
+
*/
|
|
64
|
+
export declare function selectMemoryInjection(entries: readonly HarnessEntry[], maxChars?: number): MemorySelection;
|
|
65
|
+
/** The `evolve_recall` pointer appended when rows had to be degraded or dropped. */
|
|
66
|
+
export declare const MEMORY_OVERFLOW_HINT = "- \u66F4\u591A\u8BB0\u5FC6\u53EF\u7528 evolve_recall \u6309\u9700\u8BFB\u53D6";
|
|
67
|
+
/**
|
|
68
|
+
* Render a selection as the `<memories>` block, or "" when nothing is shown.
|
|
69
|
+
* Full bodies come first in display order, then the degraded index rows, then
|
|
70
|
+
* the `evolve_recall` pointer with the degradation/drop counts.
|
|
71
|
+
*/
|
|
72
|
+
export declare function renderMemorySelection(selection: MemorySelection): string;
|
|
73
|
+
/** {@link renderMemorySelection} over a fresh selection (convenience). */
|
|
74
|
+
export declare function formatMemoriesBlock(entries: readonly HarnessEntry[], maxChars?: number): string;
|
|
75
|
+
/** Options for {@link memoryIndexSectionText}. */
|
|
76
|
+
export interface MemoryIndexOptions {
|
|
77
|
+
/** Hard budget for the memories block (default 6000). */
|
|
78
|
+
maxChars?: number;
|
|
79
|
+
/** Include the when_to_save guide (default true). */
|
|
80
|
+
guide?: boolean;
|
|
81
|
+
/** Pin the project layer explicitly (tests/tools); otherwise derived from cwd. */
|
|
82
|
+
projectKey?: string;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Compose the full `evolve:memory-index` section for one assembling agent:
|
|
86
|
+
* the persistent-memory guide (when to save / how to maintain) wrapped around
|
|
87
|
+
* the budget-bounded memories block. With the guide off and no memories the
|
|
88
|
+
* result is "" — the prompt renderer then drops the section, so an empty store
|
|
89
|
+
* keeps costing zero tokens.
|
|
90
|
+
*
|
|
91
|
+
* Injection usage is recorded for everything shown (best-effort; a failure
|
|
92
|
+
* never blocks injection), deduped per session by {@link recordInjection}.
|
|
93
|
+
*/
|
|
94
|
+
export declare function memoryIndexSectionText(engine: EvolutionEngine, agent: AgentLike | undefined, opts?: MemoryIndexOptions): string;
|
|
95
|
+
/** A per-session frozen view of the memory section text. */
|
|
96
|
+
export interface FrozenMemorySection {
|
|
97
|
+
/**
|
|
98
|
+
* The section text for this agent: built on first use per `agent.id` and
|
|
99
|
+
* reused byte-for-byte afterwards. `undefined` agent → "" (unbuilt).
|
|
100
|
+
*/
|
|
101
|
+
textFor(agent: AgentLike | undefined, build: (agent: AgentLike) => string): string;
|
|
102
|
+
/** Sessions currently frozen (test/observability hook). */
|
|
103
|
+
size(): number;
|
|
104
|
+
/** Drop every frozen session (tests; a restart rebuilds naturally). */
|
|
105
|
+
clear(): void;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Freeze the section text per session id. The frozen value is what keeps the
|
|
109
|
+
* system prompt stable across a session's assemblies (prompt-cache guard);
|
|
110
|
+
* bounded by `maxSessions` with insertion-order (LRU) eviction, so a long-lived
|
|
111
|
+
* process cannot grow without limit.
|
|
112
|
+
*/
|
|
113
|
+
export declare function createFrozenMemorySection(maxSessions?: number): FrozenMemorySection;
|
|
114
|
+
//# sourceMappingURL=memory-index.d.ts.map
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { MEMORY_TYPE_KEY, isArchived, isMemoryType } from "./types.js";
|
|
2
|
+
import { recordInjection } from "./usage.js";
|
|
3
|
+
import { MEMORY_GUIDE_INTRO, MEMORY_GUIDE_RULES } from "./memory-guide.js";
|
|
4
|
+
import { directoryLine, mergedInjectionState, recencyScore } from "./inject.js";
|
|
5
|
+
/** Hard character budget for the `<memories>` block. */
|
|
6
|
+
export const DEFAULT_MEMORY_INDEX_MAX_CHARS = 6000;
|
|
7
|
+
/** Prompt-section order of the memory index (empty 1–499 slot, see ADR). */
|
|
8
|
+
export const DEFAULT_MEMORY_SECTION_ORDER = 400;
|
|
9
|
+
/** Sessions whose frozen section text is kept before LRU eviction. */
|
|
10
|
+
export const DEFAULT_FROZEN_SESSIONS = 32;
|
|
11
|
+
/** Name of the injected section (unique — duplicate names throw upstream). */
|
|
12
|
+
export const MEMORY_SECTION_NAME = "evolve:memory-index";
|
|
13
|
+
/**
|
|
14
|
+
* Recall-type priority for the budget: durable, behavior-shaping types first.
|
|
15
|
+
* `project` (current work context) > `feedback` (how to behave) > `user` (who
|
|
16
|
+
* the user is) > `reference` (external pointers). Entries without a valid
|
|
17
|
+
* memoryType sink below every typed entry.
|
|
18
|
+
*/
|
|
19
|
+
export const MEMORY_TYPE_PRIORITY = {
|
|
20
|
+
project: 0,
|
|
21
|
+
feedback: 1,
|
|
22
|
+
user: 2,
|
|
23
|
+
reference: 3,
|
|
24
|
+
};
|
|
25
|
+
/** Priority of an entry whose metadata carries no valid recall type. */
|
|
26
|
+
const UNTYPED_PRIORITY = 4;
|
|
27
|
+
/** The entry's recall type, or undefined when the metadata key is absent/invalid. */
|
|
28
|
+
export function memoryTypeOf(entry) {
|
|
29
|
+
const value = entry.metadata[MEMORY_TYPE_KEY];
|
|
30
|
+
return isMemoryType(value) ? value : undefined;
|
|
31
|
+
}
|
|
32
|
+
/** Deterministic final tiebreak (matches `inject.ts` ranking). */
|
|
33
|
+
function stableCompare(a, b) {
|
|
34
|
+
return [a.path, a.title, a.id].join("\0").localeCompare([b.path, b.title, b.id].join("\0"));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Order memories for injection: recall-type priority first
|
|
38
|
+
* ({@link MEMORY_TYPE_PRIORITY}), then recency (newest first), then the stable
|
|
39
|
+
* dictionary order — deterministic for equal inputs, so a frozen section is
|
|
40
|
+
* reproducible across builds. The input is never mutated.
|
|
41
|
+
*/
|
|
42
|
+
export function rankMemories(entries, now = Date.now()) {
|
|
43
|
+
return [...entries].sort((a, b) => {
|
|
44
|
+
const aType = memoryTypeOf(a);
|
|
45
|
+
const bType = memoryTypeOf(b);
|
|
46
|
+
const priorityDelta = (aType ? MEMORY_TYPE_PRIORITY[aType] : UNTYPED_PRIORITY) - (bType ? MEMORY_TYPE_PRIORITY[bType] : UNTYPED_PRIORITY);
|
|
47
|
+
if (priorityDelta !== 0) {
|
|
48
|
+
return priorityDelta;
|
|
49
|
+
}
|
|
50
|
+
const recencyDelta = recencyScore(b, now) - recencyScore(a, now);
|
|
51
|
+
if (recencyDelta !== 0) {
|
|
52
|
+
return recencyDelta;
|
|
53
|
+
}
|
|
54
|
+
return stableCompare(a, b);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/** One memory rendered in full, as a list item with the body indented. */
|
|
58
|
+
export function memoryFullLine(entry) {
|
|
59
|
+
const type = memoryTypeOf(entry);
|
|
60
|
+
const body = entry.content.trim().replace(/\n/g, "\n ");
|
|
61
|
+
const head = `- [memory${type ? `:${type}` : ""}:${entry.id}] ${entry.title}`;
|
|
62
|
+
return body.length > 0 ? `${head}\n ${body}` : head;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Greedy budget fill over {@link rankMemories}: each entry takes the full body
|
|
66
|
+
* while it fits, otherwise degrades to an index row, otherwise is dropped —
|
|
67
|
+
* and because the ranking is fixed, the first entry that fits neither means
|
|
68
|
+
* every later one is dropped too (their rows are no smaller).
|
|
69
|
+
*/
|
|
70
|
+
export function selectMemoryInjection(entries, maxChars = DEFAULT_MEMORY_INDEX_MAX_CHARS) {
|
|
71
|
+
const visible = entries.filter((entry) => !isArchived(entry));
|
|
72
|
+
const ranked = rankMemories(visible);
|
|
73
|
+
const budget = Math.max(0, maxChars);
|
|
74
|
+
const full = [];
|
|
75
|
+
const indexed = [];
|
|
76
|
+
const shown = [];
|
|
77
|
+
let used = 0;
|
|
78
|
+
let dropped = 0;
|
|
79
|
+
for (const entry of ranked) {
|
|
80
|
+
const fullLine = memoryFullLine(entry);
|
|
81
|
+
if (used + fullLine.length <= budget) {
|
|
82
|
+
full.push(entry);
|
|
83
|
+
shown.push(entry);
|
|
84
|
+
used += fullLine.length;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const indexLine = directoryLine(entry);
|
|
88
|
+
if (used + indexLine.length <= budget) {
|
|
89
|
+
indexed.push(entry);
|
|
90
|
+
shown.push(entry);
|
|
91
|
+
used += indexLine.length;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
dropped = ranked.length - shown.length;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
return { full, indexed, dropped, shown };
|
|
98
|
+
}
|
|
99
|
+
/** The `evolve_recall` pointer appended when rows had to be degraded or dropped. */
|
|
100
|
+
export const MEMORY_OVERFLOW_HINT = "- 更多记忆可用 evolve_recall 按需读取";
|
|
101
|
+
/**
|
|
102
|
+
* Render a selection as the `<memories>` block, or "" when nothing is shown.
|
|
103
|
+
* Full bodies come first in display order, then the degraded index rows, then
|
|
104
|
+
* the `evolve_recall` pointer with the degradation/drop counts.
|
|
105
|
+
*/
|
|
106
|
+
export function renderMemorySelection(selection) {
|
|
107
|
+
const { full, indexed, dropped, shown } = selection;
|
|
108
|
+
if (shown.length === 0) {
|
|
109
|
+
return "";
|
|
110
|
+
}
|
|
111
|
+
const lines = [];
|
|
112
|
+
for (const entry of full) {
|
|
113
|
+
lines.push(memoryFullLine(entry));
|
|
114
|
+
}
|
|
115
|
+
for (const entry of indexed) {
|
|
116
|
+
lines.push(directoryLine(entry));
|
|
117
|
+
}
|
|
118
|
+
const omitted = [];
|
|
119
|
+
if (indexed.length > 0) {
|
|
120
|
+
omitted.push(`${indexed.length} 条仅列索引`);
|
|
121
|
+
}
|
|
122
|
+
if (dropped > 0) {
|
|
123
|
+
omitted.push(`${dropped} 条未列出`);
|
|
124
|
+
}
|
|
125
|
+
lines.push(omitted.length > 0 ? `${MEMORY_OVERFLOW_HINT}(${omitted.join(",")})` : MEMORY_OVERFLOW_HINT);
|
|
126
|
+
return `<memories>\n${lines.join("\n")}\n</memories>`;
|
|
127
|
+
}
|
|
128
|
+
/** {@link renderMemorySelection} over a fresh selection (convenience). */
|
|
129
|
+
export function formatMemoriesBlock(entries, maxChars = DEFAULT_MEMORY_INDEX_MAX_CHARS) {
|
|
130
|
+
return renderMemorySelection(selectMemoryInjection(entries, maxChars));
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Compose the full `evolve:memory-index` section for one assembling agent:
|
|
134
|
+
* the persistent-memory guide (when to save / how to maintain) wrapped around
|
|
135
|
+
* the budget-bounded memories block. With the guide off and no memories the
|
|
136
|
+
* result is "" — the prompt renderer then drops the section, so an empty store
|
|
137
|
+
* keeps costing zero tokens.
|
|
138
|
+
*
|
|
139
|
+
* Injection usage is recorded for everything shown (best-effort; a failure
|
|
140
|
+
* never blocks injection), deduped per session by {@link recordInjection}.
|
|
141
|
+
*/
|
|
142
|
+
export function memoryIndexSectionText(engine, agent, opts) {
|
|
143
|
+
if (!agent) {
|
|
144
|
+
return "";
|
|
145
|
+
}
|
|
146
|
+
const guide = opts?.guide ?? true;
|
|
147
|
+
const maxChars = opts?.maxChars ?? DEFAULT_MEMORY_INDEX_MAX_CHARS;
|
|
148
|
+
const merged = mergedInjectionState(engine, agent, opts);
|
|
149
|
+
const selection = selectMemoryInjection(Object.values(merged.entries.memory), maxChars);
|
|
150
|
+
const block = renderMemorySelection(selection);
|
|
151
|
+
if (selection.shown.length > 0) {
|
|
152
|
+
try {
|
|
153
|
+
recordInjection(engine.baseDir, selection.shown.map((entry) => `${entry.kind}:${entry.id}`), agent.id);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// Usage recording is diagnostic; never interrupt the injection path.
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (!guide) {
|
|
160
|
+
return block;
|
|
161
|
+
}
|
|
162
|
+
return [MEMORY_GUIDE_INTRO, block.length > 0 ? block : "<memories>\n(暂无记忆)\n</memories>", MEMORY_GUIDE_RULES].join("\n\n");
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Freeze the section text per session id. The frozen value is what keeps the
|
|
166
|
+
* system prompt stable across a session's assemblies (prompt-cache guard);
|
|
167
|
+
* bounded by `maxSessions` with insertion-order (LRU) eviction, so a long-lived
|
|
168
|
+
* process cannot grow without limit.
|
|
169
|
+
*/
|
|
170
|
+
export function createFrozenMemorySection(maxSessions = DEFAULT_FROZEN_SESSIONS) {
|
|
171
|
+
const limit = Math.max(1, maxSessions);
|
|
172
|
+
const frozen = new Map();
|
|
173
|
+
return {
|
|
174
|
+
textFor(agent, build) {
|
|
175
|
+
if (!agent) {
|
|
176
|
+
return "";
|
|
177
|
+
}
|
|
178
|
+
const cached = frozen.get(agent.id);
|
|
179
|
+
if (cached !== undefined) {
|
|
180
|
+
return cached;
|
|
181
|
+
}
|
|
182
|
+
const text = build(agent);
|
|
183
|
+
frozen.set(agent.id, text);
|
|
184
|
+
while (frozen.size > limit) {
|
|
185
|
+
const oldest = frozen.keys().next();
|
|
186
|
+
if (oldest.done === true) {
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
frozen.delete(oldest.value);
|
|
190
|
+
}
|
|
191
|
+
return text;
|
|
192
|
+
},
|
|
193
|
+
size: () => frozen.size,
|
|
194
|
+
clear: () => frozen.clear(),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=memory-index.js.map
|
package/lib/tool.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* the engine. `global: true` is required explicitly for cross-session edits.
|
|
5
5
|
*/
|
|
6
6
|
import type { Context } from "@deepseek-ai/cordis";
|
|
7
|
+
import type { RecordLanguagePreference } from "./record-language.js";
|
|
7
8
|
import type { HarnessScope } from "./types.js";
|
|
8
9
|
import type { EvolutionEngine } from "./service.js";
|
|
9
10
|
/**
|
|
@@ -22,6 +23,12 @@ export declare function scopeOf(value: unknown, fallback: HarnessScope): Harness
|
|
|
22
23
|
export declare function collectDeleteIds(id: unknown, ids: unknown): string[];
|
|
23
24
|
export interface ToolGateOptions {
|
|
24
25
|
requireGlobalApproval: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Plugin `recordLanguage` preference for the approval dialog copy;
|
|
28
|
+
* `auto`/absent defers to the durable client preference and the session's
|
|
29
|
+
* own user text (see `approval.ts`).
|
|
30
|
+
*/
|
|
31
|
+
recordLanguage?: RecordLanguagePreference;
|
|
25
32
|
}
|
|
26
33
|
export declare function registerEvolveTools(ctx: Context, engine: EvolutionEngine, opts: ToolGateOptions): void;
|
|
27
34
|
//# sourceMappingURL=tool.d.ts.map
|
package/lib/tool.js
CHANGED
|
@@ -83,12 +83,12 @@ function textResult(text) {
|
|
|
83
83
|
export function registerEvolveTools(ctx, engine, opts) {
|
|
84
84
|
ctx.tools.register(defineTool({
|
|
85
85
|
name: "evolve_list",
|
|
86
|
-
description: "List the
|
|
86
|
+
description: "List the harness state (prompt notes, memories, skills, subagent specs) for one store.",
|
|
87
87
|
parameters: {
|
|
88
88
|
scope: {
|
|
89
89
|
type: "string",
|
|
90
90
|
enum: SCOPES,
|
|
91
|
-
description: "
|
|
91
|
+
description: "'local' (default), 'project', or 'global'.",
|
|
92
92
|
},
|
|
93
93
|
},
|
|
94
94
|
output: {
|
|
@@ -118,25 +118,25 @@ export function registerEvolveTools(ctx, engine, opts) {
|
|
|
118
118
|
}));
|
|
119
119
|
ctx.tools.register(defineTool({
|
|
120
120
|
name: "evolve_recall",
|
|
121
|
-
description: "
|
|
121
|
+
description: "Read-only targeted recall across the harness stores: filter by query, kind, scope, and memory type; returns full content with version, source, and staleness signals. Prefer this over evolve_list when looking for something specific.",
|
|
122
122
|
parameters: {
|
|
123
|
-
query: { type: "string", description: "Free-text relevance query. Empty
|
|
123
|
+
query: { type: "string", description: "Free-text relevance query. Empty → most-recently-updated first." },
|
|
124
124
|
kinds: {
|
|
125
125
|
type: "array",
|
|
126
126
|
items: { type: "string" },
|
|
127
|
-
description: "Entry kinds
|
|
127
|
+
description: "Entry kinds (default memory only).",
|
|
128
128
|
},
|
|
129
129
|
scopes: {
|
|
130
130
|
type: "array",
|
|
131
131
|
items: { type: "string" },
|
|
132
|
-
description: "Stores to search (default
|
|
132
|
+
description: "Stores to search (default all three).",
|
|
133
133
|
},
|
|
134
134
|
memoryTypes: {
|
|
135
135
|
type: "array",
|
|
136
136
|
items: { type: "string" },
|
|
137
|
-
description: "Memory
|
|
137
|
+
description: "Memory types: user, feedback, project, reference.",
|
|
138
138
|
},
|
|
139
|
-
limit: { type: "number", description: "
|
|
139
|
+
limit: { type: "number", description: "Max hits (default 10, max 50)." },
|
|
140
140
|
includeArchived: { type: "boolean", description: "Include archived entries (default false)." },
|
|
141
141
|
},
|
|
142
142
|
output: {
|
|
@@ -157,18 +157,18 @@ export function registerEvolveTools(ctx, engine, opts) {
|
|
|
157
157
|
}));
|
|
158
158
|
ctx.tools.register(defineTool({
|
|
159
159
|
name: "evolve_add",
|
|
160
|
-
description: "Create one harness entry (prompt/memory/skill/subagent).
|
|
160
|
+
description: "Create one harness entry (prompt/memory/skill/subagent). Snapshots, versioning, and audit are automatic.",
|
|
161
161
|
parameters: {
|
|
162
162
|
kind: { type: "string", enum: ["prompt", "memory", "skill", "subagent"], required: true, description: "Entry kind." },
|
|
163
163
|
title: { type: "string", required: true, description: "Stable title." },
|
|
164
164
|
content: { type: "string", required: true, description: "Entry body." },
|
|
165
165
|
path: { type: "string", description: "Optional grouping path." },
|
|
166
|
-
skill_kind: { type: "string", enum: ["executable", "guidance"], description: "
|
|
167
|
-
reference: { type: "object", additionalProperties: true, description: "
|
|
168
|
-
arguments: { type: "object", additionalProperties: true, description: "
|
|
169
|
-
memoryType: { type: "string", enum: ["user", "feedback", "project", "reference"], description: "Required for memory
|
|
170
|
-
scope: { type: "string", enum: SCOPES, description: "
|
|
171
|
-
global: { type: "boolean", description: "
|
|
166
|
+
skill_kind: { type: "string", enum: ["executable", "guidance"], description: "Skills: executable (default, needs reference) or guidance (SKILL.md, no reference)." },
|
|
167
|
+
reference: { type: "object", additionalProperties: true, description: "Executable skills: {type:'python', import, callable}." },
|
|
168
|
+
arguments: { type: "object", additionalProperties: true, description: "Executable skills: accepted input contract." },
|
|
169
|
+
memoryType: { type: "string", enum: ["user", "feedback", "project", "reference"], description: "Required for memory (feedback/project need Why + How to apply)." },
|
|
170
|
+
scope: { type: "string", enum: SCOPES, description: "'local' (default), 'project', or 'global'." },
|
|
171
|
+
global: { type: "boolean", description: "Shorthand for scope='global' (requires human approval)." },
|
|
172
172
|
},
|
|
173
173
|
output: {
|
|
174
174
|
schema: { type: "object", additionalProperties: false, properties: { text: { type: "string", required: true } } },
|
|
@@ -186,7 +186,7 @@ export function registerEvolveTools(ctx, engine, opts) {
|
|
|
186
186
|
const targetState = engine.load(scope, storeId);
|
|
187
187
|
const hit = mostSimilarEntry(Object.values(targetState.entries[args.kind]), args.title ?? "", args.content ?? "", CONFLICT_WARN_SCORE);
|
|
188
188
|
const conflictNote = hit ? ` ⚠️ ${buildConflictNotice(hit)}——建议改用 evolve_update` : "";
|
|
189
|
-
await requireGlobalApproval(ctx, exec.agent, exec.signal, `evolve_add ${args.kind} "${args.title}" → ${storeLabel(scope)}${conflictNote}
|
|
189
|
+
await requireGlobalApproval(ctx, exec.agent, exec.signal, `evolve_add ${args.kind} "${args.title}" → ${storeLabel(scope)}${conflictNote}`, opts.recordLanguage);
|
|
190
190
|
}
|
|
191
191
|
const edit = {
|
|
192
192
|
action: "create",
|
|
@@ -216,9 +216,9 @@ export function registerEvolveTools(ctx, engine, opts) {
|
|
|
216
216
|
id: { type: "string", required: true, description: "Existing entry id." },
|
|
217
217
|
title: { type: "string" },
|
|
218
218
|
content: { type: "string" },
|
|
219
|
-
memoryType: { type: "string", enum: ["user", "feedback", "project", "reference"], description: "
|
|
220
|
-
scope: { type: "string", enum: SCOPES, description: "
|
|
221
|
-
global: { type: "boolean", description: "
|
|
219
|
+
memoryType: { type: "string", enum: ["user", "feedback", "project", "reference"], description: "Memory: (re)classify the recall type." },
|
|
220
|
+
scope: { type: "string", enum: SCOPES, description: "'local' (default), 'project', or 'global'." },
|
|
221
|
+
global: { type: "boolean", description: "Shorthand for scope='global' (requires human approval)." },
|
|
222
222
|
},
|
|
223
223
|
output: {
|
|
224
224
|
schema: { type: "object", additionalProperties: false, properties: { text: { type: "string", required: true } } },
|
|
@@ -228,7 +228,7 @@ export function registerEvolveTools(ctx, engine, opts) {
|
|
|
228
228
|
const scope = scopeOf(args.scope ?? args.global, "local");
|
|
229
229
|
const storeId = storeIdFor(scope, exec);
|
|
230
230
|
if (needsApproval(scope) && opts.requireGlobalApproval) {
|
|
231
|
-
await requireGlobalApproval(ctx, exec.agent, exec.signal, `evolve_update ${args.kind}:${args.id} → ${storeLabel(scope)}
|
|
231
|
+
await requireGlobalApproval(ctx, exec.agent, exec.signal, `evolve_update ${args.kind}:${args.id} → ${storeLabel(scope)}`, opts.recordLanguage);
|
|
232
232
|
}
|
|
233
233
|
const edit = { action: "update", kind: args.kind, id: args.id };
|
|
234
234
|
if (args.title !== undefined)
|
|
@@ -242,17 +242,17 @@ export function registerEvolveTools(ctx, engine, opts) {
|
|
|
242
242
|
}));
|
|
243
243
|
ctx.tools.register(defineTool({
|
|
244
244
|
name: "evolve_delete",
|
|
245
|
-
description: "Delete harness entries
|
|
245
|
+
description: "Delete harness entries: `id` for one, `ids` for a batch (one refinement, one approval). Ids are addressed verbatim, scope-prefixed or not.",
|
|
246
246
|
parameters: {
|
|
247
247
|
kind: { type: "string", enum: ["prompt", "memory", "skill", "subagent"], required: true },
|
|
248
|
-
id: { type: "string", description: "
|
|
248
|
+
id: { type: "string", description: "One entry id (or use ids for a batch)." },
|
|
249
249
|
ids: {
|
|
250
250
|
type: "array",
|
|
251
251
|
items: { type: "string" },
|
|
252
|
-
description: "
|
|
252
|
+
description: "Entry ids for one batch delete.",
|
|
253
253
|
},
|
|
254
|
-
scope: { type: "string", enum: SCOPES, description: "
|
|
255
|
-
global: { type: "boolean", description: "
|
|
254
|
+
scope: { type: "string", enum: SCOPES, description: "'local' (default), 'project', or 'global'." },
|
|
255
|
+
global: { type: "boolean", description: "Shorthand for scope='global' (requires human approval)." },
|
|
256
256
|
},
|
|
257
257
|
output: {
|
|
258
258
|
schema: { type: "object", additionalProperties: false, properties: { text: { type: "string", required: true } } },
|
|
@@ -263,7 +263,7 @@ export function registerEvolveTools(ctx, engine, opts) {
|
|
|
263
263
|
const storeId = storeIdFor(scope, exec);
|
|
264
264
|
const ids = collectDeleteIds(args.id, args.ids);
|
|
265
265
|
if (needsApproval(scope) && opts.requireGlobalApproval) {
|
|
266
|
-
await requireGlobalApproval(ctx, exec.agent, exec.signal, `evolve_delete ${args.kind}:${ids.length > 1 ? `${ids.length} entries (${ids.join(", ")})` : ids[0]} → ${storeLabel(scope)}
|
|
266
|
+
await requireGlobalApproval(ctx, exec.agent, exec.signal, `evolve_delete ${args.kind}:${ids.length > 1 ? `${ids.length} entries (${ids.join(", ")})` : ids[0]} → ${storeLabel(scope)}`, opts.recordLanguage);
|
|
267
267
|
}
|
|
268
268
|
const edits = ids.map((id) => ({ action: "delete", kind: args.kind, id }));
|
|
269
269
|
return textResult(applyEditsText(engine, scope, storeId, edits, exec.agent, sessionIdOf(exec)));
|
|
@@ -271,11 +271,11 @@ export function registerEvolveTools(ctx, engine, opts) {
|
|
|
271
271
|
}));
|
|
272
272
|
ctx.tools.register(defineTool({
|
|
273
273
|
name: "evolve_rollback",
|
|
274
|
-
description: "Deterministically revert a previous refinement by its id (from evolve_list history or
|
|
274
|
+
description: "Deterministically revert a previous refinement by its id (from evolve_list history or /evolve).",
|
|
275
275
|
parameters: {
|
|
276
|
-
refinementId: { type: "string", required: true, description: "
|
|
277
|
-
scope: { type: "string", enum: SCOPES, description: "
|
|
278
|
-
global: { type: "boolean", description: "
|
|
276
|
+
refinementId: { type: "string", required: true, description: "Refinement id to roll back." },
|
|
277
|
+
scope: { type: "string", enum: SCOPES, description: "'local' (default), 'project', or 'global'." },
|
|
278
|
+
global: { type: "boolean", description: "Shorthand for scope='global'." },
|
|
279
279
|
},
|
|
280
280
|
output: {
|
|
281
281
|
schema: { type: "object", additionalProperties: false, properties: { text: { type: "string", required: true } } },
|
package/lib/wrapup-command.d.ts
CHANGED
|
@@ -5,5 +5,6 @@ import type { Context } from "@deepseek-ai/cordis";
|
|
|
5
5
|
import type { CommandInvocation, CommandResult } from "@deepseek-ai/dsh-commands";
|
|
6
6
|
import type { EvolutionEngine } from "./service.js";
|
|
7
7
|
import { type PromotionPolicy } from "./promotion.js";
|
|
8
|
-
|
|
8
|
+
import { type RecordLanguagePreference } from "./record-language.js";
|
|
9
|
+
export declare function executeWrapupCommand(ctx: Context, engine: EvolutionEngine, invocation: CommandInvocation, policy?: PromotionPolicy, recordLanguage?: RecordLanguagePreference): Promise<CommandResult>;
|
|
9
10
|
//# sourceMappingURL=wrapup-command.d.ts.map
|
package/lib/wrapup-command.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { questionServiceOf, requireGlobalApproval } from "./approval.js";
|
|
2
|
-
import {
|
|
2
|
+
import { wrapupArchiveCopy } from "./copy.js";
|
|
3
3
|
import { assessLocalEntries, candidateKey, filterPromotable, listLocalCandidates, splitArchiveGuards, splitPromoteBlocked, splitPromoteProposals, valenceStampProposal, wholePromoteProposals } from "./wrapup.js";
|
|
4
4
|
import { DEFAULT_PROMOTION_POLICY } from "./promotion.js";
|
|
5
|
+
import { normalizeRecordLanguage, resolveRecordLanguage } from "./record-language.js";
|
|
6
|
+
import { recentUserText } from "./inject.js";
|
|
5
7
|
function success(text) {
|
|
6
8
|
return { kind: "success", text };
|
|
7
9
|
}
|
|
8
|
-
export async function executeWrapupCommand(ctx, engine, invocation, policy = DEFAULT_PROMOTION_POLICY) {
|
|
10
|
+
export async function executeWrapupCommand(ctx, engine, invocation, policy = DEFAULT_PROMOTION_POLICY, recordLanguage) {
|
|
9
11
|
const sessionId = invocation.agent.id;
|
|
10
12
|
const localState = engine.load("local", sessionId);
|
|
11
13
|
const globalState = engine.load("global", undefined);
|
|
@@ -102,7 +104,7 @@ export async function executeWrapupCommand(ctx, engine, invocation, policy = DEF
|
|
|
102
104
|
].join("\n")}`;
|
|
103
105
|
let promoteAllowed = true;
|
|
104
106
|
try {
|
|
105
|
-
await requireGlobalApproval(ctx, invocation.agent, invocation.signal, what);
|
|
107
|
+
await requireGlobalApproval(ctx, invocation.agent, invocation.signal, what, recordLanguage);
|
|
106
108
|
}
|
|
107
109
|
catch (cause) {
|
|
108
110
|
promoteAllowed = false;
|
|
@@ -187,7 +189,8 @@ export async function executeWrapupCommand(ctx, engine, invocation, policy = DEF
|
|
|
187
189
|
const questionId = "evolve-wrapup-archive-review";
|
|
188
190
|
let archiveConfirmed = false;
|
|
189
191
|
try {
|
|
190
|
-
const copy = wrapupArchiveCopy(candidate.title,
|
|
192
|
+
const copy = wrapupArchiveCopy(candidate.title, normalizeRecordLanguage(recordLanguage) ??
|
|
193
|
+
resolveRecordLanguage({ ctx, trajectoryText: recentUserText(invocation.agent) }));
|
|
191
194
|
const answer = await userQuestions.ask({
|
|
192
195
|
questions: [
|
|
193
196
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-continual-evolve",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.6",
|
|
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": {
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"lint": "oxlint src test",
|
|
57
57
|
"clean": "rm -rf lib",
|
|
58
58
|
"check:docs": "tsx scripts/run-gates.ts docs",
|
|
59
|
+
"check:handoff": "tsx scripts/verify-handoff-structure.ts --self-test && tsx scripts/verify-handoff-structure.ts",
|
|
59
60
|
"check:governance": "tsx scripts/verify-governance.ts"
|
|
60
61
|
},
|
|
61
62
|
"peerDependencies": {
|