dsh-continual-evolve 0.10.6 → 0.11.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 +24 -31
- package/README.zh.md +17 -21
- package/lib/approval.d.ts +2 -1
- package/lib/audit-log.d.ts +19 -0
- package/lib/audit-log.js +52 -0
- package/lib/benchmark-command.d.ts +1 -1
- package/lib/benchmark-command.js +1 -1
- package/lib/command-util.d.ts +70 -0
- package/lib/command-util.js +123 -0
- package/lib/command.d.ts +7 -43
- package/lib/command.js +24 -458
- package/lib/copy.d.ts +4 -41
- package/lib/copy.js +0 -102
- package/lib/declines.d.ts +2 -0
- package/lib/declines.js +4 -0
- package/lib/evolve-event.js +2 -1
- package/lib/extraction-phase.d.ts +132 -0
- package/lib/extraction-phase.js +236 -0
- package/lib/index.d.ts +26 -42
- package/lib/index.js +19 -33
- package/lib/inject.d.ts +13 -119
- package/lib/inject.js +12 -260
- package/lib/injection-caps.d.ts +4 -0
- package/lib/injection-caps.js +4 -0
- package/lib/injection-directory.d.ts +29 -0
- package/lib/injection-directory.js +78 -0
- package/lib/injection-rank.d.ts +85 -0
- package/lib/injection-rank.js +161 -0
- package/lib/listener.d.ts +45 -0
- package/lib/listener.js +199 -0
- package/lib/maintenance-commands.d.ts +23 -0
- package/lib/maintenance-commands.js +136 -0
- package/lib/memory-agent.d.ts +8 -44
- package/lib/memory-agent.js +8 -276
- package/lib/memory-apply.d.ts +44 -0
- package/lib/memory-apply.js +202 -0
- package/lib/memory-manifest.d.ts +16 -0
- package/lib/memory-manifest.js +70 -0
- package/lib/mount-command.js +1 -1
- package/lib/notify.d.ts +9 -20
- package/lib/notify.js +0 -40
- package/lib/plan-command.d.ts +12 -0
- package/lib/plan-command.js +38 -0
- package/lib/prefix-cache.d.ts +2 -1
- package/lib/review-scheduler.d.ts +1 -1
- package/lib/review-scheduler.js +2 -10
- package/lib/runtime-commands.d.ts +14 -0
- package/lib/runtime-commands.js +115 -0
- package/lib/service.d.ts +16 -2
- package/lib/service.js +74 -1
- package/lib/store.d.ts +1 -2
- package/lib/store.js +2 -2
- package/lib/tool.js +1 -1
- package/lib/turn-snapshot.d.ts +7 -1
- package/lib/turn-snapshot.js +14 -0
- package/lib/types.d.ts +10 -0
- package/lib/types.js +6 -0
- package/lib/wrapup-candidates.d.ts +127 -0
- package/lib/wrapup-candidates.js +161 -0
- package/lib/wrapup-proposals.d.ts +101 -0
- package/lib/wrapup-proposals.js +281 -0
- package/lib/wrapup.d.ts +8 -212
- package/lib/wrapup.js +4 -438
- package/package.json +1 -1
package/lib/command.js
CHANGED
|
@@ -1,24 +1,13 @@
|
|
|
1
|
-
import { ARCHIVED_AT_KEY } from "./types.js";
|
|
2
1
|
import { formatHarnessStateForPrompt, historyForPrompt } from "./render.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { saveHarnessState } from "./state.js";
|
|
8
|
-
import { appendResult, pruneJsonlFile, storePaths } from "./store.js";
|
|
9
|
-
import { DEFAULT_REFINEMENTS_RETAIN } from "./store.js";
|
|
10
|
-
import { entrySourceOf } from "./source.js";
|
|
11
|
-
import { filterLogBySession, formatLogLine, pluginLogFilePath } from "./logfile.js";
|
|
12
|
-
import { collectFailureSummary, formatFailureSummary } from "./failures.js";
|
|
2
|
+
import { findEntryById, renderResult, scopeArg, stripAngleBrackets, storeIdForCommand, success, error, tokenizeEvolveInput, } from "./command-util.js";
|
|
3
|
+
import { executePlanCommand } from "./plan-command.js";
|
|
4
|
+
import { executeArchiveCommand, executeConsolidateCommand, executeExportCommand, executeFailuresCommand, executeImportCommand, executeLogCommand, } from "./maintenance-commands.js";
|
|
5
|
+
import { executePauseResumeCommand, executeStatusCommand, executeUsageCommand } from "./runtime-commands.js";
|
|
13
6
|
import { executeGoalCommand } from "./goal-command.js";
|
|
14
7
|
import { executeMountCommand, executeUnmountCommand } from "./mount-command.js";
|
|
15
8
|
import { executeBenchmarkCommand } from "./benchmark-command.js";
|
|
16
9
|
import { executeWrapupCommand } from "./wrapup-command.js";
|
|
17
10
|
import { projectKeyOf } from "./project.js";
|
|
18
|
-
import { loadUsage, getUsageCount } from "./usage.js";
|
|
19
|
-
import { loadGateRuntime, saveGateRuntime } from "./runtime.js";
|
|
20
|
-
import { planConsolidation } from "./consolidate.js";
|
|
21
|
-
import { loadTokenUsage, renderTokenUsageReport } from "./token-usage.js";
|
|
22
11
|
const USAGE_COMMON = `Usage:
|
|
23
12
|
/evolve show this help and the current local store
|
|
24
13
|
/evolve list [project|global] list entries (default local staging; "project" = this project's cross-session store, "global" = cross-project store)
|
|
@@ -51,6 +40,7 @@ const USAGE_ADVANCED = `Advanced:
|
|
|
51
40
|
/evolve usage injection counts + exact direct-call token usage (benchmark subagents excluded)`;
|
|
52
41
|
/** Default help (`/evolve`, `/evolve help`): the commands worth knowing. */
|
|
53
42
|
const USAGE = USAGE_COMMON;
|
|
43
|
+
export { findEntryById, stripAngleBrackets, tokenizeEvolveInput };
|
|
54
44
|
export function registerEvolveCommand(ctx, engine, opts, runtime) {
|
|
55
45
|
ctx.commands.register({
|
|
56
46
|
name: "evolve",
|
|
@@ -59,96 +49,6 @@ export function registerEvolveCommand(ctx, engine, opts, runtime) {
|
|
|
59
49
|
handler: (invocation) => executeEvolveCommand(ctx, engine, invocation, opts, runtime),
|
|
60
50
|
});
|
|
61
51
|
}
|
|
62
|
-
function scopeArg(tokens) {
|
|
63
|
-
if (tokens[0] === "global") {
|
|
64
|
-
return { scope: "global", rest: tokens.slice(1) };
|
|
65
|
-
}
|
|
66
|
-
if (tokens[0] === "project") {
|
|
67
|
-
return { scope: "project", rest: tokens.slice(1) };
|
|
68
|
-
}
|
|
69
|
-
return { scope: "local", rest: tokens };
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* The store id a scope reads/writes from the human command: live session id
|
|
73
|
-
* for local, derived project key for project, undefined for global. Throws
|
|
74
|
-
* for project when the session cwd is unavailable.
|
|
75
|
-
*/
|
|
76
|
-
function storeIdForCommand(scope, invocation) {
|
|
77
|
-
if (scope === "local") {
|
|
78
|
-
return invocation.agent.id;
|
|
79
|
-
}
|
|
80
|
-
if (scope === "project") {
|
|
81
|
-
const key = projectKeyOf(invocation.agent);
|
|
82
|
-
if (!key) {
|
|
83
|
-
throw new Error("project scope needs the session cwd (unavailable here) — use local or global instead");
|
|
84
|
-
}
|
|
85
|
-
return key;
|
|
86
|
-
}
|
|
87
|
-
return undefined;
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Tokenize a command's raw input with shell-like quoting:
|
|
91
|
-
* - a `#` outside quotes starts a comment (rest of the line is dropped);
|
|
92
|
-
* - whitespace separates tokens;
|
|
93
|
-
* - double or single quotes group words into one token and are stripped.
|
|
94
|
-
*
|
|
95
|
-
* This lets users paste help-text examples verbatim, e.g.
|
|
96
|
-
* `/evolve benchmark add-case <bid> "<title>" "<statement>" "<rubric>"`.
|
|
97
|
-
*/
|
|
98
|
-
export function tokenizeEvolveInput(rawInput) {
|
|
99
|
-
const tokens = [];
|
|
100
|
-
let current = "";
|
|
101
|
-
let quote = null;
|
|
102
|
-
for (const char of rawInput) {
|
|
103
|
-
if (quote !== null) {
|
|
104
|
-
if (char === quote) {
|
|
105
|
-
quote = null;
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
current += char;
|
|
109
|
-
}
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
if (char === '"' || char === "'") {
|
|
113
|
-
quote = char;
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
if (char === "#") {
|
|
117
|
-
break; // rest of the line is a comment
|
|
118
|
-
}
|
|
119
|
-
if (/\s/.test(char)) {
|
|
120
|
-
if (current.length > 0) {
|
|
121
|
-
tokens.push(current);
|
|
122
|
-
current = "";
|
|
123
|
-
}
|
|
124
|
-
continue;
|
|
125
|
-
}
|
|
126
|
-
current += char;
|
|
127
|
-
}
|
|
128
|
-
if (current.length > 0) {
|
|
129
|
-
tokens.push(current);
|
|
130
|
-
}
|
|
131
|
-
return tokens;
|
|
132
|
-
}
|
|
133
|
-
/** Accept both `<id>` (help-text placeholder form) and bare `id`. */
|
|
134
|
-
export function stripAngleBrackets(value) {
|
|
135
|
-
return value.replace(/^<|>$/g, "");
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Locate an entry by id across every kind of a store. Ids are only unique
|
|
139
|
-
* within a kind, so the lookup scans all four and returns the first match
|
|
140
|
-
* (kind + entry) or undefined. Used by archive/unarchive, which take a bare
|
|
141
|
-
* id from the user.
|
|
142
|
-
*/
|
|
143
|
-
export function findEntryById(state, id) {
|
|
144
|
-
for (const kind of Object.keys(state.entries)) {
|
|
145
|
-
const entry = state.entries[kind][id];
|
|
146
|
-
if (entry) {
|
|
147
|
-
return [kind, entry];
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
return undefined;
|
|
151
|
-
}
|
|
152
52
|
async function executeEvolveCommand(ctx, engine, invocation, opts, runtime) {
|
|
153
53
|
const tokens = tokenizeEvolveInput(invocation.rawInput);
|
|
154
54
|
const sub = tokens[0] ?? "";
|
|
@@ -181,226 +81,36 @@ async function executeEvolveCommand(ctx, engine, invocation, opts, runtime) {
|
|
|
181
81
|
}
|
|
182
82
|
case "archive":
|
|
183
83
|
case "unarchive": {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
return error(`entry ${id} not found in the ${scope} store`);
|
|
193
|
-
}
|
|
194
|
-
const [kind, entry] = found;
|
|
195
|
-
const metadata = { ...entry.metadata };
|
|
196
|
-
if (sub === "archive") {
|
|
197
|
-
metadata[ARCHIVED_AT_KEY] = new Date().toISOString();
|
|
198
|
-
}
|
|
199
|
-
else {
|
|
200
|
-
delete metadata[ARCHIVED_AT_KEY];
|
|
201
|
-
}
|
|
202
|
-
const archived = sub === "archive";
|
|
203
|
-
const result = engine.apply(scope, storeIdForCommand(scope, invocation), {
|
|
204
|
-
summary: `${archived ? "Archive" : "Unarchive"} entry ${kind}:${id}`,
|
|
205
|
-
rationale: "Human-invoked archive/unarchive via the /evolve command.",
|
|
206
|
-
expectedOutcome: `Entry ${archived ? "is hidden from injection (data kept, restorable)" : "is injected again"}.`,
|
|
207
|
-
edits: [{ action: "update", kind, id, title: entry.title, content: entry.content, metadata }],
|
|
208
|
-
}, { scope });
|
|
209
|
-
return success(renderResult(result));
|
|
210
|
-
}
|
|
211
|
-
case "consolidate": {
|
|
212
|
-
// R3: deterministic global-store hygiene. Report by default;
|
|
213
|
-
// `apply` re-scans fresh state and lands the whole batch as ONE
|
|
214
|
-
// refinement (single snapshot + audit record, fully rollback-able).
|
|
215
|
-
// `merge` (P1 反膨胀) additionally merges conflict-pair content
|
|
216
|
-
// into the surviving original instead of only archiving.
|
|
217
|
-
const apply = rest[0] === "apply";
|
|
218
|
-
const merge = rest.includes("merge");
|
|
219
|
-
const state = engine.load("global", undefined);
|
|
220
|
-
const { candidates, edits } = planConsolidation(state, loadUsage(engine.baseDir), Date.now(), { mergeDuplicates: merge });
|
|
221
|
-
if (candidates.length === 0) {
|
|
222
|
-
return success("global store is already consolidated — no conflict-hinted or stale zero-use entries.");
|
|
223
|
-
}
|
|
224
|
-
const mergeCount = merge ? candidates.filter((candidate) => candidate.mergeInto).length : 0;
|
|
225
|
-
const report = candidates
|
|
226
|
-
.map((candidate, index) => {
|
|
227
|
-
const mergeNote = candidate.mergeInto && merge ? ` → 内容并入 ${candidate.mergeInto.id}` : "";
|
|
228
|
-
return `${index + 1}. [${candidate.kind}:${candidate.id}] ${candidate.title}${mergeNote}\n ${candidate.reason}`;
|
|
229
|
-
})
|
|
230
|
-
.join("\n");
|
|
231
|
-
if (!apply) {
|
|
232
|
-
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)`);
|
|
233
|
-
}
|
|
234
|
-
const result = engine.apply("global", undefined, {
|
|
235
|
-
summary: `Consolidate global store: archive ${candidates.length} entries${mergeCount > 0 ? `, merge ${mergeCount} into survivors` : ""}`,
|
|
236
|
-
rationale: "Human-invoked batch consolidation via /evolve consolidate apply.",
|
|
237
|
-
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.",
|
|
238
|
-
edits,
|
|
239
|
-
}, { scope: "global" });
|
|
240
|
-
return success(`${report}\n\napplied:\n${renderResult(result)}`);
|
|
241
|
-
}
|
|
242
|
-
case "failures": {
|
|
243
|
-
// /evolve failures — failure-signature aggregation (D1 observation):
|
|
244
|
-
// failed review-gate records + failed benchmark cells, counted by class.
|
|
245
|
-
const { summary, records } = collectFailureSummary(engine.baseDir);
|
|
246
|
-
const parts = formatFailureSummary(summary).split("\n");
|
|
247
|
-
const recent = records
|
|
248
|
-
.sort((a, b) => (b.timestamp ?? "").localeCompare(a.timestamp ?? ""))
|
|
249
|
-
.slice(0, 10)
|
|
250
|
-
.map((f) => ` [${f.timestamp ?? "(benchmark)"}] ${f.kind} · ${f.source}: ${f.message.slice(0, 140)}`);
|
|
251
|
-
if (recent.length > 0) {
|
|
252
|
-
parts.push("recent 10:");
|
|
253
|
-
parts.push(...recent);
|
|
254
|
-
}
|
|
255
|
-
return success(parts.join("\n"));
|
|
256
|
-
}
|
|
257
|
-
case "log": {
|
|
258
|
-
// /evolve log [tail N] [session <sessionId>]
|
|
259
|
-
let tail = 50;
|
|
260
|
-
let sessionFilter;
|
|
261
|
-
for (let i = 0; i < rest.length; i += 1) {
|
|
262
|
-
const token = rest[i] ?? "";
|
|
263
|
-
if (token === "session") {
|
|
264
|
-
sessionFilter = stripAngleBrackets(rest[i + 1] ?? "");
|
|
265
|
-
if (!sessionFilter) {
|
|
266
|
-
return error(`log session requires a session id (e.g. /evolve log session session-abc123).\n${USAGE}`);
|
|
267
|
-
}
|
|
268
|
-
i += 1;
|
|
269
|
-
}
|
|
270
|
-
else {
|
|
271
|
-
tail = Math.min(Math.max(parsePositiveInt(token, "tail"), 1), 1000);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
const path = pluginLogFilePath(engine.baseDir);
|
|
275
|
-
if (!existsSync(path)) {
|
|
276
|
-
return success(`(no plugin log yet — ${path} is created on the first log message)`);
|
|
277
|
-
}
|
|
278
|
-
const lines = readFileSync(path, "utf8").trimEnd().split("\n").filter((line) => line.length > 0);
|
|
279
|
-
if (lines.length === 0) {
|
|
280
|
-
return success(`(empty plugin log: ${path})`);
|
|
281
|
-
}
|
|
282
|
-
const filtered = sessionFilter ? filterLogBySession(lines, sessionFilter) : lines;
|
|
283
|
-
const shown = filtered.slice(-tail);
|
|
284
|
-
const scopeNote = sessionFilter ? `, ${filtered.length} for session ${sessionFilter}` : "";
|
|
285
|
-
return success(`plugin log ${path} (${lines.length} lines${scopeNote}, showing last ${shown.length}):\n${shown.map(formatLogLine).join("\n")}`);
|
|
286
|
-
}
|
|
84
|
+
return await executeArchiveCommand(ctx, engine, invocation, sub, scopeArg(rest), USAGE);
|
|
85
|
+
}
|
|
86
|
+
case "consolidate":
|
|
87
|
+
return executeConsolidateCommand(engine, rest);
|
|
88
|
+
case "failures":
|
|
89
|
+
return executeFailuresCommand(engine);
|
|
90
|
+
case "log":
|
|
91
|
+
return executeLogCommand(engine, rest, USAGE);
|
|
287
92
|
case "export": {
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
if (!path) {
|
|
291
|
-
return error(`export requires an output path.\n${USAGE}`);
|
|
292
|
-
}
|
|
293
|
-
const state = engine.load(scope, storeIdForCommand(scope, invocation));
|
|
294
|
-
const history = engine.history(scope, storeIdForCommand(scope, invocation));
|
|
295
|
-
const payload = {
|
|
296
|
-
version: 1,
|
|
297
|
-
scope,
|
|
298
|
-
schema: state.schema,
|
|
299
|
-
entries: state.entries,
|
|
300
|
-
refinements: state.refinements,
|
|
301
|
-
history,
|
|
302
|
-
};
|
|
303
|
-
writeFileSync(path, `${JSON.stringify(payload, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
304
|
-
return success(`exported ${scope} store (${Object.values(state.entries).reduce((n, e) => n + Object.keys(e).length, 0)} entries, ${history.length} refinements) to ${path}`);
|
|
93
|
+
const parsed = scopeArg(rest);
|
|
94
|
+
return executeExportCommand(engine, invocation, parsed, USAGE);
|
|
305
95
|
}
|
|
306
96
|
case "import": {
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
if (!path) {
|
|
310
|
-
return error(`import requires an input path.\n${USAGE}`);
|
|
311
|
-
}
|
|
312
|
-
const payload = JSON.parse(readFileSync(path, "utf8"));
|
|
313
|
-
if (!isValidExport(payload)) {
|
|
314
|
-
return error(`invalid export file shape: expected {version, entries: {prompt, memory, skill, subagent}, refinements, history}`);
|
|
315
|
-
}
|
|
316
|
-
const state = {
|
|
317
|
-
schema: typeof payload["schema"] === "number" ? payload["schema"] : 1,
|
|
318
|
-
entries: {
|
|
319
|
-
prompt: toEntryRecord(payload["entries"]["prompt"]),
|
|
320
|
-
memory: toEntryRecord(payload["entries"]["memory"]),
|
|
321
|
-
skill: toEntryRecord(payload["entries"]["skill"]),
|
|
322
|
-
subagent: toEntryRecord(payload["entries"]["subagent"]),
|
|
323
|
-
},
|
|
324
|
-
refinements: Array.isArray(payload["refinements"]) ? payload["refinements"] : [],
|
|
325
|
-
};
|
|
326
|
-
const paths = storePaths(engine.baseDir, scope, storeIdForCommand(scope, invocation));
|
|
327
|
-
saveHarnessState(paths.stateDir, state);
|
|
328
|
-
if (Array.isArray(payload["history"])) {
|
|
329
|
-
for (const result of payload["history"]) {
|
|
330
|
-
if (isResultRecord(result)) {
|
|
331
|
-
appendResult(paths, result);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
// Storage hygiene (#20): an imported history obeys the
|
|
335
|
-
// same tail budget as a live one.
|
|
336
|
-
try {
|
|
337
|
-
pruneJsonlFile(paths.resultsPath, engine.retention?.refinements ?? DEFAULT_REFINEMENTS_RETAIN);
|
|
338
|
-
}
|
|
339
|
-
catch {
|
|
340
|
-
// ignored — the next apply retries
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
return success(`imported ${scope} store from ${path}`);
|
|
97
|
+
const parsed = scopeArg(rest);
|
|
98
|
+
return executeImportCommand(engine, invocation, parsed, parsed.rest[0]);
|
|
344
99
|
}
|
|
345
100
|
case "plan": {
|
|
346
101
|
const { scope, rest: after } = scopeArg(rest);
|
|
347
|
-
|
|
348
|
-
const state = engine.load(scope, storeIdForCommand(scope, invocation));
|
|
349
|
-
const history = engine.history(scope, storeIdForCommand(scope, invocation));
|
|
350
|
-
const proposal = await planWithLlm(ctx, {
|
|
351
|
-
agent: invocation.agent,
|
|
352
|
-
state,
|
|
353
|
-
history,
|
|
354
|
-
...(instructions ? { instructions } : {}),
|
|
355
|
-
global: scope === "global",
|
|
356
|
-
signal: invocation.signal,
|
|
357
|
-
tokenUsage: {
|
|
358
|
-
baseDir: engine.baseDir,
|
|
359
|
-
sessionId,
|
|
360
|
-
retain: engine.retention.tokenUsage,
|
|
361
|
-
onError: (cause) => ctx.logger("continual-evolve").warn(`token-usage ledger failed for ${sessionId}: ${cause instanceof Error ? cause.message : String(cause)}`),
|
|
362
|
-
},
|
|
363
|
-
// skill-creator template facts (fallback: builtin guide).
|
|
364
|
-
skillsRoot: join(engine.baseDir, "skills"),
|
|
365
|
-
});
|
|
366
|
-
if ((scope === "global" || scope === "project") && opts.requireGlobalApproval && proposal.edits.length > 0) {
|
|
367
|
-
await requireGlobalApproval(ctx, invocation.agent, invocation.signal, `/evolve plan ${scope} 将应用 ${proposal.edits.length} 条编辑到${scope === "project" ? "本项目" : "跨会话"} store:${proposal.summary}`, runtime.recordLanguage);
|
|
368
|
-
}
|
|
369
|
-
const result = engine.apply(scope, storeIdForCommand(scope, invocation), proposal, {
|
|
370
|
-
scope,
|
|
371
|
-
baselineState: state,
|
|
372
|
-
...(entrySourceOf(invocation.agent, sessionId) ? { source: entrySourceOf(invocation.agent, sessionId) } : {}),
|
|
373
|
-
});
|
|
374
|
-
return success(renderResult(result));
|
|
102
|
+
return await executePlanCommand(ctx, engine, invocation, scope, after, opts, runtime);
|
|
375
103
|
}
|
|
376
104
|
case "wrapup": {
|
|
377
105
|
return await executeWrapupCommand(ctx, engine, invocation, runtime.promotionPolicy, runtime.recordLanguage);
|
|
378
106
|
}
|
|
379
107
|
case "pause":
|
|
380
|
-
case "resume":
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const current = loadGateRuntime(engine.baseDir, defaultEnabled);
|
|
387
|
-
if (pausing && current.paused) {
|
|
388
|
-
return success("automatic Memory Agent is already paused (no change).");
|
|
389
|
-
}
|
|
390
|
-
if (!pausing && current.enabled && !current.paused) {
|
|
391
|
-
return success("automatic Memory Agent is already running (no change).");
|
|
392
|
-
}
|
|
393
|
-
saveGateRuntime(engine.baseDir, pausing, pausing ? current.enabled : true);
|
|
394
|
-
return success(pausing
|
|
395
|
-
? "automatic Memory Agent paused: no automatic memory extraction calls until /evolve resume. Manual evolve_* tools and /evolve commands keep working."
|
|
396
|
-
: "automatic Memory Agent resumed: successful turns will produce incremental memory snapshots immediately.");
|
|
397
|
-
}
|
|
398
|
-
case "status": {
|
|
399
|
-
return success(renderGateStatus(engine, sessionId, projectKeyOf(invocation.agent), runtime));
|
|
400
|
-
}
|
|
401
|
-
case "usage": {
|
|
402
|
-
return success(renderUsageReport(engine, sessionId, projectKeyOf(invocation.agent)));
|
|
403
|
-
}
|
|
108
|
+
case "resume":
|
|
109
|
+
return executePauseResumeCommand(engine, sub, runtime);
|
|
110
|
+
case "status":
|
|
111
|
+
return executeStatusCommand(engine, sessionId, projectKeyOf(invocation.agent), runtime);
|
|
112
|
+
case "usage":
|
|
113
|
+
return executeUsageCommand(engine, sessionId, projectKeyOf(invocation.agent));
|
|
404
114
|
case "goal": {
|
|
405
115
|
return executeGoalCommand(ctx, invocation, rest);
|
|
406
116
|
}
|
|
@@ -421,148 +131,4 @@ async function executeEvolveCommand(ctx, engine, invocation, opts, runtime) {
|
|
|
421
131
|
return error(cause instanceof Error ? cause.message : String(cause));
|
|
422
132
|
}
|
|
423
133
|
}
|
|
424
|
-
/**
|
|
425
|
-
* Status answers whether the automatic listener is wired and what
|
|
426
|
-
* stores/retention it currently sees.
|
|
427
|
-
*/
|
|
428
|
-
function renderGateStatus(engine, sessionId, projectKey, runtime) {
|
|
429
|
-
const configuredDefault = runtime.autoReview === undefined ? "unknown" : runtime.autoReview ? "on" : "off";
|
|
430
|
-
const current = loadGateRuntime(engine.baseDir, runtime.autoReview ?? false);
|
|
431
|
-
const effective = current.paused
|
|
432
|
-
? "PAUSED (resume with /evolve resume)"
|
|
433
|
-
: current.enabled
|
|
434
|
-
? "Memory Agent running"
|
|
435
|
-
: "Memory Agent off (enable with /evolve resume)";
|
|
436
|
-
const gateLine = `gate: Memory Agent-only listener registered · config default ${configuredDefault} · runtime ${effective}`;
|
|
437
|
-
const countEntries = (state) => Object.values(state.entries).reduce((n, byKind) => n + Object.keys(byKind).length, 0);
|
|
438
|
-
const lines = [gateLine];
|
|
439
|
-
lines.push(`stores: global ${countEntries(engine.load("global", undefined))} entries · local(${sessionId}) ${countEntries(engine.load("local", sessionId))} entries`);
|
|
440
|
-
if (projectKey) {
|
|
441
|
-
try {
|
|
442
|
-
lines[lines.length - 1] += ` · project ${countEntries(engine.load("project", projectKey))} entries`;
|
|
443
|
-
}
|
|
444
|
-
catch {
|
|
445
|
-
lines[lines.length - 1] += " · project (unavailable)";
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
const retention = engine.retention;
|
|
449
|
-
if (retention) {
|
|
450
|
-
lines.push(`retention: snapshots ${retention.snapshots} · refinements ${retention.refinements} · reviews ${retention.reviews} · token usage ${retention.tokenUsage} (historyRetain)`);
|
|
451
|
-
}
|
|
452
|
-
return lines.join("\n");
|
|
453
|
-
}
|
|
454
|
-
/**
|
|
455
|
-
* #21 P0 usage ledger: injection counts per entry across the stores the
|
|
456
|
-
* human can see (global + this session + this project). Counts are
|
|
457
|
-
* per-session since the v2 usage shape — "in how many sessions did this
|
|
458
|
-
* entry surface" — the exposure half of the #18a归零实验 verdict.
|
|
459
|
-
*/
|
|
460
|
-
function renderUsageReport(engine, sessionId, projectKey) {
|
|
461
|
-
const store = loadUsage(engine.baseDir);
|
|
462
|
-
const rows = [];
|
|
463
|
-
const seen = new Set();
|
|
464
|
-
const collect = (state) => {
|
|
465
|
-
for (const kind of Object.keys(state.entries)) {
|
|
466
|
-
for (const entry of Object.values(state.entries[kind])) {
|
|
467
|
-
const key = `${kind}:${entry.id}`;
|
|
468
|
-
if (seen.has(key))
|
|
469
|
-
continue;
|
|
470
|
-
seen.add(key);
|
|
471
|
-
const count = getUsageCount(store, kind, entry.id);
|
|
472
|
-
rows.push({
|
|
473
|
-
key,
|
|
474
|
-
title: entry.title,
|
|
475
|
-
count,
|
|
476
|
-
...(store.lastSession?.[`${kind}:${entry.id}`] ? { lastSession: store.lastSession[`${kind}:${entry.id}`] } : {}),
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
};
|
|
481
|
-
collect(engine.load("global", undefined));
|
|
482
|
-
collect(engine.load("local", sessionId));
|
|
483
|
-
if (projectKey) {
|
|
484
|
-
try {
|
|
485
|
-
collect(engine.load("project", projectKey));
|
|
486
|
-
}
|
|
487
|
-
catch {
|
|
488
|
-
// project store unavailable here — global+local still report
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
const liveKeys = new Set(rows.map((r) => r.key));
|
|
492
|
-
const orphaned = Object.keys(store.counts).filter((k) => !liveKeys.has(k)).length;
|
|
493
|
-
const total = rows.reduce((n, r) => n + r.count, 0);
|
|
494
|
-
const injected = rows.filter((r) => r.count > 0).sort((a, b) => b.count - a.count || (a.key < b.key ? -1 : 1));
|
|
495
|
-
const stale = rows.filter((r) => r.count === 0).sort((a, b) => (a.key < b.key ? -1 : 1));
|
|
496
|
-
const lines = [`injected entries: ${total} injections across ${injected.length} of ${rows.length} stored entries${orphaned > 0 ? ` (+${orphaned} historical key(s) for deleted entries)` : ""}`];
|
|
497
|
-
if (injected.length > 0) {
|
|
498
|
-
lines.push("injected (top 15):");
|
|
499
|
-
for (const row of injected.slice(0, 15)) {
|
|
500
|
-
lines.push(` ${row.key} — ${row.count}× · ${row.title}${row.lastSession ? ` (last in ${row.lastSession})` : ""}`);
|
|
501
|
-
}
|
|
502
|
-
if (injected.length > 15) {
|
|
503
|
-
lines.push(` … and ${injected.length - 15} more`);
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
else {
|
|
507
|
-
lines.push("injected: (none yet — nothing has surfaced into a session prompt)");
|
|
508
|
-
}
|
|
509
|
-
if (stale.length > 0) {
|
|
510
|
-
lines.push(`never injected (${stale.length}):`);
|
|
511
|
-
for (const row of stale.slice(0, 20)) {
|
|
512
|
-
lines.push(` ${row.key} · ${row.title}`);
|
|
513
|
-
}
|
|
514
|
-
if (stale.length > 20) {
|
|
515
|
-
lines.push(` … and ${stale.length - 20} more`);
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
lines.push("", ...renderTokenUsageReport(loadTokenUsage(engine.baseDir), engine.retention.tokenUsage));
|
|
519
|
-
return lines.join("\n");
|
|
520
|
-
}
|
|
521
|
-
function renderResult(result) {
|
|
522
|
-
const applied = result.appliedEdits.filter((e) => e.applied);
|
|
523
|
-
const failed = result.appliedEdits.filter((e) => !e.applied);
|
|
524
|
-
const lines = [
|
|
525
|
-
`refinement ${result.id}${result.rollbackOf ? ` (rollback of ${result.rollbackOf})` : ""}: ${applied.length} applied, ${failed.length} failed`,
|
|
526
|
-
`summary: ${result.summary}`,
|
|
527
|
-
];
|
|
528
|
-
for (const e of applied) {
|
|
529
|
-
lines.push(`- ${e.action} ${e.kind}:${e.id} (v${(e.after?.version ?? e.before?.version) ?? "?"})`);
|
|
530
|
-
}
|
|
531
|
-
for (const e of failed) {
|
|
532
|
-
lines.push(`- failed ${e.action} ${e.kind}:${e.id ?? "(computed)"} — ${e.error ?? "unknown error"}`);
|
|
533
|
-
}
|
|
534
|
-
lines.push(`expected outcome: ${result.expectedOutcome}`);
|
|
535
|
-
return lines.join("\n");
|
|
536
|
-
}
|
|
537
|
-
function toEntryRecord(value) {
|
|
538
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
539
|
-
return {};
|
|
540
|
-
}
|
|
541
|
-
return value;
|
|
542
|
-
}
|
|
543
|
-
function parsePositiveInt(value, what) {
|
|
544
|
-
const n = Number(value);
|
|
545
|
-
if (!Number.isInteger(n) || n < 1) {
|
|
546
|
-
throw new Error(`${what} must be a positive integer, got "${value}"`);
|
|
547
|
-
}
|
|
548
|
-
return n;
|
|
549
|
-
}
|
|
550
|
-
function isValidExport(payload) {
|
|
551
|
-
if (typeof payload !== "object" || payload === null)
|
|
552
|
-
return false;
|
|
553
|
-
const entries = payload["entries"];
|
|
554
|
-
if (typeof entries !== "object" || entries === null || Array.isArray(entries))
|
|
555
|
-
return false;
|
|
556
|
-
const kinds = ["prompt", "memory", "skill", "subagent"];
|
|
557
|
-
return kinds.every((kind) => Object.prototype.hasOwnProperty.call(entries, kind));
|
|
558
|
-
}
|
|
559
|
-
function isResultRecord(value) {
|
|
560
|
-
return typeof value === "object" && value !== null && "id" in value && "appliedEdits" in value;
|
|
561
|
-
}
|
|
562
|
-
function success(text) {
|
|
563
|
-
return { kind: "success", text };
|
|
564
|
-
}
|
|
565
|
-
function error(text) {
|
|
566
|
-
return { kind: "error", text };
|
|
567
|
-
}
|
|
568
134
|
//# sourceMappingURL=command.js.map
|
package/lib/copy.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Backend user-visible copy for the
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Backend user-visible copy for the evolution confirmation dialogs, in both
|
|
3
|
+
* shipped languages. Labels stay literal per language (the parsers match
|
|
4
|
+
* them); guidance lives in `description`.
|
|
5
5
|
*
|
|
6
6
|
* Language is never hardcoded at a call site: every builder takes an
|
|
7
7
|
* explicit `lang`, and dialog entry points resolve it per call
|
|
8
8
|
* (explicit config upstream, else the durable DSH client preference,
|
|
9
9
|
* else `en`) when the caller does not supply one.
|
|
10
10
|
*/
|
|
11
|
-
import {
|
|
11
|
+
import type { RecordLanguage } from "./record-language.js";
|
|
12
12
|
export interface ConfirmOption {
|
|
13
13
|
label: string;
|
|
14
14
|
description: string;
|
|
@@ -27,18 +27,6 @@ export declare const COPY_WHAT_MAX_CHARS = 300;
|
|
|
27
27
|
* @param lang Dialog language.
|
|
28
28
|
*/
|
|
29
29
|
export declare function approvalCopy(scope: "project" | "global", what: string, lang: RecordLanguage): ConfirmCopy;
|
|
30
|
-
export interface FateConsultSections {
|
|
31
|
-
promotable: string[];
|
|
32
|
-
splits: string[];
|
|
33
|
-
reviewArchives: string[];
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Local-fate homing consult (`evolve-fate-consult`).
|
|
37
|
-
*
|
|
38
|
-
* @param sections Pre-rendered per-item lines per group.
|
|
39
|
-
* @param lang Dialog language.
|
|
40
|
-
*/
|
|
41
|
-
export declare function fateConsultCopy(sections: FateConsultSections, lang: RecordLanguage): ConfirmCopy;
|
|
42
30
|
/**
|
|
43
31
|
* Single-entry wrap-up archive confirm (`evolve-wrapup-archive-review`).
|
|
44
32
|
*
|
|
@@ -46,29 +34,4 @@ export declare function fateConsultCopy(sections: FateConsultSections, lang: Rec
|
|
|
46
34
|
* @param lang Dialog language.
|
|
47
35
|
*/
|
|
48
36
|
export declare function wrapupArchiveCopy(title: string, lang: RecordLanguage): ConfirmCopy;
|
|
49
|
-
/**
|
|
50
|
-
* Skill solidification consult (`evolve-skill-consult`).
|
|
51
|
-
*
|
|
52
|
-
* @param description Pre-rendered per-edit lines.
|
|
53
|
-
* @param lang Dialog language.
|
|
54
|
-
*/
|
|
55
|
-
export declare function skillConsultCopy(description: string, lang: RecordLanguage): ConfirmCopy;
|
|
56
|
-
/**
|
|
57
|
-
* One skill-edit line inside the solidification consult.
|
|
58
|
-
*
|
|
59
|
-
* @param action Edit action (`create` etc.).
|
|
60
|
-
* @param title Entry title or id fallback.
|
|
61
|
-
* @param skillKind `guidance` skills are SKILL.md documents.
|
|
62
|
-
* @param lang Dialog language.
|
|
63
|
-
*/
|
|
64
|
-
export declare function skillEditLine(action: string, title: string, skillKind: string | undefined, lang: RecordLanguage): string;
|
|
65
|
-
/**
|
|
66
|
-
* Resolve the dialog language for a call site that holds no explicit
|
|
67
|
-
* choice: durable client preference first, `en` otherwise. (Dialogs carry
|
|
68
|
-
* no trajectory text, so there is no detection tier here.)
|
|
69
|
-
*
|
|
70
|
-
* @param ctx Host context for the durable-preference read.
|
|
71
|
-
* @returns The dialog language.
|
|
72
|
-
*/
|
|
73
|
-
export declare function resolveDialogLanguage(ctx: unknown): RecordLanguage;
|
|
74
37
|
//# sourceMappingURL=copy.d.ts.map
|