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.
Files changed (161) hide show
  1. package/README.md +42 -115
  2. package/README.zh.md +33 -93
  3. package/client/client.js +253 -0
  4. package/cordis.patch.yml +2 -2
  5. package/lib/card-routes.d.ts +32 -0
  6. package/lib/card-routes.js +92 -0
  7. package/lib/index.d.ts +65 -217
  8. package/lib/index.js +74 -234
  9. package/lib/known-workspaces.d.ts +22 -0
  10. package/lib/known-workspaces.js +42 -0
  11. package/lib/memory-guide.d.ts +13 -14
  12. package/lib/memory-guide.js +34 -21
  13. package/lib/memory-section.d.ts +77 -0
  14. package/lib/memory-section.js +189 -0
  15. package/lib/memory-snapshot.d.ts +54 -0
  16. package/lib/memory-snapshot.js +125 -0
  17. package/package.json +8 -17
  18. package/lib/apply.d.ts +0 -22
  19. package/lib/apply.js +0 -157
  20. package/lib/approval.d.ts +0 -73
  21. package/lib/approval.js +0 -127
  22. package/lib/audit-log.d.ts +0 -19
  23. package/lib/audit-log.js +0 -52
  24. package/lib/auto.d.ts +0 -236
  25. package/lib/auto.js +0 -773
  26. package/lib/autocase.d.ts +0 -38
  27. package/lib/autocase.js +0 -76
  28. package/lib/benchmark-command.d.ts +0 -9
  29. package/lib/benchmark-command.js +0 -357
  30. package/lib/benchmark.d.ts +0 -151
  31. package/lib/benchmark.js +0 -250
  32. package/lib/command-util.d.ts +0 -70
  33. package/lib/command-util.js +0 -123
  34. package/lib/command.d.ts +0 -13
  35. package/lib/command.js +0 -134
  36. package/lib/consolidate.d.ts +0 -90
  37. package/lib/consolidate.js +0 -180
  38. package/lib/copy.d.ts +0 -37
  39. package/lib/copy.js +0 -58
  40. package/lib/declines.d.ts +0 -103
  41. package/lib/declines.js +0 -179
  42. package/lib/evaluate.d.ts +0 -74
  43. package/lib/evaluate.js +0 -271
  44. package/lib/evolve-event.d.ts +0 -40
  45. package/lib/evolve-event.js +0 -60
  46. package/lib/extraction-phase.d.ts +0 -132
  47. package/lib/extraction-phase.js +0 -236
  48. package/lib/failures.d.ts +0 -42
  49. package/lib/failures.js +0 -171
  50. package/lib/fate.d.ts +0 -130
  51. package/lib/fate.js +0 -350
  52. package/lib/goal-command.d.ts +0 -7
  53. package/lib/goal-command.js +0 -37
  54. package/lib/goal.d.ts +0 -72
  55. package/lib/goal.js +0 -72
  56. package/lib/inject.d.ts +0 -90
  57. package/lib/inject.js +0 -164
  58. package/lib/injection-caps.d.ts +0 -4
  59. package/lib/injection-caps.js +0 -4
  60. package/lib/injection-directory.d.ts +0 -29
  61. package/lib/injection-directory.js +0 -78
  62. package/lib/injection-rank.d.ts +0 -85
  63. package/lib/injection-rank.js +0 -161
  64. package/lib/listener.d.ts +0 -45
  65. package/lib/listener.js +0 -199
  66. package/lib/llm-text.d.ts +0 -104
  67. package/lib/llm-text.js +0 -140
  68. package/lib/logfile.d.ts +0 -71
  69. package/lib/logfile.js +0 -159
  70. package/lib/maintenance-commands.d.ts +0 -23
  71. package/lib/maintenance-commands.js +0 -136
  72. package/lib/memory-agent.d.ts +0 -93
  73. package/lib/memory-agent.js +0 -300
  74. package/lib/memory-apply.d.ts +0 -44
  75. package/lib/memory-apply.js +0 -202
  76. package/lib/memory-benchmark.d.ts +0 -76
  77. package/lib/memory-benchmark.js +0 -170
  78. package/lib/memory-index.d.ts +0 -114
  79. package/lib/memory-index.js +0 -197
  80. package/lib/memory-manifest.d.ts +0 -16
  81. package/lib/memory-manifest.js +0 -70
  82. package/lib/message-source.d.ts +0 -23
  83. package/lib/message-source.js +0 -12
  84. package/lib/mount-command.d.ts +0 -10
  85. package/lib/mount-command.js +0 -48
  86. package/lib/mount.d.ts +0 -42
  87. package/lib/mount.js +0 -212
  88. package/lib/notify.d.ts +0 -48
  89. package/lib/notify.js +0 -57
  90. package/lib/plan-command.d.ts +0 -12
  91. package/lib/plan-command.js +0 -38
  92. package/lib/plan.d.ts +0 -16
  93. package/lib/plan.js +0 -126
  94. package/lib/planner.d.ts +0 -61
  95. package/lib/planner.js +0 -144
  96. package/lib/pool.d.ts +0 -7
  97. package/lib/pool.js +0 -25
  98. package/lib/prefix-cache.d.ts +0 -79
  99. package/lib/prefix-cache.js +0 -168
  100. package/lib/project.d.ts +0 -28
  101. package/lib/project.js +0 -66
  102. package/lib/projection.d.ts +0 -22
  103. package/lib/projection.js +0 -112
  104. package/lib/promotion.d.ts +0 -94
  105. package/lib/promotion.js +0 -171
  106. package/lib/recall.d.ts +0 -85
  107. package/lib/recall.js +0 -142
  108. package/lib/record-language.d.ts +0 -85
  109. package/lib/record-language.js +0 -152
  110. package/lib/render.d.ts +0 -13
  111. package/lib/render.js +0 -80
  112. package/lib/review-scheduler.d.ts +0 -38
  113. package/lib/review-scheduler.js +0 -183
  114. package/lib/review.d.ts +0 -60
  115. package/lib/review.js +0 -145
  116. package/lib/rollback.d.ts +0 -9
  117. package/lib/rollback.js +0 -68
  118. package/lib/rubric.d.ts +0 -29
  119. package/lib/rubric.js +0 -119
  120. package/lib/runtime-commands.d.ts +0 -14
  121. package/lib/runtime-commands.js +0 -115
  122. package/lib/runtime.d.ts +0 -24
  123. package/lib/runtime.js +0 -87
  124. package/lib/score.d.ts +0 -64
  125. package/lib/score.js +0 -191
  126. package/lib/search.d.ts +0 -83
  127. package/lib/search.js +0 -136
  128. package/lib/service.d.ts +0 -63
  129. package/lib/service.js +0 -253
  130. package/lib/skill-render.d.ts +0 -23
  131. package/lib/skill-render.js +0 -69
  132. package/lib/skill.d.ts +0 -15
  133. package/lib/skill.js +0 -80
  134. package/lib/skillquality.d.ts +0 -85
  135. package/lib/skillquality.js +0 -320
  136. package/lib/source.d.ts +0 -30
  137. package/lib/source.js +0 -43
  138. package/lib/state.d.ts +0 -26
  139. package/lib/state.js +0 -151
  140. package/lib/store.d.ts +0 -61
  141. package/lib/store.js +0 -164
  142. package/lib/token-usage.d.ts +0 -68
  143. package/lib/token-usage.js +0 -211
  144. package/lib/tool.d.ts +0 -34
  145. package/lib/tool.js +0 -320
  146. package/lib/turn-snapshot.d.ts +0 -78
  147. package/lib/turn-snapshot.js +0 -252
  148. package/lib/types.d.ts +0 -218
  149. package/lib/types.js +0 -133
  150. package/lib/usage.d.ts +0 -36
  151. package/lib/usage.js +0 -98
  152. package/lib/validate.d.ts +0 -28
  153. package/lib/validate.js +0 -181
  154. package/lib/wrapup-candidates.d.ts +0 -127
  155. package/lib/wrapup-candidates.js +0 -161
  156. package/lib/wrapup-command.d.ts +0 -10
  157. package/lib/wrapup-command.js +0 -243
  158. package/lib/wrapup-proposals.d.ts +0 -101
  159. package/lib/wrapup-proposals.js +0 -281
  160. package/lib/wrapup.d.ts +0 -56
  161. package/lib/wrapup.js +0 -105
