@evomap/evolver 1.80.6 → 1.80.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.zh-CN.md +18 -11
  2. package/assets/gep/candidates.jsonl +3 -2
  3. package/index.js +55 -2
  4. package/package.json +1 -1
  5. package/src/adapters/opencode.js +137 -2
  6. package/src/config.js +5 -0
  7. package/src/evolve/guards.js +1 -1
  8. package/src/evolve/pipeline/collect.js +1 -1
  9. package/src/evolve/pipeline/dispatch.js +1 -1
  10. package/src/evolve/pipeline/enrich.js +1 -1
  11. package/src/evolve/pipeline/hub.js +1 -1
  12. package/src/evolve/pipeline/select.js +1 -1
  13. package/src/evolve/pipeline/signals.js +1 -1
  14. package/src/evolve/utils.js +1 -1
  15. package/src/evolve.js +1 -1
  16. package/src/gep/.integrity +0 -0
  17. package/src/gep/a2aProtocol.js +1 -1
  18. package/src/gep/assetStore.js +59 -5
  19. package/src/gep/candidateEval.js +1 -1
  20. package/src/gep/candidates.js +1 -1
  21. package/src/gep/contentHash.js +1 -1
  22. package/src/gep/crypto.js +1 -1
  23. package/src/gep/curriculum.js +1 -1
  24. package/src/gep/deviceId.js +1 -1
  25. package/src/gep/envFingerprint.js +1 -1
  26. package/src/gep/epigenetics.js +1 -0
  27. package/src/gep/explore.js +1 -1
  28. package/src/gep/hash.js +1 -0
  29. package/src/gep/hubReview.js +1 -1
  30. package/src/gep/hubSearch.js +1 -1
  31. package/src/gep/hubVerify.js +1 -1
  32. package/src/gep/integrityCheck.js +1 -1
  33. package/src/gep/learningSignals.js +1 -1
  34. package/src/gep/memoryGraph.js +1 -1
  35. package/src/gep/memoryGraphAdapter.js +1 -1
  36. package/src/gep/mutation.js +1 -1
  37. package/src/gep/narrativeMemory.js +1 -1
  38. package/src/gep/personality.js +1 -1
  39. package/src/gep/policyCheck.js +1 -1
  40. package/src/gep/prompt.js +1 -1
  41. package/src/gep/reflection.js +1 -1
  42. package/src/gep/selector.js +1 -1
  43. package/src/gep/shield.js +1 -1
  44. package/src/gep/skillDistiller.js +1 -1
  45. package/src/gep/solidify.js +1 -1
  46. package/src/gep/strategy.js +1 -1
  47. package/src/gep/taskReceiver.js +7 -2
  48. package/src/webui/client/clientJs/assets.js +111 -0
  49. package/src/webui/client/clientJs/bootstrap.js +92 -0
  50. package/src/webui/client/clientJs/common.js +77 -0
  51. package/src/webui/client/clientJs/i18n.js +366 -0
  52. package/src/webui/client/clientJs/index.js +35 -0
  53. package/src/webui/client/clientJs/interactions.js +351 -0
  54. package/src/webui/client/clientJs/overview.js +152 -0
  55. package/src/webui/client/clientJs/personality.js +285 -0
  56. package/src/webui/client/clientJs/pipelines.js +330 -0
  57. package/src/webui/client/indexHtml.js +221 -0
  58. package/src/webui/client/static.js +23 -0
  59. package/src/webui/client/stylesCss.js +639 -0
  60. package/src/webui/client/vendor/README.md +15 -0
  61. package/src/webui/client/vendor/echarts.min.js +45 -0
  62. package/src/webui/index.js +14 -0
  63. package/src/webui/observer/assets.js +146 -0
  64. package/src/webui/observer/index.js +37 -0
  65. package/src/webui/observer/interactions.js +120 -0
  66. package/src/webui/observer/jsonl.js +75 -0
  67. package/src/webui/observer/paths.js +46 -0
  68. package/src/webui/observer/personality.js +43 -0
  69. package/src/webui/observer/pipelineEvents.js +58 -0
  70. package/src/webui/observer/redact.js +63 -0
  71. package/src/webui/observer/runs.js +356 -0
  72. package/src/webui/observer/safety.js +57 -0
  73. package/src/webui/observer/skills.js +70 -0
  74. package/src/webui/observer/status.js +71 -0
  75. package/src/webui/server/http.js +138 -0
  76. package/src/webui/server/routes.js +41 -0
