@evo-dev/core 0.0.1-alpha.3 → 0.0.1-alpha.5
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/dist/config/index.js +164 -125
- package/dist/index.js +8965 -7632
- package/package.json +1 -1
- package/src/agents/index.ts +1 -1
- package/src/code-agent-traces/index.ts +3 -10
- package/src/config/settings.ts +14 -0
- package/src/config/store.ts +1 -1
- package/src/daemon/index.ts +1 -1
- package/src/evolution/candidates/index.ts +505 -0
- package/src/evolution/control/index.ts +19 -0
- package/src/evolution/evidence/analysis.ts +529 -0
- package/src/evolution/evidence/index.ts +3 -0
- package/src/evolution/evidence/session-memory/constants.ts +12 -0
- package/src/evolution/evidence/session-memory/index.ts +5 -0
- package/src/evolution/evidence/session-memory/paths.ts +29 -0
- package/src/evolution/evidence/session-memory/policy.ts +39 -0
- package/src/evolution/evidence/session-memory/segment.ts +192 -0
- package/src/evolution/evidence/session-memory/state-machine.ts +249 -0
- package/src/evolution/evidence/session-memory/storage.ts +266 -0
- package/src/evolution/evidence/session-memory/types.ts +207 -0
- package/src/evolution/evidence/session-memory/updater.ts +184 -0
- package/src/evolution/formatters.ts +169 -0
- package/src/evolution/index.ts +16 -2827
- package/src/{knowledge → evolution/knowledge}/index.ts +16 -81
- package/src/evolution/paths.ts +44 -0
- package/src/evolution/processor/distillation.ts +445 -0
- package/src/evolution/processor/index.ts +3 -0
- package/src/evolution/processor/process.ts +235 -0
- package/src/evolution/schema.ts +544 -0
- package/src/evolution/shared.ts +737 -0
- package/src/evolution/triggers/classification.ts +102 -0
- package/src/evolution/triggers/index.ts +295 -0
- package/src/hooks/index.ts +54 -7
- package/src/index.ts +10 -2
- package/src/runtime-logs/index.ts +4 -12
- package/src/team/index.ts +1 -1
- package/src/utils/errors.ts +13 -0
- package/src/utils/fs.ts +40 -0
- package/src/utils/hash.ts +9 -0
- package/src/utils/ids.ts +12 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/parsing.ts +11 -0
- package/src/utils/text.ts +18 -0
- package/src/utils/time.ts +5 -0
- /package/src/{learning → evolution/review}/index.ts +0 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { mkdir, rm, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { resolveEvoDevPaths } from "../../config/paths.ts";
|
|
4
|
+
import { createStableId, isFileExistsError, normalizeTimestamp } from "../../utils/index.ts";
|
|
5
|
+
import { analyzeEvolutionRun } from "../evidence/analysis.ts";
|
|
6
|
+
import type {
|
|
7
|
+
EvolutionProcessInput,
|
|
8
|
+
EvolutionProcessResult,
|
|
9
|
+
EvolutionTriggerStatus,
|
|
10
|
+
} from "../schema.ts";
|
|
11
|
+
import { sanitizeText } from "../shared.ts";
|
|
12
|
+
import {
|
|
13
|
+
groupTriggersByRun,
|
|
14
|
+
listEvolutionTriggers,
|
|
15
|
+
listSegmentEvolutionTriggers,
|
|
16
|
+
updateSegmentTriggers,
|
|
17
|
+
updateTriggers,
|
|
18
|
+
} from "../triggers/index.ts";
|
|
19
|
+
import {
|
|
20
|
+
activateEvolutionDistillationBatch,
|
|
21
|
+
createEvolutionDistillationBatch,
|
|
22
|
+
} from "./distillation.ts";
|
|
23
|
+
|
|
24
|
+
const PROCESS_LOCK_STALE_MS = 5 * 60 * 1000;
|
|
25
|
+
|
|
26
|
+
export async function processEvolutionTriggers(
|
|
27
|
+
input: EvolutionProcessInput,
|
|
28
|
+
): Promise<EvolutionProcessResult> {
|
|
29
|
+
const now = normalizeTimestamp(input.now);
|
|
30
|
+
const limit = input.limit ?? 20;
|
|
31
|
+
const dryRun = input.dryRun === true;
|
|
32
|
+
const warnings: string[] = [];
|
|
33
|
+
const lock = dryRun ? null : await acquireEvolutionProcessLock(input.homeDir, now);
|
|
34
|
+
if (!dryRun && lock === null) {
|
|
35
|
+
warnings.push("Evolution trigger processor is already running; this pass was skipped.");
|
|
36
|
+
return {
|
|
37
|
+
processed: 0,
|
|
38
|
+
consumed: 0,
|
|
39
|
+
skipped: 0,
|
|
40
|
+
failed: 0,
|
|
41
|
+
pending: 0,
|
|
42
|
+
triggerIds: [],
|
|
43
|
+
batchIds: [],
|
|
44
|
+
warnings,
|
|
45
|
+
dryRun,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const pendingSegments = (
|
|
51
|
+
await listSegmentEvolutionTriggers({
|
|
52
|
+
homeDir: input.homeDir,
|
|
53
|
+
projectKey: input.projectKey,
|
|
54
|
+
runId: input.runId,
|
|
55
|
+
status: "pending",
|
|
56
|
+
})
|
|
57
|
+
).slice(0, limit);
|
|
58
|
+
const remainingLimit = Math.max(0, limit - pendingSegments.length);
|
|
59
|
+
const pending =
|
|
60
|
+
remainingLimit === 0
|
|
61
|
+
? []
|
|
62
|
+
: (
|
|
63
|
+
await listEvolutionTriggers({
|
|
64
|
+
homeDir: input.homeDir,
|
|
65
|
+
projectKey: input.projectKey,
|
|
66
|
+
runId: input.runId,
|
|
67
|
+
status: "pending",
|
|
68
|
+
})
|
|
69
|
+
).slice(0, remainingLimit);
|
|
70
|
+
const grouped = groupTriggersByRun(pending);
|
|
71
|
+
const result: EvolutionProcessResult = {
|
|
72
|
+
processed: 0,
|
|
73
|
+
consumed: 0,
|
|
74
|
+
skipped: 0,
|
|
75
|
+
failed: 0,
|
|
76
|
+
pending: 0,
|
|
77
|
+
triggerIds: [
|
|
78
|
+
...pendingSegments.map((trigger) => trigger.id),
|
|
79
|
+
...pending.map((trigger) => trigger.id),
|
|
80
|
+
],
|
|
81
|
+
batchIds: [],
|
|
82
|
+
warnings,
|
|
83
|
+
dryRun,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
for (const trigger of pendingSegments) {
|
|
87
|
+
result.processed += 1;
|
|
88
|
+
if (dryRun) {
|
|
89
|
+
result.pending += 1;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
await updateSegmentTriggers(input.homeDir, [trigger], {
|
|
93
|
+
status: "processing",
|
|
94
|
+
updatedAt: now,
|
|
95
|
+
attempts: (current) => current.attempts + 1,
|
|
96
|
+
});
|
|
97
|
+
try {
|
|
98
|
+
result.consumed += 1;
|
|
99
|
+
await updateSegmentTriggers(input.homeDir, [trigger], {
|
|
100
|
+
status: "consumed",
|
|
101
|
+
updatedAt: now,
|
|
102
|
+
processedBatchId: createStableId("segment-evidence", [trigger.id]),
|
|
103
|
+
lastError: null,
|
|
104
|
+
});
|
|
105
|
+
} catch (error) {
|
|
106
|
+
result.failed += 1;
|
|
107
|
+
const message = sanitizeText(error instanceof Error ? error.message : String(error));
|
|
108
|
+
warnings.push(message);
|
|
109
|
+
await updateSegmentTriggers(input.homeDir, [trigger], {
|
|
110
|
+
status: "failed",
|
|
111
|
+
updatedAt: now,
|
|
112
|
+
lastError: message,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
for (const triggers of grouped) {
|
|
118
|
+
result.processed += triggers.length;
|
|
119
|
+
if (dryRun) {
|
|
120
|
+
result.pending += triggers.length;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
await updateTriggers(input.homeDir, triggers, {
|
|
124
|
+
status: "processing",
|
|
125
|
+
updatedAt: now,
|
|
126
|
+
attempts: (trigger) => trigger.attempts + 1,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
const first = triggers[0];
|
|
131
|
+
if (first === undefined) continue;
|
|
132
|
+
const analysis = await analyzeEvolutionRun({
|
|
133
|
+
homeDir: input.homeDir,
|
|
134
|
+
projectKey: first.projectKey,
|
|
135
|
+
runId: first.runId,
|
|
136
|
+
now,
|
|
137
|
+
});
|
|
138
|
+
warnings.push(...analysis.warnings);
|
|
139
|
+
const missingTrace = triggers.some(
|
|
140
|
+
(trigger) =>
|
|
141
|
+
trigger.eventId !== null &&
|
|
142
|
+
!analysis.evidenceWindow.events.some((event) => event.hookEventId === trigger.eventId),
|
|
143
|
+
);
|
|
144
|
+
if (missingTrace) {
|
|
145
|
+
result.pending += triggers.length;
|
|
146
|
+
await updateTriggers(input.homeDir, triggers, {
|
|
147
|
+
status: "pending",
|
|
148
|
+
updatedAt: now,
|
|
149
|
+
lastError: "waiting-for-trace",
|
|
150
|
+
});
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (!analysis.evidenceWindow.triggerPolicy.distillRecommended) {
|
|
154
|
+
result.skipped += triggers.length;
|
|
155
|
+
await updateTriggers(input.homeDir, triggers, {
|
|
156
|
+
status: "skipped",
|
|
157
|
+
updatedAt: now,
|
|
158
|
+
lastError: "no-distillation-signal",
|
|
159
|
+
});
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const batch = createEvolutionDistillationBatch({
|
|
164
|
+
evidenceWindow: analysis.evidenceWindow,
|
|
165
|
+
warnings: analysis.warnings,
|
|
166
|
+
now,
|
|
167
|
+
});
|
|
168
|
+
await activateEvolutionDistillationBatch({
|
|
169
|
+
homeDir: input.homeDir,
|
|
170
|
+
batch,
|
|
171
|
+
overwrite: true,
|
|
172
|
+
});
|
|
173
|
+
result.consumed += triggers.length;
|
|
174
|
+
result.batchIds.push(batch.id);
|
|
175
|
+
await updateTriggers(input.homeDir, triggers, {
|
|
176
|
+
status: "consumed",
|
|
177
|
+
updatedAt: now,
|
|
178
|
+
processedBatchId: batch.id,
|
|
179
|
+
lastError: null,
|
|
180
|
+
});
|
|
181
|
+
} catch (error) {
|
|
182
|
+
result.failed += triggers.length;
|
|
183
|
+
const message = sanitizeText(error instanceof Error ? error.message : String(error));
|
|
184
|
+
warnings.push(message);
|
|
185
|
+
await updateTriggers(input.homeDir, triggers, {
|
|
186
|
+
status: "failed",
|
|
187
|
+
updatedAt: now,
|
|
188
|
+
lastError: message,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return result;
|
|
194
|
+
} finally {
|
|
195
|
+
if (lock !== null) await releaseEvolutionProcessLock(lock);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function acquireEvolutionProcessLock(
|
|
200
|
+
homeDir: string,
|
|
201
|
+
now: string,
|
|
202
|
+
): Promise<{ path: string } | null> {
|
|
203
|
+
const paths = resolveEvoDevPaths(homeDir);
|
|
204
|
+
const lockPath = join(paths.stateDir, "evolution", ".process.lock");
|
|
205
|
+
await mkdir(dirname(lockPath), { recursive: true });
|
|
206
|
+
try {
|
|
207
|
+
await writeFile(
|
|
208
|
+
lockPath,
|
|
209
|
+
`${JSON.stringify(
|
|
210
|
+
{
|
|
211
|
+
schemaVersion: 1,
|
|
212
|
+
kind: "evolution-process-lock",
|
|
213
|
+
createdAt: now,
|
|
214
|
+
pid: process.pid,
|
|
215
|
+
},
|
|
216
|
+
null,
|
|
217
|
+
2,
|
|
218
|
+
)}\n`,
|
|
219
|
+
{ encoding: "utf8", flag: "wx" },
|
|
220
|
+
);
|
|
221
|
+
return { path: lockPath };
|
|
222
|
+
} catch (error) {
|
|
223
|
+
if (!isFileExistsError(error)) throw error;
|
|
224
|
+
const current = await stat(lockPath).catch(() => null);
|
|
225
|
+
if (current !== null && Date.now() - current.mtimeMs > PROCESS_LOCK_STALE_MS) {
|
|
226
|
+
await rm(lockPath, { force: true });
|
|
227
|
+
return acquireEvolutionProcessLock(homeDir, now);
|
|
228
|
+
}
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function releaseEvolutionProcessLock(lock: { path: string }): Promise<void> {
|
|
234
|
+
await rm(lock.path, { force: true });
|
|
235
|
+
}
|