@@ -1,23 +0,0 @@
1
- import type { Context } from "@deepseek-ai/cordis";
2
- import type { CommandInvocation } from "@deepseek-ai/dsh-commands";
3
- import { type HarnessScope } from "./types.js";
4
- import type { EvolutionEngine } from "./service.js";
5
- import { type CommandTextResult } from "./command-util.js";
6
- type ScopeArg = {
7
- scope: HarnessScope;
8
- rest: string[];
9
- };
10
- /** `/evolve archive|unarchive <id> [global|project]` — hide or restore an entry. */
11
- export declare function executeArchiveCommand(_ctx: Context, engine: EvolutionEngine, invocation: CommandInvocation, sub: "archive" | "unarchive", parsed: ScopeArg, usage: string): Promise<CommandTextResult>;
12
- /** `/evolve consolidate [apply] [merge]` — deterministic global-store hygiene. */
13
- export declare function executeConsolidateCommand(engine: EvolutionEngine, rest: string[]): CommandTextResult;
14
- /** `/evolve failures` — failure-signature aggregation (D1 observation). */
15
- export declare function executeFailuresCommand(engine: EvolutionEngine): CommandTextResult;
16
- /** `/evolve log [tail N] [session <sessionId>]` — plugin file log inspection. */
17
- export declare function executeLogCommand(engine: EvolutionEngine, rest: string[], usage: string): CommandTextResult;
18
- /** `/evolve export [global|project] <path>` — backup a store to a JSON file. */
19
- export declare function executeExportCommand(engine: EvolutionEngine, invocation: CommandInvocation, parsed: ScopeArg, usage: string): CommandTextResult;
20
- /** `/evolve import [global|project] <path>` — restore a store from an export file. */
21
- export declare function executeImportCommand(engine: EvolutionEngine, invocation: CommandInvocation, parsed: ScopeArg, path: string | undefined): CommandTextResult;
22
- export {};
23
- //# sourceMappingURL=maintenance-commands.d.ts.map
@@ -1,136 +0,0 @@
1
- /**
2
- * `/evolve` maintenance and observability subcommands: global-store
3
- * consolidation, failure aggregation, plugin log inspection, store
4
- * export/import backup, and entry archive/unarchive.
5
- */
6
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
7
- import { ARCHIVED_AT_KEY } from "./types.js";
8
- import { filterLogBySession, formatLogLine, pluginLogFilePath } from "./logfile.js";
9
- import { collectFailureSummary, formatFailureSummary } from "./failures.js";
10
- import { planConsolidation } from "./consolidate.js";
11
- import { loadUsage } from "./usage.js";
12
- import { findEntryById, parsePositiveInt, renderResult, stripAngleBrackets, storeIdForCommand, success, error, } from "./command-util.js";
13
- /** `/evolve archive|unarchive <id> [global|project]` — hide or restore an entry. */
14
- export async function executeArchiveCommand(_ctx, engine, invocation, sub, parsed, usage) {
15
- const id = stripAngleBrackets(parsed.rest[0] ?? "");
16
- if (!id) {
17
- return error(`${sub} requires an entry id.\n${usage}`);
18
- }
19
- const state = engine.load(parsed.scope, storeIdForCommand(parsed.scope, invocation));
20
- const found = findEntryById(state, id);
21
- if (!found) {
22
- return error(`entry ${id} not found in the ${parsed.scope} store`);
23
- }
24
- const [kind, entry] = found;
25
- const metadata = { ...entry.metadata };
26
- if (sub === "archive") {
27
- metadata[ARCHIVED_AT_KEY] = new Date().toISOString();
28
- }
29
- else {
30
- delete metadata[ARCHIVED_AT_KEY];
31
- }
32
- const archived = sub === "archive";
33
- const result = engine.apply(parsed.scope, storeIdForCommand(parsed.scope, invocation), {
34
- summary: `${archived ? "Archive" : "Unarchive"} entry ${kind}:${id}`,
35
- rationale: "Human-invoked archive/unarchive via the /evolve command.",
36
- expectedOutcome: `Entry ${archived ? "is hidden from injection (data kept, restorable)" : "is injected again"}.`,
37
- edits: [{ action: "update", kind, id, title: entry.title, content: entry.content, metadata }],
38
- }, { scope: parsed.scope });
39
- return success(renderResult(result));
40
- }
41
- /** `/evolve consolidate [apply] [merge]` — deterministic global-store hygiene. */
42
- export function executeConsolidateCommand(engine, rest) {
43
- // R3: deterministic global-store hygiene. Report by default; `apply`
44
- // re-scans fresh state and lands the whole batch as ONE refinement
45
- // (single snapshot + audit record, fully rollback-able). `merge`
46
- // (P1 反膨胀) additionally merges conflict-pair content into the
47
- // surviving original instead of only archiving.
48
- const apply = rest[0] === "apply";
49
- const merge = rest.includes("merge");
50
- const state = engine.load("global", undefined);
51
- const { candidates, edits } = planConsolidation(state, loadUsage(engine.baseDir), Date.now(), { mergeDuplicates: merge });
52
- if (candidates.length === 0) {
53
- return success("global store is already consolidated — no conflict-hinted or stale zero-use entries.");
54
- }
55
- const mergeCount = merge ? candidates.filter((candidate) => candidate.mergeInto).length : 0;
56
- const report = candidates
57
- .map((candidate, index) => {
58
- const mergeNote = candidate.mergeInto && merge ? ` → 内容并入 ${candidate.mergeInto.id}` : "";
59
- return `${index + 1}. [${candidate.kind}:${candidate.id}] ${candidate.title}${mergeNote}\n ${candidate.reason}`;
60
- })
61
- .join("\n");
62
- if (!apply) {
63
- return success(`consolidation plan — ${candidates.length} archive candidate(s):\n${report}\n(run "/evolve consolidate apply" to archive all of them in one refinement; add "merge" to fold near-duplicate content into the survivors)`);
64
- }
65
- const result = engine.apply("global", undefined, {
66
- summary: `Consolidate global store: archive ${candidates.length} entries${mergeCount > 0 ? `, merge ${mergeCount} into survivors` : ""}`,
67
- rationale: "Human-invoked batch consolidation via /evolve consolidate apply.",
68
- expectedOutcome: "Candidate entries are hidden from injection (data kept; restorable via /evolve unarchive). Merged survivors carry the near-duplicate content with a mergedFrom provenance stamp.",
69
- edits,
70
- }, { scope: "global" });
71
- return success(`${report}\n\napplied:\n${renderResult(result)}`);
72
- }
73
- /** `/evolve failures` — failure-signature aggregation (D1 observation). */
74
- export function executeFailuresCommand(engine) {
75
- const { summary, records } = collectFailureSummary(engine.baseDir);
76
- const parts = formatFailureSummary(summary).split("\n");
77
- const recent = records
78
- .sort((a, b) => (b.timestamp ?? "").localeCompare(a.timestamp ?? ""))
79
- .slice(0, 10)
80
- .map((f) => ` [${f.timestamp ?? "(benchmark)"}] ${f.kind} · ${f.source}: ${f.message.slice(0, 140)}`);
81
- if (recent.length > 0) {
82
- parts.push("recent 10:");
83
- parts.push(...recent);
84
- }
85
- return success(parts.join("\n"));
86
- }
87
- /** `/evolve log [tail N] [session <sessionId>]` — plugin file log inspection. */
88
- export function executeLogCommand(engine, rest, usage) {
89
- let tail = 50;
90
- let sessionFilter;
91
- for (let i = 0; i < rest.length; i += 1) {
92
- const token = rest[i] ?? "";
93
- if (token === "session") {
94
- sessionFilter = stripAngleBrackets(rest[i + 1] ?? "");
95
- if (!sessionFilter) {
96
- return error(`log session requires a session id (e.g. /evolve log session session-abc123).\n${usage}`);
97
- }
98
- i += 1;
99
- }
100
- else {
101
- tail = Math.min(Math.max(parsePositiveInt(token, "tail"), 1), 1000);
102
- }
103
- }
104
- const path = pluginLogFilePath(engine.baseDir);
105
- if (!existsSync(path)) {
106
- return success(`(no plugin log yet — ${path} is created on the first log message)`);
107
- }
108
- const lines = readFileSync(path, "utf8").trimEnd().split("\n").filter((line) => line.length > 0);
109
- if (lines.length === 0) {
110
- return success(`(empty plugin log: ${path})`);
111
- }
112
- const filtered = sessionFilter ? filterLogBySession(lines, sessionFilter) : lines;
113
- const shown = filtered.slice(-tail);
114
- const scopeNote = sessionFilter ? `, ${filtered.length} for session ${sessionFilter}` : "";
115
- return success(`plugin log ${path} (${lines.length} lines${scopeNote}, showing last ${shown.length}):\n${shown.map(formatLogLine).join("\n")}`);
116
- }
117
- /** `/evolve export [global|project] <path>` — backup a store to a JSON file. */
118
- export function executeExportCommand(engine, invocation, parsed, usage) {
119
- const path = parsed.rest[0];
120
- if (!path) {
121
- return error(`export requires an output path.\n${usage}`);
122
- }
123
- const payload = engine.exportStore(parsed.scope, storeIdForCommand(parsed.scope, invocation));
124
- writeFileSync(path, `${JSON.stringify(payload, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
125
- return success(`exported ${parsed.scope} store (${Object.values(payload.entries).reduce((n, e) => n + Object.keys(e).length, 0)} entries, ${payload.history.length} refinements) to ${path}`);
126
- }
127
- /** `/evolve import [global|project] <path>` — restore a store from an export file. */
128
- export function executeImportCommand(engine, invocation, parsed, path) {
129
- if (!path) {
130
- return error(`import requires an input path.`);
131
- }
132
- const payload = JSON.parse(readFileSync(path, "utf8"));
133
- const summary = engine.importStore(parsed.scope, storeIdForCommand(parsed.scope, invocation), payload);
134
- return success(`imported ${parsed.scope} store from ${path} (${summary.entries} entries, ${summary.refinements} refinements)`);
135
- }
136
- //# sourceMappingURL=maintenance-commands.js.map
@@ -1,93 +0,0 @@
1
- /**
2
- * Dedicated background memory extraction agent.
3
- *
4
- * This is the DSH counterpart of ZCode's project-memory agent loop. It keeps
5
- * an independent request history, exposes only a frozen memory manifest and a
6
- * structured proposal tool, and never writes state itself. Every accepted edit
7
- * is handed to EvolutionEngine so scope approval, snapshots, versioning,
8
- * rollback, and audit remain mandatory.
9
- *
10
- * Split (2026-10-03 refactor): the manifest view lives in
11
- * `memory-manifest.ts`, proposal application in `memory-apply.ts`.
12
- */
13
- import type { Context } from "@deepseek-ai/cordis";
14
- import { type ToolSchema } from "@deepseek-ai/dsh-llm";
15
- import { type LlmSessionId } from "./llm-text.js";
16
- import { type MemoryManifestEntry } from "./memory-manifest.js";
17
- import { type PrefixCacheOptions } from "./prefix-cache.js";
18
- import { type TokenUsageTarget } from "./token-usage.js";
19
- import { type RecordLanguage } from "./record-language.js";
20
- import type { HarnessScope, HarnessState, RefinementEdit, RefinementProposal, RefinementResult } from "./types.js";
21
- /** ZCode parity: the extractor may inspect memory and propose at most five times. */
22
- export declare const MEMORY_AGENT_MAX_TURNS = 5;
23
- /** Bound one proposal so approval text and engine compensation stay reviewable. */
24
- export declare const MEMORY_AGENT_MAX_EDITS = 20;
25
- /**
26
- * System-prompt size budget (chars): the prompt ships on EVERY extractor
27
- * turn (up to five per snapshot), so each added line is recurring token
28
- * spend, not one-off text. Raise this cap only in the same commit that
29
- * justifies the added lines (regression pressure beats silent bloat).
30
- */
31
- export declare const MEMORY_AGENT_SYSTEM_PROMPT_BUDGET_CHARS = 3000;
32
- /** The complete tool-name allowlist exposed by the memory agent. */
33
- export declare const MEMORY_AGENT_TOOL_NAMES: readonly ["memory_search", "memory_propose"];
34
- export type MemoryAgentToolName = (typeof MEMORY_AGENT_TOOL_NAMES)[number];
35
- /** One frozen memory entry visible to the extractor for the whole run. */
36
- /** A memory-only edit with an explicit persistence target. */
37
- export type ScopedMemoryEdit = RefinementEdit & {
38
- kind: "memory";
39
- targetScope: HarnessScope;
40
- };
41
- /** Structured result returned by `memory_propose`. */
42
- export interface MemoryExtractionProposal extends Omit<RefinementProposal, "edits"> {
43
- edits: ScopedMemoryEdit[];
44
- }
45
- /** Baseline states captured before the first extractor model turn. */
46
- export interface MemoryScopeBaselines {
47
- local: HarnessState;
48
- project?: HarnessState;
49
- global: HarnessState;
50
- }
51
- /** Result of one bounded extractor loop. */
52
- export interface MemoryAgentRun {
53
- proposal: MemoryExtractionProposal;
54
- turns: number;
55
- searches: number;
56
- }
57
- /** Scoped results after the proposal crosses the governed engine boundary. */
58
- export interface MemoryApplicationResult {
59
- results: RefinementResult[];
60
- declinedScopes: HarnessScope[];
61
- }
62
- /** Provider-facing schemas for the closed memory-only tool set. */
63
- export declare const MEMORY_AGENT_TOOL_SCHEMAS: readonly ToolSchema[];
64
- /** Instructions for the dedicated loop; model text is always untrusted. */
65
- export declare const MEMORY_AGENT_SYSTEM_PROMPT = "You are the dedicated background memory extraction agent for a continual-evolution harness.\n\nYou 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.\n\nRules:\n- Use memory_search before creating when the manifest may already cover the topic. Update an existing entry instead of creating a near-duplicate.\n- Every memory entry contains exactly one fact. Classify it with metadata.memoryType = user | feedback | project | reference.\n- feedback and project memories must state the fact, Why it matters, and How to apply it next time.\n- Never invent a user preference. Ground every proposal in the supplied conversation evidence and existing memory.\n- Every edit must have kind=memory, an explicit targetScope, and a coherent blastRadius.\n- targetScope=local is session staging. project persists within the current repository. global persists across projects. project/global require human approval before application.\n- 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.\n- 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).\n- 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.\n- 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.\n- Granularity examples (vague \u2192 reject; sharp \u2192 save):\n - \"user communicates in Chinese\" \u2192 \"user reads English with effort \u2014 write memory and handoff content in Chinese prose\".\n - \"user works on the desktop project\" \u2192 \"desktop profile loads npm-installed plugin copies \u2014 source edits need a release before they take effect\".\n - \"fixed fcitx5 by setting GTK_IM_MODULE tonight\" \u2192 reject (one-off applied fix, already on disk, no recurrence evidence).\n- 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.\n- If no durable memory is justified, call memory_propose with edits=[].\n- Always finish by calling memory_propose. Do not return unproposed prose.";
66
- /** Build the frozen memory manifest from a merged harness view. */ export interface MemoryAgentOptions {
67
- provider: string;
68
- model: string;
69
- sessionId?: LlmSessionId | string;
70
- manifest: readonly MemoryManifestEntry[];
71
- trajectory: string;
72
- trajectoryEvents?: readonly unknown[];
73
- prefixCache?: PrefixCacheOptions;
74
- maxOutputTokens?: number;
75
- signal?: AbortSignal;
76
- tokenUsage?: TokenUsageTarget;
77
- /**
78
- * Authoring language for the proposal. Absent → resolved per call
79
- * (explicit config upstream, else durable client preference, else
80
- * trajectory detection, else `en`).
81
- */
82
- language?: RecordLanguage;
83
- }
84
- /**
85
- * Run the dedicated loop against a frozen manifest.
86
- *
87
- * @throws On provider failure, abort, malformed tool arguments, policy denial,
88
- * a non-memory proposal, or exhaustion of the bounded internal turn budget.
89
- */
90
- export declare function runMemoryAgent(ctx: Context, options: MemoryAgentOptions): Promise<MemoryAgentRun>;
91
- /** Strictly parse and policy-check the structured `memory_propose` payload. */
92
- export declare function parseMemoryExtractionProposal(value: unknown, manifest: readonly MemoryManifestEntry[]): MemoryExtractionProposal;
93
- //# sourceMappingURL=memory-agent.d.ts.map
@@ -1,300 +0,0 @@
1
- import { createAssistantMessage, createToolResultMessage, createUserMessage, } from "@deepseek-ai/dsh-llm";
2
- import { EVOLVE_MESSAGE_SOURCE } from "./message-source.js";
3
- import { asLlmSessionId, streamModelTurn } from "./llm-text.js";
4
- import { formatMemoryManifest, manifestResult, searchMemoryManifest } from "./memory-manifest.js";
5
- import { validateBlastRadiusScope, validateEdit } from "./validate.js";
6
- import { extractJsonObject, parseJsonCandidate } from "./plan.js";
7
- import { buildPrefixMessages, detectPlannerRoute, resolvePrefixCache } from "./prefix-cache.js";
8
- import { createTokenUsageObserver } from "./token-usage.js";
9
- import { recordLanguageInstruction, resolveRecordLanguage } from "./record-language.js";
10
- import { MEMORY_TYPE_KEY, slug } from "./types.js";
11
- /** ZCode parity: the extractor may inspect memory and propose at most five times. */
12
- export const MEMORY_AGENT_MAX_TURNS = 5;
13
- /** Bound one proposal so approval text and engine compensation stay reviewable. */
14
- export const MEMORY_AGENT_MAX_EDITS = 20;
15
- /**
16
- * System-prompt size budget (chars): the prompt ships on EVERY extractor
17
- * turn (up to five per snapshot), so each added line is recurring token
18
- * spend, not one-off text. Raise this cap only in the same commit that
19
- * justifies the added lines (regression pressure beats silent bloat).
20
- */
21
- export const MEMORY_AGENT_SYSTEM_PROMPT_BUDGET_CHARS = 3000;
22
- /** The complete tool-name allowlist exposed by the memory agent. */
23
- export const MEMORY_AGENT_TOOL_NAMES = ["memory_search", "memory_propose"];
24
- /** Provider-facing schemas for the closed memory-only tool set. */
25
- export const MEMORY_AGENT_TOOL_SCHEMAS = [
26
- {
27
- name: "memory_search",
28
- description: "Read full content from the frozen memory manifest. This is the only data-reading tool available to the memory extractor.",
29
- parameters: {
30
- type: "object",
31
- additionalProperties: false,
32
- properties: {
33
- query: { type: "string", description: "Keywords, title text, scope:id, or content terms to retrieve." },
34
- },
35
- required: ["query"],
36
- },
37
- },
38
- {
39
- name: "memory_propose",
40
- description: "Finish extraction with one auditable memory-only proposal. An empty edits array is a valid no-op.",
41
- parameters: {
42
- type: "object",
43
- additionalProperties: false,
44
- properties: {
45
- summary: { type: "string" },
46
- rationale: { type: "string" },
47
- expectedOutcome: { type: "string" },
48
- edits: {
49
- type: "array",
50
- maxItems: MEMORY_AGENT_MAX_EDITS,
51
- items: {
52
- type: "object",
53
- additionalProperties: false,
54
- properties: {
55
- action: { type: "string", enum: ["create", "update", "delete", "archive"] },
56
- kind: { type: "string", enum: ["memory"] },
57
- targetScope: { type: "string", enum: ["local", "project", "global"] },
58
- blastRadius: { type: "string", enum: ["general", "project", "session"] },
59
- id: { type: "string", pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$" },
60
- title: { type: "string" },
61
- content: { type: "string" },
62
- path: { type: "string" },
63
- metadata: {
64
- type: "object",
65
- additionalProperties: false,
66
- properties: { memoryType: { type: "string", enum: ["user", "feedback", "project", "reference"] } },
67
- },
68
- reason: { type: "string" },
69
- },
70
- required: ["action", "kind", "targetScope", "blastRadius"],
71
- },
72
- },
73
- },
74
- required: ["summary", "rationale", "expectedOutcome", "edits"],
75
- },
76
- },
77
- ];
78
- /** Instructions for the dedicated loop; model text is always untrusted. */
79
- export const MEMORY_AGENT_SYSTEM_PROMPT = `You are the dedicated background memory extraction agent for a continual-evolution harness.
80
-
81
- 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.
82
-
83
- Rules:
84
- - Use memory_search before creating when the manifest may already cover the topic. Update an existing entry instead of creating a near-duplicate.
85
- - Every memory entry contains exactly one fact. Classify it with metadata.memoryType = user | feedback | project | reference.
86
- - feedback and project memories must state the fact, Why it matters, and How to apply it next time.
87
- - Never invent a user preference. Ground every proposal in the supplied conversation evidence and existing memory.
88
- - Every edit must have kind=memory, an explicit targetScope, and a coherent blastRadius.
89
- - targetScope=local is session staging. project persists within the current repository. global persists across projects. project/global require human approval before application.
90
- - 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.
91
- - 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).
92
- - 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.
93
- - 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.
94
- - Granularity examples (vague → reject; sharp → save):
95
- - "user communicates in Chinese" → "user reads English with effort — write memory and handoff content in Chinese prose".
96
- - "user works on the desktop project" → "desktop profile loads npm-installed plugin copies — source edits need a release before they take effect".
97
- - "fixed fcitx5 by setting GTK_IM_MODULE tonight" → reject (one-off applied fix, already on disk, no recurrence evidence).
98
- - 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.
99
- - If no durable memory is justified, call memory_propose with edits=[].
100
- - Always finish by calling memory_propose. Do not return unproposed prose.`;
101
- /**
102
- * Run the dedicated loop against a frozen manifest.
103
- *
104
- * @throws On provider failure, abort, malformed tool arguments, policy denial,
105
- * a non-memory proposal, or exhaustion of the bounded internal turn budget.
106
- */
107
- export async function runMemoryAgent(ctx, options) {
108
- const events = options.trajectoryEvents ?? [];
109
- const routing = resolvePrefixCache(options.prefixCache);
110
- const route = detectPlannerRoute(events, routing.mode);
111
- const prefixMessages = route === "A"
112
- ? buildPrefixMessages(events, { provider: options.provider, model: options.model, maxChars: routing.maxChars })
113
- : [];
114
- const conversation = prefixMessages.length > 0 ? "" : `<conversation>\n${options.trajectory}\n</conversation>`;
115
- const prompt = [
116
- `<memory_manifest>\n${formatMemoryManifest(options.manifest)}\n</memory_manifest>`,
117
- conversation,
118
- "Search before deciding, then call memory_propose with the complete memory-only proposal. Empty edits is a valid no-op.",
119
- ]
120
- .filter(Boolean)
121
- .join("\n\n");
122
- const messages = [
123
- ...prefixMessages,
124
- createUserMessage({
125
- content: [{ type: "text", text: prompt }],
126
- source: EVOLVE_MESSAGE_SOURCE,
127
- }),
128
- ];
129
- let searches = 0;
130
- const language = options.language ?? resolveRecordLanguage({ ctx, trajectoryText: options.trajectory });
131
- const systemPrompt = `${MEMORY_AGENT_SYSTEM_PROMPT}\n\n${recordLanguageInstruction(language)}`;
132
- for (let turn = 1; turn <= MEMORY_AGENT_MAX_TURNS; turn += 1) {
133
- options.signal?.throwIfAborted();
134
- const blocks = await streamModelTurn(ctx, {
135
- provider: options.provider,
136
- model: options.model,
137
- ...(options.sessionId ? { sessionId: asLlmSessionId(options.sessionId) } : {}),
138
- system: systemPrompt,
139
- messages,
140
- tools: MEMORY_AGENT_TOOL_SCHEMAS,
141
- requireTextOrToolCall: true,
142
- maxTokens: options.maxOutputTokens ?? 4096,
143
- ...(options.signal ? { signal: options.signal } : {}),
144
- ...(options.tokenUsage
145
- ? { onUsage: createTokenUsageObserver(options.tokenUsage, "memory", options.provider, options.model) }
146
- : {}),
147
- });
148
- const hasText = blocks.some((block) => block.type === "text" && block.text.length > 0);
149
- const hasToolCall = blocks.some((block) => block.type === "tool-call");
150
- if (!hasText && !hasToolCall)
151
- throw new Error("memory agent produced no model output");
152
- messages.push(createAssistantMessage({
153
- content: blocks,
154
- source: { provider: options.provider, model: options.model },
155
- }));
156
- const toolCalls = blocks.filter((block) => block.type === "tool-call");
157
- if (toolCalls.length === 0) {
158
- const text = textOf(blocks);
159
- return {
160
- proposal: parseMemoryExtractionProposal(extractJsonObject(text), options.manifest),
161
- turns: turn,
162
- searches,
163
- };
164
- }
165
- let proposal;
166
- for (const call of toolCalls) {
167
- const execution = executeMemoryTool(call, options.manifest);
168
- if (call.name === "memory_search")
169
- searches += 1;
170
- proposal ??= execution.proposal;
171
- messages.push(createToolResultMessage({
172
- callId: call.id,
173
- content: [{ type: "text", text: execution.text }],
174
- isError: execution.isError,
175
- }));
176
- }
177
- if (proposal)
178
- return { proposal, turns: turn, searches };
179
- }
180
- throw new Error(`memory agent exhausted ${MEMORY_AGENT_MAX_TURNS} internal turns without a valid proposal`);
181
- }
182
- /** Strictly parse and policy-check the structured `memory_propose` payload. */
183
- export function parseMemoryExtractionProposal(value, manifest) {
184
- const record = asRecord(value);
185
- if (!record)
186
- throw new Error("memory proposal must be an object");
187
- const summary = requiredString(record["summary"], "summary");
188
- const rationale = requiredString(record["rationale"], "rationale");
189
- const expectedOutcome = requiredString(record["expectedOutcome"], "expectedOutcome");
190
- if (!Array.isArray(record["edits"]))
191
- throw new Error("memory proposal edits must be an array");
192
- if (record["edits"].length > MEMORY_AGENT_MAX_EDITS) {
193
- throw new Error(`memory proposal exceeds ${MEMORY_AGENT_MAX_EDITS} edits`);
194
- }
195
- const byScopeId = new Map(manifest.map((entry) => [`${entry.scope}:${entry.id}`, entry]));
196
- const edits = record["edits"].map((raw, index) => parseScopedMemoryEdit(raw, index, byScopeId));
197
- return { summary, rationale, expectedOutcome, edits };
198
- }
199
- function executeMemoryTool(call, manifest) {
200
- if (!MEMORY_AGENT_TOOL_NAMES.includes(call.name)) {
201
- return { text: `tool denied: only ${MEMORY_AGENT_TOOL_NAMES.join(" and ")} are available`, isError: true };
202
- }
203
- try {
204
- const args = parseJsonCandidate(call.arguments.trim() || "{}");
205
- if (call.name === "memory_search") {
206
- const record = asRecord(args);
207
- const query = record ? requiredString(record["query"], "query") : "";
208
- const matches = searchMemoryManifest(manifest, query).map(manifestResult);
209
- return { text: JSON.stringify({ matches }), isError: false };
210
- }
211
- return { proposal: parseMemoryExtractionProposal(args, manifest), text: JSON.stringify({ accepted: true }), isError: false };
212
- }
213
- catch (cause) {
214
- return {
215
- text: `tool error: ${cause instanceof Error ? cause.message : String(cause)}`,
216
- isError: true,
217
- };
218
- }
219
- }
220
- function parseScopedMemoryEdit(raw, index, byScopeId) {
221
- const record = asRecord(raw);
222
- if (!record)
223
- throw new Error(`memory edit ${index} must be an object`);
224
- const action = requiredString(record["action"], `edits[${index}].action`);
225
- if (!isOneOf(action, ["create", "update", "delete", "archive"])) {
226
- throw new Error(`memory edit ${index} has unsupported action ${action}`);
227
- }
228
- if (record["kind"] !== "memory")
229
- throw new Error(`memory edit ${index} must have kind=memory`);
230
- const targetScope = requiredString(record["targetScope"], `edits[${index}].targetScope`);
231
- if (!isOneOf(targetScope, ["local", "project", "global"])) {
232
- throw new Error(`memory edit ${index} has unsupported targetScope ${targetScope}`);
233
- }
234
- const blastRadius = requiredString(record["blastRadius"], `edits[${index}].blastRadius`);
235
- if (!isOneOf(blastRadius, ["general", "project", "session"])) {
236
- throw new Error(`memory edit ${index} has unsupported blastRadius ${blastRadius}`);
237
- }
238
- const blastError = validateBlastRadiusScope(targetScope, blastRadius);
239
- if (blastError)
240
- throw new Error(`memory edit ${index}: ${blastError}`);
241
- if (record["reference"] !== undefined || record["arguments"] !== undefined || record["skill_kind"] !== undefined) {
242
- throw new Error(`memory edit ${index} cannot carry skill-only fields`);
243
- }
244
- const edit = {
245
- action,
246
- kind: "memory",
247
- targetScope,
248
- blastRadius,
249
- };
250
- for (const field of ["id", "title", "content", "path", "reason"]) {
251
- const fieldValue = record[field];
252
- if (fieldValue !== undefined) {
253
- if (typeof fieldValue !== "string")
254
- throw new Error(`memory edit ${index}.${field} must be a string`);
255
- edit[field] = fieldValue;
256
- }
257
- }
258
- if (record["metadata"] !== undefined) {
259
- const metadata = asRecord(record["metadata"]);
260
- if (!metadata)
261
- throw new Error(`memory edit ${index}.metadata must be an object`);
262
- const unexpected = Object.keys(metadata).filter((key) => key !== MEMORY_TYPE_KEY);
263
- if (unexpected.length > 0) {
264
- throw new Error(`memory edit ${index}.metadata contains engine-owned or unsupported keys: ${unexpected.join(", ")}`);
265
- }
266
- edit.metadata = { ...metadata };
267
- }
268
- if (action === "create" && edit.id && !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(edit.id)) {
269
- throw new Error(`memory edit ${index}.id contains unsupported identifier characters`);
270
- }
271
- const before = edit.id ? byScopeId.get(`${targetScope}:${edit.id}`) : undefined;
272
- if (action !== "create" && !before) {
273
- throw new Error(`memory edit ${index} ${action} target does not exist in ${targetScope}: ${edit.id ?? "(missing id)"}`);
274
- }
275
- if (action === "create" && edit.id && before) {
276
- throw new Error(`memory edit ${index} create duplicates ${targetScope}:${edit.id}; use update instead`);
277
- }
278
- const validationError = validateEdit(edit, edit.id ?? slug(edit.title ?? "memory", "memory"), targetScope, before);
279
- if (validationError)
280
- throw new Error(`memory edit ${index}: ${validationError}`);
281
- return edit;
282
- }
283
- function textOf(blocks) {
284
- return blocks
285
- .map((block) => (block.type === "text" ? block.text : ""))
286
- .filter((text) => text.length > 0)
287
- .join("\n");
288
- }
289
- function asRecord(value) {
290
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
291
- }
292
- function requiredString(value, field) {
293
- if (typeof value !== "string" || value.trim().length === 0)
294
- throw new Error(`${field} must be a non-empty string`);
295
- return value;
296
- }
297
- function isOneOf(value, values) {
298
- return values.includes(value);
299
- }
300
- //# sourceMappingURL=memory-agent.js.map
@@ -1,44 +0,0 @@
1
- /**
2
- * Memory proposal application: take the extractor's structured proposal and
3
- * land it through the governed path — per-scope human approval (unique,
4
- * explicit, fail-loud), credential screening, conflict notices, engine
5
- * apply with baseline checks, decline recording, and cross-scope
6
- * compensation on partial failure.
7
- */
8
- import type { Context } from "@deepseek-ai/cordis";
9
- import type { Agent } from "@deepseek-ai/dsh-agent";
10
- import type { EntrySource, HarnessState } from "./types.js";
11
- import { type ScopeApprovalDecision } from "./approval.js";
12
- import { type EvolutionEngine } from "./service.js";
13
- import type { MemoryApplicationResult, MemoryExtractionProposal, MemoryScopeBaselines } from "./memory-agent.js";
14
- import type { RecordLanguagePreference } from "./record-language.js";
15
- export interface ApplyMemoryProposalOptions {
16
- agent: Agent;
17
- baselines: MemoryScopeBaselines;
18
- projectKey?: string;
19
- requireApproval: boolean;
20
- source?: EntrySource;
21
- signal?: AbortSignal;
22
- /** Outer cursor used to scope reusable approval decisions. */
23
- decisionCursor?: string;
24
- /** Prior decisions for the same cursor and exact scoped proposal. */
25
- scopeDecisions?: Readonly<Record<string, ScopeApprovalDecision>>;
26
- /** Persist each decision before a later scope can fail. */
27
- onScopeDecision?: (key: string, decision: ScopeApprovalDecision) => void;
28
- /** Plugin `recordLanguage` preference for the approval dialog copy. */
29
- recordLanguage?: RecordLanguagePreference;
30
- }
31
- /**
32
- * Render the authoritative persistent-scope diff shown to the human. Model
33
- * prose is retained only as an explicitly untrusted summary; actions, targets,
34
- * content previews, memory types, and conflict warnings come from code.
35
- */
36
- export declare function renderMemoryApprovalDetails(proposal: MemoryExtractionProposal, scope: "project" | "global", state: HarnessState, maxChars?: number): string;
37
- /**
38
- * Apply one accepted proposal through the only mutation engine. All persistent
39
- * approvals are resolved before the first write, so an unavailable approval
40
- * boundary cannot leave a partially applied batch. Explicit rejection is a
41
- * no-op for that scope; service/question failures remain retryable errors.
42
- */
43
- export declare function applyMemoryExtractionProposal(ctx: Context, engine: EvolutionEngine, proposal: MemoryExtractionProposal, options: ApplyMemoryProposalOptions): Promise<MemoryApplicationResult>;
44
- //# sourceMappingURL=memory-apply.d.ts.map