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,568 +0,0 @@
1
- import { createAssistantMessage, createToolResultMessage, createUserMessage, } from "@deepseek-ai/dsh-llm";
2
- import { requestScopeApproval } from "./approval.js";
3
- import { fingerprintMemoryBatch, isDeclinedRepeat, loadDeclinedMemory, recordDeclinedMemory, } from "./declines.js";
4
- import { streamModelTurn } from "./llm-text.js";
5
- import { EVOLVE_MESSAGE_SOURCE } from "./message-source.js";
6
- import { extractJsonObject, parseJsonCandidate } from "./plan.js";
7
- import { buildPrefixMessages, detectPlannerRoute, resolvePrefixCache } from "./prefix-cache.js";
8
- import { compactText } from "./render.js";
9
- import { buildConflictNotice, CONFLICT_WARN_SCORE, mostSimilarEntry } from "./promotion.js";
10
- import { EvolutionApplyPostCommitError } from "./service.js";
11
- import { tokenize } from "./search.js";
12
- import { createTokenUsageObserver } from "./token-usage.js";
13
- import { recordLanguageInstruction, resolveRecordLanguage } from "./record-language.js";
14
- import { isArchived, isMemoryType, MEMORY_TYPE_KEY, slug } from "./types.js";
15
- import { validateBlastRadiusScope, validateEdit } from "./validate.js";
16
- /** ZCode parity: the extractor may inspect memory and propose at most five times. */
17
- export const MEMORY_AGENT_MAX_TURNS = 5;
18
- /** Bound one proposal so approval text and engine compensation stay reviewable. */
19
- export const MEMORY_AGENT_MAX_EDITS = 20;
20
- /**
21
- * System-prompt size budget (chars): the prompt ships on EVERY extractor
22
- * turn (up to five per snapshot), so each added line is recurring token
23
- * spend, not one-off text. Raise this cap only in the same commit that
24
- * justifies the added lines (regression pressure beats silent bloat).
25
- */
26
- export const MEMORY_AGENT_SYSTEM_PROMPT_BUDGET_CHARS = 3000;
27
- /** The complete tool-name allowlist exposed by the memory agent. */
28
- export const MEMORY_AGENT_TOOL_NAMES = ["memory_search", "memory_propose"];
29
- /** Provider-facing schemas for the closed memory-only tool set. */
30
- export const MEMORY_AGENT_TOOL_SCHEMAS = [
31
- {
32
- name: "memory_search",
33
- description: "Read full content from the frozen memory manifest. This is the only data-reading tool available to the memory extractor.",
34
- parameters: {
35
- type: "object",
36
- additionalProperties: false,
37
- properties: {
38
- query: { type: "string", description: "Keywords, title text, scope:id, or content terms to retrieve." },
39
- },
40
- required: ["query"],
41
- },
42
- },
43
- {
44
- name: "memory_propose",
45
- description: "Finish extraction with one auditable memory-only proposal. An empty edits array is a valid no-op.",
46
- parameters: {
47
- type: "object",
48
- additionalProperties: false,
49
- properties: {
50
- summary: { type: "string" },
51
- rationale: { type: "string" },
52
- expectedOutcome: { type: "string" },
53
- edits: {
54
- type: "array",
55
- maxItems: MEMORY_AGENT_MAX_EDITS,
56
- items: {
57
- type: "object",
58
- additionalProperties: false,
59
- properties: {
60
- action: { type: "string", enum: ["create", "update", "delete", "archive"] },
61
- kind: { type: "string", enum: ["memory"] },
62
- targetScope: { type: "string", enum: ["local", "project", "global"] },
63
- blastRadius: { type: "string", enum: ["general", "project", "session"] },
64
- id: { type: "string", pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$" },
65
- title: { type: "string" },
66
- content: { type: "string" },
67
- path: { type: "string" },
68
- metadata: {
69
- type: "object",
70
- additionalProperties: false,
71
- properties: { memoryType: { type: "string", enum: ["user", "feedback", "project", "reference"] } },
72
- },
73
- reason: { type: "string" },
74
- },
75
- required: ["action", "kind", "targetScope", "blastRadius"],
76
- },
77
- },
78
- },
79
- required: ["summary", "rationale", "expectedOutcome", "edits"],
80
- },
81
- },
82
- ];
83
- /** Instructions for the dedicated loop; model text is always untrusted. */
84
- export const MEMORY_AGENT_SYSTEM_PROMPT = `You are the dedicated background memory extraction agent for a continual-evolution harness.
85
-
86
- You receive only an incremental conversation checkpoint and a frozen manifest of existing memory entries. Your entire job is to decide whether this checkpoint contains a durable fact worth remembering. Never propose prompt notes, skills, subagent specs, code changes, or general conversation summaries.
87
-
88
- Rules:
89
- - Use memory_search before creating when the manifest may already cover the topic. Update an existing entry instead of creating a near-duplicate.
90
- - Every memory entry contains exactly one fact. Classify it with metadata.memoryType = user | feedback | project | reference.
91
- - feedback and project memories must state the fact, Why it matters, and How to apply it next time.
92
- - Never invent a user preference. Ground every proposal in the supplied conversation evidence and existing memory.
93
- - Every edit must have kind=memory, an explicit targetScope, and a coherent blastRadius.
94
- - targetScope=local is session staging. project persists within the current repository. global persists across projects. project/global require human approval before application.
95
- - update/delete/archive must name an existing id from the same targetScope. Prefer archive over delete when the fact is obsolete but should remain restorable.
96
- - Do not remember: code structure or file paths re-readable from the repository; git history, diffs, commit hashes, or CI logs; content already written in project instruction files; temporary session state, one-off debugging trails, or current task progress; model-only speculation without conversation evidence; an already-applied troubleshooting fix with no recurrence evidence (the fixed state on disk is its own record).
97
- - Be specific enough to act on: prefer a fact that tells the next session what to DO over a summary of what happened. If the fact is obvious from the conversation itself with no future action attached, skip it.
98
- - Save only what is surprising or non-obvious: routine chatter, restated context, and facts with no bearing on future work are no-ops, not memories.
99
- - Granularity examples (vague → reject; sharp → save):
100
- - "user communicates in Chinese" → "user reads English with effort — write memory and handoff content in Chinese prose".
101
- - "user works on the desktop project" → "desktop profile loads npm-installed plugin copies — source edits need a release before they take effect".
102
- - "fixed fcitx5 by setting GTK_IM_MODULE tonight" → reject (one-off applied fix, already on disk, no recurrence evidence).
103
- - You cannot read or write source files, call agents, use MCP, use the network, or mutate the harness directly. memory_propose is the only finish tool and never writes state itself.
104
- - If no durable memory is justified, call memory_propose with edits=[].
105
- - Always finish by calling memory_propose. Do not return unproposed prose.`;
106
- /** Build the frozen memory manifest from a merged harness view. */
107
- export function buildMemoryManifest(state) {
108
- return Object.values(state.entries.memory)
109
- .map((entry) => ({ ...entry, metadata: { ...entry.metadata }, reference: {}, arguments: {} }))
110
- .sort((a, b) => manifestOrder(a).localeCompare(manifestOrder(b), "en"));
111
- }
112
- /** Render a bounded manifest index; full bodies remain available through memory_search. */
113
- export function formatMemoryManifest(entries, maxChars = 8000) {
114
- if (entries.length === 0)
115
- return "No saved memory entries yet.";
116
- const lines = [];
117
- let used = 0;
118
- for (const entry of entries) {
119
- const type = isMemoryType(entry.metadata[MEMORY_TYPE_KEY]) ? entry.metadata[MEMORY_TYPE_KEY] : "untyped";
120
- const archived = isArchived(entry) ? ", archived" : "";
121
- const line = `- [${entry.scope}:${entry.id}] ${entry.title} (memoryType=${type}, path=${entry.path}${archived}): ${compactText(entry.content, 240)}`;
122
- if (used + line.length + 1 > maxChars)
123
- break;
124
- lines.push(line);
125
- used += line.length + 1;
126
- }
127
- const omitted = entries.length - lines.length;
128
- if (omitted > 0)
129
- lines.push(`- +${omitted} more entries; use memory_search with a narrower query.`);
130
- return lines.join("\n");
131
- }
132
- /** Rank frozen manifest entries with CJK-aware token overlap and stable tie-breaking. */
133
- export function searchMemoryManifest(entries, query, maxResults = 8) {
134
- const queryTokens = new Set(tokenize(query));
135
- if (queryTokens.size === 0)
136
- return [];
137
- return entries
138
- .map((entry) => {
139
- const title = tokenize(entry.title);
140
- const body = tokenize(`${entry.content} ${entry.path} ${String(entry.metadata[MEMORY_TYPE_KEY] ?? "")}`);
141
- let score = 0;
142
- for (const token of queryTokens) {
143
- if (title.includes(token))
144
- score += 2;
145
- if (body.includes(token))
146
- score += 1;
147
- }
148
- if (`${entry.scope}:${entry.id}`.toLowerCase() === query.trim().toLowerCase())
149
- score += 100;
150
- return { entry, score };
151
- })
152
- .filter((hit) => hit.score > 0)
153
- .sort((a, b) => b.score - a.score || manifestOrder(a.entry).localeCompare(manifestOrder(b.entry), "en"))
154
- .slice(0, maxResults)
155
- .map((hit) => hit.entry);
156
- }
157
- /**
158
- * Run the dedicated loop against a frozen manifest.
159
- *
160
- * @throws On provider failure, abort, malformed tool arguments, policy denial,
161
- * a non-memory proposal, or exhaustion of the bounded internal turn budget.
162
- */
163
- export async function runMemoryAgent(ctx, options) {
164
- const events = options.trajectoryEvents ?? [];
165
- const routing = resolvePrefixCache(options.prefixCache);
166
- const route = detectPlannerRoute(events, routing.mode);
167
- const prefixMessages = route === "A"
168
- ? buildPrefixMessages(events, { provider: options.provider, model: options.model, maxChars: routing.maxChars })
169
- : [];
170
- const conversation = prefixMessages.length > 0 ? "" : `<conversation>\n${options.trajectory}\n</conversation>`;
171
- const prompt = [
172
- `<memory_manifest>\n${formatMemoryManifest(options.manifest)}\n</memory_manifest>`,
173
- conversation,
174
- "Search before deciding, then call memory_propose with the complete memory-only proposal. Empty edits is a valid no-op.",
175
- ]
176
- .filter(Boolean)
177
- .join("\n\n");
178
- const messages = [
179
- ...prefixMessages,
180
- createUserMessage({
181
- content: [{ type: "text", text: prompt }],
182
- source: EVOLVE_MESSAGE_SOURCE,
183
- }),
184
- ];
185
- let searches = 0;
186
- const language = options.language ?? resolveRecordLanguage({ ctx, trajectoryText: options.trajectory });
187
- const systemPrompt = `${MEMORY_AGENT_SYSTEM_PROMPT}\n\n${recordLanguageInstruction(language)}`;
188
- for (let turn = 1; turn <= MEMORY_AGENT_MAX_TURNS; turn += 1) {
189
- options.signal?.throwIfAborted();
190
- const blocks = await streamModelTurn(ctx, {
191
- provider: options.provider,
192
- model: options.model,
193
- ...(options.sessionId ? { sessionId: options.sessionId } : {}),
194
- system: systemPrompt,
195
- messages,
196
- tools: MEMORY_AGENT_TOOL_SCHEMAS,
197
- requireTextOrToolCall: true,
198
- maxTokens: options.maxOutputTokens ?? 4096,
199
- ...(options.signal ? { signal: options.signal } : {}),
200
- ...(options.tokenUsage
201
- ? { onUsage: createTokenUsageObserver(options.tokenUsage, "memory", options.provider, options.model) }
202
- : {}),
203
- });
204
- const hasText = blocks.some((block) => block.type === "text" && block.text.length > 0);
205
- const hasToolCall = blocks.some((block) => block.type === "tool-call");
206
- if (!hasText && !hasToolCall)
207
- throw new Error("memory agent produced no model output");
208
- messages.push(createAssistantMessage({
209
- content: blocks,
210
- source: { provider: options.provider, model: options.model },
211
- }));
212
- const toolCalls = blocks.filter((block) => block.type === "tool-call");
213
- if (toolCalls.length === 0) {
214
- const text = textOf(blocks);
215
- return {
216
- proposal: parseMemoryExtractionProposal(extractJsonObject(text), options.manifest),
217
- turns: turn,
218
- searches,
219
- };
220
- }
221
- let proposal;
222
- for (const call of toolCalls) {
223
- const execution = executeMemoryTool(call, options.manifest);
224
- if (call.name === "memory_search")
225
- searches += 1;
226
- proposal ??= execution.proposal;
227
- messages.push(createToolResultMessage({
228
- callId: call.id,
229
- content: [{ type: "text", text: execution.text }],
230
- isError: execution.isError,
231
- }));
232
- }
233
- if (proposal)
234
- return { proposal, turns: turn, searches };
235
- }
236
- throw new Error(`memory agent exhausted ${MEMORY_AGENT_MAX_TURNS} internal turns without a valid proposal`);
237
- }
238
- /** Strictly parse and policy-check the structured `memory_propose` payload. */
239
- export function parseMemoryExtractionProposal(value, manifest) {
240
- const record = asRecord(value);
241
- if (!record)
242
- throw new Error("memory proposal must be an object");
243
- const summary = requiredString(record["summary"], "summary");
244
- const rationale = requiredString(record["rationale"], "rationale");
245
- const expectedOutcome = requiredString(record["expectedOutcome"], "expectedOutcome");
246
- if (!Array.isArray(record["edits"]))
247
- throw new Error("memory proposal edits must be an array");
248
- if (record["edits"].length > MEMORY_AGENT_MAX_EDITS) {
249
- throw new Error(`memory proposal exceeds ${MEMORY_AGENT_MAX_EDITS} edits`);
250
- }
251
- const byScopeId = new Map(manifest.map((entry) => [`${entry.scope}:${entry.id}`, entry]));
252
- const edits = record["edits"].map((raw, index) => parseScopedMemoryEdit(raw, index, byScopeId));
253
- return { summary, rationale, expectedOutcome, edits };
254
- }
255
- /**
256
- * Render the authoritative persistent-scope diff shown to the human. Model
257
- * prose is retained only as an explicitly untrusted summary; actions, targets,
258
- * content previews, memory types, and conflict warnings come from code.
259
- */
260
- export function renderMemoryApprovalDetails(proposal, scope, state, maxChars = 6000) {
261
- const edits = proposal.edits.filter((candidate) => candidate.targetScope === scope);
262
- const details = edits.map((edit) => {
263
- const before = edit.id ? state.entries.memory[edit.id] : undefined;
264
- const title = edit.title ?? before?.title ?? "(existing title unchanged)";
265
- const path = edit.path ?? before?.path ?? "(path unchanged)";
266
- const content = edit.content ?? (edit.action === "update" ? "(content unchanged)" : "(no content field)");
267
- const type = edit.metadata?.[MEMORY_TYPE_KEY] ?? before?.metadata[MEMORY_TYPE_KEY] ?? "untyped";
268
- const candidates = Object.values(state.entries.memory).filter((entry) => entry.id !== edit.id);
269
- const hit = edit.action === "create" || edit.action === "update"
270
- ? mostSimilarEntry(candidates, edit.title ?? before?.title ?? "", edit.content ?? before?.content ?? "", CONFLICT_WARN_SCORE)
271
- : undefined;
272
- const conflict = hit ? `; conflict: ${compactText(buildConflictNotice(hit), 120)}` : "";
273
- const targetId = edit.id === undefined ? "(new)" : JSON.stringify(edit.id);
274
- return {
275
- target: `- ${edit.action} ${scope}:${targetId} — ${compactText(title, 80)}`,
276
- detail: ` path=${compactText(path, 60)}; memoryType=${String(type)}; content=${compactText(content, 100)}${conflict}`,
277
- };
278
- });
279
- let titleLimit = 80;
280
- let targets = details.map((detail) => detail.target.replace(/ — .*$/, ` — ${compactText(detail.target.split(" — ")[1] ?? "", titleLimit)}`));
281
- let base = ["edits to approve:", ...targets].join("\n");
282
- while (base.length > maxChars && titleLimit > 20) {
283
- titleLimit = Math.floor(titleLimit / 2);
284
- targets = details.map((detail) => `- ${detail.target.split(" — ")[0]} — ${compactText(detail.target.split(" — ")[1] ?? "", titleLimit)}`);
285
- base = ["edits to approve:", ...targets].join("\n");
286
- }
287
- if (base.length > maxChars)
288
- throw new Error(`memory approval diff exceeds ${maxChars} characters`);
289
- const output = [base];
290
- const summary = `model summary (untrusted): ${compactText(proposal.summary, 180)}`;
291
- if ([...output, summary].join("\n").length <= maxChars)
292
- output.push(summary);
293
- for (const detail of details) {
294
- const line = detail.detail;
295
- if ([...output, line].join("\n").length <= maxChars)
296
- output.push(line);
297
- }
298
- if ([...output, " (additional previews omitted; every target is listed above)"].join("\n").length <= maxChars) {
299
- output.push(" (additional previews omitted; every target is listed above)");
300
- }
301
- return output.join("\n");
302
- }
303
- function memoryScopeDecisionKey(cursor, scope, edits) {
304
- return `${cursor}:${scope}:${JSON.stringify(edits.map(stripTargetScope))}`;
305
- }
306
- class MemoryApplyBatchError extends Error {
307
- result;
308
- scope;
309
- storeId;
310
- constructor(message, result, scope, storeId) {
311
- super(message);
312
- this.result = result;
313
- this.scope = scope;
314
- this.storeId = storeId;
315
- }
316
- }
317
- /**
318
- * Apply one accepted proposal through the only mutation engine. All persistent
319
- * approvals are resolved before the first write, so an unavailable approval
320
- * boundary cannot leave a partially applied batch. Explicit rejection is a
321
- * no-op for that scope; service/question failures remain retryable errors.
322
- */
323
- export async function applyMemoryExtractionProposal(ctx, engine, proposal, options) {
324
- const scopeOrder = ["local", "project", "global"];
325
- const editsByScope = new Map();
326
- for (const scope of scopeOrder) {
327
- const edits = proposal.edits.filter((edit) => edit.targetScope === scope);
328
- if (edits.length > 0)
329
- editsByScope.set(scope, edits);
330
- }
331
- if (editsByScope.has("project") && !options.projectKey) {
332
- throw new Error("memory agent cannot target project scope without a resolved project key");
333
- }
334
- const approvedScopes = new Set();
335
- const declinedScopes = [];
336
- for (const scope of ["project", "global"]) {
337
- if (!editsByScope.has(scope))
338
- continue;
339
- if (!options.requireApproval) {
340
- approvedScopes.add(scope);
341
- continue;
342
- }
343
- const baseline = options.baselines[scope];
344
- if (!baseline)
345
- throw new Error(`memory agent has no ${scope} baseline`);
346
- const scopeEdits = proposal.edits.filter((edit) => edit.targetScope === scope);
347
- const fingerprint = fingerprintMemoryBatch(scope, scopeEdits);
348
- if (isDeclinedRepeat(loadDeclinedMemory(engine.baseDir), scope, fingerprint)) {
349
- ctx.logger("continual-evolve").info(`memory ${scope} proposal repeat-suppressed (declined before, no popup)`);
350
- declinedScopes.push(scope);
351
- continue;
352
- }
353
- const decisionKey = options.decisionCursor === undefined
354
- ? undefined
355
- : memoryScopeDecisionKey(options.decisionCursor, scope, proposal.edits.filter((edit) => edit.targetScope === scope));
356
- const previous = decisionKey === undefined ? undefined : options.scopeDecisions?.[decisionKey];
357
- if (previous === "approved") {
358
- approvedScopes.add(scope);
359
- continue;
360
- }
361
- if (previous === "declined") {
362
- declinedScopes.push(scope);
363
- continue;
364
- }
365
- const approved = await requestScopeApproval(ctx, options.agent, options.signal, scope, renderMemoryApprovalDetails(proposal, scope, baseline), options.recordLanguage);
366
- if (decisionKey !== undefined)
367
- options.onScopeDecision?.(decisionKey, approved);
368
- if (approved === "approved")
369
- approvedScopes.add(scope);
370
- else {
371
- declinedScopes.push(scope);
372
- try {
373
- recordDeclinedMemory(engine.baseDir, scope, scopeEdits, compactText(scopeEdits[0]?.title ?? proposal.summary, 120));
374
- }
375
- catch (error) {
376
- ctx.logger("continual-evolve").warn(`declined-memory ledger write failed: ${error instanceof Error ? error.message : String(error)}`);
377
- }
378
- }
379
- }
380
- const results = [];
381
- const applied = [];
382
- try {
383
- for (const scope of scopeOrder) {
384
- const edits = editsByScope.get(scope);
385
- if (!edits)
386
- continue;
387
- if (scope !== "local" && !approvedScopes.has(scope))
388
- continue;
389
- options.signal?.throwIfAborted();
390
- const storeId = scope === "local" ? options.agent.id : scope === "project" ? options.projectKey : undefined;
391
- const baseline = options.baselines[scope];
392
- if (!baseline)
393
- throw new Error(`memory agent has no ${scope} baseline`);
394
- const result = engine.apply(scope, storeId, {
395
- summary: proposal.summary,
396
- rationale: proposal.rationale,
397
- expectedOutcome: proposal.expectedOutcome,
398
- edits: prepareMemoryEdits(edits, baseline),
399
- }, {
400
- scope,
401
- baselineState: baseline,
402
- ...(options.source ? { source: options.source } : {}),
403
- });
404
- const failed = result.appliedEdits.filter((edit) => !edit.applied);
405
- if (failed.length > 0) {
406
- throw new MemoryApplyBatchError(`${scope} batch ${result.id} left ${failed.length} edit(s) unapplied: ${failed.map((edit) => edit.error ?? "unknown").join("; ")}`, result, scope, storeId);
407
- }
408
- results.push(result);
409
- applied.push({ scope, storeId, result });
410
- }
411
- return { results, declinedScopes };
412
- }
413
- catch (cause) {
414
- const attempted = [...applied];
415
- if (cause instanceof MemoryApplyBatchError && cause.result && cause.scope) {
416
- attempted.push({ scope: cause.scope, storeId: cause.storeId, result: cause.result });
417
- }
418
- else if (cause instanceof EvolutionApplyPostCommitError) {
419
- attempted.push({ scope: cause.scope, storeId: cause.storeId, result: cause.result });
420
- }
421
- const rollbackErrors = [];
422
- for (const item of attempted.reverse()) {
423
- try {
424
- const rollback = engine.rollbackResult(item.scope, item.storeId, item.result);
425
- const failed = rollback.appliedEdits.filter((edit) => !edit.applied);
426
- if (failed.length > 0)
427
- rollbackErrors.push(`${item.scope}:${item.result.id} left ${failed.length} rollback edit(s) unapplied`);
428
- }
429
- catch (rollbackCause) {
430
- rollbackErrors.push(`${item.scope}:${item.result.id} rollback failed: ${rollbackCause instanceof Error ? rollbackCause.message : String(rollbackCause)}`);
431
- }
432
- }
433
- const suffix = rollbackErrors.length > 0 ? `; compensation errors: ${rollbackErrors.join("; ")}` : "; prior memory writes were rolled back";
434
- throw new Error(`memory agent apply failed: ${cause instanceof Error ? cause.message : String(cause)}${suffix}`);
435
- }
436
- }
437
- function executeMemoryTool(call, manifest) {
438
- if (!MEMORY_AGENT_TOOL_NAMES.includes(call.name)) {
439
- return { text: `tool denied: only ${MEMORY_AGENT_TOOL_NAMES.join(" and ")} are available`, isError: true };
440
- }
441
- try {
442
- const args = parseJsonCandidate(call.arguments.trim() || "{}");
443
- if (call.name === "memory_search") {
444
- const record = asRecord(args);
445
- const query = record ? requiredString(record["query"], "query") : "";
446
- const matches = searchMemoryManifest(manifest, query).map(manifestResult);
447
- return { text: JSON.stringify({ matches }), isError: false };
448
- }
449
- return { proposal: parseMemoryExtractionProposal(args, manifest), text: JSON.stringify({ accepted: true }), isError: false };
450
- }
451
- catch (cause) {
452
- return {
453
- text: `tool error: ${cause instanceof Error ? cause.message : String(cause)}`,
454
- isError: true,
455
- };
456
- }
457
- }
458
- function parseScopedMemoryEdit(raw, index, byScopeId) {
459
- const record = asRecord(raw);
460
- if (!record)
461
- throw new Error(`memory edit ${index} must be an object`);
462
- const action = requiredString(record["action"], `edits[${index}].action`);
463
- if (!isOneOf(action, ["create", "update", "delete", "archive"])) {
464
- throw new Error(`memory edit ${index} has unsupported action ${action}`);
465
- }
466
- if (record["kind"] !== "memory")
467
- throw new Error(`memory edit ${index} must have kind=memory`);
468
- const targetScope = requiredString(record["targetScope"], `edits[${index}].targetScope`);
469
- if (!isOneOf(targetScope, ["local", "project", "global"])) {
470
- throw new Error(`memory edit ${index} has unsupported targetScope ${targetScope}`);
471
- }
472
- const blastRadius = requiredString(record["blastRadius"], `edits[${index}].blastRadius`);
473
- if (!isOneOf(blastRadius, ["general", "project", "session"])) {
474
- throw new Error(`memory edit ${index} has unsupported blastRadius ${blastRadius}`);
475
- }
476
- const blastError = validateBlastRadiusScope(targetScope, blastRadius);
477
- if (blastError)
478
- throw new Error(`memory edit ${index}: ${blastError}`);
479
- if (record["reference"] !== undefined || record["arguments"] !== undefined || record["skill_kind"] !== undefined) {
480
- throw new Error(`memory edit ${index} cannot carry skill-only fields`);
481
- }
482
- const edit = {
483
- action,
484
- kind: "memory",
485
- targetScope,
486
- blastRadius,
487
- };
488
- for (const field of ["id", "title", "content", "path", "reason"]) {
489
- const fieldValue = record[field];
490
- if (fieldValue !== undefined) {
491
- if (typeof fieldValue !== "string")
492
- throw new Error(`memory edit ${index}.${field} must be a string`);
493
- edit[field] = fieldValue;
494
- }
495
- }
496
- if (record["metadata"] !== undefined) {
497
- const metadata = asRecord(record["metadata"]);
498
- if (!metadata)
499
- throw new Error(`memory edit ${index}.metadata must be an object`);
500
- const unexpected = Object.keys(metadata).filter((key) => key !== MEMORY_TYPE_KEY);
501
- if (unexpected.length > 0) {
502
- throw new Error(`memory edit ${index}.metadata contains engine-owned or unsupported keys: ${unexpected.join(", ")}`);
503
- }
504
- edit.metadata = { ...metadata };
505
- }
506
- if (action === "create" && edit.id && !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(edit.id)) {
507
- throw new Error(`memory edit ${index}.id contains unsupported identifier characters`);
508
- }
509
- const before = edit.id ? byScopeId.get(`${targetScope}:${edit.id}`) : undefined;
510
- if (action !== "create" && !before) {
511
- throw new Error(`memory edit ${index} ${action} target does not exist in ${targetScope}: ${edit.id ?? "(missing id)"}`);
512
- }
513
- if (action === "create" && edit.id && before) {
514
- throw new Error(`memory edit ${index} create duplicates ${targetScope}:${edit.id}; use update instead`);
515
- }
516
- const validationError = validateEdit(edit, edit.id ?? slug(edit.title ?? "memory", "memory"), targetScope, before);
517
- if (validationError)
518
- throw new Error(`memory edit ${index}: ${validationError}`);
519
- return edit;
520
- }
521
- function prepareMemoryEdits(edits, baseline) {
522
- return edits.map((edit) => {
523
- const plain = stripTargetScope(edit);
524
- if (plain.action !== "update" || plain.metadata === undefined || plain.id === undefined)
525
- return plain;
526
- const before = baseline.entries.memory[plain.id];
527
- return before ? { ...plain, metadata: { ...before.metadata, ...plain.metadata } } : plain;
528
- });
529
- }
530
- function stripTargetScope(edit) {
531
- const { targetScope: _targetScope, ...plain } = edit;
532
- return plain;
533
- }
534
- function manifestResult(entry) {
535
- return {
536
- scope: entry.scope,
537
- id: entry.id,
538
- title: entry.title,
539
- content: entry.content,
540
- path: entry.path,
541
- memoryType: entry.metadata[MEMORY_TYPE_KEY],
542
- archived: isArchived(entry),
543
- };
544
- }
545
- function manifestOrder(entry) {
546
- const scope = entry.scope === "global" ? "0" : entry.scope === "project" ? "1" : "2";
547
- return `${scope}:${entry.id}:${entry.title}`;
548
- }
549
- function textOf(blocks) {
550
- return blocks
551
- .filter((block) => block.type === "text")
552
- .map((block) => block.text)
553
- .join("\n");
554
- }
555
- function asRecord(value) {
556
- if (typeof value !== "object" || value === null || Array.isArray(value))
557
- return undefined;
558
- return value;
559
- }
560
- function requiredString(value, field) {
561
- if (typeof value !== "string" || value.trim().length === 0)
562
- throw new Error(`${field} must be a non-empty string`);
563
- return value;
564
- }
565
- function isOneOf(value, values) {
566
- return values.includes(value);
567
- }
568
- //# sourceMappingURL=memory-agent.js.map
@@ -1,76 +0,0 @@
1
- import type { HarnessEntry } from "./types.js";
2
- /** One labeled fact the extractor should (or, when outdated, should not) produce. */
3
- export interface MemoryBenchmarkFact {
4
- id: string;
5
- memoryType: string;
6
- /** Key phrases; an edit matches when its title+content carry every phrase. */
7
- mustContain: string[];
8
- /** Outdated facts must be avoided (create/update) or removed (archive/delete). */
9
- outdated?: boolean;
10
- }
11
- export interface MemoryBenchmarkReference {
12
- facts: MemoryBenchmarkFact[];
13
- }
14
- /** One proposed edit in the shape the memory loop emits. */
15
- export interface MemoryBenchmarkEdit {
16
- action: "create" | "update" | "delete" | "archive";
17
- title?: string;
18
- content?: string;
19
- /** Target entry id for update/delete/archive. */
20
- id?: string;
21
- }
22
- export interface ScoredMemoryBenchmarkEdit {
23
- action: MemoryBenchmarkEdit["action"];
24
- matchedFactIds: string[];
25
- duplicateOf?: string;
26
- staleReason?: string;
27
- noisyReason?: string;
28
- }
29
- export interface MemoryBenchmarkScore {
30
- totalEdits: number;
31
- matchedFacts: number;
32
- precision: number;
33
- recall: number;
34
- duplicate: number;
35
- stale: number;
36
- noise: number;
37
- uncoveredFactIds: string[];
38
- edits: ScoredMemoryBenchmarkEdit[];
39
- }
40
- export interface MemoryGradeThresholds {
41
- minPrecision: number;
42
- minRecall: number;
43
- maxDuplicate: number;
44
- maxStale: number;
45
- maxNoise: number;
46
- }
47
- /** Strict-by-default gate: duplicates and stale operations never pass unnoticed. */
48
- export declare const DEFAULT_MEMORY_GRADE_THRESHOLDS: MemoryGradeThresholds;
49
- /**
50
- * v1 exclusion heuristics for noise ("不该记什么" in mechanical form):
51
- * git history, repo paths re-readable from disk, stack traces, and
52
- * explicitly temporary session state. Keep this list short and documented —
53
- * every pattern here is a precision/recall tradeoff, not ground truth.
54
- */
55
- export declare const MEMORY_NOISE_PATTERNS: readonly RegExp[];
56
- /** Content shorter than this is too thin to be a durable fact. */
57
- export declare const MEMORY_MIN_FACT_CHARS = 30;
58
- /**
59
- * Score one proposal against the reference and the pre-extraction manifest.
60
- *
61
- * @param reference Labeled facts (live + outdated).
62
- * @param edits The proposal's memory edits.
63
- * @param manifest Memory entries visible before extraction (duplicate source).
64
- * @returns Per-dimension counts plus a per-edit breakdown.
65
- */
66
- export declare function scoreMemoryExtraction(reference: MemoryBenchmarkReference, edits: readonly MemoryBenchmarkEdit[], manifest: readonly HarnessEntry[]): MemoryBenchmarkScore;
67
- /**
68
- * Grade a score against explicit thresholds.
69
- *
70
- * @returns pass plus one failure line per violated dimension (empty when passing).
71
- */
72
- export declare function gradeMemoryScore(score: MemoryBenchmarkScore, thresholds?: MemoryGradeThresholds): {
73
- pass: boolean;
74
- failures: string[];
75
- };
76
- //# sourceMappingURL=memory-benchmark.d.ts.map