cipher-security 2.0.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/bin/cipher.js +566 -0
- package/lib/api/billing.js +321 -0
- package/lib/api/compliance.js +693 -0
- package/lib/api/controls.js +1401 -0
- package/lib/api/index.js +49 -0
- package/lib/api/marketplace.js +467 -0
- package/lib/api/openai-proxy.js +383 -0
- package/lib/api/server.js +685 -0
- package/lib/autonomous/feedback-loop.js +554 -0
- package/lib/autonomous/framework.js +512 -0
- package/lib/autonomous/index.js +97 -0
- package/lib/autonomous/leaderboard.js +594 -0
- package/lib/autonomous/modes/architect.js +412 -0
- package/lib/autonomous/modes/blue.js +386 -0
- package/lib/autonomous/modes/incident.js +684 -0
- package/lib/autonomous/modes/privacy.js +369 -0
- package/lib/autonomous/modes/purple.js +294 -0
- package/lib/autonomous/modes/recon.js +250 -0
- package/lib/autonomous/parallel.js +587 -0
- package/lib/autonomous/researcher.js +583 -0
- package/lib/autonomous/runner.js +955 -0
- package/lib/autonomous/scheduler.js +615 -0
- package/lib/autonomous/task-parser.js +127 -0
- package/lib/autonomous/validators/forensic.js +266 -0
- package/lib/autonomous/validators/osint.js +216 -0
- package/lib/autonomous/validators/privacy.js +296 -0
- package/lib/autonomous/validators/purple.js +298 -0
- package/lib/autonomous/validators/sigma.js +248 -0
- package/lib/autonomous/validators/threat-model.js +363 -0
- package/lib/benchmark/agent.js +119 -0
- package/lib/benchmark/baselines.js +43 -0
- package/lib/benchmark/builder.js +143 -0
- package/lib/benchmark/config.js +35 -0
- package/lib/benchmark/coordinator.js +91 -0
- package/lib/benchmark/index.js +20 -0
- package/lib/benchmark/llm.js +58 -0
- package/lib/benchmark/models.js +137 -0
- package/lib/benchmark/reporter.js +103 -0
- package/lib/benchmark/runner.js +103 -0
- package/lib/benchmark/sandbox.js +96 -0
- package/lib/benchmark/scorer.js +32 -0
- package/lib/benchmark/solver.js +166 -0
- package/lib/benchmark/tools.js +62 -0
- package/lib/bot/bot.js +238 -0
- package/lib/brand.js +105 -0
- package/lib/commands.js +100 -0
- package/lib/complexity.js +377 -0
- package/lib/config.js +213 -0
- package/lib/gateway/client.js +309 -0
- package/lib/gateway/commands.js +991 -0
- package/lib/gateway/config-validate.js +109 -0
- package/lib/gateway/gateway.js +367 -0
- package/lib/gateway/index.js +62 -0
- package/lib/gateway/mode.js +309 -0
- package/lib/gateway/plugins.js +222 -0
- package/lib/gateway/prompt.js +214 -0
- package/lib/mcp/server.js +262 -0
- package/lib/memory/compressor.js +425 -0
- package/lib/memory/engine.js +763 -0
- package/lib/memory/evolution.js +668 -0
- package/lib/memory/index.js +58 -0
- package/lib/memory/orchestrator.js +506 -0
- package/lib/memory/retriever.js +515 -0
- package/lib/memory/synthesizer.js +333 -0
- package/lib/pipeline/async-scanner.js +510 -0
- package/lib/pipeline/binary-analysis.js +1043 -0
- package/lib/pipeline/dom-xss-scanner.js +435 -0
- package/lib/pipeline/github-actions.js +792 -0
- package/lib/pipeline/index.js +124 -0
- package/lib/pipeline/osint.js +498 -0
- package/lib/pipeline/sarif.js +373 -0
- package/lib/pipeline/scanner.js +880 -0
- package/lib/pipeline/template-manager.js +525 -0
- package/lib/pipeline/xss-scanner.js +353 -0
- package/lib/setup-wizard.js +288 -0
- package/package.json +31 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
// Copyright (c) 2026 defconxt. All rights reserved.
|
|
2
|
+
// Licensed under AGPL-3.0 — see LICENSE file for details.
|
|
3
|
+
// CIPHER is a trademark of defconxt.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* CIPHER Memory — Stage 1: Semantic Structured Compression
|
|
7
|
+
*
|
|
8
|
+
* Converts raw security engagement dialogues into atomic, self-contained
|
|
9
|
+
* memory entries through extraction:
|
|
10
|
+
*
|
|
11
|
+
* - Security-specific entity extraction (IPs, CVEs, MITRE ATT&CK, tools)
|
|
12
|
+
* - Information density gating (skip low-information exchanges)
|
|
13
|
+
* - Heuristic compression (always works, no LLM required)
|
|
14
|
+
* - LLM compression (stubbed — S03 provides the LLM client)
|
|
15
|
+
*
|
|
16
|
+
* Ported from Python memory/core/compressor.py.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { randomUUID } from 'node:crypto';
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Security-specific extraction patterns
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
const _IP_RE = /\b(?:\d{1,3}\.){3}\d{1,3}\b/g;
|
|
26
|
+
const _CVE_RE = /CVE-\d{4}-\d{4,}/g;
|
|
27
|
+
const _MITRE_RE = /T\d{4}(?:\.\d{3})?/g;
|
|
28
|
+
const _PORT_RE = /\b(?:port\s+)?(\d{1,5})(?:\/(?:tcp|udp))?\b/gi;
|
|
29
|
+
const _DOMAIN_RE =
|
|
30
|
+
/\b(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}\b/g;
|
|
31
|
+
const _HASH_MD5_RE = /\b[a-fA-F0-9]{32}\b/g;
|
|
32
|
+
const _HASH_SHA256_RE = /\b[a-fA-F0-9]{64}\b/g;
|
|
33
|
+
|
|
34
|
+
// File extensions to filter from domain matches
|
|
35
|
+
const _FILE_EXTENSIONS = ['.py', '.js', '.md', '.yaml', '.json', '.txt'];
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Entity Extraction
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Extract security-relevant entities from text using regex patterns.
|
|
43
|
+
* @param {string} text
|
|
44
|
+
* @returns {{ ips: string[], cves: string[], mitre: string[], domains: string[], hashes_sha256: string[], hashes_md5: string[] }}
|
|
45
|
+
*/
|
|
46
|
+
function extractSecurityEntities(text) {
|
|
47
|
+
// Reset lastIndex on all regexes (they have /g flag)
|
|
48
|
+
const matchAll = (re, str) => [...str.matchAll(new RegExp(re.source, re.flags))].map((m) => m[0]);
|
|
49
|
+
|
|
50
|
+
const ips = [...new Set(matchAll(_IP_RE, text))];
|
|
51
|
+
const cves = [...new Set(matchAll(_CVE_RE, text))];
|
|
52
|
+
const mitre = [...new Set(matchAll(_MITRE_RE, text))];
|
|
53
|
+
const hashes_sha256 = [...new Set(matchAll(_HASH_SHA256_RE, text))];
|
|
54
|
+
const hashes_md5 = [...new Set(matchAll(_HASH_MD5_RE, text))];
|
|
55
|
+
|
|
56
|
+
// Filter hashes: SHA256 matches are 64 chars, MD5 are 32 chars.
|
|
57
|
+
// If a 64-char hash also matches the 32-char pattern, remove from MD5.
|
|
58
|
+
const sha256Set = new Set(hashes_sha256);
|
|
59
|
+
const filteredMd5 = hashes_md5.filter(
|
|
60
|
+
(h) => !hashes_sha256.some((s) => s.includes(h)),
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const rawDomains = [...new Set(matchAll(_DOMAIN_RE, text))];
|
|
64
|
+
const domains = rawDomains.filter(
|
|
65
|
+
(d) => !_FILE_EXTENSIONS.some((ext) => d.endsWith(ext)),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
return { ips, cves, mitre, domains, hashes_sha256, hashes_md5: filteredMd5 };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// DialogueTurn
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Single turn in a security engagement dialogue.
|
|
77
|
+
*/
|
|
78
|
+
class DialogueTurn {
|
|
79
|
+
/**
|
|
80
|
+
* @param {{ turnId: number, role: string, content: string, timestamp?: string, toolName?: string, toolOutput?: string }} opts
|
|
81
|
+
*/
|
|
82
|
+
constructor(opts = {}) {
|
|
83
|
+
this.turnId = opts.turnId ?? 0;
|
|
84
|
+
this.role = opts.role ?? 'user';
|
|
85
|
+
this.content = opts.content ?? '';
|
|
86
|
+
this.timestamp = opts.timestamp ?? '';
|
|
87
|
+
this.toolName = opts.toolName ?? '';
|
|
88
|
+
this.toolOutput = opts.toolOutput ?? '';
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// CompressedEntry
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Atomic memory unit produced by Stage 1 compression.
|
|
98
|
+
*/
|
|
99
|
+
class CompressedEntry {
|
|
100
|
+
constructor(opts = {}) {
|
|
101
|
+
this.entryId = opts.entryId ?? randomUUID();
|
|
102
|
+
this.losslessRestatement = opts.losslessRestatement ?? '';
|
|
103
|
+
this.memoryType = opts.memoryType ?? 'note';
|
|
104
|
+
this.confidence = opts.confidence ?? 'confirmed';
|
|
105
|
+
|
|
106
|
+
// Security-specific extracted metadata
|
|
107
|
+
this.targets = opts.targets ?? [];
|
|
108
|
+
this.cveIds = opts.cveIds ?? [];
|
|
109
|
+
this.mitreAttack = opts.mitreAttack ?? [];
|
|
110
|
+
this.severity = opts.severity ?? '';
|
|
111
|
+
this.toolsUsed = opts.toolsUsed ?? [];
|
|
112
|
+
this.hashes = opts.hashes ?? [];
|
|
113
|
+
|
|
114
|
+
// General metadata
|
|
115
|
+
this.keywords = opts.keywords ?? [];
|
|
116
|
+
this.persons = opts.persons ?? [];
|
|
117
|
+
this.entities = opts.entities ?? [];
|
|
118
|
+
this.timestamp = opts.timestamp ?? '';
|
|
119
|
+
this.topic = opts.topic ?? '';
|
|
120
|
+
|
|
121
|
+
// Source tracking
|
|
122
|
+
this.sourceTurns = opts.sourceTurns ?? [];
|
|
123
|
+
this.engagementId = opts.engagementId ?? '';
|
|
124
|
+
this.sourceSkill = opts.sourceSkill ?? '';
|
|
125
|
+
|
|
126
|
+
this.createdAt = opts.createdAt ?? new Date().toISOString();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// DensityGate
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Information density gate — filters out low-information dialogue turns.
|
|
136
|
+
*
|
|
137
|
+
* Scores based on: technical content, content length, lexical diversity.
|
|
138
|
+
*/
|
|
139
|
+
class DensityGate {
|
|
140
|
+
static LOW_INFO_PATTERNS = [
|
|
141
|
+
/^(?:ok|okay|sure|yes|no|thanks|thank you|got it|understood)[.!?]?$/i,
|
|
142
|
+
/^(?:let me|i'll|i will|going to)\s/i,
|
|
143
|
+
/^(?:here (?:is|are)|sure,?\s+here)/i,
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @param {number} minScore — minimum density score to include a turn (default 0.3)
|
|
148
|
+
*/
|
|
149
|
+
constructor(minScore = 0.3) {
|
|
150
|
+
this.minScore = minScore;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Score information density of a dialogue turn (0.0 - 1.0).
|
|
155
|
+
* @param {DialogueTurn} turn
|
|
156
|
+
* @returns {number}
|
|
157
|
+
*/
|
|
158
|
+
score(turn) {
|
|
159
|
+
const text = (turn.content || '').trim();
|
|
160
|
+
if (!text) return 0.0;
|
|
161
|
+
|
|
162
|
+
let score = 0.0;
|
|
163
|
+
|
|
164
|
+
// Length score
|
|
165
|
+
const length = text.length;
|
|
166
|
+
if (length > 200) {
|
|
167
|
+
score += 0.3;
|
|
168
|
+
} else if (length > 50) {
|
|
169
|
+
score += 0.15;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Technical content indicators
|
|
173
|
+
if (_IP_RE.test(text)) score += 0.15;
|
|
174
|
+
_IP_RE.lastIndex = 0;
|
|
175
|
+
if (_CVE_RE.test(text)) score += 0.2;
|
|
176
|
+
_CVE_RE.lastIndex = 0;
|
|
177
|
+
if (_MITRE_RE.test(text)) score += 0.15;
|
|
178
|
+
_MITRE_RE.lastIndex = 0;
|
|
179
|
+
if (text.includes('```')) score += 0.2;
|
|
180
|
+
if (_HASH_SHA256_RE.test(text) || _HASH_MD5_RE.test(text)) score += 0.1;
|
|
181
|
+
_HASH_SHA256_RE.lastIndex = 0;
|
|
182
|
+
_HASH_MD5_RE.lastIndex = 0;
|
|
183
|
+
|
|
184
|
+
// Unique term ratio (lexical diversity)
|
|
185
|
+
const words = text.toLowerCase().split(/\s+/);
|
|
186
|
+
if (words.length > 0) {
|
|
187
|
+
const uniqueRatio = new Set(words).size / words.length;
|
|
188
|
+
score += uniqueRatio * 0.2;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Penalize boilerplate
|
|
192
|
+
for (const pat of DensityGate.LOW_INFO_PATTERNS) {
|
|
193
|
+
if (pat.test(text)) {
|
|
194
|
+
score *= 0.3;
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Tool output is almost always high-info
|
|
200
|
+
if (turn.role === 'tool' && turn.toolOutput) {
|
|
201
|
+
score = Math.max(score, 0.5);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return Math.min(score, 1.0);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* @param {DialogueTurn} turn
|
|
209
|
+
* @returns {boolean}
|
|
210
|
+
*/
|
|
211
|
+
shouldInclude(turn) {
|
|
212
|
+
return this.score(turn) >= this.minScore;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
// SemanticCompressor
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
|
|
220
|
+
// Stop words for keyword extraction
|
|
221
|
+
const _STOP_WORDS = new Set([
|
|
222
|
+
'the', 'and', 'for', 'that', 'this', 'with', 'from', 'have', 'has',
|
|
223
|
+
'been', 'were', 'was', 'are', 'will', 'would', 'could', 'should',
|
|
224
|
+
'into', 'about', 'which',
|
|
225
|
+
]);
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Stage 1: Semantic Structured Compression.
|
|
229
|
+
*
|
|
230
|
+
* Processes dialogue windows through:
|
|
231
|
+
* 1. Density gating — filter low-info turns
|
|
232
|
+
* 2. Entity extraction — security-specific pattern matching
|
|
233
|
+
* 3. LLM compression (stubbed) or heuristic extraction
|
|
234
|
+
* 4. Atomic entry creation — self-contained memory units
|
|
235
|
+
*/
|
|
236
|
+
class SemanticCompressor {
|
|
237
|
+
/**
|
|
238
|
+
* @param {{ llmClient?: any, windowSize?: number, overlapSize?: number, densityThreshold?: number, model?: string }} opts
|
|
239
|
+
*/
|
|
240
|
+
constructor(opts = {}) {
|
|
241
|
+
this.llmClient = opts.llmClient ?? null;
|
|
242
|
+
this.windowSize = opts.windowSize ?? 20;
|
|
243
|
+
this.overlapSize = opts.overlapSize ?? 2;
|
|
244
|
+
this.densityGate = new DensityGate(opts.densityThreshold ?? 0.3);
|
|
245
|
+
this.model = opts.model ?? 'claude-sonnet-4-20250514';
|
|
246
|
+
this._previousContext = '';
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Compress dialogue turns into atomic memory entries.
|
|
251
|
+
* @param {DialogueTurn[]} turns
|
|
252
|
+
* @param {string} engagementId
|
|
253
|
+
* @param {string} sourceSkill
|
|
254
|
+
* @returns {Promise<CompressedEntry[]>}
|
|
255
|
+
*/
|
|
256
|
+
async compress(turns, engagementId = '', sourceSkill = '') {
|
|
257
|
+
// Step 1: Density gating
|
|
258
|
+
const filtered = turns.filter((t) => this.densityGate.shouldInclude(t));
|
|
259
|
+
if (filtered.length === 0) return [];
|
|
260
|
+
|
|
261
|
+
// Step 2: Window processing
|
|
262
|
+
const allEntries = [];
|
|
263
|
+
const step = this.windowSize - this.overlapSize;
|
|
264
|
+
for (let i = 0; i < filtered.length; i += step) {
|
|
265
|
+
const window = filtered.slice(i, i + this.windowSize);
|
|
266
|
+
const entries = await this._processWindow(window, engagementId, sourceSkill);
|
|
267
|
+
allEntries.push(...entries);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return allEntries;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Process a single window of dialogue turns.
|
|
275
|
+
* @private
|
|
276
|
+
*/
|
|
277
|
+
async _processWindow(window, engagementId, sourceSkill) {
|
|
278
|
+
const combinedText = window.map((t) => t.content).join(' ');
|
|
279
|
+
const entities = extractSecurityEntities(combinedText);
|
|
280
|
+
|
|
281
|
+
let entries;
|
|
282
|
+
if (this.llmClient !== null) {
|
|
283
|
+
entries = await this._llmCompress(window, entities);
|
|
284
|
+
} else {
|
|
285
|
+
entries = this._heuristicCompress(window, entities);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Annotate all entries with engagement context
|
|
289
|
+
for (const entry of entries) {
|
|
290
|
+
entry.engagementId = engagementId;
|
|
291
|
+
entry.sourceSkill = sourceSkill;
|
|
292
|
+
entry.targets = entities.ips.concat(entities.domains);
|
|
293
|
+
entry.cveIds = entities.cves;
|
|
294
|
+
entry.mitreAttack = entities.mitre;
|
|
295
|
+
entry.hashes = entities.hashes_sha256.concat(entities.hashes_md5);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
this._previousContext = this._buildContextSummary(entries);
|
|
299
|
+
return entries;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* LLM compression — stubbed for S03 integration.
|
|
304
|
+
* Falls back to heuristic compression.
|
|
305
|
+
* @private
|
|
306
|
+
*/
|
|
307
|
+
async _llmCompress(window, entities) {
|
|
308
|
+
// TODO: S03 LLM integration — call this.llmClient with extraction prompt
|
|
309
|
+
// For now, fall back to heuristic compression
|
|
310
|
+
return this._heuristicCompress(window, entities);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Fallback heuristic compression when no LLM is available.
|
|
315
|
+
* @private
|
|
316
|
+
*/
|
|
317
|
+
_heuristicCompress(window, entities) {
|
|
318
|
+
const entries = [];
|
|
319
|
+
|
|
320
|
+
for (const turn of window) {
|
|
321
|
+
const sentences = turn.content.split(/[.!?\n]+/);
|
|
322
|
+
for (let sent of sentences) {
|
|
323
|
+
sent = sent.trim();
|
|
324
|
+
if (sent.length < 20) continue;
|
|
325
|
+
|
|
326
|
+
const sentEntities = extractSecurityEntities(sent);
|
|
327
|
+
const hasSecurityContent = Object.values(sentEntities).some(
|
|
328
|
+
(arr) => arr.length > 0,
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
if (
|
|
332
|
+
hasSecurityContent ||
|
|
333
|
+
this.densityGate.score(new DialogueTurn({ turnId: 0, role: turn.role, content: sent })) > 0.5
|
|
334
|
+
) {
|
|
335
|
+
// Determine memory type from content
|
|
336
|
+
let memType = 'note';
|
|
337
|
+
if (sentEntities.cves.length > 0) {
|
|
338
|
+
memType = 'finding';
|
|
339
|
+
} else if (sentEntities.ips.length > 0 || sentEntities.domains.length > 0) {
|
|
340
|
+
memType = 'ioc';
|
|
341
|
+
} else if (sentEntities.mitre.length > 0) {
|
|
342
|
+
memType = 'ttp';
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Extract keywords (content words > 4 chars)
|
|
346
|
+
const words = sent.toLowerCase().split(/\s+/);
|
|
347
|
+
const keywords = words
|
|
348
|
+
.map((w) => w.replace(/[.,;:!?()[\]{}"']/g, ''))
|
|
349
|
+
.filter((w) => w.length > 4 && !_STOP_WORDS.has(w))
|
|
350
|
+
.slice(0, 10);
|
|
351
|
+
|
|
352
|
+
entries.push(
|
|
353
|
+
new CompressedEntry({
|
|
354
|
+
losslessRestatement: sent,
|
|
355
|
+
memoryType: memType,
|
|
356
|
+
keywords,
|
|
357
|
+
sourceTurns: [turn.turnId],
|
|
358
|
+
timestamp: turn.timestamp || new Date().toISOString(),
|
|
359
|
+
}),
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return entries;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Format a window of turns into human-readable text.
|
|
370
|
+
* @private
|
|
371
|
+
*/
|
|
372
|
+
_formatWindow(window) {
|
|
373
|
+
const lines = [];
|
|
374
|
+
for (const turn of window) {
|
|
375
|
+
const ts = turn.timestamp ? ` [${turn.timestamp}]` : '';
|
|
376
|
+
let prefix = turn.role.toUpperCase();
|
|
377
|
+
if (turn.toolName) prefix = `TOOL(${turn.toolName})`;
|
|
378
|
+
lines.push(`${prefix}${ts}: ${turn.content}`);
|
|
379
|
+
if (turn.toolOutput) {
|
|
380
|
+
lines.push(` OUTPUT: ${turn.toolOutput.slice(0, 500)}`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return lines.join('\n');
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Build extraction prompt for LLM compression.
|
|
388
|
+
* @private
|
|
389
|
+
*/
|
|
390
|
+
_buildExtractionPrompt(dialogueText, entities) {
|
|
391
|
+
let contextSection = '';
|
|
392
|
+
if (this._previousContext) {
|
|
393
|
+
contextSection = `## Previous Context (avoid duplication):\n${this._previousContext}\n\n---\n`;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
let entityHints = '';
|
|
397
|
+
if (Object.values(entities).some((arr) => arr.length > 0)) {
|
|
398
|
+
const parts = [];
|
|
399
|
+
if (entities.ips.length > 0) parts.push(`IPs: ${entities.ips.join(', ')}`);
|
|
400
|
+
if (entities.cves.length > 0) parts.push(`CVEs: ${entities.cves.join(', ')}`);
|
|
401
|
+
if (entities.mitre.length > 0) parts.push(`MITRE: ${entities.mitre.join(', ')}`);
|
|
402
|
+
if (entities.domains.length > 0) parts.push(`Domains: ${entities.domains.slice(0, 10).join(', ')}`);
|
|
403
|
+
entityHints = `\n## Detected Entities:\n${parts.join('\n')}\n`;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return `${contextSection}${entityHints}\n## Engagement Dialogue:\n${dialogueText}`;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Build summary of recently compressed entries for context tracking.
|
|
411
|
+
* @private
|
|
412
|
+
*/
|
|
413
|
+
_buildContextSummary(entries) {
|
|
414
|
+
if (entries.length === 0) return '';
|
|
415
|
+
return entries.slice(-5).map((e) => `- ${e.losslessRestatement}`).join('\n');
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export {
|
|
420
|
+
extractSecurityEntities,
|
|
421
|
+
DialogueTurn,
|
|
422
|
+
CompressedEntry,
|
|
423
|
+
DensityGate,
|
|
424
|
+
SemanticCompressor,
|
|
425
|
+
};
|