dsh-continual-evolve 0.11.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -115
- package/README.zh.md +33 -93
- package/client/client.js +253 -0
- package/cordis.patch.yml +2 -2
- package/lib/card-routes.d.ts +32 -0
- package/lib/card-routes.js +92 -0
- package/lib/index.d.ts +65 -217
- package/lib/index.js +74 -234
- package/lib/known-workspaces.d.ts +22 -0
- package/lib/known-workspaces.js +42 -0
- package/lib/memory-guide.d.ts +13 -14
- package/lib/memory-guide.js +34 -21
- package/lib/memory-section.d.ts +77 -0
- package/lib/memory-section.js +189 -0
- package/lib/memory-snapshot.d.ts +54 -0
- package/lib/memory-snapshot.js +125 -0
- package/package.json +8 -17
- package/lib/apply.d.ts +0 -22
- package/lib/apply.js +0 -157
- package/lib/approval.d.ts +0 -73
- package/lib/approval.js +0 -127
- package/lib/audit-log.d.ts +0 -19
- package/lib/audit-log.js +0 -52
- package/lib/auto.d.ts +0 -236
- package/lib/auto.js +0 -773
- package/lib/autocase.d.ts +0 -38
- package/lib/autocase.js +0 -76
- package/lib/benchmark-command.d.ts +0 -9
- package/lib/benchmark-command.js +0 -357
- package/lib/benchmark.d.ts +0 -151
- package/lib/benchmark.js +0 -250
- package/lib/command-util.d.ts +0 -70
- package/lib/command-util.js +0 -123
- package/lib/command.d.ts +0 -13
- package/lib/command.js +0 -134
- package/lib/consolidate.d.ts +0 -90
- package/lib/consolidate.js +0 -180
- package/lib/copy.d.ts +0 -37
- package/lib/copy.js +0 -58
- package/lib/declines.d.ts +0 -103
- package/lib/declines.js +0 -179
- package/lib/evaluate.d.ts +0 -74
- package/lib/evaluate.js +0 -271
- package/lib/evolve-event.d.ts +0 -40
- package/lib/evolve-event.js +0 -60
- package/lib/extraction-phase.d.ts +0 -132
- package/lib/extraction-phase.js +0 -236
- package/lib/failures.d.ts +0 -42
- package/lib/failures.js +0 -171
- package/lib/fate.d.ts +0 -130
- package/lib/fate.js +0 -350
- package/lib/goal-command.d.ts +0 -7
- package/lib/goal-command.js +0 -37
- package/lib/goal.d.ts +0 -72
- package/lib/goal.js +0 -72
- package/lib/inject.d.ts +0 -90
- package/lib/inject.js +0 -164
- package/lib/injection-caps.d.ts +0 -4
- package/lib/injection-caps.js +0 -4
- package/lib/injection-directory.d.ts +0 -29
- package/lib/injection-directory.js +0 -78
- package/lib/injection-rank.d.ts +0 -85
- package/lib/injection-rank.js +0 -161
- package/lib/listener.d.ts +0 -45
- package/lib/listener.js +0 -199
- package/lib/llm-text.d.ts +0 -104
- package/lib/llm-text.js +0 -140
- package/lib/logfile.d.ts +0 -71
- package/lib/logfile.js +0 -159
- package/lib/maintenance-commands.d.ts +0 -23
- package/lib/maintenance-commands.js +0 -136
- package/lib/memory-agent.d.ts +0 -93
- package/lib/memory-agent.js +0 -300
- package/lib/memory-apply.d.ts +0 -44
- package/lib/memory-apply.js +0 -202
- package/lib/memory-benchmark.d.ts +0 -76
- package/lib/memory-benchmark.js +0 -170
- package/lib/memory-index.d.ts +0 -114
- package/lib/memory-index.js +0 -197
- package/lib/memory-manifest.d.ts +0 -16
- package/lib/memory-manifest.js +0 -70
- package/lib/message-source.d.ts +0 -23
- package/lib/message-source.js +0 -12
- package/lib/mount-command.d.ts +0 -10
- package/lib/mount-command.js +0 -48
- package/lib/mount.d.ts +0 -42
- package/lib/mount.js +0 -212
- package/lib/notify.d.ts +0 -48
- package/lib/notify.js +0 -57
- package/lib/plan-command.d.ts +0 -12
- package/lib/plan-command.js +0 -38
- package/lib/plan.d.ts +0 -16
- package/lib/plan.js +0 -126
- package/lib/planner.d.ts +0 -61
- package/lib/planner.js +0 -144
- package/lib/pool.d.ts +0 -7
- package/lib/pool.js +0 -25
- package/lib/prefix-cache.d.ts +0 -79
- package/lib/prefix-cache.js +0 -168
- package/lib/project.d.ts +0 -28
- package/lib/project.js +0 -66
- package/lib/projection.d.ts +0 -22
- package/lib/projection.js +0 -112
- package/lib/promotion.d.ts +0 -94
- package/lib/promotion.js +0 -171
- package/lib/recall.d.ts +0 -85
- package/lib/recall.js +0 -142
- package/lib/record-language.d.ts +0 -85
- package/lib/record-language.js +0 -152
- package/lib/render.d.ts +0 -13
- package/lib/render.js +0 -80
- package/lib/review-scheduler.d.ts +0 -38
- package/lib/review-scheduler.js +0 -183
- package/lib/review.d.ts +0 -60
- package/lib/review.js +0 -145
- package/lib/rollback.d.ts +0 -9
- package/lib/rollback.js +0 -68
- package/lib/rubric.d.ts +0 -29
- package/lib/rubric.js +0 -119
- package/lib/runtime-commands.d.ts +0 -14
- package/lib/runtime-commands.js +0 -115
- package/lib/runtime.d.ts +0 -24
- package/lib/runtime.js +0 -87
- package/lib/score.d.ts +0 -64
- package/lib/score.js +0 -191
- package/lib/search.d.ts +0 -83
- package/lib/search.js +0 -136
- package/lib/service.d.ts +0 -63
- package/lib/service.js +0 -253
- package/lib/skill-render.d.ts +0 -23
- package/lib/skill-render.js +0 -69
- package/lib/skill.d.ts +0 -15
- package/lib/skill.js +0 -80
- package/lib/skillquality.d.ts +0 -85
- package/lib/skillquality.js +0 -320
- package/lib/source.d.ts +0 -30
- package/lib/source.js +0 -43
- package/lib/state.d.ts +0 -26
- package/lib/state.js +0 -151
- package/lib/store.d.ts +0 -61
- package/lib/store.js +0 -164
- package/lib/token-usage.d.ts +0 -68
- package/lib/token-usage.js +0 -211
- package/lib/tool.d.ts +0 -34
- package/lib/tool.js +0 -320
- package/lib/turn-snapshot.d.ts +0 -78
- package/lib/turn-snapshot.js +0 -252
- package/lib/types.d.ts +0 -218
- package/lib/types.js +0 -133
- package/lib/usage.d.ts +0 -36
- package/lib/usage.js +0 -98
- package/lib/validate.d.ts +0 -28
- package/lib/validate.js +0 -181
- package/lib/wrapup-candidates.d.ts +0 -127
- package/lib/wrapup-candidates.js +0 -161
- package/lib/wrapup-command.d.ts +0 -10
- package/lib/wrapup-command.js +0 -243
- package/lib/wrapup-proposals.d.ts +0 -101
- package/lib/wrapup-proposals.js +0 -281
- package/lib/wrapup.d.ts +0 -56
- package/lib/wrapup.js +0 -105
package/lib/memory-index.js
DELETED
|
@@ -1,197 +0,0 @@
|
|
|
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/memory-manifest.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The frozen memory manifest: a deterministic snapshot view of every live
|
|
3
|
-
* memory entry the extractor may see, plus its bounded serialization and
|
|
4
|
-
* manifest-scoped search. Pure functions over harness state.
|
|
5
|
-
*/
|
|
6
|
-
import type { HarnessEntry, HarnessState } from "./types.js";
|
|
7
|
-
/** One manifest row is exactly one live memory entry. */
|
|
8
|
-
export type MemoryManifestEntry = HarnessEntry;
|
|
9
|
-
export declare function buildMemoryManifest(state: HarnessState): MemoryManifestEntry[];
|
|
10
|
-
/** Render a bounded manifest index; full bodies remain available through memory_search. */
|
|
11
|
-
export declare function formatMemoryManifest(entries: readonly MemoryManifestEntry[], maxChars?: number): string;
|
|
12
|
-
/** Rank frozen manifest entries with CJK-aware token overlap and stable tie-breaking. */
|
|
13
|
-
export declare function searchMemoryManifest(entries: readonly MemoryManifestEntry[], query: string, maxResults?: number): MemoryManifestEntry[];
|
|
14
|
-
/** Inputs for one bounded background memory extraction loop. */
|
|
15
|
-
export declare function manifestResult(entry: MemoryManifestEntry): Record<string, unknown>;
|
|
16
|
-
//# sourceMappingURL=memory-manifest.d.ts.map
|
package/lib/memory-manifest.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { isArchived, isMemoryType, MEMORY_TYPE_KEY } from "./types.js";
|
|
2
|
-
import { tokenize } from "./search.js";
|
|
3
|
-
import { compactText } from "./render.js";
|
|
4
|
-
export function buildMemoryManifest(state) {
|
|
5
|
-
return Object.values(state.entries.memory)
|
|
6
|
-
.map((entry) => ({ ...entry, metadata: { ...entry.metadata }, reference: {}, arguments: {} }))
|
|
7
|
-
.sort((a, b) => manifestOrder(a).localeCompare(manifestOrder(b), "en"));
|
|
8
|
-
}
|
|
9
|
-
/** Render a bounded manifest index; full bodies remain available through memory_search. */
|
|
10
|
-
export function formatMemoryManifest(entries, maxChars = 8000) {
|
|
11
|
-
if (entries.length === 0)
|
|
12
|
-
return "No saved memory entries yet.";
|
|
13
|
-
const lines = [];
|
|
14
|
-
let used = 0;
|
|
15
|
-
for (const entry of entries) {
|
|
16
|
-
const type = isMemoryType(entry.metadata[MEMORY_TYPE_KEY]) ? entry.metadata[MEMORY_TYPE_KEY] : "untyped";
|
|
17
|
-
const archived = isArchived(entry) ? ", archived" : "";
|
|
18
|
-
const line = `- [${entry.scope}:${entry.id}] ${entry.title} (memoryType=${type}, path=${entry.path}${archived}): ${compactText(entry.content, 240)}`;
|
|
19
|
-
if (used + line.length + 1 > maxChars)
|
|
20
|
-
break;
|
|
21
|
-
lines.push(line);
|
|
22
|
-
used += line.length + 1;
|
|
23
|
-
}
|
|
24
|
-
const omitted = entries.length - lines.length;
|
|
25
|
-
if (omitted > 0)
|
|
26
|
-
lines.push(`- +${omitted} more entries; use memory_search with a narrower query.`);
|
|
27
|
-
return lines.join("\n");
|
|
28
|
-
}
|
|
29
|
-
/** Rank frozen manifest entries with CJK-aware token overlap and stable tie-breaking. */
|
|
30
|
-
export function searchMemoryManifest(entries, query, maxResults = 8) {
|
|
31
|
-
const queryTokens = new Set(tokenize(query));
|
|
32
|
-
if (queryTokens.size === 0)
|
|
33
|
-
return [];
|
|
34
|
-
return entries
|
|
35
|
-
.map((entry) => {
|
|
36
|
-
const title = tokenize(entry.title);
|
|
37
|
-
const body = tokenize(`${entry.content} ${entry.path} ${String(entry.metadata[MEMORY_TYPE_KEY] ?? "")}`);
|
|
38
|
-
let score = 0;
|
|
39
|
-
for (const token of queryTokens) {
|
|
40
|
-
if (title.includes(token))
|
|
41
|
-
score += 2;
|
|
42
|
-
if (body.includes(token))
|
|
43
|
-
score += 1;
|
|
44
|
-
}
|
|
45
|
-
if (`${entry.scope}:${entry.id}`.toLowerCase() === query.trim().toLowerCase())
|
|
46
|
-
score += 100;
|
|
47
|
-
return { entry, score };
|
|
48
|
-
})
|
|
49
|
-
.filter((hit) => hit.score > 0)
|
|
50
|
-
.sort((a, b) => b.score - a.score || manifestOrder(a.entry).localeCompare(manifestOrder(b.entry), "en"))
|
|
51
|
-
.slice(0, maxResults)
|
|
52
|
-
.map((hit) => hit.entry);
|
|
53
|
-
}
|
|
54
|
-
/** Inputs for one bounded background memory extraction loop. */
|
|
55
|
-
export function manifestResult(entry) {
|
|
56
|
-
return {
|
|
57
|
-
scope: entry.scope,
|
|
58
|
-
id: entry.id,
|
|
59
|
-
title: entry.title,
|
|
60
|
-
content: entry.content,
|
|
61
|
-
path: entry.path,
|
|
62
|
-
memoryType: entry.metadata[MEMORY_TYPE_KEY],
|
|
63
|
-
archived: isArchived(entry),
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
function manifestOrder(entry) {
|
|
67
|
-
const scope = entry.scope === "global" ? "0" : entry.scope === "project" ? "1" : "2";
|
|
68
|
-
return `${scope}:${entry.id}:${entry.title}`;
|
|
69
|
-
}
|
|
70
|
-
//# sourceMappingURL=memory-manifest.js.map
|
package/lib/message-source.d.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Producer declaration for every message this plugin injects.
|
|
3
|
-
*
|
|
4
|
-
* Harness message sources are a merge-extensible sum type: each producer
|
|
5
|
-
* declares its own `kind` in its own module, and dsh 0.1.7 removed the shared
|
|
6
|
-
* catch-all `kind: "plugin"`. Declaring our kind once, here, keeps every
|
|
7
|
-
* injection site checked against the host vocabulary instead of repeating a
|
|
8
|
-
* local literal that silently drifts when that vocabulary changes.
|
|
9
|
-
*/
|
|
10
|
-
declare module "@deepseek-ai/dsh-llm" {
|
|
11
|
-
interface MessageSourceMap {
|
|
12
|
-
"continual-evolve": EvolveMessageSource;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
/** Source tag identifying dsh-continual-evolve as the message producer. */
|
|
16
|
-
export interface EvolveMessageSource {
|
|
17
|
-
kind: "continual-evolve";
|
|
18
|
-
}
|
|
19
|
-
/** Source value carried by every message this plugin injects. */
|
|
20
|
-
export declare const EVOLVE_MESSAGE_SOURCE: {
|
|
21
|
-
readonly kind: "continual-evolve";
|
|
22
|
-
};
|
|
23
|
-
//# sourceMappingURL=message-source.d.ts.map
|
package/lib/message-source.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Producer declaration for every message this plugin injects.
|
|
3
|
-
*
|
|
4
|
-
* Harness message sources are a merge-extensible sum type: each producer
|
|
5
|
-
* declares its own `kind` in its own module, and dsh 0.1.7 removed the shared
|
|
6
|
-
* catch-all `kind: "plugin"`. Declaring our kind once, here, keeps every
|
|
7
|
-
* injection site checked against the host vocabulary instead of repeating a
|
|
8
|
-
* local literal that silently drifts when that vocabulary changes.
|
|
9
|
-
*/
|
|
10
|
-
/** Source value carried by every message this plugin injects. */
|
|
11
|
-
export const EVOLVE_MESSAGE_SOURCE = { kind: "continual-evolve" };
|
|
12
|
-
//# sourceMappingURL=message-source.js.map
|
package/lib/mount-command.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The `/evolve mount` and `/evolve unmount` subcommand handlers.
|
|
3
|
-
* Extracted from command.ts (P2-2).
|
|
4
|
-
*/
|
|
5
|
-
import type { Context } from "@deepseek-ai/cordis";
|
|
6
|
-
import type { CommandInvocation, CommandResult } from "@deepseek-ai/dsh-commands";
|
|
7
|
-
import type { EvolutionEngine } from "./service.js";
|
|
8
|
-
export declare function executeMountCommand(ctx: Context, engine: EvolutionEngine, invocation: CommandInvocation, rest: string[]): Promise<CommandResult>;
|
|
9
|
-
export declare function executeUnmountCommand(ctx: Context, engine: EvolutionEngine, rest: string[]): Promise<CommandResult>;
|
|
10
|
-
//# sourceMappingURL=mount-command.d.ts.map
|
package/lib/mount-command.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { loadLedger, mountSkill, unmountSkill } from "./mount.js";
|
|
2
|
-
import { stripAngleBrackets } from "./command-util.js";
|
|
3
|
-
function success(text) {
|
|
4
|
-
return { kind: "success", text };
|
|
5
|
-
}
|
|
6
|
-
function error(text) {
|
|
7
|
-
return { kind: "error", text };
|
|
8
|
-
}
|
|
9
|
-
export async function executeMountCommand(ctx, engine, invocation, rest) {
|
|
10
|
-
const sub = rest[0] ?? "";
|
|
11
|
-
if (sub === "list") {
|
|
12
|
-
const ledger = loadLedger(engine.baseDir);
|
|
13
|
-
if (ledger.mounted.length === 0) {
|
|
14
|
-
return success("(no hot-mounted plugins — /evolve mount <skillId>)");
|
|
15
|
-
}
|
|
16
|
-
return success(ledger.mounted.map((m) => `- ${m.id} (${m.entryId}, v${m.version}, ${m.mountedAt})`).join("\n"));
|
|
17
|
-
}
|
|
18
|
-
const skillId = stripAngleBrackets(sub);
|
|
19
|
-
if (!skillId) {
|
|
20
|
-
return error(`mount requires a skill entry id.\nUsage: /evolve mount <skillId> | /evolve mount list`);
|
|
21
|
-
}
|
|
22
|
-
const sessionId = invocation.agent.id;
|
|
23
|
-
const local = engine.load("local", sessionId);
|
|
24
|
-
const globalState = engine.load("global", undefined);
|
|
25
|
-
const entry = local.entries.skill[skillId] ??
|
|
26
|
-
globalState.entries.skill[skillId] ??
|
|
27
|
-
Object.values(local.entries.skill).find((e) => e.id === skillId) ??
|
|
28
|
-
Object.values(globalState.entries.skill).find((e) => e.id === skillId);
|
|
29
|
-
if (!entry) {
|
|
30
|
-
return error(`skill entry ${skillId} not found in local or global store`);
|
|
31
|
-
}
|
|
32
|
-
try {
|
|
33
|
-
const record = await mountSkill(ctx, engine.baseDir, entry);
|
|
34
|
-
return success(`mounted ${record.id} as ${record.entryId} (v${record.version}) — tool: skill_${record.id.replace(/_/g, "-")}`);
|
|
35
|
-
}
|
|
36
|
-
catch (cause) {
|
|
37
|
-
return error(cause instanceof Error ? cause.message : String(cause));
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
export async function executeUnmountCommand(ctx, engine, rest) {
|
|
41
|
-
const id = stripAngleBrackets(rest[0] ?? "");
|
|
42
|
-
if (!id) {
|
|
43
|
-
return error(`unmount requires a mount id (see /evolve mount list).`);
|
|
44
|
-
}
|
|
45
|
-
const record = await unmountSkill(ctx, engine.baseDir, id);
|
|
46
|
-
return record ? success(`unmounted ${record.id} (${record.entryId})`) : error(`no mount found for ${id}`);
|
|
47
|
-
}
|
|
48
|
-
//# sourceMappingURL=mount-command.js.map
|
package/lib/mount.d.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import type { Context } from "@deepseek-ai/cordis";
|
|
2
|
-
import type { HarnessEntry } from "./types.js";
|
|
3
|
-
export interface MountRecord {
|
|
4
|
-
id: string;
|
|
5
|
-
/** Loader entry id (the `evolve:<name>` key). */
|
|
6
|
-
entryId: string;
|
|
7
|
-
/** Absolute path of the generated plugin package. */
|
|
8
|
-
path: string;
|
|
9
|
-
/** Entry revision the mount was generated from. */
|
|
10
|
-
version: number;
|
|
11
|
-
mountedAt: string;
|
|
12
|
-
}
|
|
13
|
-
export interface MountLedger {
|
|
14
|
-
mounted: MountRecord[];
|
|
15
|
-
}
|
|
16
|
-
export declare function mountedDir(baseDir: string): string;
|
|
17
|
-
export declare function ledgerPath(baseDir: string): string;
|
|
18
|
-
export declare function loadLedger(baseDir: string): MountLedger;
|
|
19
|
-
export declare function saveLedger(baseDir: string, ledger: MountLedger): void;
|
|
20
|
-
/** Generate the plugin package files for one skill entry; returns the package dir. */
|
|
21
|
-
export declare function renderMountPackage(baseDir: string, entry: HarnessEntry): string;
|
|
22
|
-
/** The generated plugin source: one tool registration, no external imports. */
|
|
23
|
-
export declare function renderPluginSource(toolName: string, entry: HarnessEntry): string;
|
|
24
|
-
/**
|
|
25
|
-
* Map a skill entry's arguments contract to the tool-parameter schema.
|
|
26
|
-
* Requiredness goes in a root-level `required` array (valid JSON Schema):
|
|
27
|
-
* the mounted plugin registers via raw `ctx.tools.register`, and dsh-llm
|
|
28
|
-
* sends `parameters` verbatim to the API, which rejects per-property
|
|
29
|
-
* `required: true` ("true is not of type array").
|
|
30
|
-
*/
|
|
31
|
-
export declare function renderParameters(entry: HarnessEntry): Record<string, unknown>;
|
|
32
|
-
/**
|
|
33
|
-
* Mount one skill entry into the live loader. Requires the `loader` service
|
|
34
|
-
* (resolved lazily, see FAQ #1); without it the package is still written and
|
|
35
|
-
* the ledger records the entry for the next boot.
|
|
36
|
-
*/
|
|
37
|
-
export declare function mountSkill(ctx: Context, baseDir: string, entry: HarnessEntry): Promise<MountRecord>;
|
|
38
|
-
/** Unmount a skill entry: remove the loader entry and the generated package. */
|
|
39
|
-
export declare function unmountSkill(ctx: Context, baseDir: string, id: string): Promise<MountRecord | undefined>;
|
|
40
|
-
/** Re-mount every ledger entry at plugin boot (restart persistence). */
|
|
41
|
-
export declare function restoreMounted(ctx: Context, baseDir: string): Promise<void>;
|
|
42
|
-
//# sourceMappingURL=mount.d.ts.map
|
package/lib/mount.js
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hot mounting: materialize a skill-kind harness entry as a real cordis
|
|
3
|
-
* plugin (v2 optional item, design.md §6) and load it into the live loader
|
|
4
|
-
* tree — no process restart. The generated plugin registers a model-facing
|
|
5
|
-
* tool that carries the skill's description, argument contract, and python
|
|
6
|
-
* reference; the `skill` tool remains the way to actually execute it.
|
|
7
|
-
*
|
|
8
|
-
* Layout under `<baseDir>/evolve/mounted/`:
|
|
9
|
-
* index.json mount ledger (id -> path, restored at boot)
|
|
10
|
-
* <skillName>/package.json plugin manifest (ESM, main index.js)
|
|
11
|
-
* <skillName>/index.js generated plugin: registers one tool
|
|
12
|
-
*
|
|
13
|
-
* The generated plugin imports nothing outside node builtins, so the mount
|
|
14
|
-
* directory needs no node_modules of its own.
|
|
15
|
-
*/
|
|
16
|
-
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
17
|
-
import { join } from "node:path";
|
|
18
|
-
import { skillNameOf } from "./skill.js";
|
|
19
|
-
import { secretLeakReason } from "./promotion.js";
|
|
20
|
-
export function mountedDir(baseDir) {
|
|
21
|
-
return join(baseDir, "evolve", "mounted");
|
|
22
|
-
}
|
|
23
|
-
export function ledgerPath(baseDir) {
|
|
24
|
-
return join(mountedDir(baseDir), "index.json");
|
|
25
|
-
}
|
|
26
|
-
export function loadLedger(baseDir) {
|
|
27
|
-
const path = ledgerPath(baseDir);
|
|
28
|
-
if (!existsSync(path)) {
|
|
29
|
-
return { mounted: [] };
|
|
30
|
-
}
|
|
31
|
-
try {
|
|
32
|
-
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
33
|
-
return { mounted: Array.isArray(raw.mounted) ? raw.mounted : [] };
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
return { mounted: [] };
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
export function saveLedger(baseDir, ledger) {
|
|
40
|
-
mkdirSync(mountedDir(baseDir), { recursive: true });
|
|
41
|
-
writeFileSync(ledgerPath(baseDir), `${JSON.stringify(ledger, null, 2)}\n`, "utf8");
|
|
42
|
-
}
|
|
43
|
-
/** Generate the plugin package files for one skill entry; returns the package dir. */
|
|
44
|
-
export function renderMountPackage(baseDir, entry) {
|
|
45
|
-
// Secret-leak quarantine: the entry content AND its reference contract are
|
|
46
|
-
// embedded verbatim into the generated index.js, so a credential in either
|
|
47
|
-
// would be written to disk (and executed in-process). Block before ANY
|
|
48
|
-
// file is created (review audit 2026-08-28 B2: reference was unscreened).
|
|
49
|
-
const secret = secretLeakReason([entry.title, entry.content, JSON.stringify(entry.reference ?? {}), JSON.stringify(entry.arguments ?? {})].join("\n"));
|
|
50
|
-
if (secret) {
|
|
51
|
-
throw new Error(`mount blocked: ${secret}`);
|
|
52
|
-
}
|
|
53
|
-
const dir = join(mountedDir(baseDir), skillNameOf(entry.id));
|
|
54
|
-
mkdirSync(dir, { recursive: true });
|
|
55
|
-
const toolName = `skill_${skillNameOf(entry.id)}`;
|
|
56
|
-
writeFileSync(join(dir, "package.json"), `{\n "name": "evolve-skill-${skillNameOf(entry.id)}",\n "version": "1.0.0",\n "main": "index.js",\n "type": "module"\n}\n`, "utf8");
|
|
57
|
-
writeFileSync(join(dir, "index.js"), renderPluginSource(toolName, entry), "utf8");
|
|
58
|
-
return dir;
|
|
59
|
-
}
|
|
60
|
-
/** The generated plugin source: one tool registration, no external imports. */
|
|
61
|
-
export function renderPluginSource(toolName, entry) {
|
|
62
|
-
const description = `${oneLine(entry.title)}. ${oneLine(entry.content)}`.slice(0, 400);
|
|
63
|
-
const parameters = renderParameters(entry);
|
|
64
|
-
const reference = JSON.stringify(entry.reference ?? {}, null, 2);
|
|
65
|
-
const entryId = JSON.stringify(entry.id);
|
|
66
|
-
const entryVersion = JSON.stringify(entry.version);
|
|
67
|
-
const entryTitle = JSON.stringify(oneLine(entry.title));
|
|
68
|
-
const skillName = JSON.stringify(skillNameOf(entry.id));
|
|
69
|
-
return `/**
|
|
70
|
-
* Generated by dsh-continual-evolve mount (${entry.id} v${entry.version}).
|
|
71
|
-
* Hot-mounted into the live loader tree; edit the harness entry and remount
|
|
72
|
-
* to regenerate. Execute the skill through the \`skill\` tool — this plugin
|
|
73
|
-
* exposes its contract to the model.
|
|
74
|
-
*/
|
|
75
|
-
export const name = "evolve-skill-${skillNameOf(entry.id)}";
|
|
76
|
-
|
|
77
|
-
// Cordis requires declared service access; without this, \`ctx.tools\` throws
|
|
78
|
-
// "cannot get property \\"tools\\" without inject".
|
|
79
|
-
export const inject = ["tools"];
|
|
80
|
-
|
|
81
|
-
export function apply(ctx) {
|
|
82
|
-
ctx.tools.register({
|
|
83
|
-
name: ${JSON.stringify(toolName)},
|
|
84
|
-
description: ${JSON.stringify(description)},
|
|
85
|
-
parameters: ${JSON.stringify(parameters, null, 2)},
|
|
86
|
-
output: {
|
|
87
|
-
// No \`required\` on the string property: the direct-register value-schema
|
|
88
|
-
// path rejects it ("required is not supported on type string").
|
|
89
|
-
schema: { type: "object", additionalProperties: false, properties: { text: { type: "string" } } },
|
|
90
|
-
render: (_args, value) => [{ type: "text", text: value?.text ?? "" }],
|
|
91
|
-
},
|
|
92
|
-
execute: async (args) => ({
|
|
93
|
-
text: "Mounted skill " + ${entryId} + " (v" + ${entryVersion} + ").\\n" +
|
|
94
|
-
"Description: " + ${entryTitle} + "\\n" +
|
|
95
|
-
"Argument contract: " + JSON.stringify(args ?? {}) + "\\n" +
|
|
96
|
-
"Python reference: " + ${JSON.stringify(reference)} + "\\n" +
|
|
97
|
-
"Execute via the \`skill\` tool (skill name " + ${skillName} + ").",
|
|
98
|
-
}),
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
`;
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Map a skill entry's arguments contract to the tool-parameter schema.
|
|
105
|
-
* Requiredness goes in a root-level `required` array (valid JSON Schema):
|
|
106
|
-
* the mounted plugin registers via raw `ctx.tools.register`, and dsh-llm
|
|
107
|
-
* sends `parameters` verbatim to the API, which rejects per-property
|
|
108
|
-
* `required: true` ("true is not of type array").
|
|
109
|
-
*/
|
|
110
|
-
export function renderParameters(entry) {
|
|
111
|
-
const contract = entry.arguments ?? {};
|
|
112
|
-
const properties = {};
|
|
113
|
-
const required = [];
|
|
114
|
-
for (const [key, spec] of Object.entries(contract)) {
|
|
115
|
-
const record = typeof spec === "object" && spec !== null && !Array.isArray(spec) ? spec : {};
|
|
116
|
-
const type = typeof record["type"] === "string" ? record["type"] : "string";
|
|
117
|
-
const description = typeof record["description"] === "string" ? record["description"] : key;
|
|
118
|
-
properties[key] = { type, description };
|
|
119
|
-
if (record["required"] === true) {
|
|
120
|
-
required.push(key);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return {
|
|
124
|
-
type: "object",
|
|
125
|
-
additionalProperties: false,
|
|
126
|
-
properties,
|
|
127
|
-
...(required.length > 0 ? { required } : {}),
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Mount one skill entry into the live loader. Requires the `loader` service
|
|
132
|
-
* (resolved lazily, see FAQ #1); without it the package is still written and
|
|
133
|
-
* the ledger records the entry for the next boot.
|
|
134
|
-
*/
|
|
135
|
-
export async function mountSkill(ctx, baseDir, entry) {
|
|
136
|
-
// Guidance skills are SKILL.md documents with no python reference — there
|
|
137
|
-
// is no function to mount as a tool; only executable skills can hot-mount.
|
|
138
|
-
if (entry.skill_kind === "guidance" || Object.keys(entry.reference ?? {}).length === 0) {
|
|
139
|
-
throw new Error(`skill ${entry.id} has no python reference (guidance skills cannot be mounted — load them with the skill tool instead)`);
|
|
140
|
-
}
|
|
141
|
-
const dir = renderMountPackage(baseDir, entry);
|
|
142
|
-
const entryId = `evolve-mount-${skillNameOf(entry.id)}`;
|
|
143
|
-
const loader = ctx.get("loader");
|
|
144
|
-
if (loader) {
|
|
145
|
-
try {
|
|
146
|
-
// EntryOptions: {id, name (module specifier), config, group, disabled, inject} —
|
|
147
|
-
// `name` must resolve as an ES module: Node ESM does not support
|
|
148
|
-
// directory imports, so point at the generated index.js explicitly.
|
|
149
|
-
await loader.create({ id: entryId, name: join(dir, "index.js") });
|
|
150
|
-
}
|
|
151
|
-
catch (cause) {
|
|
152
|
-
throw new Error(`hot mount failed: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
const record = {
|
|
156
|
-
id: entry.id,
|
|
157
|
-
entryId,
|
|
158
|
-
path: dir,
|
|
159
|
-
version: entry.version,
|
|
160
|
-
mountedAt: new Date().toISOString(),
|
|
161
|
-
};
|
|
162
|
-
const ledger = loadLedger(baseDir);
|
|
163
|
-
ledger.mounted = [...ledger.mounted.filter((m) => m.id !== entry.id), record];
|
|
164
|
-
saveLedger(baseDir, ledger);
|
|
165
|
-
return record;
|
|
166
|
-
}
|
|
167
|
-
/** Unmount a skill entry: remove the loader entry and the generated package. */
|
|
168
|
-
export async function unmountSkill(ctx, baseDir, id) {
|
|
169
|
-
const ledger = loadLedger(baseDir);
|
|
170
|
-
const record = ledger.mounted.find((m) => m.id === id);
|
|
171
|
-
if (!record) {
|
|
172
|
-
return undefined;
|
|
173
|
-
}
|
|
174
|
-
const loader = ctx.get("loader");
|
|
175
|
-
if (loader) {
|
|
176
|
-
try {
|
|
177
|
-
await loader.remove(record.entryId);
|
|
178
|
-
}
|
|
179
|
-
catch (cause) {
|
|
180
|
-
throw new Error(`hot unmount failed: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
if (existsSync(record.path)) {
|
|
184
|
-
rmSync(record.path, { recursive: true, force: true });
|
|
185
|
-
}
|
|
186
|
-
ledger.mounted = ledger.mounted.filter((m) => m.id !== id);
|
|
187
|
-
saveLedger(baseDir, ledger);
|
|
188
|
-
return record;
|
|
189
|
-
}
|
|
190
|
-
/** Re-mount every ledger entry at plugin boot (restart persistence). */
|
|
191
|
-
export async function restoreMounted(ctx, baseDir) {
|
|
192
|
-
const ledger = loadLedger(baseDir);
|
|
193
|
-
const loader = ctx.get("loader");
|
|
194
|
-
for (const record of ledger.mounted) {
|
|
195
|
-
if (!existsSync(join(record.path, "index.js"))) {
|
|
196
|
-
continue; // package was removed; ledger prunes on next unmount
|
|
197
|
-
}
|
|
198
|
-
if (!loader) {
|
|
199
|
-
continue;
|
|
200
|
-
}
|
|
201
|
-
try {
|
|
202
|
-
await loader.create({ id: record.entryId, name: join(record.path, "index.js") });
|
|
203
|
-
}
|
|
204
|
-
catch (cause) {
|
|
205
|
-
ctx.logger("continual-evolve").warn(`mount restore failed for ${record.id}: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
function oneLine(text) {
|
|
210
|
-
return text.replace(/\s+/g, " ").trim();
|
|
211
|
-
}
|
|
212
|
-
//# sourceMappingURL=mount.js.map
|