@evo-dev/core 0.0.1-alpha.1 → 0.0.1-alpha.10
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/assets/skills/coding/knowledge-distillation/SKILL.md +117 -114
- package/assets/skills/coding/knowledge-distillation/references/knowledge-distillation-methods.md +11 -7
- package/assets/team/agents/code-reviewer.md +48 -0
- package/assets/team/agents/docs-maintainer.md +51 -0
- package/assets/team/agents/implementation-engineer.md +51 -0
- package/assets/team/agents/product-scope-analyst.md +58 -0
- package/assets/team/agents/release-engineer.md +55 -0
- package/assets/team/agents/security-boundary-reviewer.md +50 -0
- package/assets/team/agents/solution-architect.md +51 -0
- package/assets/team/agents/verification-engineer.md +51 -0
- package/assets/team/team.md +102 -0
- package/dist/config/index.js +925 -97
- package/dist/index.js +13107 -5618
- package/package.json +5 -1
- package/src/agents/index.ts +56 -264
- package/src/code-agent-traces/index.ts +520 -0
- package/src/config/index.ts +5 -0
- package/src/config/paths.ts +1 -1
- package/src/config/settings.ts +149 -0
- package/src/config/store.ts +2 -0
- package/src/daemon/index.ts +99 -50
- package/src/evolution/candidates/index.ts +564 -0
- package/src/evolution/control/index.ts +20 -0
- package/src/evolution/evidence/analysis.ts +533 -0
- package/src/evolution/evidence/index.ts +3 -0
- package/src/evolution/evidence/session-memory/analysis.ts +281 -0
- package/src/evolution/evidence/session-memory/constants.ts +9 -0
- package/src/evolution/evidence/session-memory/index.ts +7 -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 +202 -0
- package/src/evolution/evidence/session-memory/sensitivity.ts +335 -0
- package/src/evolution/evidence/session-memory/state-machine.ts +249 -0
- package/src/evolution/evidence/session-memory/storage.ts +379 -0
- package/src/evolution/evidence/session-memory/types.ts +221 -0
- package/src/evolution/evidence/session-memory/updater.ts +191 -0
- package/src/evolution/formatters.ts +169 -0
- package/src/evolution/index.ts +16 -2356
- package/src/evolution/knowledge/index.ts +5427 -0
- package/src/evolution/paths.ts +44 -0
- package/src/evolution/processor/distillation.ts +518 -0
- package/src/evolution/processor/index.ts +3 -0
- package/src/evolution/processor/process.ts +528 -0
- package/src/{learning → evolution/review}/index.ts +10 -14
- package/src/evolution/schema.ts +568 -0
- package/src/evolution/shared.ts +758 -0
- package/src/evolution/triggers/classification.ts +102 -0
- package/src/evolution/triggers/index.ts +295 -0
- package/src/hooks/index.ts +438 -179
- package/src/index.ts +12 -3
- package/src/projects/index.ts +453 -0
- package/src/runtime-logs/index.ts +490 -24
- package/src/team/index.ts +1429 -185
- package/src/team/mcp.ts +9 -5
- package/src/team/prompts.ts +141 -0
- 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/workflow/index.ts +3 -21
- package/src/project/index.ts +0 -507
- package/src/task/index.ts +0 -840
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
1
|
import { appendFile, mkdir } from "node:fs/promises";
|
|
3
2
|
import { dirname, isAbsolute, join, relative } from "node:path";
|
|
4
3
|
import { resolveEvoDevPaths } from "../config/paths.ts";
|
|
4
|
+
import { normalizeTimestamp, sha256Short } from "../utils/index.ts";
|
|
5
5
|
|
|
6
6
|
export type EvoDevLogPhase = "started" | "completed" | "failed";
|
|
7
|
+
export type EvoDevDebugLogLevel = "verbose" | "debug" | "info" | "warn" | "error";
|
|
8
|
+
export type EvoDevExecutionEventTarget = "claude" | "codex" | "cli" | "team" | "unknown";
|
|
9
|
+
export type EvoDevExecutionEventMetadataValue = string | number | boolean | string[];
|
|
7
10
|
|
|
8
11
|
export interface CliLogEntryV1 {
|
|
9
12
|
version: 1;
|
|
@@ -56,6 +59,50 @@ export interface TraceLogEntryV1 {
|
|
|
56
59
|
team: TraceLogTeamContext | null;
|
|
57
60
|
}
|
|
58
61
|
|
|
62
|
+
export interface EvoDevExecutionEventV1 {
|
|
63
|
+
version: 1;
|
|
64
|
+
kind: "evodev-execution-event";
|
|
65
|
+
eventId: string;
|
|
66
|
+
timestamp: string;
|
|
67
|
+
date: string;
|
|
68
|
+
target: EvoDevExecutionEventTarget;
|
|
69
|
+
eventType: string;
|
|
70
|
+
projectKey: string | null;
|
|
71
|
+
runId: string | null;
|
|
72
|
+
roleId: string | null;
|
|
73
|
+
taskId: string | null;
|
|
74
|
+
sessionKey: string;
|
|
75
|
+
summary: string;
|
|
76
|
+
metadata: Record<string, EvoDevExecutionEventMetadataValue>;
|
|
77
|
+
decision: {
|
|
78
|
+
action: "allow" | "warn";
|
|
79
|
+
reason: string;
|
|
80
|
+
} | null;
|
|
81
|
+
codeAgentTraceRefId: string | null;
|
|
82
|
+
rawPayloadStored: false;
|
|
83
|
+
rawPromptStored: false;
|
|
84
|
+
rawOutputStored: false;
|
|
85
|
+
sourceContentStored: false;
|
|
86
|
+
secretsStored: false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface DebugLogEntryV1 {
|
|
90
|
+
version: 1;
|
|
91
|
+
kind: "debug";
|
|
92
|
+
timestamp: string;
|
|
93
|
+
date: string;
|
|
94
|
+
level: EvoDevDebugLogLevel;
|
|
95
|
+
category: string;
|
|
96
|
+
message: string;
|
|
97
|
+
data: unknown;
|
|
98
|
+
context: {
|
|
99
|
+
command: string | null;
|
|
100
|
+
argv: string[];
|
|
101
|
+
cwd: string | null;
|
|
102
|
+
pid: number;
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
59
106
|
export interface TraceLogTeamContext {
|
|
60
107
|
runId: string;
|
|
61
108
|
roleId: string;
|
|
@@ -87,10 +134,48 @@ export interface TraceLogInput {
|
|
|
87
134
|
team?: TraceLogTeamContext | null;
|
|
88
135
|
}
|
|
89
136
|
|
|
137
|
+
export interface EvoDevExecutionEventInput {
|
|
138
|
+
target: string;
|
|
139
|
+
eventType: string;
|
|
140
|
+
sessionKey: string;
|
|
141
|
+
summary: string;
|
|
142
|
+
metadata?: Record<string, unknown>;
|
|
143
|
+
timestamp?: Date | string;
|
|
144
|
+
eventId?: string;
|
|
145
|
+
projectKey?: string | null;
|
|
146
|
+
runId?: string | null;
|
|
147
|
+
roleId?: string | null;
|
|
148
|
+
taskId?: string | null;
|
|
149
|
+
decision?: EvoDevExecutionEventV1["decision"];
|
|
150
|
+
codeAgentTraceRefId?: string | null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface DebugLogInput {
|
|
154
|
+
level?: EvoDevDebugLogLevel;
|
|
155
|
+
category: string;
|
|
156
|
+
message: string;
|
|
157
|
+
data?: unknown;
|
|
158
|
+
timestamp?: Date | string;
|
|
159
|
+
argv?: string[];
|
|
160
|
+
cwd?: string | null;
|
|
161
|
+
pid?: number;
|
|
162
|
+
}
|
|
163
|
+
|
|
90
164
|
export interface EvoDevLogPaths {
|
|
91
165
|
logsDir: string;
|
|
92
166
|
cliDir: string;
|
|
93
167
|
cliLogPath: string;
|
|
168
|
+
debugDir: string;
|
|
169
|
+
debugLogPath: string;
|
|
170
|
+
evodevEventRootDir: string;
|
|
171
|
+
evodevEventDateDir: string;
|
|
172
|
+
evodevEventSessionDir: string | null;
|
|
173
|
+
evodevEventsPath: string | null;
|
|
174
|
+
teamsRootDir: string;
|
|
175
|
+
teamRunDir: string | null;
|
|
176
|
+
teamRunAgentsDir: string | null;
|
|
177
|
+
teamAgentLogDir: string | null;
|
|
178
|
+
teamAgentEventsPath: string | null;
|
|
94
179
|
traceRootDir: string;
|
|
95
180
|
traceDateDir: string;
|
|
96
181
|
traceSessionDir: string | null;
|
|
@@ -102,6 +187,35 @@ export interface EvoDevLogPaths {
|
|
|
102
187
|
}
|
|
103
188
|
|
|
104
189
|
const MAX_LOG_STRING_LENGTH = 4096;
|
|
190
|
+
const MAX_EVENT_STRING_LENGTH = 512;
|
|
191
|
+
const MAX_DEBUG_ARRAY_LENGTH = 50;
|
|
192
|
+
const MAX_DEBUG_OBJECT_KEYS = 100;
|
|
193
|
+
const MAX_DEBUG_DEPTH = 5;
|
|
194
|
+
const SAFE_EXECUTION_METADATA_KEYS = new Set([
|
|
195
|
+
"phase",
|
|
196
|
+
"runtimeSurface",
|
|
197
|
+
"stdinBytes",
|
|
198
|
+
"durationMs",
|
|
199
|
+
"enabled",
|
|
200
|
+
"stateWriteCount",
|
|
201
|
+
"warningCount",
|
|
202
|
+
"commandClass",
|
|
203
|
+
"exitCode",
|
|
204
|
+
"status",
|
|
205
|
+
"redactionCount",
|
|
206
|
+
"redactionLabels",
|
|
207
|
+
"normalizedEventType",
|
|
208
|
+
]);
|
|
209
|
+
const SENSITIVE_EVENT_TEXT_PATTERN =
|
|
210
|
+
/https?:\/\/\S+|(^|[^a-z0-9])(secret|token|password|passwd|private|internal|api[_-]?key|apikey|credential|credentials|stdout|stderr|transcript|formattedresponse|additionalcontext)([^a-z0-9]|$)|raw[\s_-]?(payload|prompt|output|source)|source[\s_-]?dump/i;
|
|
211
|
+
const SAFE_REDACTION_LABELS = new Set([
|
|
212
|
+
"raw-command",
|
|
213
|
+
"raw-command-output",
|
|
214
|
+
"raw-prompt",
|
|
215
|
+
"sensitive-command",
|
|
216
|
+
"sensitive-text",
|
|
217
|
+
]);
|
|
218
|
+
|
|
105
219
|
export function createCliLogEntry(input: CliLogInput): CliLogEntryV1 {
|
|
106
220
|
const timestamp = normalizeTimestamp(input.timestamp);
|
|
107
221
|
return {
|
|
@@ -120,6 +234,27 @@ export function createCliLogEntry(input: CliLogInput): CliLogEntryV1 {
|
|
|
120
234
|
};
|
|
121
235
|
}
|
|
122
236
|
|
|
237
|
+
export function createDebugLogEntry(input: DebugLogInput): DebugLogEntryV1 {
|
|
238
|
+
const timestamp = normalizeTimestamp(input.timestamp);
|
|
239
|
+
const argv = input.argv ?? [];
|
|
240
|
+
return {
|
|
241
|
+
version: 1,
|
|
242
|
+
kind: "debug",
|
|
243
|
+
timestamp,
|
|
244
|
+
date: formatLocalDateKey(timestamp),
|
|
245
|
+
level: input.level ?? "debug",
|
|
246
|
+
category: truncateString(input.category),
|
|
247
|
+
message: truncateString(input.message),
|
|
248
|
+
data: input.data === undefined ? null : sanitizeDebugValue(input.data),
|
|
249
|
+
context: {
|
|
250
|
+
command: argv[0] ?? null,
|
|
251
|
+
argv: argv.map((arg) => truncateString(arg)),
|
|
252
|
+
cwd: input.cwd === undefined ? process.cwd() : input.cwd,
|
|
253
|
+
pid: input.pid ?? process.pid,
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
123
258
|
export function createTraceLogEntry(input: TraceLogInput): TraceLogEntryV1 {
|
|
124
259
|
const timestamp = normalizeTimestamp(input.timestamp);
|
|
125
260
|
const payload = input.payload ?? null;
|
|
@@ -159,6 +294,82 @@ export function createTraceLogEntry(input: TraceLogInput): TraceLogEntryV1 {
|
|
|
159
294
|
};
|
|
160
295
|
}
|
|
161
296
|
|
|
297
|
+
export function createEvoDevExecutionEvent(
|
|
298
|
+
input: EvoDevExecutionEventInput,
|
|
299
|
+
): EvoDevExecutionEventV1 {
|
|
300
|
+
const timestamp = normalizeTimestamp(input.timestamp);
|
|
301
|
+
const sessionKey = sanitizePersistentIdentifier(input.sessionKey, "session");
|
|
302
|
+
const target = normalizeExecutionEventTarget(input.target);
|
|
303
|
+
const eventType = sanitizeEventText(input.eventType, "unknown");
|
|
304
|
+
const projectKey =
|
|
305
|
+
input.projectKey === undefined || input.projectKey === null
|
|
306
|
+
? null
|
|
307
|
+
: sanitizePersistentIdentifier(input.projectKey, "project");
|
|
308
|
+
const runId =
|
|
309
|
+
input.runId === undefined || input.runId === null
|
|
310
|
+
? null
|
|
311
|
+
: sanitizePersistentIdentifier(input.runId, "run");
|
|
312
|
+
const roleId =
|
|
313
|
+
input.roleId === undefined || input.roleId === null
|
|
314
|
+
? null
|
|
315
|
+
: sanitizePersistentIdentifier(input.roleId, "role");
|
|
316
|
+
const taskId =
|
|
317
|
+
input.taskId === undefined || input.taskId === null
|
|
318
|
+
? null
|
|
319
|
+
: sanitizePersistentIdentifier(input.taskId, "task");
|
|
320
|
+
const metadata = sanitizeExecutionMetadata(input.metadata ?? {});
|
|
321
|
+
const summary = sanitizeEventText(input.summary, "EvoDev execution event recorded.");
|
|
322
|
+
const decision: EvoDevExecutionEventV1["decision"] =
|
|
323
|
+
input.decision === undefined || input.decision === null
|
|
324
|
+
? null
|
|
325
|
+
: {
|
|
326
|
+
action: input.decision.action === "warn" ? "warn" : "allow",
|
|
327
|
+
reason: sanitizeEventText(input.decision.reason, "Decision reason redacted."),
|
|
328
|
+
};
|
|
329
|
+
const codeAgentTraceRefId =
|
|
330
|
+
input.codeAgentTraceRefId === undefined || input.codeAgentTraceRefId === null
|
|
331
|
+
? null
|
|
332
|
+
: sanitizePersistentIdentifier(input.codeAgentTraceRefId, "trace-ref");
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
version: 1,
|
|
336
|
+
kind: "evodev-execution-event",
|
|
337
|
+
eventId:
|
|
338
|
+
input.eventId === undefined
|
|
339
|
+
? createExecutionEventId({
|
|
340
|
+
timestamp,
|
|
341
|
+
target,
|
|
342
|
+
eventType,
|
|
343
|
+
sessionKey,
|
|
344
|
+
projectKey,
|
|
345
|
+
runId,
|
|
346
|
+
roleId,
|
|
347
|
+
taskId,
|
|
348
|
+
summary,
|
|
349
|
+
metadata,
|
|
350
|
+
})
|
|
351
|
+
: sanitizePersistentIdentifier(input.eventId, "event"),
|
|
352
|
+
timestamp,
|
|
353
|
+
date: formatLocalDateKey(timestamp),
|
|
354
|
+
target,
|
|
355
|
+
eventType,
|
|
356
|
+
projectKey,
|
|
357
|
+
runId,
|
|
358
|
+
roleId,
|
|
359
|
+
taskId,
|
|
360
|
+
sessionKey,
|
|
361
|
+
summary,
|
|
362
|
+
metadata,
|
|
363
|
+
decision,
|
|
364
|
+
codeAgentTraceRefId,
|
|
365
|
+
rawPayloadStored: false,
|
|
366
|
+
rawPromptStored: false,
|
|
367
|
+
rawOutputStored: false,
|
|
368
|
+
sourceContentStored: false,
|
|
369
|
+
secretsStored: false,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
162
373
|
export function resolveEvoDevLogPaths(input: {
|
|
163
374
|
homeDir: string;
|
|
164
375
|
timestamp?: Date | string;
|
|
@@ -168,30 +379,63 @@ export function resolveEvoDevLogPaths(input: {
|
|
|
168
379
|
const paths = resolveEvoDevPaths(input.homeDir);
|
|
169
380
|
const date = formatLocalDateKey(normalizeTimestamp(input.timestamp));
|
|
170
381
|
const cliDir = join(paths.logsDir, "cli");
|
|
382
|
+
const debugDir = join(paths.logsDir, "debug");
|
|
383
|
+
const evodevEventRootDir = join(paths.logsDir, "evodev");
|
|
384
|
+
const evodevEventDateDir = join(evodevEventRootDir, date);
|
|
385
|
+
const evodevEventSessionDir =
|
|
386
|
+
input.sessionKey === undefined || input.sessionKey === null
|
|
387
|
+
? null
|
|
388
|
+
: join(evodevEventDateDir, sanitizePersistentIdentifier(input.sessionKey, "session"));
|
|
389
|
+
const teamsRootDir = join(paths.logsDir, "teams");
|
|
390
|
+
const teamRunDir =
|
|
391
|
+
input.team === undefined || input.team === null
|
|
392
|
+
? null
|
|
393
|
+
: join(
|
|
394
|
+
teamsRootDir,
|
|
395
|
+
sanitizePersistentIdentifier(input.team.projectKey, "project"),
|
|
396
|
+
sanitizePersistentIdentifier(input.team.runId, "run"),
|
|
397
|
+
);
|
|
398
|
+
const teamRunAgentsDir = teamRunDir === null ? null : join(teamRunDir, "agents");
|
|
399
|
+
const teamAgentLogDir =
|
|
400
|
+
teamRunAgentsDir === null || input.team === undefined || input.team === null
|
|
401
|
+
? null
|
|
402
|
+
: join(teamRunAgentsDir, sanitizePersistentIdentifier(input.team.roleId, "role"));
|
|
171
403
|
const traceRootDir = join(paths.logsDir, "trace");
|
|
172
404
|
const traceDateDir = join(traceRootDir, date);
|
|
173
405
|
const traceSessionDir =
|
|
174
406
|
input.sessionKey === undefined || input.sessionKey === null
|
|
175
407
|
? null
|
|
176
|
-
: join(traceDateDir,
|
|
408
|
+
: join(traceDateDir, sanitizePersistentIdentifier(input.sessionKey, "session"));
|
|
177
409
|
const projectRunDir =
|
|
178
410
|
input.team === undefined || input.team === null
|
|
179
411
|
? null
|
|
180
412
|
: join(
|
|
181
413
|
paths.logsDir,
|
|
182
|
-
|
|
183
|
-
|
|
414
|
+
sanitizePersistentIdentifier(input.team.projectKey, "project"),
|
|
415
|
+
sanitizePersistentIdentifier(input.team.runId, "run"),
|
|
184
416
|
);
|
|
185
417
|
const projectRunAgentsDir = projectRunDir === null ? null : join(projectRunDir, "agents");
|
|
186
418
|
const agentLogDir =
|
|
187
419
|
projectRunAgentsDir === null || input.team === undefined || input.team === null
|
|
188
420
|
? null
|
|
189
|
-
: join(projectRunAgentsDir,
|
|
421
|
+
: join(projectRunAgentsDir, sanitizePersistentIdentifier(input.team.roleId, "role"));
|
|
190
422
|
|
|
191
423
|
return {
|
|
192
424
|
logsDir: paths.logsDir,
|
|
193
425
|
cliDir,
|
|
194
426
|
cliLogPath: join(cliDir, `${date}.log`),
|
|
427
|
+
debugDir,
|
|
428
|
+
debugLogPath: join(debugDir, `${date}.log`),
|
|
429
|
+
evodevEventRootDir,
|
|
430
|
+
evodevEventDateDir,
|
|
431
|
+
evodevEventSessionDir,
|
|
432
|
+
evodevEventsPath:
|
|
433
|
+
evodevEventSessionDir === null ? null : join(evodevEventSessionDir, "events.jsonl"),
|
|
434
|
+
teamsRootDir,
|
|
435
|
+
teamRunDir,
|
|
436
|
+
teamRunAgentsDir,
|
|
437
|
+
teamAgentLogDir,
|
|
438
|
+
teamAgentEventsPath: teamAgentLogDir === null ? null : join(teamAgentLogDir, "events.jsonl"),
|
|
195
439
|
traceRootDir,
|
|
196
440
|
traceDateDir,
|
|
197
441
|
traceSessionDir,
|
|
@@ -209,29 +453,70 @@ export async function appendCliLogEntry(homeDir: string, entry: CliLogEntryV1):
|
|
|
209
453
|
return path;
|
|
210
454
|
}
|
|
211
455
|
|
|
456
|
+
export async function appendDebugLogEntry(
|
|
457
|
+
homeDir: string,
|
|
458
|
+
entry: DebugLogEntryV1,
|
|
459
|
+
): Promise<string> {
|
|
460
|
+
const path = resolveEvoDevLogPaths({ homeDir, timestamp: entry.timestamp }).debugLogPath;
|
|
461
|
+
await appendJsonLine(path, entry);
|
|
462
|
+
return path;
|
|
463
|
+
}
|
|
464
|
+
|
|
212
465
|
export async function appendTraceLogEntry(
|
|
213
466
|
homeDir: string,
|
|
214
467
|
entry: TraceLogEntryV1,
|
|
215
468
|
): Promise<string> {
|
|
469
|
+
const sanitizedEntry = sanitizeTraceLogEntryForAppend(entry);
|
|
216
470
|
const path = resolveEvoDevLogPaths({
|
|
217
471
|
homeDir,
|
|
218
|
-
timestamp:
|
|
219
|
-
sessionKey:
|
|
472
|
+
timestamp: sanitizedEntry.timestamp,
|
|
473
|
+
sessionKey: sanitizedEntry.sessionKey,
|
|
220
474
|
}).traceLogPath;
|
|
221
475
|
if (path === null) throw new Error("Cannot resolve trace log path without a session key.");
|
|
222
|
-
await appendJsonLine(path,
|
|
476
|
+
await appendJsonLine(path, sanitizedEntry);
|
|
223
477
|
const agentTraceLogPath = resolveEvoDevLogPaths({
|
|
224
478
|
homeDir,
|
|
225
|
-
timestamp:
|
|
226
|
-
sessionKey:
|
|
227
|
-
team:
|
|
479
|
+
timestamp: sanitizedEntry.timestamp,
|
|
480
|
+
sessionKey: sanitizedEntry.sessionKey,
|
|
481
|
+
team: sanitizedEntry.team,
|
|
228
482
|
}).agentTraceLogPath;
|
|
229
483
|
if (agentTraceLogPath !== null) {
|
|
230
|
-
await appendJsonLine(agentTraceLogPath,
|
|
484
|
+
await appendJsonLine(agentTraceLogPath, sanitizedEntry);
|
|
231
485
|
}
|
|
232
486
|
return path;
|
|
233
487
|
}
|
|
234
488
|
|
|
489
|
+
export async function appendEvoDevExecutionEvent(
|
|
490
|
+
homeDir: string,
|
|
491
|
+
event: EvoDevExecutionEventV1,
|
|
492
|
+
): Promise<string> {
|
|
493
|
+
const sanitizedEvent = sanitizeEvoDevExecutionEventForAppend(event);
|
|
494
|
+
const team =
|
|
495
|
+
sanitizedEvent.projectKey === null ||
|
|
496
|
+
sanitizedEvent.runId === null ||
|
|
497
|
+
sanitizedEvent.roleId === null
|
|
498
|
+
? null
|
|
499
|
+
: {
|
|
500
|
+
projectKey: sanitizedEvent.projectKey,
|
|
501
|
+
runId: sanitizedEvent.runId,
|
|
502
|
+
roleId: sanitizedEvent.roleId,
|
|
503
|
+
};
|
|
504
|
+
const paths = resolveEvoDevLogPaths({
|
|
505
|
+
homeDir,
|
|
506
|
+
timestamp: sanitizedEvent.timestamp,
|
|
507
|
+
sessionKey: sanitizedEvent.sessionKey,
|
|
508
|
+
team,
|
|
509
|
+
});
|
|
510
|
+
if (paths.evodevEventsPath === null) {
|
|
511
|
+
throw new Error("Cannot resolve EvoDev execution event path without a session key.");
|
|
512
|
+
}
|
|
513
|
+
await appendJsonLine(paths.evodevEventsPath, sanitizedEvent);
|
|
514
|
+
if (paths.teamAgentEventsPath !== null) {
|
|
515
|
+
await appendJsonLine(paths.teamAgentEventsPath, sanitizedEvent);
|
|
516
|
+
}
|
|
517
|
+
return paths.evodevEventsPath;
|
|
518
|
+
}
|
|
519
|
+
|
|
235
520
|
export function resolveTraceTeamContext(input: {
|
|
236
521
|
homeDir: string;
|
|
237
522
|
environment?: Record<string, string | undefined>;
|
|
@@ -246,8 +531,8 @@ export function resolveTraceTeamContext(input: {
|
|
|
246
531
|
optionalString(input.payload?.cwd) ??
|
|
247
532
|
input.homeDir;
|
|
248
533
|
return {
|
|
249
|
-
runId:
|
|
250
|
-
roleId:
|
|
534
|
+
runId: sanitizePersistentIdentifier(runId, "run"),
|
|
535
|
+
roleId: sanitizePersistentIdentifier(roleId, "role"),
|
|
251
536
|
projectKey: resolveProjectLogKey(input.homeDir, repoRoot),
|
|
252
537
|
};
|
|
253
538
|
}
|
|
@@ -260,13 +545,13 @@ export function resolveProjectLogKey(homeDir: string, repoRoot: string): string
|
|
|
260
545
|
relativePath !== "" && !relativePath.startsWith("..") && !isAbsolute(relativePath)
|
|
261
546
|
? relativePath
|
|
262
547
|
: trimmedRepo.replace(/^[/\\]+/, "");
|
|
263
|
-
|
|
548
|
+
const projectKey =
|
|
264
549
|
source
|
|
265
550
|
.split(/[/\\]+/)
|
|
266
551
|
.filter(Boolean)
|
|
267
552
|
.map((part) => sanitizePathSegment(part))
|
|
268
|
-
.join("-") || "project-local"
|
|
269
|
-
);
|
|
553
|
+
.join("-") || "project-local";
|
|
554
|
+
return sanitizePersistentIdentifier(projectKey, "project");
|
|
270
555
|
}
|
|
271
556
|
|
|
272
557
|
export function resolveTraceSessionKey(payload: unknown): string {
|
|
@@ -274,7 +559,84 @@ export function resolveTraceSessionKey(payload: unknown): string {
|
|
|
274
559
|
const sessionId = optionalString(record.session_id ?? record.sessionId);
|
|
275
560
|
const cwd = optionalString(record.cwd);
|
|
276
561
|
const source = sessionId ?? cwd ?? "local";
|
|
277
|
-
return `session-${
|
|
562
|
+
return `session-${sha256Short(source)}`;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function sanitizeTraceLogEntryForAppend(entry: TraceLogEntryV1): TraceLogEntryV1 {
|
|
566
|
+
return {
|
|
567
|
+
version: 1,
|
|
568
|
+
kind: "trace",
|
|
569
|
+
timestamp: normalizeTimestamp(entry.timestamp),
|
|
570
|
+
date: formatLocalDateKey(normalizeTimestamp(entry.timestamp)),
|
|
571
|
+
phase: entry.phase,
|
|
572
|
+
sessionKey: sanitizePersistentIdentifier(entry.sessionKey, "session"),
|
|
573
|
+
target: normalizeExecutionEventTarget(entry.target),
|
|
574
|
+
runtime: {
|
|
575
|
+
surface: entry.runtime.surface,
|
|
576
|
+
argv: [],
|
|
577
|
+
runtimeFile: null,
|
|
578
|
+
},
|
|
579
|
+
input:
|
|
580
|
+
entry.input === null
|
|
581
|
+
? null
|
|
582
|
+
: {
|
|
583
|
+
stdinBytes: entry.input.stdinBytes,
|
|
584
|
+
payload: null,
|
|
585
|
+
},
|
|
586
|
+
event:
|
|
587
|
+
entry.event === null
|
|
588
|
+
? null
|
|
589
|
+
: {
|
|
590
|
+
eventId: sanitizePersistentIdentifier(entry.event.eventId, "event"),
|
|
591
|
+
type: sanitizeEventText(entry.event.type, "unknown"),
|
|
592
|
+
summary: "Legacy trace event payload omitted; metadata-only compatibility entry.",
|
|
593
|
+
metadata: {},
|
|
594
|
+
decision: null,
|
|
595
|
+
},
|
|
596
|
+
result:
|
|
597
|
+
entry.result === null
|
|
598
|
+
? null
|
|
599
|
+
: {
|
|
600
|
+
enabled: entry.result.enabled,
|
|
601
|
+
summary: "Legacy trace result payload omitted; metadata-only compatibility entry.",
|
|
602
|
+
stateWrites: [],
|
|
603
|
+
warnings: [],
|
|
604
|
+
formattedResponse: null,
|
|
605
|
+
durationMs: entry.result.durationMs,
|
|
606
|
+
},
|
|
607
|
+
error:
|
|
608
|
+
entry.error === null
|
|
609
|
+
? null
|
|
610
|
+
: "Legacy trace error omitted; metadata-only compatibility entry.",
|
|
611
|
+
team:
|
|
612
|
+
entry.team === null
|
|
613
|
+
? null
|
|
614
|
+
: {
|
|
615
|
+
runId: sanitizePersistentIdentifier(entry.team.runId, "run"),
|
|
616
|
+
roleId: sanitizePersistentIdentifier(entry.team.roleId, "role"),
|
|
617
|
+
projectKey: sanitizePersistentIdentifier(entry.team.projectKey, "project"),
|
|
618
|
+
},
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function sanitizeEvoDevExecutionEventForAppend(
|
|
623
|
+
event: EvoDevExecutionEventV1,
|
|
624
|
+
): EvoDevExecutionEventV1 {
|
|
625
|
+
return createEvoDevExecutionEvent({
|
|
626
|
+
eventId: event.eventId,
|
|
627
|
+
timestamp: event.timestamp,
|
|
628
|
+
target: event.target,
|
|
629
|
+
eventType: event.eventType,
|
|
630
|
+
projectKey: event.projectKey,
|
|
631
|
+
runId: event.runId,
|
|
632
|
+
roleId: event.roleId,
|
|
633
|
+
taskId: event.taskId,
|
|
634
|
+
sessionKey: event.sessionKey,
|
|
635
|
+
summary: event.summary,
|
|
636
|
+
metadata: event.metadata,
|
|
637
|
+
decision: event.decision,
|
|
638
|
+
codeAgentTraceRefId: event.codeAgentTraceRefId,
|
|
639
|
+
});
|
|
278
640
|
}
|
|
279
641
|
|
|
280
642
|
async function appendJsonLine(path: string, value: unknown): Promise<void> {
|
|
@@ -282,12 +644,6 @@ async function appendJsonLine(path: string, value: unknown): Promise<void> {
|
|
|
282
644
|
await appendFile(path, `${JSON.stringify(value)}\n`, "utf8");
|
|
283
645
|
}
|
|
284
646
|
|
|
285
|
-
function normalizeTimestamp(value?: Date | string): string {
|
|
286
|
-
if (value instanceof Date) return value.toISOString();
|
|
287
|
-
if (typeof value === "string" && value.trim() !== "") return new Date(value).toISOString();
|
|
288
|
-
return new Date().toISOString();
|
|
289
|
-
}
|
|
290
|
-
|
|
291
647
|
function formatLocalDateKey(timestamp: string): string {
|
|
292
648
|
const date = new Date(timestamp);
|
|
293
649
|
const year = date.getFullYear();
|
|
@@ -301,10 +657,120 @@ function truncateString(value: string): string {
|
|
|
301
657
|
return `${value.slice(0, MAX_LOG_STRING_LENGTH)}...[truncated:${value.length - MAX_LOG_STRING_LENGTH}]`;
|
|
302
658
|
}
|
|
303
659
|
|
|
660
|
+
function truncateEventString(value: string): string {
|
|
661
|
+
if (value.length <= MAX_EVENT_STRING_LENGTH) return value;
|
|
662
|
+
return `${value.slice(0, MAX_EVENT_STRING_LENGTH)}...[truncated:${value.length - MAX_EVENT_STRING_LENGTH}]`;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function sanitizeEventText(value: string, fallback: string): string {
|
|
666
|
+
const text = truncateEventString(value.trim());
|
|
667
|
+
if (text === "") return fallback;
|
|
668
|
+
if (SENSITIVE_EVENT_TEXT_PATTERN.test(text)) return fallback;
|
|
669
|
+
return text;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function sanitizeExecutionMetadata(
|
|
673
|
+
metadata: Record<string, unknown>,
|
|
674
|
+
): Record<string, EvoDevExecutionEventMetadataValue> {
|
|
675
|
+
const safe: Record<string, EvoDevExecutionEventMetadataValue> = {};
|
|
676
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
677
|
+
if (!SAFE_EXECUTION_METADATA_KEYS.has(key)) continue;
|
|
678
|
+
const sanitized = sanitizeExecutionMetadataValue(key, value);
|
|
679
|
+
if (sanitized !== null) safe[key] = sanitized;
|
|
680
|
+
}
|
|
681
|
+
return safe;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function sanitizeExecutionMetadataValue(
|
|
685
|
+
key: string,
|
|
686
|
+
value: unknown,
|
|
687
|
+
): EvoDevExecutionEventMetadataValue | null {
|
|
688
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
689
|
+
if (typeof value === "boolean") return value;
|
|
690
|
+
if (typeof value === "string") return sanitizeEventText(value, "[redacted]");
|
|
691
|
+
if (Array.isArray(value)) {
|
|
692
|
+
const items = value
|
|
693
|
+
.filter((item): item is string => typeof item === "string")
|
|
694
|
+
.slice(0, 50)
|
|
695
|
+
.map((item) =>
|
|
696
|
+
key === "redactionLabels" && SAFE_REDACTION_LABELS.has(item)
|
|
697
|
+
? item
|
|
698
|
+
: sanitizeEventText(item, "[redacted]"),
|
|
699
|
+
);
|
|
700
|
+
return items;
|
|
701
|
+
}
|
|
702
|
+
return null;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function normalizeExecutionEventTarget(value: string): EvoDevExecutionEventTarget {
|
|
706
|
+
if (value === "claude" || value === "codex" || value === "cli" || value === "team") {
|
|
707
|
+
return value;
|
|
708
|
+
}
|
|
709
|
+
return "unknown";
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function createExecutionEventId(input: {
|
|
713
|
+
timestamp: string;
|
|
714
|
+
target: EvoDevExecutionEventTarget;
|
|
715
|
+
eventType: string;
|
|
716
|
+
sessionKey: string;
|
|
717
|
+
projectKey: string | null;
|
|
718
|
+
runId: string | null;
|
|
719
|
+
roleId: string | null;
|
|
720
|
+
taskId: string | null;
|
|
721
|
+
summary: string;
|
|
722
|
+
metadata: Record<string, EvoDevExecutionEventMetadataValue>;
|
|
723
|
+
}): string {
|
|
724
|
+
return `exec-${sha256Short(JSON.stringify(input))}`;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function sanitizeDebugValue(value: unknown, depth = 0): unknown {
|
|
728
|
+
if (typeof value === "string") return truncateString(value);
|
|
729
|
+
if (
|
|
730
|
+
value === null ||
|
|
731
|
+
typeof value === "number" ||
|
|
732
|
+
typeof value === "boolean" ||
|
|
733
|
+
typeof value === "undefined"
|
|
734
|
+
) {
|
|
735
|
+
return value;
|
|
736
|
+
}
|
|
737
|
+
if (typeof value === "bigint") return value.toString();
|
|
738
|
+
if (value instanceof Error) return describeError(value);
|
|
739
|
+
if (depth >= MAX_DEBUG_DEPTH) return "[max-depth]";
|
|
740
|
+
if (Array.isArray(value)) {
|
|
741
|
+
const items = value
|
|
742
|
+
.slice(0, MAX_DEBUG_ARRAY_LENGTH)
|
|
743
|
+
.map((item) => sanitizeDebugValue(item, depth + 1));
|
|
744
|
+
if (value.length > MAX_DEBUG_ARRAY_LENGTH) {
|
|
745
|
+
items.push(`[truncated:${value.length - MAX_DEBUG_ARRAY_LENGTH}]`);
|
|
746
|
+
}
|
|
747
|
+
return items;
|
|
748
|
+
}
|
|
749
|
+
if (isRecord(value)) {
|
|
750
|
+
const output: Record<string, unknown> = {};
|
|
751
|
+
const entries = Object.entries(value).slice(0, MAX_DEBUG_OBJECT_KEYS);
|
|
752
|
+
for (const [key, nestedValue] of entries) {
|
|
753
|
+
output[truncateString(key)] = sanitizeDebugValue(nestedValue, depth + 1);
|
|
754
|
+
}
|
|
755
|
+
const extraKeys = Object.keys(value).length - entries.length;
|
|
756
|
+
if (extraKeys > 0) output["[truncatedKeys]"] = extraKeys;
|
|
757
|
+
return output;
|
|
758
|
+
}
|
|
759
|
+
return truncateString(String(value));
|
|
760
|
+
}
|
|
761
|
+
|
|
304
762
|
function sanitizePathSegment(value: string): string {
|
|
305
763
|
return value.replace(/[^a-zA-Z0-9._-]/g, "-").slice(0, 120) || "session-local";
|
|
306
764
|
}
|
|
307
765
|
|
|
766
|
+
function sanitizePersistentIdentifier(value: string, prefix: string): string {
|
|
767
|
+
const pathSafe = value.replace(/[^a-zA-Z0-9._-]/g, "-").slice(0, 120) || `${prefix}-local`;
|
|
768
|
+
if (!SENSITIVE_EVENT_TEXT_PATTERN.test(value) && !SENSITIVE_EVENT_TEXT_PATTERN.test(pathSafe)) {
|
|
769
|
+
return pathSafe;
|
|
770
|
+
}
|
|
771
|
+
return `${prefix}-${sha256Short(value)}`;
|
|
772
|
+
}
|
|
773
|
+
|
|
308
774
|
function stripTrailingSlash(path: string): string {
|
|
309
775
|
if (path === "/" || /^[A-Za-z]:[\\/]?$/.test(path)) return path;
|
|
310
776
|
return path.replace(/[/\\]+$/, "");
|