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,211 +0,0 @@
1
- /**
2
- * Provider-reported token accounting for plugin-owned direct LLM calls.
3
- *
4
- * The review gate, planner, dedicated memory agent, wrap-up command, and
5
- * local-fate classifier all use the shared model-call boundary. These calls do
6
- * not belong to an Agent turn (they pass no sessionId), so the host session
7
- * token-meter cannot attribute them. This ledger records the exact
8
- * `TokenUsage` emitted by the adapter at the one shared stream boundary.
9
- *
10
- * Storage: `<baseDir>/evolve/token-usage.jsonl`. The file contains route and
11
- * count metadata only — never prompts, responses, or tool arguments — and is
12
- * trimmed to its dedicated retention budget after each synchronous append.
13
- */
14
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
15
- import { join } from "node:path";
16
- import { EVOLVE_DIR, pruneJsonlFile } from "./store.js";
17
- const TOKEN_USAGE_FILE = "token-usage.jsonl";
18
- export const TOKEN_USAGE_LEDGER_VERSION = 1;
19
- /** Plugin-owned direct call paths represented in the ledger. */
20
- export const TOKEN_USAGE_PHASES = ["review", "memory", "planner", "wrapup", "fate"];
21
- /** Absolute path of the direct-call token ledger. */
22
- export function tokenUsagePath(baseDir) {
23
- return join(baseDir, EVOLVE_DIR, TOKEN_USAGE_FILE);
24
- }
25
- /** Exact full-call total, with the old-peer fallback only when `totalTokens` is absent. */
26
- export function exactTokenTotal(usage) {
27
- if (usage.totalTokens !== undefined)
28
- return usage.totalTokens;
29
- return usage.inputTokens + usage.outputTokens + (usage.cacheReadTokens ?? 0) + (usage.cacheWriteTokens ?? 0);
30
- }
31
- /** Build the observer attached to one `streamText` call. Errors are diagnostic-only. */
32
- export function createTokenUsageObserver(target, phase, provider, model) {
33
- return (observation) => {
34
- try {
35
- appendTokenUsage(target, {
36
- version: TOKEN_USAGE_LEDGER_VERSION,
37
- timestamp: new Date().toISOString(),
38
- sessionId: target.sessionId,
39
- phase,
40
- provider,
41
- model,
42
- outcome: observation.outcome,
43
- usageStatus: observation.usage ? "reported" : "missing",
44
- ...(observation.usage ? { usage: observation.usage } : {}),
45
- totalSource: observation.usage
46
- ? observation.usage.totalTokens !== undefined
47
- ? "provider"
48
- : "derived"
49
- : "unavailable",
50
- });
51
- }
52
- catch (cause) {
53
- target.onError?.(cause);
54
- }
55
- };
56
- }
57
- /**
58
- * Append one call and enforce the dedicated tail budget. Append + conditional
59
- * prune stay in one synchronous block, so concurrent async callers in one DSH
60
- * process cannot interleave the read/truncate/write. Multiple DSH processes
61
- * sharing one baseDir are outside the plugin's storage contract.
62
- */
63
- export function appendTokenUsage(target, record) {
64
- if (!isTokenUsageRecord(record)) {
65
- throw new Error("evolve: refusing to append an invalid token-usage record");
66
- }
67
- const dir = join(target.baseDir, EVOLVE_DIR);
68
- mkdirSync(dir, { recursive: true, mode: 0o700 });
69
- writeFileSync(tokenUsagePath(target.baseDir), `${JSON.stringify(record)}\n`, {
70
- encoding: "utf8",
71
- flag: "a",
72
- mode: 0o600,
73
- });
74
- pruneJsonlFile(tokenUsagePath(target.baseDir), target.retain);
75
- }
76
- /** Load valid records from the retained ledger; malformed lines never break `/evolve usage`. */
77
- export function loadTokenUsage(baseDir) {
78
- const path = tokenUsagePath(baseDir);
79
- if (!existsSync(path))
80
- return { records: [], corruptLines: 0 };
81
- let lines;
82
- try {
83
- lines = readFileSync(path, "utf8").split("\n");
84
- }
85
- catch {
86
- return { records: [], corruptLines: 0 };
87
- }
88
- const records = [];
89
- let corruptLines = 0;
90
- for (const line of lines) {
91
- if (line.trim().length === 0)
92
- continue;
93
- try {
94
- const parsed = JSON.parse(line);
95
- if (isTokenUsageRecord(parsed))
96
- records.push(parsed);
97
- else
98
- corruptLines += 1;
99
- }
100
- catch {
101
- corruptLines += 1;
102
- }
103
- }
104
- return { records, corruptLines };
105
- }
106
- /** Sum exact provider-reported buckets without double-counting reasoning or cache fields. */
107
- export function aggregateTokenUsage(records) {
108
- const totals = {
109
- calls: records.length,
110
- reportedCalls: 0,
111
- missingUsageCalls: 0,
112
- uncachedInputTokens: 0,
113
- cacheReadTokens: 0,
114
- cacheWriteTokens: 0,
115
- outputTokens: 0,
116
- reasoningTokens: 0,
117
- totalTokens: 0,
118
- };
119
- for (const record of records) {
120
- if (record.usageStatus === "missing" || !record.usage) {
121
- totals.missingUsageCalls += 1;
122
- continue;
123
- }
124
- totals.reportedCalls += 1;
125
- totals.uncachedInputTokens += record.usage.inputTokens;
126
- totals.cacheReadTokens += record.usage.cacheReadTokens ?? 0;
127
- totals.cacheWriteTokens += record.usage.cacheWriteTokens ?? 0;
128
- totals.outputTokens += record.usage.outputTokens;
129
- totals.reasoningTokens += record.usage.reasoningTokens ?? 0;
130
- totals.totalTokens += exactTokenTotal(record.usage);
131
- }
132
- return totals;
133
- }
134
- /** Render the human-facing direct-call ledger section. */
135
- export function renderTokenUsageReport(ledger, retain) {
136
- const { records, corruptLines } = ledger;
137
- if (records.length === 0) {
138
- const corrupt = corruptLines > 0 ? ` ${corruptLines} corrupt line(s) ignored.` : "";
139
- return [
140
- `direct LLM token usage: no valid calls in the retained tail.${corrupt}`,
141
- `window: last ${retain} call(s), not a lifetime total.`,
142
- "scope: review/memory/planner/wrapup/fate only; host benchmark subagents, their agent-loop calls, and per-entry injection cost are excluded.",
143
- ];
144
- }
145
- const totals = aggregateTokenUsage(records);
146
- const lines = [
147
- `direct LLM token usage: ${totals.calls} valid calls (${totals.reportedCalls} reported usage, ${totals.missingUsageCalls} missing)${corruptLines > 0 ? `; ${corruptLines} corrupt line(s) ignored` : ""}`,
148
- ` uncached input: ${totals.uncachedInputTokens}`,
149
- ` cache read: ${totals.cacheReadTokens}`,
150
- ` cache write: ${totals.cacheWriteTokens}`,
151
- ` output: ${totals.outputTokens}`,
152
- ` reasoning: ${totals.reasoningTokens}${records.some((record) => record.usageStatus === "reported" && record.usage?.reasoningTokens === undefined) ? " (partial provider reports)" : ""}`,
153
- ` exact total: ${totals.totalTokens} (reported calls only)`,
154
- `window: last ${retain} call(s), not a lifetime total.`,
155
- "by phase:",
156
- ];
157
- for (const phase of TOKEN_USAGE_PHASES) {
158
- const phaseRecords = records.filter((record) => record.phase === phase);
159
- if (phaseRecords.length === 0)
160
- continue;
161
- const phaseTotals = aggregateTokenUsage(phaseRecords);
162
- lines.push(` ${phase.padEnd(7)} ${phaseTotals.calls} calls · ${phaseTotals.totalTokens} tokens`);
163
- }
164
- const last = records.at(-1);
165
- if (last) {
166
- const lastTotal = last.usage ? exactTokenTotal(last.usage) : "usage unavailable";
167
- lines.push(`last: ${last.phase} ${last.provider}/${last.model} · ${last.outcome} · ${lastTotal} tokens · ${last.timestamp}`);
168
- }
169
- lines.push("scope: review/memory/planner/wrapup/fate only; host benchmark subagents, their agent-loop calls, and per-entry injection cost are excluded.");
170
- return lines;
171
- }
172
- function isTokenUsageRecord(value) {
173
- if (typeof value !== "object" || value === null || Array.isArray(value))
174
- return false;
175
- const record = value;
176
- if (record["version"] !== TOKEN_USAGE_LEDGER_VERSION ||
177
- typeof record["timestamp"] !== "string" ||
178
- typeof record["sessionId"] !== "string" ||
179
- typeof record["provider"] !== "string" ||
180
- typeof record["model"] !== "string" ||
181
- typeof record["outcome"] !== "string" ||
182
- !TOKEN_USAGE_PHASES.includes(record["phase"]) ||
183
- !["success", "max-tokens", "error", "aborted", "empty"].includes(record["outcome"]) ||
184
- !["reported", "missing"].includes(record["usageStatus"]) ||
185
- !["provider", "derived", "unavailable"].includes(record["totalSource"])) {
186
- return false;
187
- }
188
- if (record["usageStatus"] === "missing") {
189
- return record["usage"] === undefined && record["totalSource"] === "unavailable";
190
- }
191
- if (!isTokenUsage(record["usage"]))
192
- return false;
193
- const expectedSource = record["usage"]["totalTokens"] !== undefined ? "provider" : "derived";
194
- return record["totalSource"] === expectedSource;
195
- }
196
- function isCount(value) {
197
- return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
198
- }
199
- function isTokenUsage(value) {
200
- if (typeof value !== "object" || value === null || Array.isArray(value))
201
- return false;
202
- const usage = value;
203
- if (!isCount(usage["inputTokens"]) || !isCount(usage["outputTokens"]))
204
- return false;
205
- for (const key of ["totalTokens", "cacheReadTokens", "cacheWriteTokens", "reasoningTokens"]) {
206
- if (usage[key] !== undefined && !isCount(usage[key]))
207
- return false;
208
- }
209
- return true;
210
- }
211
- //# sourceMappingURL=token-usage.js.map
package/lib/tool.d.ts DELETED
@@ -1,34 +0,0 @@
1
- /**
2
- * Model-facing evolve_* tools. The model supplies content; every guarantee
3
- * (validation, snapshot, versioning, history, rollback) is code-enforced in
4
- * the engine. `global: true` is required explicitly for cross-session edits.
5
- */
6
- import type { Context } from "@deepseek-ai/cordis";
7
- import type { RecordLanguagePreference } from "./record-language.js";
8
- import type { HarnessScope } from "./types.js";
9
- import type { EvolutionEngine } from "./service.js";
10
- /**
11
- * Accept the string form (`scope: "project"`) with the legacy boolean tool
12
- * parameter (`global: true`) as fallback. `scope` wins when both are given.
13
- */
14
- export declare function scopeOf(value: unknown, fallback: HarnessScope): HarnessScope;
15
- /**
16
- * Collect the target ids for an evolve_delete call: legacy single `id`
17
- * plus the batch `ids` array, de-duplicated in first-seen order. Throws
18
- * loudly when neither carries an id — a silent no-op delete would look
19
- * like success while deleting nothing.
20
- *
21
- * @throws when both `id` and `ids` are absent or empty.
22
- */
23
- export declare function collectDeleteIds(id: unknown, ids: unknown): string[];
24
- export interface ToolGateOptions {
25
- requireGlobalApproval: boolean;
26
- /**
27
- * Plugin `recordLanguage` preference for the approval dialog copy;
28
- * `auto`/absent defers to the durable client preference and the session's
29
- * own user text (see `approval.ts`).
30
- */
31
- recordLanguage?: RecordLanguagePreference;
32
- }
33
- export declare function registerEvolveTools(ctx: Context, engine: EvolutionEngine, opts: ToolGateOptions): void;
34
- //# sourceMappingURL=tool.d.ts.map
package/lib/tool.js DELETED
@@ -1,320 +0,0 @@
1
- import { defineTool } from "@deepseek-ai/dsh-tools";
2
- import { MEMORY_TYPE_KEY } from "./types.js";
3
- import { projectKeyOf } from "./project.js";
4
- import { formatHarnessStateForPrompt } from "./render.js";
5
- import { requireGlobalApproval } from "./approval.js";
6
- import { CONFLICT_WARN_SCORE, buildConflictNotice, mostSimilarEntry } from "./promotion.js";
7
- import { entrySourceOf } from "./source.js";
8
- import { formatRecallResult, recallMemories } from "./recall.js";
9
- import { getUsageCount, loadUsage } from "./usage.js";
10
- import { buildEvolveCompleteEvent, emitEvolveComplete } from "./evolve-event.js";
11
- import { DEFAULT_REVIEWS_RETAIN } from "./store.js";
12
- const SCOPES = ["local", "project", "global"];
13
- /**
14
- * Accept the string form (`scope: "project"`) with the legacy boolean tool
15
- * parameter (`global: true`) as fallback. `scope` wins when both are given.
16
- */
17
- export function scopeOf(value, fallback) {
18
- if (value === "global" || value === true) {
19
- return "global";
20
- }
21
- if (value === "project") {
22
- return "project";
23
- }
24
- return fallback;
25
- }
26
- /** The calling agent's session id; tools always run inside an agent scope. */
27
- function sessionIdOf(exec) {
28
- return exec.agent?.id;
29
- }
30
- /**
31
- * Collect the target ids for an evolve_delete call: legacy single `id`
32
- * plus the batch `ids` array, de-duplicated in first-seen order. Throws
33
- * loudly when neither carries an id — a silent no-op delete would look
34
- * like success while deleting nothing.
35
- *
36
- * @throws when both `id` and `ids` are absent or empty.
37
- */
38
- export function collectDeleteIds(id, ids) {
39
- const collected = [];
40
- if (typeof id === "string" && id.length > 0)
41
- collected.push(id);
42
- if (Array.isArray(ids)) {
43
- for (const item of ids) {
44
- if (typeof item === "string" && item.length > 0 && !collected.includes(item)) {
45
- collected.push(item);
46
- }
47
- }
48
- }
49
- if (collected.length === 0) {
50
- throw new Error("evolve_delete requires id or a non-empty ids array");
51
- }
52
- return collected;
53
- }
54
- /**
55
- * The store id a scope reads/writes: session id for local, derived project
56
- * key for project, undefined for global. Throws for project when the
57
- * session cwd is unavailable — fail loud instead of writing local-by-mistake.
58
- */
59
- function storeIdFor(scope, exec) {
60
- if (scope === "local") {
61
- return sessionIdOf(exec);
62
- }
63
- if (scope === "project") {
64
- const key = projectKeyOf(exec.agent);
65
- if (!key) {
66
- throw new Error("project scope needs the session cwd (unavailable for this agent) — use local or global instead");
67
- }
68
- return key;
69
- }
70
- return undefined;
71
- }
72
- /** Human-approval gate shared by global and project writes (both cross-session). */
73
- function needsApproval(scope) {
74
- return scope === "global" || scope === "project";
75
- }
76
- /** Store label used in approval prompts. */
77
- function storeLabel(scope) {
78
- return scope === "project" ? "本项目跨会话 store" : "跨会话全局 store";
79
- }
80
- function textResult(text) {
81
- return { text };
82
- }
83
- export function registerEvolveTools(ctx, engine, opts) {
84
- ctx.tools.register(defineTool({
85
- name: "evolve_list",
86
- description: "List the harness state (prompt notes, memories, skills, subagent specs) for one store.",
87
- parameters: {
88
- scope: {
89
- type: "string",
90
- enum: SCOPES,
91
- description: "'local' (default), 'project', or 'global'.",
92
- },
93
- },
94
- output: {
95
- schema: { type: "object", additionalProperties: false, properties: { text: { type: "string", required: true } } },
96
- render: (_args, value) => [{ type: "text", text: value.text ?? "" }],
97
- },
98
- execute: async (args, exec) => {
99
- const scope = scopeOf(args.scope, "local");
100
- const state = engine.load(scope, storeIdFor(scope, exec));
101
- const text = formatHarnessStateForPrompt(state);
102
- // Append injection usage counts (gap B1).
103
- const usage = loadUsage(engine.baseDir);
104
- const usageLines = [];
105
- for (const kind of Object.keys(state.entries)) {
106
- for (const entry of Object.values(state.entries[kind])) {
107
- const count = getUsageCount(usage, kind, entry.id);
108
- if (count > 0) {
109
- usageLines.push(`${kind}:${entry.id} — injected ${count}×`);
110
- }
111
- }
112
- }
113
- if (usageLines.length > 0) {
114
- return textResult(`${text}\n\n# Injection Usage\n${usageLines.join("\n")}`);
115
- }
116
- return textResult(text);
117
- },
118
- }));
119
- ctx.tools.register(defineTool({
120
- name: "evolve_recall",
121
- description: "Read-only targeted recall across the harness stores: filter by query, kind, scope, and memory type; returns full content with version, source, and staleness signals. Prefer this over evolve_list when looking for something specific.",
122
- parameters: {
123
- query: { type: "string", description: "Free-text relevance query. Empty → most-recently-updated first." },
124
- kinds: {
125
- type: "array",
126
- items: { type: "string" },
127
- description: "Entry kinds (default memory only).",
128
- },
129
- scopes: {
130
- type: "array",
131
- items: { type: "string" },
132
- description: "Stores to search (default all three).",
133
- },
134
- memoryTypes: {
135
- type: "array",
136
- items: { type: "string" },
137
- description: "Memory types: user, feedback, project, reference.",
138
- },
139
- limit: { type: "number", description: "Max hits (default 10, max 50)." },
140
- includeArchived: { type: "boolean", description: "Include archived entries (default false)." },
141
- },
142
- output: {
143
- schema: { type: "object", additionalProperties: false, properties: { text: { type: "string", required: true } } },
144
- render: (_args, value) => [{ type: "text", text: value.text ?? "" }],
145
- },
146
- execute: async (args, exec) => {
147
- const result = recallMemories(engine, { sessionId: sessionIdOf(exec), projectKey: projectKeyOf(exec.agent) ?? undefined }, {
148
- ...(typeof args.query === "string" ? { query: args.query } : {}),
149
- ...(Array.isArray(args.kinds) ? { kinds: args.kinds } : {}),
150
- ...(Array.isArray(args.scopes) ? { scopes: args.scopes } : {}),
151
- ...(Array.isArray(args.memoryTypes) ? { memoryTypes: args.memoryTypes } : {}),
152
- ...(typeof args.limit === "number" ? { limit: args.limit } : {}),
153
- ...(typeof args.includeArchived === "boolean" ? { includeArchived: args.includeArchived } : {}),
154
- });
155
- return textResult(formatRecallResult(result, typeof args.query === "string" ? args.query : undefined));
156
- },
157
- }));
158
- ctx.tools.register(defineTool({
159
- name: "evolve_add",
160
- description: "Create one harness entry (prompt/memory/skill/subagent). Snapshots, versioning, and audit are automatic.",
161
- parameters: {
162
- kind: { type: "string", enum: ["prompt", "memory", "skill", "subagent"], required: true, description: "Entry kind." },
163
- title: { type: "string", required: true, description: "Stable title." },
164
- content: { type: "string", required: true, description: "Entry body." },
165
- path: { type: "string", description: "Optional grouping path." },
166
- skill_kind: { type: "string", enum: ["executable", "guidance"], description: "Skills: executable (default, needs reference) or guidance (SKILL.md, no reference)." },
167
- reference: { type: "object", additionalProperties: true, description: "Executable skills: {type:'python', import, callable}." },
168
- arguments: { type: "object", additionalProperties: true, description: "Executable skills: accepted input contract." },
169
- memoryType: { type: "string", enum: ["user", "feedback", "project", "reference"], description: "Required for memory (feedback/project need Why + How to apply)." },
170
- scope: { type: "string", enum: SCOPES, description: "'local' (default), 'project', or 'global'." },
171
- global: { type: "boolean", description: "Shorthand for scope='global' (requires human approval)." },
172
- },
173
- output: {
174
- schema: { type: "object", additionalProperties: false, properties: { text: { type: "string", required: true } } },
175
- render: (_args, value) => [{ type: "text", text: value.text ?? "" }],
176
- },
177
- execute: async (args, exec) => {
178
- const scope = scopeOf(args.scope ?? args.global, "local");
179
- // Resolve first: a project scope without a session cwd fails
180
- // here, before any human approval question is asked.
181
- const storeId = storeIdFor(scope, exec);
182
- if (needsApproval(scope) && opts.requireGlobalApproval) {
183
- // Informed approval: surface a similarity hit against the
184
- // target store BEFORE the human decides — the engine's
185
- // write-time guard still has the final say.
186
- const targetState = engine.load(scope, storeId);
187
- const hit = mostSimilarEntry(Object.values(targetState.entries[args.kind]), args.title ?? "", args.content ?? "", CONFLICT_WARN_SCORE);
188
- const conflictNote = hit ? ` ⚠️ ${buildConflictNotice(hit)}——建议改用 evolve_update` : "";
189
- await requireGlobalApproval(ctx, exec.agent, exec.signal, `evolve_add ${args.kind} "${args.title}" → ${storeLabel(scope)}${conflictNote}`, opts.recordLanguage);
190
- }
191
- const edit = {
192
- action: "create",
193
- kind: args.kind,
194
- title: args.title,
195
- content: args.content,
196
- };
197
- if (args.path !== undefined)
198
- edit.path = args.path;
199
- if (args.skill_kind !== undefined)
200
- edit.skill_kind = args.skill_kind;
201
- if (args.reference !== undefined)
202
- edit.reference = args.reference;
203
- if (args.arguments !== undefined)
204
- edit.arguments = args.arguments;
205
- if (args.kind === "memory" && args.memoryType !== undefined) {
206
- edit.metadata = { [MEMORY_TYPE_KEY]: args.memoryType };
207
- }
208
- return textResult(applyEditsText(engine, scope, storeId, [edit], exec.agent, sessionIdOf(exec)));
209
- },
210
- }));
211
- ctx.tools.register(defineTool({
212
- name: "evolve_update",
213
- description: "Update one harness entry by id. Pass only the fields that change.",
214
- parameters: {
215
- kind: { type: "string", enum: ["prompt", "memory", "skill", "subagent"], required: true },
216
- id: { type: "string", required: true, description: "Existing entry id." },
217
- title: { type: "string" },
218
- content: { type: "string" },
219
- memoryType: { type: "string", enum: ["user", "feedback", "project", "reference"], description: "Memory: (re)classify the recall type." },
220
- scope: { type: "string", enum: SCOPES, description: "'local' (default), 'project', or 'global'." },
221
- global: { type: "boolean", description: "Shorthand for scope='global' (requires human approval)." },
222
- },
223
- output: {
224
- schema: { type: "object", additionalProperties: false, properties: { text: { type: "string", required: true } } },
225
- render: (_args, value) => [{ type: "text", text: value.text ?? "" }],
226
- },
227
- execute: async (args, exec) => {
228
- const scope = scopeOf(args.scope ?? args.global, "local");
229
- const storeId = storeIdFor(scope, exec);
230
- if (needsApproval(scope) && opts.requireGlobalApproval) {
231
- await requireGlobalApproval(ctx, exec.agent, exec.signal, `evolve_update ${args.kind}:${args.id} → ${storeLabel(scope)}`, opts.recordLanguage);
232
- }
233
- const edit = { action: "update", kind: args.kind, id: args.id };
234
- if (args.title !== undefined)
235
- edit.title = args.title;
236
- if (args.content !== undefined)
237
- edit.content = args.content;
238
- if (args.memoryType !== undefined)
239
- edit.metadata = { [MEMORY_TYPE_KEY]: args.memoryType };
240
- return textResult(applyEditsText(engine, scope, storeId, [edit], exec.agent, sessionIdOf(exec)));
241
- },
242
- }));
243
- ctx.tools.register(defineTool({
244
- name: "evolve_delete",
245
- description: "Delete harness entries: `id` for one, `ids` for a batch (one refinement, one approval). Ids are addressed verbatim, scope-prefixed or not.",
246
- parameters: {
247
- kind: { type: "string", enum: ["prompt", "memory", "skill", "subagent"], required: true },
248
- id: { type: "string", description: "One entry id (or use ids for a batch)." },
249
- ids: {
250
- type: "array",
251
- items: { type: "string" },
252
- description: "Entry ids for one batch delete.",
253
- },
254
- scope: { type: "string", enum: SCOPES, description: "'local' (default), 'project', or 'global'." },
255
- global: { type: "boolean", description: "Shorthand for scope='global' (requires human approval)." },
256
- },
257
- output: {
258
- schema: { type: "object", additionalProperties: false, properties: { text: { type: "string", required: true } } },
259
- render: (_args, value) => [{ type: "text", text: value.text ?? "" }],
260
- },
261
- execute: async (args, exec) => {
262
- const scope = scopeOf(args.scope ?? args.global, "local");
263
- const storeId = storeIdFor(scope, exec);
264
- const ids = collectDeleteIds(args.id, args.ids);
265
- if (needsApproval(scope) && opts.requireGlobalApproval) {
266
- await requireGlobalApproval(ctx, exec.agent, exec.signal, `evolve_delete ${args.kind}:${ids.length > 1 ? `${ids.length} entries (${ids.join(", ")})` : ids[0]} → ${storeLabel(scope)}`, opts.recordLanguage);
267
- }
268
- const edits = ids.map((id) => ({ action: "delete", kind: args.kind, id }));
269
- return textResult(applyEditsText(engine, scope, storeId, edits, exec.agent, sessionIdOf(exec)));
270
- },
271
- }));
272
- ctx.tools.register(defineTool({
273
- name: "evolve_rollback",
274
- description: "Deterministically revert a previous refinement by its id (from evolve_list history or /evolve).",
275
- parameters: {
276
- refinementId: { type: "string", required: true, description: "Refinement id to roll back." },
277
- scope: { type: "string", enum: SCOPES, description: "'local' (default), 'project', or 'global'." },
278
- global: { type: "boolean", description: "Shorthand for scope='global'." },
279
- },
280
- output: {
281
- schema: { type: "object", additionalProperties: false, properties: { text: { type: "string", required: true } } },
282
- render: (_args, value) => [{ type: "text", text: value.text ?? "" }],
283
- },
284
- execute: async (args, exec) => {
285
- const scope = scopeOf(args.scope ?? args.global, "local");
286
- const result = engine.rollback(scope, storeIdFor(scope, exec), args.refinementId);
287
- return textResult(`Rolled back ${result.rollbackOf ?? result.id}: ${result.appliedEdits.filter((e) => e.applied).length} edit(s) reverted.`);
288
- },
289
- }));
290
- }
291
- function applyEditsText(engine, scope, storeId, edits, agent, eventSessionId) {
292
- const result = engine.apply(scope, storeId, {
293
- summary: "Direct tool edit",
294
- rationale: "Model-invoked single edit via evolve_* tool.",
295
- expectedOutcome: "Entry is created, updated, or deleted as requested.",
296
- edits,
297
- }, agent
298
- ? {
299
- scope,
300
- ...(entrySourceOf(agent, eventSessionId ?? storeId) ? { source: entrySourceOf(agent, eventSessionId ?? storeId) } : {}),
301
- }
302
- : { scope });
303
- const applied = result.appliedEdits.filter((e) => e.applied);
304
- const failed = result.appliedEdits.filter((e) => !e.applied);
305
- // Gap C4: emit structured evolve_complete event for third-party consumers.
306
- // The event keys on the live session (not the project store key).
307
- const eventSession = eventSessionId ?? storeId;
308
- if (applied.length > 0 && eventSession) {
309
- emitEvolveComplete(engine.baseDir, buildEvolveCompleteEvent(result, "manual_tool", eventSession), engine.retention?.reviews ?? DEFAULT_REVIEWS_RETAIN);
310
- }
311
- const lines = [`refinement ${result.id}: ${applied.length} applied, ${failed.length} failed`];
312
- for (const e of applied) {
313
- lines.push(`- ${e.action} ${e.kind}:${e.id} (v${(e.after?.version ?? e.before?.version) ?? "?"})`);
314
- }
315
- for (const e of failed) {
316
- lines.push(`- failed ${e.action} ${e.kind}:${e.id ?? "(computed)"} — ${e.error ?? "unknown error"}`);
317
- }
318
- return lines.join("\n");
319
- }
320
- //# sourceMappingURL=tool.js.map
@@ -1,72 +0,0 @@
1
- /**
2
- * Turn-boundary snapshots for the automatic review gate.
3
- *
4
- * The scheduler must see a bounded, mechanically filtered slice of the
5
- * current session, not a fresh full-history LLM request on every turn. This
6
- * module owns the snapshot boundary, cursor-compatible incremental rows, and
7
- * the cheap eligibility checks that run before any model call.
8
- */
9
- import type { Context } from "@deepseek-ai/cordis";
10
- import type { Agent } from "@deepseek-ai/dsh-agent";
11
- /** Why a scheduler snapshot was captured. */
12
- export type TurnSnapshotReason = "turn_snapshot" | "compact";
13
- /** Mechanical reasons a snapshot can be skipped without an LLM call. */
14
- export type SnapshotSkipReason = "no-new-events" | "internal-agent" | "direct-memory-write" | "no-user-prose";
15
- /** One immutable scheduler input. The cursor is a durable event boundary. */
16
- export interface TurnSnapshot {
17
- readonly sessionId: string;
18
- readonly projectKey?: string;
19
- readonly turn: number;
20
- readonly reason: TurnSnapshotReason;
21
- readonly cursor: string;
22
- readonly events: readonly unknown[];
23
- readonly trajectory: string;
24
- readonly userText: string;
25
- readonly sourceSeqs: readonly number[];
26
- readonly maxChars: number;
27
- readonly minUserWords: number;
28
- /** Cursor supplied by the shared scheduler before this snapshot selected events. */
29
- readonly capturedAfterCursor?: string;
30
- readonly eligible: boolean;
31
- readonly skipReason?: SnapshotSkipReason;
32
- }
33
- /** Eligibility result kept separate so tests and future extractors share it. */
34
- export interface SnapshotEligibility {
35
- readonly eligible: boolean;
36
- readonly reason?: SnapshotSkipReason;
37
- readonly userText: string;
38
- }
39
- /** ZCode's minimum user-prose threshold, counted as lexical words. */
40
- export declare const MEMORY_MIN_USER_WORDS = 3;
41
- /**
42
- * Capture the current durable surface and select only rows after `cursor`.
43
- * A seq-bearing session uses `seq:<n>`; older/test surfaces fall back to an
44
- * event-count cursor so the scheduler still has a deterministic boundary.
45
- */
46
- export declare function captureTurnSnapshot(ctx: Context, agent: Agent, opts: {
47
- turn: number;
48
- reason: TurnSnapshotReason;
49
- cursor?: string;
50
- maxChars: number;
51
- minUserWords?: number;
52
- }): Promise<TurnSnapshot>;
53
- /**
54
- * Rebase one captured snapshot onto a phase-specific cursor while preserving
55
- * its outer boundary. The memory phase uses this after a downstream review or
56
- * fate failure: already-processed evidence is removed, but newly captured rows
57
- * remain available without moving the shared scheduler cursor backwards.
58
- */
59
- export declare function sliceTurnSnapshot(snapshot: TurnSnapshot, cursor: string): TurnSnapshot;
60
- /**
61
- * Decide whether the incremental rows deserve a model call. This intentionally
62
- * does not decide whether a fact is valuable; that remains the review model's
63
- * job. It only removes mechanical noise before the scheduler spends tokens.
64
- */
65
- export declare function evaluateSnapshotEligibility(events: readonly unknown[], internalAgent?: boolean, minUserWords?: number): SnapshotEligibility;
66
- /** True when an assistant row records an explicit evolve memory mutation. */
67
- export declare function containsDirectMemoryWrite(events: readonly unknown[]): boolean;
68
- /** Serialize only user/assistant text, retaining the bounded tail. */
69
- export declare function serializeSnapshotEvents(events: readonly unknown[], maxChars: number): string;
70
- /** A DSH subagent header is an internal source when its origin says so. */
71
- export declare function isInternalAgent(agent: Agent | undefined): boolean;
72
- //# sourceMappingURL=turn-snapshot.d.ts.map