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.
- package/README.md +35 -122
- package/README.zh.md +26 -97
- package/cordis.patch.yml +2 -2
- package/lib/index.d.ts +44 -241
- package/lib/index.js +30 -253
- package/lib/memory-guide.d.ts +13 -14
- package/lib/memory-guide.js +32 -21
- package/lib/memory-section.d.ts +77 -0
- package/lib/memory-section.js +189 -0
- package/package.json +2 -17
- package/lib/apply.d.ts +0 -22
- package/lib/apply.js +0 -157
- package/lib/approval.d.ts +0 -72
- package/lib/approval.js +0 -127
- package/lib/auto.d.ts +0 -236
- package/lib/auto.js +0 -773
- package/lib/autocase.d.ts +0 -38
- package/lib/autocase.js +0 -76
- package/lib/benchmark-command.d.ts +0 -9
- package/lib/benchmark-command.js +0 -357
- package/lib/benchmark.d.ts +0 -151
- package/lib/benchmark.js +0 -250
- package/lib/command.d.ts +0 -49
- package/lib/command.js +0 -568
- package/lib/consolidate.d.ts +0 -90
- package/lib/consolidate.js +0 -180
- package/lib/copy.d.ts +0 -74
- package/lib/copy.js +0 -160
- package/lib/declines.d.ts +0 -101
- package/lib/declines.js +0 -175
- package/lib/evaluate.d.ts +0 -74
- package/lib/evaluate.js +0 -271
- package/lib/evolve-event.d.ts +0 -40
- package/lib/evolve-event.js +0 -59
- package/lib/failures.d.ts +0 -42
- package/lib/failures.js +0 -171
- package/lib/fate.d.ts +0 -130
- package/lib/fate.js +0 -350
- package/lib/goal-command.d.ts +0 -7
- package/lib/goal-command.js +0 -37
- package/lib/goal.d.ts +0 -72
- package/lib/goal.js +0 -72
- package/lib/inject.d.ts +0 -196
- package/lib/inject.js +0 -412
- package/lib/llm-text.d.ts +0 -104
- package/lib/llm-text.js +0 -140
- package/lib/logfile.d.ts +0 -71
- package/lib/logfile.js +0 -159
- package/lib/memory-agent.d.ts +0 -129
- package/lib/memory-agent.js +0 -568
- package/lib/memory-benchmark.d.ts +0 -76
- package/lib/memory-benchmark.js +0 -170
- package/lib/memory-index.d.ts +0 -114
- package/lib/memory-index.js +0 -197
- package/lib/message-source.d.ts +0 -23
- package/lib/message-source.js +0 -12
- package/lib/mount-command.d.ts +0 -10
- package/lib/mount-command.js +0 -48
- package/lib/mount.d.ts +0 -42
- package/lib/mount.js +0 -212
- package/lib/notify.d.ts +0 -59
- package/lib/notify.js +0 -97
- package/lib/plan.d.ts +0 -16
- package/lib/plan.js +0 -126
- package/lib/planner.d.ts +0 -61
- package/lib/planner.js +0 -144
- package/lib/pool.d.ts +0 -7
- package/lib/pool.js +0 -25
- package/lib/prefix-cache.d.ts +0 -78
- package/lib/prefix-cache.js +0 -168
- package/lib/project.d.ts +0 -28
- package/lib/project.js +0 -66
- package/lib/projection.d.ts +0 -22
- package/lib/projection.js +0 -112
- package/lib/promotion.d.ts +0 -94
- package/lib/promotion.js +0 -171
- package/lib/recall.d.ts +0 -85
- package/lib/recall.js +0 -142
- package/lib/record-language.d.ts +0 -85
- package/lib/record-language.js +0 -152
- package/lib/render.d.ts +0 -13
- package/lib/render.js +0 -80
- package/lib/review-scheduler.d.ts +0 -38
- package/lib/review-scheduler.js +0 -191
- package/lib/review.d.ts +0 -60
- package/lib/review.js +0 -145
- package/lib/rollback.d.ts +0 -9
- package/lib/rollback.js +0 -68
- package/lib/rubric.d.ts +0 -29
- package/lib/rubric.js +0 -119
- package/lib/runtime.d.ts +0 -24
- package/lib/runtime.js +0 -87
- package/lib/score.d.ts +0 -64
- package/lib/score.js +0 -191
- package/lib/search.d.ts +0 -83
- package/lib/search.js +0 -136
- package/lib/service.d.ts +0 -49
- package/lib/service.js +0 -180
- package/lib/skill-render.d.ts +0 -23
- package/lib/skill-render.js +0 -69
- package/lib/skill.d.ts +0 -15
- package/lib/skill.js +0 -80
- package/lib/skillquality.d.ts +0 -85
- package/lib/skillquality.js +0 -320
- package/lib/source.d.ts +0 -30
- package/lib/source.js +0 -43
- package/lib/state.d.ts +0 -26
- package/lib/state.js +0 -151
- package/lib/store.d.ts +0 -62
- package/lib/store.js +0 -164
- package/lib/token-usage.d.ts +0 -68
- package/lib/token-usage.js +0 -211
- package/lib/tool.d.ts +0 -34
- package/lib/tool.js +0 -320
- package/lib/turn-snapshot.d.ts +0 -72
- package/lib/turn-snapshot.js +0 -238
- package/lib/types.d.ts +0 -208
- package/lib/types.js +0 -127
- package/lib/usage.d.ts +0 -36
- package/lib/usage.js +0 -98
- package/lib/validate.d.ts +0 -28
- package/lib/validate.js +0 -181
- package/lib/wrapup-command.d.ts +0 -10
- package/lib/wrapup-command.js +0 -243
- package/lib/wrapup.d.ts +0 -260
- package/lib/wrapup.js +0 -539
package/lib/token-usage.js
DELETED
|
@@ -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
|
package/lib/turn-snapshot.d.ts
DELETED
|
@@ -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
|