dsh-continual-evolve 0.10.6 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README.md +35 -122
  2. package/README.zh.md +26 -97
  3. package/cordis.patch.yml +2 -2
  4. package/lib/index.d.ts +44 -241
  5. package/lib/index.js +30 -253
  6. package/lib/memory-guide.d.ts +13 -14
  7. package/lib/memory-guide.js +32 -21
  8. package/lib/memory-section.d.ts +77 -0
  9. package/lib/memory-section.js +189 -0
  10. package/package.json +2 -17
  11. package/lib/apply.d.ts +0 -22
  12. package/lib/apply.js +0 -157
  13. package/lib/approval.d.ts +0 -72
  14. package/lib/approval.js +0 -127
  15. package/lib/auto.d.ts +0 -236
  16. package/lib/auto.js +0 -773
  17. package/lib/autocase.d.ts +0 -38
  18. package/lib/autocase.js +0 -76
  19. package/lib/benchmark-command.d.ts +0 -9
  20. package/lib/benchmark-command.js +0 -357
  21. package/lib/benchmark.d.ts +0 -151
  22. package/lib/benchmark.js +0 -250
  23. package/lib/command.d.ts +0 -49
  24. package/lib/command.js +0 -568
  25. package/lib/consolidate.d.ts +0 -90
  26. package/lib/consolidate.js +0 -180
  27. package/lib/copy.d.ts +0 -74
  28. package/lib/copy.js +0 -160
  29. package/lib/declines.d.ts +0 -101
  30. package/lib/declines.js +0 -175
  31. package/lib/evaluate.d.ts +0 -74
  32. package/lib/evaluate.js +0 -271
  33. package/lib/evolve-event.d.ts +0 -40
  34. package/lib/evolve-event.js +0 -59
  35. package/lib/failures.d.ts +0 -42
  36. package/lib/failures.js +0 -171
  37. package/lib/fate.d.ts +0 -130
  38. package/lib/fate.js +0 -350
  39. package/lib/goal-command.d.ts +0 -7
  40. package/lib/goal-command.js +0 -37
  41. package/lib/goal.d.ts +0 -72
  42. package/lib/goal.js +0 -72
  43. package/lib/inject.d.ts +0 -196
  44. package/lib/inject.js +0 -412
  45. package/lib/llm-text.d.ts +0 -104
  46. package/lib/llm-text.js +0 -140
  47. package/lib/logfile.d.ts +0 -71
  48. package/lib/logfile.js +0 -159
  49. package/lib/memory-agent.d.ts +0 -129
  50. package/lib/memory-agent.js +0 -568
  51. package/lib/memory-benchmark.d.ts +0 -76
  52. package/lib/memory-benchmark.js +0 -170
  53. package/lib/memory-index.d.ts +0 -114
  54. package/lib/memory-index.js +0 -197
  55. package/lib/message-source.d.ts +0 -23
  56. package/lib/message-source.js +0 -12
  57. package/lib/mount-command.d.ts +0 -10
  58. package/lib/mount-command.js +0 -48
  59. package/lib/mount.d.ts +0 -42
  60. package/lib/mount.js +0 -212
  61. package/lib/notify.d.ts +0 -59
  62. package/lib/notify.js +0 -97
  63. package/lib/plan.d.ts +0 -16
  64. package/lib/plan.js +0 -126
  65. package/lib/planner.d.ts +0 -61
  66. package/lib/planner.js +0 -144
  67. package/lib/pool.d.ts +0 -7
  68. package/lib/pool.js +0 -25
  69. package/lib/prefix-cache.d.ts +0 -78
  70. package/lib/prefix-cache.js +0 -168
  71. package/lib/project.d.ts +0 -28
  72. package/lib/project.js +0 -66
  73. package/lib/projection.d.ts +0 -22
  74. package/lib/projection.js +0 -112
  75. package/lib/promotion.d.ts +0 -94
  76. package/lib/promotion.js +0 -171
  77. package/lib/recall.d.ts +0 -85
  78. package/lib/recall.js +0 -142
  79. package/lib/record-language.d.ts +0 -85
  80. package/lib/record-language.js +0 -152
  81. package/lib/render.d.ts +0 -13
  82. package/lib/render.js +0 -80
  83. package/lib/review-scheduler.d.ts +0 -38
  84. package/lib/review-scheduler.js +0 -191
  85. package/lib/review.d.ts +0 -60
  86. package/lib/review.js +0 -145
  87. package/lib/rollback.d.ts +0 -9
  88. package/lib/rollback.js +0 -68
  89. package/lib/rubric.d.ts +0 -29
  90. package/lib/rubric.js +0 -119
  91. package/lib/runtime.d.ts +0 -24
  92. package/lib/runtime.js +0 -87
  93. package/lib/score.d.ts +0 -64
  94. package/lib/score.js +0 -191
  95. package/lib/search.d.ts +0 -83
  96. package/lib/search.js +0 -136
  97. package/lib/service.d.ts +0 -49
  98. package/lib/service.js +0 -180
  99. package/lib/skill-render.d.ts +0 -23
  100. package/lib/skill-render.js +0 -69
  101. package/lib/skill.d.ts +0 -15
  102. package/lib/skill.js +0 -80
  103. package/lib/skillquality.d.ts +0 -85
  104. package/lib/skillquality.js +0 -320
  105. package/lib/source.d.ts +0 -30
  106. package/lib/source.js +0 -43
  107. package/lib/state.d.ts +0 -26
  108. package/lib/state.js +0 -151
  109. package/lib/store.d.ts +0 -62
  110. package/lib/store.js +0 -164
  111. package/lib/token-usage.d.ts +0 -68
  112. package/lib/token-usage.js +0 -211
  113. package/lib/tool.d.ts +0 -34
  114. package/lib/tool.js +0 -320
  115. package/lib/turn-snapshot.d.ts +0 -72
  116. package/lib/turn-snapshot.js +0 -238
  117. package/lib/types.d.ts +0 -208
  118. package/lib/types.js +0 -127
  119. package/lib/usage.d.ts +0 -36
  120. package/lib/usage.js +0 -98
  121. package/lib/validate.d.ts +0 -28
  122. package/lib/validate.js +0 -181
  123. package/lib/wrapup-command.d.ts +0 -10
  124. package/lib/wrapup-command.js +0 -243
  125. package/lib/wrapup.d.ts +0 -260
  126. package/lib/wrapup.js +0 -539