@@ -0,0 +1,14 @@
1
+ 'use strict';
2
+
3
+ const { WebUiServer } = require('./server/http');
4
+
5
+ async function startWebUi(opts = {}) {
6
+ const server = new WebUiServer(opts);
7
+ const info = await server.start();
8
+ return { server, ...info };
9
+ }
10
+
11
+ module.exports = {
12
+ startWebUi,
13
+ WebUiServer,
14
+ };
@@ -0,0 +1,146 @@
1
+ 'use strict';
2
+
3
+ const { getObserverPaths } = require('./paths');
4
+ const { readJsonSafe, readJsonl, paginate } = require('./jsonl');
5
+ const { redactValue } = require('./redact');
6
+
7
+ function getAssetOverview(query = {}) {
8
+ const paths = getObserverPaths();
9
+ const genes = readGenes(paths);
10
+ const capsules = readCapsules(paths);
11
+ const failedCapsules = readFailedCapsules(paths);
12
+ const events = readJsonl(paths.eventsPath, { last: query.eventsLast || 500 }).map(redactValue);
13
+ const candidates = readJsonl(paths.candidatesPath, { last: query.candidatesLast || 200 }).map(redactValue);
14
+ const externalCandidates = readJsonl(paths.externalCandidatesPath, { last: query.candidatesLast || 200 }).map(redactValue);
15
+ const assetCalls = readJsonl(paths.assetCallLogPath, { last: query.assetCallsLast || 500 }).map(redactValue);
16
+
17
+ return {
18
+ counts: {
19
+ genes: genes.length,
20
+ capsules: capsules.length,
21
+ failedCapsules: failedCapsules.length,
22
+ events: events.length,
23
+ candidates: candidates.length,
24
+ externalCandidates: externalCandidates.length,
25
+ assetCalls: assetCalls.length,
26
+ },
27
+ genesByCategory: countBy(genes, 'category'),
28
+ capsulesByOutcome: countBy(capsules, (c) => c.outcome && c.outcome.status || 'unknown'),
29
+ assetCallsByAction: countBy(assetCalls, 'action'),
30
+ recentEvents: events.slice(-20).reverse(),
31
+ recentAssetCalls: assetCalls.slice(-20).reverse(),
32
+ };
33
+ }
34
+
35
+ function listGenes(query = {}) {
36
+ const genes = readGenes(getObserverPaths());
37
+ return {
38
+ ...paginate(filterText(genes, query.q), query),
39
+ categories: countBy(genes, 'category'),
40
+ };
41
+ }
42
+
43
+ function listCapsules(query = {}) {
44
+ const paths = getObserverPaths();
45
+ const all = readCapsules(paths).concat(readFailedCapsules(paths).map((c) => ({ ...c, failed_store: true })));
46
+ return {
47
+ ...paginate(filterText(all, query.q), query),
48
+ outcomes: countBy(all, (c) => c.outcome && c.outcome.status || 'unknown'),
49
+ };
50
+ }
51
+
52
+ function listEvents(query = {}) {
53
+ const events = readJsonl(getObserverPaths().eventsPath).map(redactValue).reverse();
54
+ return paginate(filterText(events, query.q), query);
55
+ }
56
+
57
+ function listCandidates(query = {}) {
58
+ const paths = getObserverPaths();
59
+ const local = readJsonl(paths.candidatesPath).map((entry) => ({ ...entry, source: 'local' }));
60
+ const external = readJsonl(paths.externalCandidatesPath).map((entry) => ({ ...entry, source: 'external' }));
61
+ return paginate(filterText(local.concat(external).map(redactValue).reverse(), query.q), query);
62
+ }
63
+
64
+ function listAssetCalls(query = {}) {
65
+ const calls = readJsonl(getObserverPaths().assetCallLogPath).map(redactValue).reverse();
66
+ return paginate(filterText(calls, query.q), query);
67
+ }
68
+
69
+ function getLineage(id) {
70
+ const paths = getObserverPaths();
71
+ const genes = readGenes(paths);
72
+ const capsules = readCapsules(paths).concat(readFailedCapsules(paths));
73
+ const events = readJsonl(paths.eventsPath).map(redactValue);
74
+ const assetCalls = readJsonl(paths.assetCallLogPath).map(redactValue);
75
+ return {
76
+ id,
77
+ genes: genes.filter((g) => matchesAsset(g, id)),
78
+ capsules: capsules.filter((c) => matchesAsset(c, id) || c.gene === id),
79
+ events: events.filter((e) => eventMentions(e, id)),
80
+ assetCalls: assetCalls.filter((entry) => matchesAsset(entry, id)),
81
+ };
82
+ }
83
+
84
+ function readGenes(paths) {
85
+ const fromJson = readJsonSafe(paths.genesPath, { genes: [] }).genes || [];
86
+ const fromJsonl = readJsonl(paths.genesJsonlPath).filter((g) => g && g.type === 'Gene');
87
+ return dedupeById(fromJson.concat(fromJsonl)).map(redactValue);
88
+ }
89
+
90
+ function readCapsules(paths) {
91
+ const fromJson = readJsonSafe(paths.capsulesPath, { capsules: [] }).capsules || [];
92
+ const fromJsonl = readJsonl(paths.capsulesJsonlPath).filter((c) => c && c.type === 'Capsule');
93
+ return dedupeById(fromJson.concat(fromJsonl)).map(redactValue);
94
+ }
95
+
96
+ function readFailedCapsules(paths) {
97
+ const failed = readJsonSafe(paths.failedCapsulesPath, { failed_capsules: [] }).failed_capsules || [];
98
+ return failed.map(redactValue);
99
+ }
100
+
101
+ function dedupeById(items) {
102
+ const out = new Map();
103
+ for (const item of items) {
104
+ if (item && item.id) out.set(String(item.id), item);
105
+ }
106
+ return Array.from(out.values());
107
+ }
108
+
109
+ function countBy(items, keyOrFn) {
110
+ const counts = {};
111
+ for (const item of items) {
112
+ const key = typeof keyOrFn === 'function' ? keyOrFn(item) : item && item[keyOrFn];
113
+ const safeKey = key || 'unknown';
114
+ counts[safeKey] = (counts[safeKey] || 0) + 1;
115
+ }
116
+ return counts;
117
+ }
118
+
119
+ function filterText(items, q) {
120
+ if (!q) return items;
121
+ const needle = String(q).toLowerCase();
122
+ return items.filter((item) => JSON.stringify(item).toLowerCase().includes(needle));
123
+ }
124
+
125
+ function matchesAsset(item, id) {
126
+ if (!item || !id) return false;
127
+ return item.id === id || item.asset_id === id || item.asset_id === `sha256:${id}`;
128
+ }
129
+
130
+ function eventMentions(event, id) {
131
+ if (matchesAsset(event, id)) return true;
132
+ if (event.capsule_id === id || event.mutation_id === id) return true;
133
+ return Array.isArray(event.genes_used) && event.genes_used.includes(id);
134
+ }
135
+
136
+ module.exports = {
137
+ getAssetOverview,
138
+ listGenes,
139
+ listCapsules,
140
+ listEvents,
141
+ listCandidates,
142
+ listAssetCalls,
143
+ getLineage,
144
+ readGenes,
145
+ readCapsules,
146
+ };
@@ -0,0 +1,37 @@
1
+ 'use strict';
2
+
3
+ const { getStatus } = require('./status');
4
+ const { getSafetyState } = require('./safety');
5
+ const { listRuns, getRun } = require('./runs');
6
+ const { getAssetOverview, listGenes, listCapsules, listEvents, listCandidates, listAssetCalls, getLineage } = require('./assets');
7
+ const { getInteractions } = require('./interactions');
8
+ const { getPersonality, getMemoryGraph } = require('./personality');
9
+ const { listSkills } = require('./skills');
10
+ const { getObserverPaths } = require('./paths');
11
+ const { tailText } = require('./jsonl');
12
+ const { redactText } = require('./redact');
13
+
14
+ function getEvolverLog(query = {}) {
15
+ const tail = query.tail || 200;
16
+ const text = tailText(getObserverPaths().evolverLogPath, tail);
17
+ return { text: redactText(text), tail: Number(tail) || 200 };
18
+ }
19
+
20
+ module.exports = {
21
+ getStatus,
22
+ getSafetyState,
23
+ listRuns,
24
+ getRun,
25
+ getAssetOverview,
26
+ listGenes,
27
+ listCapsules,
28
+ listEvents,
29
+ listCandidates,
30
+ listAssetCalls,
31
+ getLineage,
32
+ getInteractions,
33
+ getPersonality,
34
+ getMemoryGraph,
35
+ listSkills,
36
+ getEvolverLog,
37
+ };
@@ -0,0 +1,120 @@
1
+ 'use strict';
2
+
3
+ const http = require('http');
4
+ const os = require('os');
5
+ const path = require('path');
6
+ const { getProxySettings } = require('./status');
7
+ const { readJsonl, paginate } = require('./jsonl');
8
+ const { redactValue } = require('./redact');
9
+
10
+ const DEFAULT_MAILBOX_DIR = path.join(os.homedir(), '.evomap', 'mailbox');
11
+
12
+ async function getInteractions(query = {}) {
13
+ const proxy = getProxySettings();
14
+ const proxySnapshots = proxy.url ? await readProxySnapshots(proxy.url) : {};
15
+ const mailbox = readMailboxMessages(query);
16
+ return {
17
+ proxy,
18
+ proxySnapshots,
19
+ mailbox: paginate(mailbox, query),
20
+ };
21
+ }
22
+
23
+ function readMailboxMessages(query = {}) {
24
+ const mailboxDir = query.mailboxDir || process.env.EVOMAP_MAILBOX_DIR || DEFAULT_MAILBOX_DIR;
25
+ const messages = readJsonl(path.join(mailboxDir, 'messages.jsonl'), { last: query.last || 500 })
26
+ .filter((row) => row && !row._op)
27
+ .map((row) => normalizeMessage(redactValue(row)));
28
+ return filterMessages(messages, query);
29
+ }
30
+
31
+ async function readProxySnapshots(baseUrl) {
32
+ const endpoints = {
33
+ status: '/proxy/status',
34
+ hubStatus: '/proxy/hub-status',
35
+ taskMetrics: '/task/metrics',
36
+ assetSubmissions: '/asset/submissions?limit=20',
37
+ sessions: '/session/list?limit=20',
38
+ dms: '/dm/list?limit=20',
39
+ atpPolicy: '/atp/policy',
40
+ atpProofs: '/atp/proofs?limit=20',
41
+ };
42
+ // Issue all proxy requests concurrently. Sequential await meant the
43
+ // worst-case (every endpoint timing out at 1500ms each) blocked the
44
+ // /webui/interactions response for ~12s; parallel keeps it ~1.5s.
45
+ const entries = await Promise.all(
46
+ Object.entries(endpoints).map(async ([key, endpoint]) =>
47
+ [key, await requestJson(baseUrl + endpoint)],
48
+ ),
49
+ );
50
+ return redactValue(Object.fromEntries(entries));
51
+ }
52
+
53
+ function requestJson(url) {
54
+ return new Promise((resolve) => {
55
+ const req = http.get(url, { timeout: 1500 }, (res) => {
56
+ const chunks = [];
57
+ res.on('data', (chunk) => chunks.push(chunk));
58
+ res.on('end', () => {
59
+ const raw = Buffer.concat(chunks).toString();
60
+ try {
61
+ resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, status: res.statusCode, body: JSON.parse(raw) });
62
+ } catch {
63
+ resolve({ ok: false, status: res.statusCode, body: null });
64
+ }
65
+ });
66
+ });
67
+ req.on('timeout', () => {
68
+ req.destroy();
69
+ resolve({ ok: false, status: 0, error: 'timeout' });
70
+ });
71
+ req.on('error', (err) => resolve({ ok: false, status: 0, error: err.message }));
72
+ });
73
+ }
74
+
75
+ function normalizeMessage(msg) {
76
+ return {
77
+ id: msg.id || null,
78
+ source: 'mailbox',
79
+ direction: msg.direction || null,
80
+ type: msg.type || null,
81
+ status: msg.status || null,
82
+ timestamp: toIso(msg.created_at || msg.updated_at || msg.synced_at),
83
+ refId: msg.ref_id || msg.payload && msg.payload.ref_id || null,
84
+ priority: msg.priority || null,
85
+ summary: summarizePayload(msg),
86
+ payload: msg.payload || null,
87
+ };
88
+ }
89
+
90
+ function filterMessages(messages, query) {
91
+ return messages.filter((msg) => {
92
+ if (query.type && msg.type !== query.type) return false;
93
+ if (query.direction && msg.direction !== query.direction) return false;
94
+ if (query.status && msg.status !== query.status) return false;
95
+ return true;
96
+ }).reverse();
97
+ }
98
+
99
+ function summarizePayload(msg) {
100
+ const payload = msg.payload || {};
101
+ if (payload.task_id) return `Task ${payload.task_id}`;
102
+ if (payload.asset_id) return `Asset ${payload.asset_id}`;
103
+ if (payload.session_id) return `Session ${payload.session_id}`;
104
+ if (payload.title) return String(payload.title);
105
+ if (msg.type) return String(msg.type).replace(/_/g, ' ');
106
+ return 'message';
107
+ }
108
+
109
+ function toIso(value) {
110
+ if (!value) return null;
111
+ if (typeof value === 'string') return value;
112
+ const n = Number(value);
113
+ return Number.isFinite(n) ? new Date(n).toISOString() : null;
114
+ }
115
+
116
+ module.exports = {
117
+ getInteractions,
118
+ readMailboxMessages,
119
+ readProxySnapshots,
120
+ };
@@ -0,0 +1,75 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+
5
+ function readJsonSafe(filePath, fallback) {
6
+ try {
7
+ if (!filePath || !fs.existsSync(filePath)) return fallback;
8
+ const raw = fs.readFileSync(filePath, 'utf8');
9
+ if (!raw.trim()) return fallback;
10
+ return JSON.parse(raw);
11
+ } catch {
12
+ return fallback;
13
+ }
14
+ }
15
+
16
+ function readJsonl(filePath, opts = {}) {
17
+ try {
18
+ if (!filePath || !fs.existsSync(filePath)) return [];
19
+ const raw = fs.readFileSync(filePath, 'utf8');
20
+ const lines = raw.split('\n').map((line) => line.trim()).filter(Boolean);
21
+ const selected = opts.last ? lines.slice(-toPositiveInt(opts.last, lines.length)) : lines;
22
+ const rows = [];
23
+ for (const line of selected) {
24
+ try {
25
+ rows.push(JSON.parse(line));
26
+ } catch {
27
+ // Corrupt JSONL rows should not break the dashboard.
28
+ }
29
+ }
30
+ return rows;
31
+ } catch {
32
+ return [];
33
+ }
34
+ }
35
+
36
+ function tailText(filePath, maxLines) {
37
+ try {
38
+ if (!filePath || !fs.existsSync(filePath)) return '';
39
+ const raw = fs.readFileSync(filePath, 'utf8');
40
+ const lines = raw.split('\n');
41
+ return lines.slice(-toPositiveInt(maxLines, 200)).join('\n');
42
+ } catch {
43
+ return '';
44
+ }
45
+ }
46
+
47
+ function paginate(items, query = {}) {
48
+ const limit = Math.min(toPositiveInt(query.limit, 50), 200);
49
+ const offset = Math.max(0, toPositiveInt(query.offset || query.cursor, 0));
50
+ const data = items.slice(offset, offset + limit);
51
+ const nextOffset = offset + data.length;
52
+ return {
53
+ data,
54
+ pagination: {
55
+ limit,
56
+ offset,
57
+ totalItems: items.length,
58
+ nextCursor: nextOffset < items.length ? String(nextOffset) : null,
59
+ },
60
+ };
61
+ }
62
+
63
+ function toPositiveInt(value, fallback) {
64
+ const n = Number(value);
65
+ if (!Number.isFinite(n) || n < 0) return fallback;
66
+ return Math.floor(n);
67
+ }
68
+
69
+ module.exports = {
70
+ readJsonSafe,
71
+ readJsonl,
72
+ tailText,
73
+ paginate,
74
+ toPositiveInt,
75
+ };
@@ -0,0 +1,46 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const {
5
+ getRepoRoot,
6
+ getWorkspaceRoot,
7
+ getEvolutionDir,
8
+ getGepAssetsDir,
9
+ getSkillsDir,
10
+ getEvolverLogPath,
11
+ getAgentSessionsDir,
12
+ getNarrativePath,
13
+ getReflectionLogPath,
14
+ } = require('../../gep/paths');
15
+
16
+ function getObserverPaths() {
17
+ const evolutionDir = getEvolutionDir();
18
+ const gepAssetsDir = getGepAssetsDir();
19
+ return {
20
+ repoRoot: getRepoRoot(),
21
+ workspaceRoot: getWorkspaceRoot(),
22
+ evolutionDir,
23
+ gepAssetsDir,
24
+ skillsDir: getSkillsDir(),
25
+ agentSessionsDir: getAgentSessionsDir(),
26
+ evolverLogPath: getEvolverLogPath(),
27
+ cycleProgressPath: path.join(evolutionDir, 'cycle_progress.json'),
28
+ solidifyStatePath: path.join(evolutionDir, 'evolution_solidify_state.json'),
29
+ evolutionStatePath: path.join(evolutionDir, 'evolution_state.json'),
30
+ pipelineEventsPath: path.join(evolutionDir, 'pipeline_events.jsonl'),
31
+ assetCallLogPath: path.join(evolutionDir, 'asset_call_log.jsonl'),
32
+ reflectionLogPath: getReflectionLogPath(),
33
+ narrativePath: getNarrativePath(),
34
+ memoryGraphPath: path.join(evolutionDir, 'memory_graph.jsonl'),
35
+ genesPath: path.join(gepAssetsDir, 'genes.json'),
36
+ genesJsonlPath: path.join(gepAssetsDir, 'genes.jsonl'),
37
+ capsulesPath: path.join(gepAssetsDir, 'capsules.json'),
38
+ capsulesJsonlPath: path.join(gepAssetsDir, 'capsules.jsonl'),
39
+ eventsPath: path.join(gepAssetsDir, 'events.jsonl'),
40
+ candidatesPath: path.join(gepAssetsDir, 'candidates.jsonl'),
41
+ externalCandidatesPath: path.join(gepAssetsDir, 'external_candidates.jsonl'),
42
+ failedCapsulesPath: path.join(gepAssetsDir, 'failed_capsules.json'),
43
+ };
44
+ }
45
+
46
+ module.exports = { getObserverPaths };
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const fs = require('fs');
5
+ const { getMemoryDir, getEvolutionDir } = require('../../gep/paths');
6
+ const { readJsonSafe, readJsonl } = require('./jsonl');
7
+ const { redactValue } = require('./redact');
8
+
9
+ function getPersonality() {
10
+ const evoDir = getEvolutionDir();
11
+ const memDir = getMemoryDir();
12
+ const candidates = [
13
+ path.join(evoDir, 'personality_state.json'),
14
+ path.join(memDir, 'personality_state.json'),
15
+ ];
16
+ for (const candidate of candidates) {
17
+ const data = readJsonSafe(candidate, null);
18
+ if (data) return { exists: true, ...redactValue(data) };
19
+ }
20
+ return { exists: false, current: null, history: [] };
21
+ }
22
+
23
+ function getMemoryGraph(query = {}) {
24
+ const limit = Math.min(Number(query.limit) || 50, 500);
25
+ const graphPath = path.join(getEvolutionDir(), 'memory_graph.jsonl');
26
+ if (!fs.existsSync(graphPath)) return { exists: false, items: [] };
27
+ const rows = readJsonl(graphPath, { last: limit }).map(redactValue);
28
+ return {
29
+ exists: true,
30
+ total: countLines(graphPath),
31
+ items: rows.reverse(),
32
+ };
33
+ }
34
+
35
+ function countLines(filePath) {
36
+ try {
37
+ return fs.readFileSync(filePath, 'utf8').split('\n').filter(Boolean).length;
38
+ } catch {
39
+ return 0;
40
+ }
41
+ }
42
+
43
+ module.exports = { getPersonality, getMemoryGraph };
@@ -0,0 +1,58 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { getObserverPaths } = require('./paths');
6
+ const { readJsonl } = require('./jsonl');
7
+ const { redactValue } = require('./redact');
8
+
9
+ const VALID_PHASE_STATUSES = new Set(['pending', 'running', 'success', 'failed', 'skipped', 'blocked']);
10
+
11
+ function readPipelineEvents(opts = {}) {
12
+ return readJsonl(getObserverPaths().pipelineEventsPath, opts).map(normalizePipelineEvent);
13
+ }
14
+
15
+ function logPipelineEvent(event) {
16
+ const normalized = normalizePipelineEvent(event);
17
+ const target = getObserverPaths().pipelineEventsPath;
18
+ fs.mkdirSync(path.dirname(target), { recursive: true });
19
+ fs.appendFileSync(target, JSON.stringify(normalized) + '\n', 'utf8');
20
+ return normalized;
21
+ }
22
+
23
+ function normalizePipelineEvent(event) {
24
+ const input = event && typeof event === 'object' ? event : {};
25
+ const status = VALID_PHASE_STATUSES.has(input.status) ? input.status : 'running';
26
+ return redactValue({
27
+ run_id: stringOrNull(input.run_id),
28
+ cycle_id: stringOrNull(input.cycle_id),
29
+ phase: stringOrDefault(input.phase, 'unknown'),
30
+ status,
31
+ started_at: stringOrNull(input.started_at),
32
+ finished_at: stringOrNull(input.finished_at),
33
+ summary: stringOrDefault(input.summary, ''),
34
+ evidence_refs: arrayOfObjects(input.evidence_refs),
35
+ asset_refs: arrayOfObjects(input.asset_refs),
36
+ validation_refs: arrayOfObjects(input.validation_refs),
37
+ requires_confirmation: Boolean(input.requires_confirmation),
38
+ timestamp: input.timestamp || new Date().toISOString(),
39
+ });
40
+ }
41
+
42
+ function stringOrNull(value) {
43
+ return typeof value === 'string' && value ? value : null;
44
+ }
45
+
46
+ function stringOrDefault(value, fallback) {
47
+ return typeof value === 'string' ? value : fallback;
48
+ }
49
+
50
+ function arrayOfObjects(value) {
51
+ return Array.isArray(value) ? value.filter((entry) => entry && typeof entry === 'object') : [];
52
+ }
53
+
54
+ module.exports = {
55
+ readPipelineEvents,
56
+ logPipelineEvent,
57
+ normalizePipelineEvent,
58
+ };
@@ -0,0 +1,63 @@
1
+ 'use strict';
2
+
3
+ const SECRET_KEY_RE = /(secret|token|api[_-]?key|authorization|cookie|oauth|password|private[_-]?key|node_secret)/i;
4
+ // Three alternation arms, each with one capturing group so the replace()
5
+ // callback can keep the prefix and only mask the value:
6
+ // 1) HTTP Bearer auth header
7
+ // 2) Env/INI/CLI style NAME=value (or NAME: value) where NAME ends in
8
+ // SECRET / TOKEN / KEY / PASSWORD / PASSWD / PWD. The original
9
+ // regex only matched SECRET / TOKEN; without KEY / PASSWORD a line
10
+ // like `OPENAI_API_KEY=sk-...` or `DB_PASSWORD=hunter2` would slip
11
+ // through to the /webui/logs/evolver tail, leaking live credentials.
12
+ // 3) Bare OpenAI/Anthropic-shape API keys (`sk-...` / `sk-ant-...`) for
13
+ // cases where they appear as standalone tokens in log lines without
14
+ // a NAME= prefix.
15
+ // Flagged /gi so lower-case variants (`password=...`, `bearer ...`,
16
+ // `sk-...`) are also caught; over-redacting harmless prose tokens like
17
+ // "monkey=" is acceptable, leaking a real key is not.
18
+ const SECRET_TEXT_RE = new RegExp([
19
+ '(Bearer\\s+)[A-Za-z0-9._~+/-]+=*',
20
+ '([A-Za-z0-9_]*(?:SECRET|TOKEN|KEY|PASSWORD|PASSWD|PWD)[A-Za-z0-9_]*\\s*[:=]\\s*)["\']?[^\\s,"\'`]+',
21
+ '(sk-(?:ant-)?[A-Za-z0-9_-]{20,})',
22
+ ].join('|'), 'gi');
23
+
24
+ function redactValue(value, depth = 0) {
25
+ if (depth > 8) return '[REDACTED_DEPTH]';
26
+ if (Array.isArray(value)) return value.map((entry) => redactValue(entry, depth + 1));
27
+ if (!value || typeof value !== 'object') {
28
+ return typeof value === 'string' ? redactText(value) : value;
29
+ }
30
+
31
+ const out = {};
32
+ for (const [key, child] of Object.entries(value)) {
33
+ if (SECRET_KEY_RE.test(key)) {
34
+ out[key] = '[REDACTED]';
35
+ } else if (isLargeSensitiveField(key, child)) {
36
+ out[key] = '[REDACTED_SENSITIVE_DETAIL]';
37
+ } else {
38
+ out[key] = redactValue(child, depth + 1);
39
+ }
40
+ }
41
+ return out;
42
+ }
43
+
44
+ function redactText(text) {
45
+ if (typeof text !== 'string') return text;
46
+ return text.replace(SECRET_TEXT_RE, (match, bearer, namedPrefix, bareKey) => {
47
+ if (bearer) return `${bearer}[REDACTED]`;
48
+ if (namedPrefix) return `${namedPrefix}[REDACTED]`;
49
+ if (bareKey) return '[REDACTED]';
50
+ return '[REDACTED]';
51
+ });
52
+ }
53
+
54
+ function isLargeSensitiveField(key, value) {
55
+ if (typeof value !== 'string') return false;
56
+ if (!/(prompt|diff|content|transcript|message|raw)/i.test(key)) return false;
57
+ return value.length > 500;
58
+ }
59
+
60
+ module.exports = {
61
+ redactValue,
62
+ redactText,
63
+ };