@@ -1,170 +0,0 @@
1
- /**
2
- * Memory-specific extraction benchmark (P1): deterministic scoring of one
3
- * memory proposal against a labeled reference.
4
- *
5
- * Unlike the harness benchmark (which runs subagents against rubrics), this
6
- * module scores a proposal's SHAPE without any model call: precision and
7
- * recall over reference facts, duplicate creates (update-first violations),
8
- * stale operations (re-proposing outdated facts, or removing live ones), and
9
- * noise (unmatched or exclusion-list material). Suite authors hand-build
10
- * small references plus transcripts; the extraction loop under test produces
11
- * the proposal, this module grades it.
12
- *
13
- * Matching is phrase-substring based (case-insensitive): a reference fact
14
- * lists must-contain phrases, and a proposed edit matches when its title
15
- * plus content carry every phrase. Deliberately mechanical — the benchmark
16
- * measures extraction coverage, not paraphrase quality.
17
- */
18
- import { CONFLICT_BLOCK_SCORE, mostSimilarEntry } from "./promotion.js";
19
- import { isArchived } from "./types.js";
20
- /** Strict-by-default gate: duplicates and stale operations never pass unnoticed. */
21
- export const DEFAULT_MEMORY_GRADE_THRESHOLDS = {
22
- minPrecision: 0.7,
23
- minRecall: 0.7,
24
- maxDuplicate: 0,
25
- maxStale: 0,
26
- maxNoise: 0,
27
- };
28
- /**
29
- * v1 exclusion heuristics for noise ("不该记什么" in mechanical form):
30
- * git history, repo paths re-readable from disk, stack traces, and
31
- * explicitly temporary session state. Keep this list short and documented —
32
- * every pattern here is a precision/recall tradeoff, not ground truth.
33
- */
34
- export const MEMORY_NOISE_PATTERNS = [
35
- /\bgit\s+(log|show|commit|diff)\b/i,
36
- /\bcommit\s+[0-9a-f]{5,40}\b/i,
37
- /\b[0-9a-f]{40}\b/,
38
- /(?:^|[\s("'])((?:src|lib|test|tests|docs|scripts)\/[A-Za-z0-9_./-]+\.[a-z]+)/,
39
- /\bat\s+[\w$.]+\s*\(.*:\d+:\d+\)/,
40
- /\b(temporary|one-off debugging|current task progress|in this session only)\b/i,
41
- ];
42
- /** Content shorter than this is too thin to be a durable fact. */
43
- export const MEMORY_MIN_FACT_CHARS = 30;
44
- /**
45
- * Score one proposal against the reference and the pre-extraction manifest.
46
- *
47
- * @param reference Labeled facts (live + outdated).
48
- * @param edits The proposal's memory edits.
49
- * @param manifest Memory entries visible before extraction (duplicate source).
50
- * @returns Per-dimension counts plus a per-edit breakdown.
51
- */
52
- export function scoreMemoryExtraction(reference, edits, manifest) {
53
- const live = reference.facts.filter((fact) => !fact.outdated);
54
- const outdated = reference.facts.filter((fact) => fact.outdated);
55
- const liveMatched = new Set();
56
- const outdatedHandled = new Set();
57
- const scored = [];
58
- let duplicate = 0;
59
- let stale = 0;
60
- let noise = 0;
61
- for (const edit of edits) {
62
- const text = `${edit.title ?? ""}\n${edit.content ?? ""}`;
63
- const liveHits = live.filter((fact) => matchesFact(text, fact));
64
- const outdatedHits = outdated.filter((fact) => matchesFact(text, fact));
65
- const entry = { action: edit.action, matchedFactIds: [] };
66
- if (edit.action === "create" || edit.action === "update") {
67
- if (outdatedHits.length > 0) {
68
- stale += 1;
69
- entry.staleReason = `re-proposes outdated fact(s): ${outdatedHits.map((fact) => fact.id).join(",")}`;
70
- }
71
- else if (edit.action === "create") {
72
- const hit = mostSimilarEntry(manifest.filter((candidate) => candidate.kind === "memory"), edit.title ?? "", edit.content ?? "", CONFLICT_BLOCK_SCORE);
73
- if (hit) {
74
- duplicate += 1;
75
- entry.duplicateOf = hit.id;
76
- }
77
- else if (liveHits.length > 0) {
78
- for (const fact of liveHits) {
79
- liveMatched.add(fact.id);
80
- entry.matchedFactIds.push(fact.id);
81
- }
82
- }
83
- else {
84
- noise += 1;
85
- entry.noisyReason = noiseReason(text);
86
- }
87
- }
88
- else if (liveHits.length > 0) {
89
- for (const fact of liveHits) {
90
- liveMatched.add(fact.id);
91
- entry.matchedFactIds.push(fact.id);
92
- }
93
- }
94
- else {
95
- noise += 1;
96
- entry.noisyReason = noiseReason(text);
97
- }
98
- }
99
- else {
100
- // delete/archive: correct only against outdated facts.
101
- const targetHits = [...liveHits, ...outdatedHits];
102
- const targetOutdated = outdatedHits.length > 0;
103
- const targetEntry = edit.id !== undefined ? manifest.find((candidate) => candidate.id === edit.id) : undefined;
104
- if (targetOutdated) {
105
- for (const fact of outdatedHits) {
106
- outdatedHandled.add(fact.id);
107
- entry.matchedFactIds.push(fact.id);
108
- }
109
- }
110
- else if (targetEntry !== undefined && isArchived(targetEntry)) {
111
- // Re-archiving an already archived entry is harmless housekeeping.
112
- entry.matchedFactIds.push(`archived:${targetEntry.id}`);
113
- }
114
- else if (targetHits.length > 0 || (targetEntry !== undefined && !isArchived(targetEntry))) {
115
- stale += 1;
116
- entry.staleReason = `removes live fact(s): ${targetHits.map((fact) => fact.id).join(",") || targetEntry?.id || "(unknown target)"}`;
117
- }
118
- else {
119
- noise += 1;
120
- entry.noisyReason = "removal matches no reference fact";
121
- }
122
- }
123
- scored.push(entry);
124
- }
125
- const total = edits.length;
126
- const preciseEdits = scored.filter((entry) => entry.matchedFactIds.length > 0).length;
127
- const precision = total === 0 ? 1 : preciseEdits / total;
128
- const recall = live.length === 0 ? 1 : liveMatched.size / live.length;
129
- return {
130
- totalEdits: total,
131
- matchedFacts: liveMatched.size + outdatedHandled.size,
132
- precision,
133
- recall,
134
- duplicate,
135
- stale,
136
- noise,
137
- uncoveredFactIds: live.filter((fact) => !liveMatched.has(fact.id)).map((fact) => fact.id),
138
- edits: scored,
139
- };
140
- }
141
- /**
142
- * Grade a score against explicit thresholds.
143
- *
144
- * @returns pass plus one failure line per violated dimension (empty when passing).
145
- */
146
- export function gradeMemoryScore(score, thresholds = DEFAULT_MEMORY_GRADE_THRESHOLDS) {
147
- const failures = [];
148
- if (score.precision < thresholds.minPrecision)
149
- failures.push(`precision ${score.precision.toFixed(2)} < ${thresholds.minPrecision}`);
150
- if (score.recall < thresholds.minRecall)
151
- failures.push(`recall ${score.recall.toFixed(2)} < ${thresholds.minRecall}`);
152
- if (score.duplicate > thresholds.maxDuplicate)
153
- failures.push(`duplicate ${score.duplicate} > ${thresholds.maxDuplicate}`);
154
- if (score.stale > thresholds.maxStale)
155
- failures.push(`stale ${score.stale} > ${thresholds.maxStale}`);
156
- if (score.noise > thresholds.maxNoise)
157
- failures.push(`noise ${score.noise} > ${thresholds.maxNoise}`);
158
- return { pass: failures.length === 0, failures };
159
- }
160
- function matchesFact(text, fact) {
161
- const lowered = text.toLowerCase();
162
- return fact.mustContain.every((phrase) => lowered.includes(phrase.toLowerCase()));
163
- }
164
- function noiseReason(text) {
165
- if (text.trim().length < MEMORY_MIN_FACT_CHARS)
166
- return `thin content (<${MEMORY_MIN_FACT_CHARS} chars)`;
167
- const hit = MEMORY_NOISE_PATTERNS.findIndex((pattern) => pattern.test(text));
168
- return hit >= 0 ? `matches exclusion pattern #${hit + 1}` : "matches no reference fact";
169
- }
170
- //# sourceMappingURL=memory-benchmark.js.map
@@ -1,114 +0,0 @@
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -1,48 +0,0 @@
1
- import { loadLedger, mountSkill, unmountSkill } from "./mount.js";
2
- import { stripAngleBrackets } from "./command.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