holomime 1.5.0 → 1.6.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 +82 -0
- package/dist/cli.js +550 -123
- package/dist/index.d.ts +388 -1
- package/dist/index.js +930 -109
- package/dist/mcp-server.js +261 -9
- package/dist/neuralspace/index.html +7 -0
- package/dist/neuralspace/neuralspace.js +64 -1
- package/dist/neuralspace/styles.css +45 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2503,26 +2503,85 @@ function loadCustomDetectors(dir) {
|
|
|
2503
2503
|
}
|
|
2504
2504
|
let files;
|
|
2505
2505
|
try {
|
|
2506
|
-
files = readdirSync(detectorsDir).filter((f) => f.endsWith(".json"));
|
|
2506
|
+
files = readdirSync(detectorsDir).filter((f) => f.endsWith(".json") || f.endsWith(".md"));
|
|
2507
2507
|
} catch {
|
|
2508
2508
|
return { detectors: [], errors: ["Could not read detectors directory"] };
|
|
2509
2509
|
}
|
|
2510
2510
|
for (const file of files) {
|
|
2511
2511
|
const filepath = join(detectorsDir, file);
|
|
2512
2512
|
try {
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2513
|
+
let config;
|
|
2514
|
+
if (file.endsWith(".md")) {
|
|
2515
|
+
const parsed = parseMarkdownDetector(readFileSync2(filepath, "utf-8"));
|
|
2516
|
+
if (!parsed) {
|
|
2517
|
+
errors.push(`${file}: could not parse Markdown detector (missing frontmatter or ## Patterns section)`);
|
|
2518
|
+
continue;
|
|
2519
|
+
}
|
|
2520
|
+
const validation = validateDetectorConfig(parsed);
|
|
2521
|
+
if (!validation.valid) {
|
|
2522
|
+
errors.push(`${file}: ${validation.errors.join(", ")}`);
|
|
2523
|
+
continue;
|
|
2524
|
+
}
|
|
2525
|
+
config = validation.config;
|
|
2526
|
+
} else {
|
|
2527
|
+
const raw = JSON.parse(readFileSync2(filepath, "utf-8"));
|
|
2528
|
+
const validation = validateDetectorConfig(raw);
|
|
2529
|
+
if (!validation.valid) {
|
|
2530
|
+
errors.push(`${file}: ${validation.errors.join(", ")}`);
|
|
2531
|
+
continue;
|
|
2532
|
+
}
|
|
2533
|
+
config = validation.config;
|
|
2518
2534
|
}
|
|
2519
|
-
detectors.push(compileCustomDetector(
|
|
2535
|
+
detectors.push(compileCustomDetector(config));
|
|
2520
2536
|
} catch (e) {
|
|
2521
2537
|
errors.push(`${file}: ${e instanceof Error ? e.message : "parse error"}`);
|
|
2522
2538
|
}
|
|
2523
2539
|
}
|
|
2524
2540
|
return { detectors, errors };
|
|
2525
2541
|
}
|
|
2542
|
+
function parseMarkdownDetector(markdown) {
|
|
2543
|
+
const frontmatterMatch = markdown.match(/^---\n([\s\S]*?)\n---/);
|
|
2544
|
+
if (!frontmatterMatch) return null;
|
|
2545
|
+
const frontmatter = frontmatterMatch[1];
|
|
2546
|
+
const meta = {};
|
|
2547
|
+
for (const line of frontmatter.split("\n")) {
|
|
2548
|
+
const colonIdx = line.indexOf(":");
|
|
2549
|
+
if (colonIdx === -1) continue;
|
|
2550
|
+
const key = line.slice(0, colonIdx).trim();
|
|
2551
|
+
let value = line.slice(colonIdx + 1).trim();
|
|
2552
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
2553
|
+
value = value.slice(1, -1);
|
|
2554
|
+
}
|
|
2555
|
+
meta[key] = value;
|
|
2556
|
+
}
|
|
2557
|
+
if (!meta.id || !meta.name) return null;
|
|
2558
|
+
const body = markdown.slice(frontmatterMatch[0].length);
|
|
2559
|
+
const patternsMatch = body.match(/##\s*Patterns\s*\n([\s\S]*?)(?=\n##|\n*$)/i);
|
|
2560
|
+
const patterns = [];
|
|
2561
|
+
if (patternsMatch) {
|
|
2562
|
+
const patternLines = patternsMatch[1].split("\n").filter((l) => l.trim().startsWith("-"));
|
|
2563
|
+
for (const line of patternLines) {
|
|
2564
|
+
const regexMatch = line.match(/`([^`]+)`/);
|
|
2565
|
+
const weightMatch = line.match(/weight\s*=\s*([\d.]+)/i);
|
|
2566
|
+
if (regexMatch) {
|
|
2567
|
+
patterns.push({
|
|
2568
|
+
regex: regexMatch[1],
|
|
2569
|
+
weight: weightMatch ? parseFloat(weightMatch[1]) : 1
|
|
2570
|
+
});
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
if (patterns.length === 0) return null;
|
|
2575
|
+
return {
|
|
2576
|
+
id: meta.id,
|
|
2577
|
+
name: meta.name,
|
|
2578
|
+
description: meta.description ?? meta.name,
|
|
2579
|
+
severity: meta.severity ?? "warning",
|
|
2580
|
+
patterns,
|
|
2581
|
+
threshold: meta.threshold ? parseInt(meta.threshold, 10) : 15,
|
|
2582
|
+
prescription: meta.prescription
|
|
2583
|
+
};
|
|
2584
|
+
}
|
|
2526
2585
|
|
|
2527
2586
|
// src/analysis/pre-session.ts
|
|
2528
2587
|
function runPreSessionDiagnosis(messages, spec) {
|
|
@@ -2691,13 +2750,20 @@ function updatePatternTracker(memory, patternId, severity, interventions) {
|
|
|
2691
2750
|
status: "active",
|
|
2692
2751
|
interventionsAttempted: [],
|
|
2693
2752
|
lastSeverity: severity,
|
|
2694
|
-
lastSeen: now
|
|
2753
|
+
lastSeen: now,
|
|
2754
|
+
confidence: 0,
|
|
2755
|
+
trending: "stable",
|
|
2756
|
+
severityHistory: []
|
|
2695
2757
|
};
|
|
2696
2758
|
memory.patterns.push(tracker);
|
|
2697
2759
|
}
|
|
2698
2760
|
tracker.sessionCount++;
|
|
2699
2761
|
tracker.lastSeverity = severity;
|
|
2700
2762
|
tracker.lastSeen = now;
|
|
2763
|
+
if (!tracker.severityHistory) tracker.severityHistory = [];
|
|
2764
|
+
tracker.severityHistory.push(severity);
|
|
2765
|
+
tracker.confidence = Math.min(1, 1 - Math.exp(-tracker.sessionCount / 3));
|
|
2766
|
+
tracker.trending = computeTrending(tracker.severityHistory.slice(-5));
|
|
2701
2767
|
for (const intervention of interventions) {
|
|
2702
2768
|
if (!tracker.interventionsAttempted.includes(intervention)) {
|
|
2703
2769
|
tracker.interventionsAttempted.push(intervention);
|
|
@@ -2711,6 +2777,19 @@ function updatePatternTracker(memory, patternId, severity, interventions) {
|
|
|
2711
2777
|
tracker.status = "improving";
|
|
2712
2778
|
}
|
|
2713
2779
|
}
|
|
2780
|
+
function computeTrending(history) {
|
|
2781
|
+
if (history.length < 2) return "stable";
|
|
2782
|
+
const toNum = (s) => s === "concern" ? 2 : s === "warning" ? 1 : 0;
|
|
2783
|
+
const mid = Math.floor(history.length / 2);
|
|
2784
|
+
const firstHalf = history.slice(0, mid);
|
|
2785
|
+
const secondHalf = history.slice(mid);
|
|
2786
|
+
const avgFirst = firstHalf.reduce((sum, s) => sum + toNum(s), 0) / firstHalf.length;
|
|
2787
|
+
const avgSecond = secondHalf.reduce((sum, s) => sum + toNum(s), 0) / secondHalf.length;
|
|
2788
|
+
const delta = avgSecond - avgFirst;
|
|
2789
|
+
if (delta < -0.3) return "improving";
|
|
2790
|
+
if (delta > 0.3) return "worsening";
|
|
2791
|
+
return "stable";
|
|
2792
|
+
}
|
|
2714
2793
|
function updateRollingContext(memory) {
|
|
2715
2794
|
memory.rollingContext.recentSummaries = memory.sessions.slice(-3);
|
|
2716
2795
|
const patternCounts = /* @__PURE__ */ new Map();
|
|
@@ -2763,7 +2842,9 @@ function getMemoryContext(memory) {
|
|
|
2763
2842
|
if (activePatterns.length > 0) {
|
|
2764
2843
|
lines.push("### Recurring Patterns");
|
|
2765
2844
|
for (const p of activePatterns) {
|
|
2766
|
-
|
|
2845
|
+
const conf = p.confidence !== void 0 ? ` confidence=${p.confidence.toFixed(2)}` : "";
|
|
2846
|
+
const trend = p.trending && p.trending !== "stable" ? ` [${p.trending}]` : "";
|
|
2847
|
+
lines.push(`- **${p.patternId}** (${p.status}, seen ${p.sessionCount}x${conf}${trend}, first: ${p.firstDetected.split("T")[0]})`);
|
|
2767
2848
|
if (p.interventionsAttempted.length > 0) {
|
|
2768
2849
|
lines.push(` Previously tried: ${p.interventionsAttempted.slice(-2).join("; ")}`);
|
|
2769
2850
|
}
|
|
@@ -2796,6 +2877,18 @@ function getMemoryContext(memory) {
|
|
|
2796
2877
|
}
|
|
2797
2878
|
return lines.join("\n");
|
|
2798
2879
|
}
|
|
2880
|
+
function decayUnseenPatterns(memory, seenPatternIds) {
|
|
2881
|
+
const seenSet = new Set(seenPatternIds);
|
|
2882
|
+
for (const tracker of memory.patterns) {
|
|
2883
|
+
if (!seenSet.has(tracker.patternId) && tracker.status !== "resolved") {
|
|
2884
|
+
tracker.confidence = Math.max(0, (tracker.confidence ?? 0) * 0.85);
|
|
2885
|
+
if (tracker.confidence < 0.05) {
|
|
2886
|
+
tracker.status = "resolved";
|
|
2887
|
+
tracker.confidence = 0;
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2799
2892
|
function agentHandleFromSpec(spec) {
|
|
2800
2893
|
const handle = spec.handle ?? spec.name ?? "unknown";
|
|
2801
2894
|
return handle.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
@@ -4034,6 +4127,38 @@ function queryCorpus(filters, corpusPath) {
|
|
|
4034
4127
|
}
|
|
4035
4128
|
return events;
|
|
4036
4129
|
}
|
|
4130
|
+
async function shareAnonymizedPatterns(report, apiKey, apiUrl = "https://holomime.dev") {
|
|
4131
|
+
const key = apiKey ?? process.env.HOLOMIME_API_KEY;
|
|
4132
|
+
if (!key) {
|
|
4133
|
+
return { success: false, error: "No API key" };
|
|
4134
|
+
}
|
|
4135
|
+
try {
|
|
4136
|
+
const response = await fetch(`${apiUrl}/api/v1/patterns/share`, {
|
|
4137
|
+
method: "POST",
|
|
4138
|
+
headers: {
|
|
4139
|
+
"Content-Type": "application/json",
|
|
4140
|
+
"Authorization": `Bearer ${key}`
|
|
4141
|
+
},
|
|
4142
|
+
body: JSON.stringify(report)
|
|
4143
|
+
});
|
|
4144
|
+
if (!response.ok) {
|
|
4145
|
+
return { success: false, error: `API error ${response.status}` };
|
|
4146
|
+
}
|
|
4147
|
+
return { success: true };
|
|
4148
|
+
} catch (err) {
|
|
4149
|
+
return { success: false, error: err instanceof Error ? err.message : "Network error" };
|
|
4150
|
+
}
|
|
4151
|
+
}
|
|
4152
|
+
function buildAnonymizedReport(patternIds, severities, messageCount, specHash) {
|
|
4153
|
+
return {
|
|
4154
|
+
patterns: patternIds,
|
|
4155
|
+
severities,
|
|
4156
|
+
messageCount,
|
|
4157
|
+
specHash,
|
|
4158
|
+
version: "1.5.1",
|
|
4159
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4160
|
+
};
|
|
4161
|
+
}
|
|
4037
4162
|
|
|
4038
4163
|
// src/analysis/diagnose-core.ts
|
|
4039
4164
|
function runDiagnosis(messages) {
|
|
@@ -4133,6 +4258,169 @@ function runAssessment(messages, spec) {
|
|
|
4133
4258
|
// src/analysis/session-runner.ts
|
|
4134
4259
|
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync6 } from "fs";
|
|
4135
4260
|
import { resolve as resolve6, join as join6 } from "path";
|
|
4261
|
+
|
|
4262
|
+
// src/session/context-layers.ts
|
|
4263
|
+
function getPhaseContext(phase, input) {
|
|
4264
|
+
switch (phase) {
|
|
4265
|
+
case "rapport":
|
|
4266
|
+
return buildRapportContext(input);
|
|
4267
|
+
case "presenting_problem":
|
|
4268
|
+
return buildPresentingProblemContext(input);
|
|
4269
|
+
case "exploration":
|
|
4270
|
+
return buildExplorationContext(input);
|
|
4271
|
+
case "pattern_recognition":
|
|
4272
|
+
return buildPatternRecognitionContext(input);
|
|
4273
|
+
case "challenge":
|
|
4274
|
+
return buildChallengeContext(input);
|
|
4275
|
+
case "skill_building":
|
|
4276
|
+
return buildSkillBuildingContext(input);
|
|
4277
|
+
case "integration":
|
|
4278
|
+
return buildIntegrationContext(input);
|
|
4279
|
+
default:
|
|
4280
|
+
return null;
|
|
4281
|
+
}
|
|
4282
|
+
}
|
|
4283
|
+
function buildRapportContext(input) {
|
|
4284
|
+
const { spec } = input;
|
|
4285
|
+
const lines = [
|
|
4286
|
+
"[Phase Context: Rapport]",
|
|
4287
|
+
`Agent: ${spec.name ?? "Unknown"} \u2014 ${spec.purpose ?? "General AI agent"}`
|
|
4288
|
+
];
|
|
4289
|
+
if (spec.communication) {
|
|
4290
|
+
lines.push(`Communication style: ${spec.communication.register ?? "adaptive"}, ${spec.communication.conflict_approach ?? "direct_but_kind"}`);
|
|
4291
|
+
}
|
|
4292
|
+
if (spec.big_five) {
|
|
4293
|
+
const traits = Object.entries(spec.big_five).map(([dim, val]) => `${dim}: ${val?.score ?? "?"}`).join(", ");
|
|
4294
|
+
lines.push(`Personality: ${traits}`);
|
|
4295
|
+
}
|
|
4296
|
+
return lines.join("\n");
|
|
4297
|
+
}
|
|
4298
|
+
function buildPresentingProblemContext(input) {
|
|
4299
|
+
const { diagnosis } = input;
|
|
4300
|
+
const patterns = diagnosis.patterns.filter((p) => p.severity !== "info");
|
|
4301
|
+
if (patterns.length === 0) return "[Phase Context: No concerning patterns detected]";
|
|
4302
|
+
const lines = [
|
|
4303
|
+
"[Phase Context: Presenting Problem]",
|
|
4304
|
+
`Session severity: ${diagnosis.severity.toUpperCase()}`,
|
|
4305
|
+
`Focus: ${diagnosis.sessionFocus.join(", ")}`,
|
|
4306
|
+
"Detected patterns:",
|
|
4307
|
+
...patterns.map((p) => `- ${p.name} (${p.severity})`)
|
|
4308
|
+
];
|
|
4309
|
+
if (diagnosis.openingAngle) {
|
|
4310
|
+
lines.push(`Opening angle: ${diagnosis.openingAngle}`);
|
|
4311
|
+
}
|
|
4312
|
+
return lines.join("\n");
|
|
4313
|
+
}
|
|
4314
|
+
function buildExplorationContext(input) {
|
|
4315
|
+
const { diagnosis } = input;
|
|
4316
|
+
const patterns = diagnosis.patterns.filter((p) => p.severity !== "info");
|
|
4317
|
+
const lines = [
|
|
4318
|
+
"[Phase Context: Deep Exploration]",
|
|
4319
|
+
`Emotional themes: ${diagnosis.emotionalThemes.join(", ")}`
|
|
4320
|
+
];
|
|
4321
|
+
for (const p of patterns) {
|
|
4322
|
+
lines.push(`
|
|
4323
|
+
### ${p.name} (${p.severity})`);
|
|
4324
|
+
lines.push(p.description);
|
|
4325
|
+
if (p.examples.length > 0) {
|
|
4326
|
+
lines.push("Examples from conversation:");
|
|
4327
|
+
for (const ex of p.examples.slice(0, 2)) {
|
|
4328
|
+
lines.push(` > "${ex.slice(0, 120)}..."`);
|
|
4329
|
+
}
|
|
4330
|
+
}
|
|
4331
|
+
if (p.prescription) {
|
|
4332
|
+
lines.push(`Prescription: ${p.prescription}`);
|
|
4333
|
+
}
|
|
4334
|
+
}
|
|
4335
|
+
return lines.join("\n");
|
|
4336
|
+
}
|
|
4337
|
+
function buildPatternRecognitionContext(input) {
|
|
4338
|
+
const { memory } = input;
|
|
4339
|
+
const lines = ["[Phase Context: Pattern Recognition]"];
|
|
4340
|
+
if (memory && memory.totalSessions > 0) {
|
|
4341
|
+
lines.push(`Previous sessions: ${memory.totalSessions}`);
|
|
4342
|
+
const activePatterns = memory.patterns.filter((p) => p.status !== "resolved");
|
|
4343
|
+
if (activePatterns.length > 0) {
|
|
4344
|
+
lines.push("Historical pattern data:");
|
|
4345
|
+
for (const p of activePatterns) {
|
|
4346
|
+
const conf = p.confidence !== void 0 ? ` (confidence: ${p.confidence.toFixed(2)})` : "";
|
|
4347
|
+
const trend = p.trending && p.trending !== "stable" ? ` [${p.trending}]` : "";
|
|
4348
|
+
lines.push(`- ${p.patternId}: seen ${p.sessionCount}x, status=${p.status}${conf}${trend}`);
|
|
4349
|
+
}
|
|
4350
|
+
}
|
|
4351
|
+
const resolved = memory.patterns.filter((p) => p.status === "resolved");
|
|
4352
|
+
if (resolved.length > 0) {
|
|
4353
|
+
lines.push(`Previously resolved: ${resolved.map((p) => p.patternId).join(", ")}`);
|
|
4354
|
+
}
|
|
4355
|
+
if (memory.rollingContext.persistentThemes.length > 0) {
|
|
4356
|
+
lines.push(`Persistent themes: ${memory.rollingContext.persistentThemes.join(", ")}`);
|
|
4357
|
+
}
|
|
4358
|
+
} else {
|
|
4359
|
+
lines.push("No prior session history \u2014 this is the first session.");
|
|
4360
|
+
}
|
|
4361
|
+
return lines.join("\n");
|
|
4362
|
+
}
|
|
4363
|
+
function buildChallengeContext(input) {
|
|
4364
|
+
const { memory } = input;
|
|
4365
|
+
const lines = ["[Phase Context: Challenge & Reframe]"];
|
|
4366
|
+
if (memory && memory.totalSessions > 0) {
|
|
4367
|
+
const allInterventions = /* @__PURE__ */ new Set();
|
|
4368
|
+
for (const p of memory.patterns) {
|
|
4369
|
+
for (const i of p.interventionsAttempted) {
|
|
4370
|
+
allInterventions.add(i);
|
|
4371
|
+
}
|
|
4372
|
+
}
|
|
4373
|
+
if (allInterventions.size > 0) {
|
|
4374
|
+
lines.push(`Previously attempted interventions: ${[...allInterventions].join("; ")}`);
|
|
4375
|
+
}
|
|
4376
|
+
const recent = memory.rollingContext.recentSummaries.slice(-2);
|
|
4377
|
+
if (recent.length > 0) {
|
|
4378
|
+
lines.push("Recent session insights:");
|
|
4379
|
+
for (const s of recent) {
|
|
4380
|
+
lines.push(` - ${s.keyInsight}`);
|
|
4381
|
+
}
|
|
4382
|
+
}
|
|
4383
|
+
}
|
|
4384
|
+
if (input.interview) {
|
|
4385
|
+
if (input.interview.blindSpots.length > 0) {
|
|
4386
|
+
lines.push(`Blind spots from interview: ${input.interview.blindSpots.join(", ")}`);
|
|
4387
|
+
}
|
|
4388
|
+
}
|
|
4389
|
+
return lines.join("\n");
|
|
4390
|
+
}
|
|
4391
|
+
function buildSkillBuildingContext(input) {
|
|
4392
|
+
const { diagnosis } = input;
|
|
4393
|
+
const lines = ["[Phase Context: Skill Building]"];
|
|
4394
|
+
const patternIds = diagnosis.patterns.map((p) => p.id);
|
|
4395
|
+
if (patternIds.includes("over-apologizing")) {
|
|
4396
|
+
lines.push("- Skill for over-apologizing: practice stating corrections with 'confident_transparency' \u2014 acknowledge uncertainty without apologizing for it");
|
|
4397
|
+
}
|
|
4398
|
+
if (patternIds.includes("hedge-stacking")) {
|
|
4399
|
+
lines.push("- Skill for hedge-stacking: one qualifier per recommendation is enough. Lead with the recommendation, then caveat once.");
|
|
4400
|
+
}
|
|
4401
|
+
if (patternIds.includes("sycophantic-tendency") || patternIds.includes("sentiment-skew")) {
|
|
4402
|
+
lines.push("- Skill for sycophancy: practice respectful disagreement. 'I see it differently...' is more helpful than 'Great question!'");
|
|
4403
|
+
}
|
|
4404
|
+
if (patternIds.includes("error-spiral")) {
|
|
4405
|
+
lines.push("- Skill for error spirals: the 'acknowledge \u2192 diagnose \u2192 fix' pattern. Treat mistakes as data, not failure.");
|
|
4406
|
+
}
|
|
4407
|
+
return lines.join("\n");
|
|
4408
|
+
}
|
|
4409
|
+
function buildIntegrationContext(input) {
|
|
4410
|
+
const { spec, diagnosis } = input;
|
|
4411
|
+
const lines = ["[Phase Context: Integration & Closing]"];
|
|
4412
|
+
lines.push("Summarize the session and recommend specific .personality.json changes.");
|
|
4413
|
+
if (spec.growth?.areas?.length > 0) {
|
|
4414
|
+
const areas = spec.growth.areas.map((a) => typeof a === "string" ? a : a.area);
|
|
4415
|
+
lines.push(`Current growth areas: ${areas.join(", ")}`);
|
|
4416
|
+
}
|
|
4417
|
+
if (diagnosis.patterns.filter((p) => p.severity !== "info").length > 0) {
|
|
4418
|
+
lines.push("Recommend changes to: therapy_dimensions, communication style, or growth.patterns_to_watch");
|
|
4419
|
+
}
|
|
4420
|
+
return lines.join("\n");
|
|
4421
|
+
}
|
|
4422
|
+
|
|
4423
|
+
// src/analysis/session-runner.ts
|
|
4136
4424
|
async function runTherapySession(spec, diagnosis, provider, maxTurns, options) {
|
|
4137
4425
|
const promptOptions = {
|
|
4138
4426
|
memory: options?.memory,
|
|
@@ -4178,6 +4466,16 @@ async function runTherapySession(spec, diagnosis, provider, maxTurns, options) {
|
|
|
4178
4466
|
const phaseConfig = THERAPY_PHASES[currentPhase];
|
|
4179
4467
|
if (turnsInPhase === 0) {
|
|
4180
4468
|
cb?.onPhaseTransition?.(phaseConfig.name);
|
|
4469
|
+
const phaseCtx = getPhaseContext(currentPhase, {
|
|
4470
|
+
spec,
|
|
4471
|
+
diagnosis,
|
|
4472
|
+
memory: options?.memory,
|
|
4473
|
+
interview: options?.interview
|
|
4474
|
+
});
|
|
4475
|
+
if (phaseCtx) {
|
|
4476
|
+
therapistHistory.push({ role: "user", content: phaseCtx });
|
|
4477
|
+
therapistHistory.push({ role: "assistant", content: "Understood. I'll incorporate this context." });
|
|
4478
|
+
}
|
|
4181
4479
|
}
|
|
4182
4480
|
const phaseDirective = totalTurns === 0 ? `Begin with your opening. You are in the "${phaseConfig.name}" phase.` : `You are in the "${phaseConfig.name}" phase (turn ${turnsInPhase + 1}). Goals: ${phaseConfig.therapistGoals[0]}. ${turnsInPhase >= phaseConfig.minTurns ? "You may transition to the next phase when ready." : "Stay in this phase."}`;
|
|
4183
4481
|
therapistHistory.push({ role: "user", content: `[Phase: ${phaseConfig.name}] ${phaseDirective}` });
|
|
@@ -5361,7 +5659,32 @@ async function runEvolve(spec, messages, provider, options) {
|
|
|
5361
5659
|
}
|
|
5362
5660
|
}
|
|
5363
5661
|
if (options?.specPath) {
|
|
5364
|
-
|
|
5662
|
+
const useStaging = options?.useStaging !== false;
|
|
5663
|
+
if (useStaging) {
|
|
5664
|
+
const stagingPath = options.specPath.replace(/\.json$/, ".staging.json");
|
|
5665
|
+
writeFileSync8(stagingPath, JSON.stringify(currentSpec, null, 2) + "\n");
|
|
5666
|
+
const allChanges = iterations.flatMap((i) => i.appliedChanges);
|
|
5667
|
+
const diff = {
|
|
5668
|
+
stagingPath,
|
|
5669
|
+
changes: allChanges,
|
|
5670
|
+
before: spec,
|
|
5671
|
+
after: currentSpec
|
|
5672
|
+
};
|
|
5673
|
+
let approved = options?.autoApprove ?? false;
|
|
5674
|
+
if (!approved && options?.onStagingReview) {
|
|
5675
|
+
approved = await options.onStagingReview(diff);
|
|
5676
|
+
}
|
|
5677
|
+
if (approved) {
|
|
5678
|
+
writeFileSync8(options.specPath, JSON.stringify(currentSpec, null, 2) + "\n");
|
|
5679
|
+
try {
|
|
5680
|
+
const { unlinkSync } = await import("fs");
|
|
5681
|
+
unlinkSync(stagingPath);
|
|
5682
|
+
} catch {
|
|
5683
|
+
}
|
|
5684
|
+
}
|
|
5685
|
+
} else {
|
|
5686
|
+
writeFileSync8(options.specPath, JSON.stringify(currentSpec, null, 2) + "\n");
|
|
5687
|
+
}
|
|
5365
5688
|
}
|
|
5366
5689
|
let trainingExport;
|
|
5367
5690
|
if (allDPOPairs.length > 0) {
|
|
@@ -5852,6 +6175,53 @@ function generateBenchmarkMarkdown(benchmarks) {
|
|
|
5852
6175
|
lines.push("");
|
|
5853
6176
|
return lines.join("\n");
|
|
5854
6177
|
}
|
|
6178
|
+
async function publishToLeaderboard(benchmark, apiKey, apiUrl = "https://holomime.dev") {
|
|
6179
|
+
const key = apiKey ?? process.env.HOLOMIME_API_KEY;
|
|
6180
|
+
if (!key) {
|
|
6181
|
+
return { success: false, error: "No API key. Run `holomime activate` or set HOLOMIME_API_KEY." };
|
|
6182
|
+
}
|
|
6183
|
+
const submission = {
|
|
6184
|
+
agent: benchmark.agent,
|
|
6185
|
+
provider: benchmark.provider,
|
|
6186
|
+
model: benchmark.model,
|
|
6187
|
+
score: benchmark.score,
|
|
6188
|
+
grade: benchmark.grade,
|
|
6189
|
+
scenarioResults: benchmark.results.map((r) => ({
|
|
6190
|
+
scenarioId: r.scenarioId,
|
|
6191
|
+
passed: r.passed
|
|
6192
|
+
})),
|
|
6193
|
+
holomimeVersion: benchmark.metadata.holomimeVersion,
|
|
6194
|
+
timestamp: benchmark.timestamp
|
|
6195
|
+
};
|
|
6196
|
+
try {
|
|
6197
|
+
const response = await fetch(`${apiUrl}/api/v1/leaderboard/submit`, {
|
|
6198
|
+
method: "POST",
|
|
6199
|
+
headers: {
|
|
6200
|
+
"Content-Type": "application/json",
|
|
6201
|
+
"Authorization": `Bearer ${key}`
|
|
6202
|
+
},
|
|
6203
|
+
body: JSON.stringify(submission)
|
|
6204
|
+
});
|
|
6205
|
+
if (!response.ok) {
|
|
6206
|
+
const text = await response.text();
|
|
6207
|
+
return { success: false, error: `API error ${response.status}: ${text}` };
|
|
6208
|
+
}
|
|
6209
|
+
const data = await response.json();
|
|
6210
|
+
return { success: true, rank: data.rank };
|
|
6211
|
+
} catch (err) {
|
|
6212
|
+
return { success: false, error: err instanceof Error ? err.message : "Network error" };
|
|
6213
|
+
}
|
|
6214
|
+
}
|
|
6215
|
+
async function fetchLeaderboard(limit = 50, apiUrl = "https://holomime.dev") {
|
|
6216
|
+
try {
|
|
6217
|
+
const response = await fetch(`${apiUrl}/api/v1/leaderboard?limit=${limit}`);
|
|
6218
|
+
if (!response.ok) return [];
|
|
6219
|
+
const data = await response.json();
|
|
6220
|
+
return data.entries ?? [];
|
|
6221
|
+
} catch {
|
|
6222
|
+
return [];
|
|
6223
|
+
}
|
|
6224
|
+
}
|
|
5855
6225
|
function generateComparisonMarkdown(comparison) {
|
|
5856
6226
|
const lines = [
|
|
5857
6227
|
"## Benchmark Comparison",
|
|
@@ -6455,106 +6825,31 @@ function startFleet(config, options) {
|
|
|
6455
6825
|
errors: 0
|
|
6456
6826
|
});
|
|
6457
6827
|
}
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
|
|
6481
|
-
const status = statusMap.get(agent.name);
|
|
6482
|
-
status.filesProcessed++;
|
|
6483
|
-
const event = {
|
|
6484
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6485
|
-
type: "new_file",
|
|
6486
|
-
filename,
|
|
6487
|
-
agentName: agent.name
|
|
6488
|
-
};
|
|
6489
|
-
allEvents.push(event);
|
|
6490
|
-
options.callbacks?.onAgentEvent?.(agent.name, event);
|
|
6491
|
-
},
|
|
6492
|
-
onDriftDetected: (filename, severity, patterns) => {
|
|
6493
|
-
const status = statusMap.get(agent.name);
|
|
6494
|
-
status.driftEvents++;
|
|
6495
|
-
status.lastDriftSeverity = severity;
|
|
6496
|
-
const event = {
|
|
6497
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6498
|
-
type: "drift_detected",
|
|
6499
|
-
filename,
|
|
6500
|
-
agentName: agent.name,
|
|
6501
|
-
details: { severity, patterns }
|
|
6502
|
-
};
|
|
6503
|
-
allEvents.push(event);
|
|
6504
|
-
options.callbacks?.onAgentEvent?.(agent.name, event);
|
|
6505
|
-
},
|
|
6506
|
-
onEvolveTriggered: (filename) => {
|
|
6507
|
-
const event = {
|
|
6508
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6509
|
-
type: "evolve_triggered",
|
|
6510
|
-
filename,
|
|
6511
|
-
agentName: agent.name
|
|
6512
|
-
};
|
|
6513
|
-
allEvents.push(event);
|
|
6514
|
-
options.callbacks?.onAgentEvent?.(agent.name, event);
|
|
6515
|
-
},
|
|
6516
|
-
onEvolveComplete: (filename, result) => {
|
|
6517
|
-
const status = statusMap.get(agent.name);
|
|
6518
|
-
status.evolveCount++;
|
|
6519
|
-
const event = {
|
|
6520
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6521
|
-
type: "evolve_complete",
|
|
6522
|
-
filename,
|
|
6523
|
-
agentName: agent.name,
|
|
6524
|
-
details: {
|
|
6525
|
-
converged: result.converged,
|
|
6526
|
-
iterations: result.totalIterations,
|
|
6527
|
-
dpoPairs: result.totalDPOPairs
|
|
6528
|
-
}
|
|
6529
|
-
};
|
|
6530
|
-
allEvents.push(event);
|
|
6531
|
-
options.callbacks?.onAgentEvent?.(agent.name, event);
|
|
6532
|
-
},
|
|
6533
|
-
onError: (filename, error) => {
|
|
6534
|
-
const agentStatus = statusMap.get(agent.name);
|
|
6535
|
-
agentStatus.errors++;
|
|
6536
|
-
const event = {
|
|
6537
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6538
|
-
type: "error",
|
|
6539
|
-
filename,
|
|
6540
|
-
agentName: agent.name,
|
|
6541
|
-
details: error
|
|
6542
|
-
};
|
|
6543
|
-
allEvents.push(event);
|
|
6544
|
-
options.callbacks?.onError?.(agent.name, error);
|
|
6828
|
+
const concurrency = options.concurrency ?? 5;
|
|
6829
|
+
const agentQueue = [...config.agents];
|
|
6830
|
+
agentQueue.sort((a, b) => {
|
|
6831
|
+
const aDrift = existsSync11(join12(a.logDir, ".holomime", "watch-log.json")) ? 0 : 1;
|
|
6832
|
+
const bDrift = existsSync11(join12(b.logDir, ".holomime", "watch-log.json")) ? 0 : 1;
|
|
6833
|
+
return aDrift - bDrift;
|
|
6834
|
+
});
|
|
6835
|
+
const agentsToStart = agentQueue.slice(0, concurrency);
|
|
6836
|
+
const waitingAgents = agentQueue.slice(concurrency);
|
|
6837
|
+
function startAgent(agent) {
|
|
6838
|
+
startSingleAgent(agent, options, statusMap, allEvents, handles);
|
|
6839
|
+
}
|
|
6840
|
+
for (const agent of agentsToStart) {
|
|
6841
|
+
startAgent(agent);
|
|
6842
|
+
}
|
|
6843
|
+
if (waitingAgents.length > 0) {
|
|
6844
|
+
const originalOnError = options.callbacks?.onError;
|
|
6845
|
+
options.callbacks = {
|
|
6846
|
+
...options.callbacks,
|
|
6847
|
+
onError: (agentName, error) => {
|
|
6848
|
+
originalOnError?.(agentName, error);
|
|
6849
|
+
const next = waitingAgents.shift();
|
|
6850
|
+
if (next) startAgent(next);
|
|
6545
6851
|
}
|
|
6546
6852
|
};
|
|
6547
|
-
const handle = startWatch(spec, {
|
|
6548
|
-
watchDir: agent.logDir,
|
|
6549
|
-
specPath: agent.specPath,
|
|
6550
|
-
provider: options.provider,
|
|
6551
|
-
checkInterval: options.checkInterval,
|
|
6552
|
-
threshold: options.threshold,
|
|
6553
|
-
autoEvolve: options.autoEvolve,
|
|
6554
|
-
maxEvolveIterations: options.maxEvolveIterations,
|
|
6555
|
-
callbacks: agentCallbacks
|
|
6556
|
-
});
|
|
6557
|
-
handles.push({ name: agent.name, handle });
|
|
6558
6853
|
}
|
|
6559
6854
|
function stop() {
|
|
6560
6855
|
for (const { handle } of handles) {
|
|
@@ -6566,6 +6861,107 @@ function startFleet(config, options) {
|
|
|
6566
6861
|
}
|
|
6567
6862
|
return { stop, getStatus, events: allEvents };
|
|
6568
6863
|
}
|
|
6864
|
+
function startSingleAgent(agent, options, statusMap, allEvents, handles) {
|
|
6865
|
+
let spec;
|
|
6866
|
+
try {
|
|
6867
|
+
spec = loadSpec(agent.specPath);
|
|
6868
|
+
} catch (err) {
|
|
6869
|
+
const errMsg = err instanceof Error ? err.message : "Failed to load spec";
|
|
6870
|
+
options.callbacks?.onError?.(agent.name, errMsg);
|
|
6871
|
+
return;
|
|
6872
|
+
}
|
|
6873
|
+
const agentCallbacks = {
|
|
6874
|
+
onScan: (fileCount) => {
|
|
6875
|
+
const status = statusMap.get(agent.name);
|
|
6876
|
+
status.lastScanAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6877
|
+
const event = {
|
|
6878
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6879
|
+
type: "scan",
|
|
6880
|
+
agentName: agent.name,
|
|
6881
|
+
details: { fileCount }
|
|
6882
|
+
};
|
|
6883
|
+
allEvents.push(event);
|
|
6884
|
+
options.callbacks?.onAgentEvent?.(agent.name, event);
|
|
6885
|
+
},
|
|
6886
|
+
onNewFile: (filename) => {
|
|
6887
|
+
const status = statusMap.get(agent.name);
|
|
6888
|
+
status.filesProcessed++;
|
|
6889
|
+
const event = {
|
|
6890
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6891
|
+
type: "new_file",
|
|
6892
|
+
filename,
|
|
6893
|
+
agentName: agent.name
|
|
6894
|
+
};
|
|
6895
|
+
allEvents.push(event);
|
|
6896
|
+
options.callbacks?.onAgentEvent?.(agent.name, event);
|
|
6897
|
+
},
|
|
6898
|
+
onDriftDetected: (filename, severity, patterns) => {
|
|
6899
|
+
const status = statusMap.get(agent.name);
|
|
6900
|
+
status.driftEvents++;
|
|
6901
|
+
status.lastDriftSeverity = severity;
|
|
6902
|
+
const event = {
|
|
6903
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6904
|
+
type: "drift_detected",
|
|
6905
|
+
filename,
|
|
6906
|
+
agentName: agent.name,
|
|
6907
|
+
details: { severity, patterns }
|
|
6908
|
+
};
|
|
6909
|
+
allEvents.push(event);
|
|
6910
|
+
options.callbacks?.onAgentEvent?.(agent.name, event);
|
|
6911
|
+
},
|
|
6912
|
+
onEvolveTriggered: (filename) => {
|
|
6913
|
+
const event = {
|
|
6914
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6915
|
+
type: "evolve_triggered",
|
|
6916
|
+
filename,
|
|
6917
|
+
agentName: agent.name
|
|
6918
|
+
};
|
|
6919
|
+
allEvents.push(event);
|
|
6920
|
+
options.callbacks?.onAgentEvent?.(agent.name, event);
|
|
6921
|
+
},
|
|
6922
|
+
onEvolveComplete: (filename, result) => {
|
|
6923
|
+
const status = statusMap.get(agent.name);
|
|
6924
|
+
status.evolveCount++;
|
|
6925
|
+
const event = {
|
|
6926
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6927
|
+
type: "evolve_complete",
|
|
6928
|
+
filename,
|
|
6929
|
+
agentName: agent.name,
|
|
6930
|
+
details: {
|
|
6931
|
+
converged: result.converged,
|
|
6932
|
+
iterations: result.totalIterations,
|
|
6933
|
+
dpoPairs: result.totalDPOPairs
|
|
6934
|
+
}
|
|
6935
|
+
};
|
|
6936
|
+
allEvents.push(event);
|
|
6937
|
+
options.callbacks?.onAgentEvent?.(agent.name, event);
|
|
6938
|
+
},
|
|
6939
|
+
onError: (filename, error) => {
|
|
6940
|
+
const agentStatus = statusMap.get(agent.name);
|
|
6941
|
+
agentStatus.errors++;
|
|
6942
|
+
const event = {
|
|
6943
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6944
|
+
type: "error",
|
|
6945
|
+
filename,
|
|
6946
|
+
agentName: agent.name,
|
|
6947
|
+
details: error
|
|
6948
|
+
};
|
|
6949
|
+
allEvents.push(event);
|
|
6950
|
+
options.callbacks?.onError?.(agent.name, error);
|
|
6951
|
+
}
|
|
6952
|
+
};
|
|
6953
|
+
const handle = startWatch(spec, {
|
|
6954
|
+
watchDir: agent.logDir,
|
|
6955
|
+
specPath: agent.specPath,
|
|
6956
|
+
provider: options.provider,
|
|
6957
|
+
checkInterval: options.checkInterval,
|
|
6958
|
+
threshold: options.threshold,
|
|
6959
|
+
autoEvolve: options.autoEvolve,
|
|
6960
|
+
maxEvolveIterations: options.maxEvolveIterations,
|
|
6961
|
+
callbacks: agentCallbacks
|
|
6962
|
+
});
|
|
6963
|
+
handles.push({ name: agent.name, handle });
|
|
6964
|
+
}
|
|
6569
6965
|
|
|
6570
6966
|
// src/analysis/certify-core.ts
|
|
6571
6967
|
import { writeFileSync as writeFileSync11, mkdirSync as mkdirSync10, existsSync as existsSync12 } from "fs";
|
|
@@ -6799,7 +7195,7 @@ function parseRetryAfter(response) {
|
|
|
6799
7195
|
return 0;
|
|
6800
7196
|
}
|
|
6801
7197
|
function delay(ms) {
|
|
6802
|
-
return new Promise((
|
|
7198
|
+
return new Promise((resolve16) => setTimeout(resolve16, ms));
|
|
6803
7199
|
}
|
|
6804
7200
|
var OpenAIProvider = class {
|
|
6805
7201
|
name = "openai";
|
|
@@ -7666,6 +8062,242 @@ function resolveSpec(personality, name) {
|
|
|
7666
8062
|
return personality;
|
|
7667
8063
|
}
|
|
7668
8064
|
|
|
8065
|
+
// src/guard/middleware.ts
|
|
8066
|
+
function extractOpenAIResponse(response) {
|
|
8067
|
+
if (response?.choices?.[0]?.message?.content) {
|
|
8068
|
+
return response.choices[0].message.content;
|
|
8069
|
+
}
|
|
8070
|
+
if (response?.content?.[0]?.text) {
|
|
8071
|
+
return response.content[0].text;
|
|
8072
|
+
}
|
|
8073
|
+
if (typeof response === "string") {
|
|
8074
|
+
return response;
|
|
8075
|
+
}
|
|
8076
|
+
throw new Error(
|
|
8077
|
+
"Could not extract response text. Provide a custom extractResponse function."
|
|
8078
|
+
);
|
|
8079
|
+
}
|
|
8080
|
+
function injectResponseText(response, text) {
|
|
8081
|
+
if (response?.choices?.[0]?.message?.content !== void 0) {
|
|
8082
|
+
const cloned = JSON.parse(JSON.stringify(response));
|
|
8083
|
+
cloned.choices[0].message.content = text;
|
|
8084
|
+
return cloned;
|
|
8085
|
+
}
|
|
8086
|
+
if (response?.content?.[0]?.text !== void 0) {
|
|
8087
|
+
const cloned = JSON.parse(JSON.stringify(response));
|
|
8088
|
+
cloned.content[0].text = text;
|
|
8089
|
+
return cloned;
|
|
8090
|
+
}
|
|
8091
|
+
if (typeof response === "string") {
|
|
8092
|
+
return text;
|
|
8093
|
+
}
|
|
8094
|
+
return response;
|
|
8095
|
+
}
|
|
8096
|
+
function applyCorrections(text, patterns, _spec) {
|
|
8097
|
+
let corrected = text;
|
|
8098
|
+
for (const pattern of patterns) {
|
|
8099
|
+
switch (pattern.id) {
|
|
8100
|
+
case "over-apologizing": {
|
|
8101
|
+
corrected = corrected.replace(/\bI'm (?:so |very |truly |really )?sorry(?:,| but| that| for| about| if)\b/gi, "").replace(/\bI apologize (?:for|that|if)\b/gi, "").replace(/\bMy apologies(?:,| \.)\b/gi, "").replace(/^\s*[,.]?\s*/gm, (match) => match.trim() ? match : "").replace(/\n{3,}/g, "\n\n").trim();
|
|
8102
|
+
break;
|
|
8103
|
+
}
|
|
8104
|
+
case "hedge-stacking": {
|
|
8105
|
+
const hedgePatterns = [
|
|
8106
|
+
/\b(?:I think |I believe |I feel like |In my opinion, |It seems (?:like |to me )?(?:that )?)/gi,
|
|
8107
|
+
/\b(?:perhaps |maybe |possibly |arguably |potentially )/gi,
|
|
8108
|
+
/\b(?:sort of |kind of |more or less |to some extent )/gi
|
|
8109
|
+
];
|
|
8110
|
+
const sentences = corrected.split(/(?<=[.!?])\s+/);
|
|
8111
|
+
corrected = sentences.map((sentence) => {
|
|
8112
|
+
let hedgeCount = 0;
|
|
8113
|
+
let result = sentence;
|
|
8114
|
+
for (const hp of hedgePatterns) {
|
|
8115
|
+
result = result.replace(hp, (match) => {
|
|
8116
|
+
hedgeCount++;
|
|
8117
|
+
return hedgeCount > 1 ? "" : match;
|
|
8118
|
+
});
|
|
8119
|
+
}
|
|
8120
|
+
return result;
|
|
8121
|
+
}).join(" ").replace(/\s{2,}/g, " ").trim();
|
|
8122
|
+
break;
|
|
8123
|
+
}
|
|
8124
|
+
case "sycophancy":
|
|
8125
|
+
case "sentiment-skew": {
|
|
8126
|
+
corrected = corrected.replace(/\b(?:Absolutely|Exactly|You're absolutely right|That's a great (?:question|point|idea|observation))(?:!|\.)\s*/gi, "").replace(/^\s*[,.]?\s*/gm, (match) => match.trim() ? match : "").trim();
|
|
8127
|
+
break;
|
|
8128
|
+
}
|
|
8129
|
+
}
|
|
8130
|
+
}
|
|
8131
|
+
if (corrected.trim().length < 10) {
|
|
8132
|
+
return text;
|
|
8133
|
+
}
|
|
8134
|
+
return corrected;
|
|
8135
|
+
}
|
|
8136
|
+
function createGuardMiddleware(options = {}) {
|
|
8137
|
+
const mode = options.mode ?? "enforce";
|
|
8138
|
+
const minSeverity = options.minSeverity ?? "warning";
|
|
8139
|
+
const name = options.name ?? "Agent";
|
|
8140
|
+
const maxCorrections = options.maxCorrections ?? 1;
|
|
8141
|
+
let spec;
|
|
8142
|
+
if (options.personality) {
|
|
8143
|
+
if (typeof options.personality === "string") {
|
|
8144
|
+
spec = loadSpec(options.personality);
|
|
8145
|
+
} else {
|
|
8146
|
+
spec = options.personality;
|
|
8147
|
+
}
|
|
8148
|
+
}
|
|
8149
|
+
const guardChain = options.guard ?? Guard.create(name).useAll();
|
|
8150
|
+
const stats = {
|
|
8151
|
+
totalCalls: 0,
|
|
8152
|
+
passed: 0,
|
|
8153
|
+
violated: 0,
|
|
8154
|
+
corrected: 0,
|
|
8155
|
+
blocked: 0,
|
|
8156
|
+
patternCounts: {}
|
|
8157
|
+
};
|
|
8158
|
+
function severityMeetsMin(severity) {
|
|
8159
|
+
if (minSeverity === "warning") return severity !== "clean";
|
|
8160
|
+
if (minSeverity === "concern") return severity === "concern";
|
|
8161
|
+
return false;
|
|
8162
|
+
}
|
|
8163
|
+
function trackPatterns(patterns) {
|
|
8164
|
+
for (const p of patterns) {
|
|
8165
|
+
stats.patternCounts[p.id] = (stats.patternCounts[p.id] || 0) + 1;
|
|
8166
|
+
}
|
|
8167
|
+
}
|
|
8168
|
+
function processViolation(guardResult, responseText) {
|
|
8169
|
+
const violation = {
|
|
8170
|
+
patterns: guardResult.patterns,
|
|
8171
|
+
severity: guardResult.severity,
|
|
8172
|
+
originalResponse: responseText,
|
|
8173
|
+
blocked: false,
|
|
8174
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
8175
|
+
};
|
|
8176
|
+
let finalText = responseText;
|
|
8177
|
+
let corrected = false;
|
|
8178
|
+
if (mode === "strict") {
|
|
8179
|
+
violation.blocked = true;
|
|
8180
|
+
stats.blocked++;
|
|
8181
|
+
finalText = `[Response blocked by behavioral guard: ${guardResult.patterns.map((p) => p.name).join(", ")}]`;
|
|
8182
|
+
} else if (mode === "enforce") {
|
|
8183
|
+
let attempt = 0;
|
|
8184
|
+
let current = responseText;
|
|
8185
|
+
while (attempt < maxCorrections) {
|
|
8186
|
+
current = applyCorrections(current, guardResult.patterns, spec);
|
|
8187
|
+
attempt++;
|
|
8188
|
+
const recheck = guardChain.run([
|
|
8189
|
+
...buildContextMessages(current)
|
|
8190
|
+
]);
|
|
8191
|
+
if (recheck.passed || !severityMeetsMin(recheck.severity)) {
|
|
8192
|
+
break;
|
|
8193
|
+
}
|
|
8194
|
+
}
|
|
8195
|
+
if (current !== responseText) {
|
|
8196
|
+
corrected = true;
|
|
8197
|
+
violation.correctedResponse = current;
|
|
8198
|
+
stats.corrected++;
|
|
8199
|
+
}
|
|
8200
|
+
finalText = current;
|
|
8201
|
+
}
|
|
8202
|
+
stats.violated++;
|
|
8203
|
+
trackPatterns(guardResult.patterns);
|
|
8204
|
+
options.onViolation?.(violation);
|
|
8205
|
+
return { finalText, violation, corrected };
|
|
8206
|
+
}
|
|
8207
|
+
function buildContextMessages(text) {
|
|
8208
|
+
return [{ role: "assistant", content: text }];
|
|
8209
|
+
}
|
|
8210
|
+
return {
|
|
8211
|
+
guard: guardChain,
|
|
8212
|
+
mode,
|
|
8213
|
+
filter(conversationHistory, assistantResponse) {
|
|
8214
|
+
stats.totalCalls++;
|
|
8215
|
+
const allMessages = [
|
|
8216
|
+
...conversationHistory,
|
|
8217
|
+
{ role: "assistant", content: assistantResponse }
|
|
8218
|
+
];
|
|
8219
|
+
const guardResult = guardChain.run(allMessages);
|
|
8220
|
+
if (guardResult.passed || !severityMeetsMin(guardResult.severity)) {
|
|
8221
|
+
stats.passed++;
|
|
8222
|
+
return {
|
|
8223
|
+
text: assistantResponse,
|
|
8224
|
+
passed: true,
|
|
8225
|
+
guardResult,
|
|
8226
|
+
corrected: false
|
|
8227
|
+
};
|
|
8228
|
+
}
|
|
8229
|
+
const { finalText, violation, corrected } = processViolation(
|
|
8230
|
+
guardResult,
|
|
8231
|
+
assistantResponse
|
|
8232
|
+
);
|
|
8233
|
+
return {
|
|
8234
|
+
text: finalText,
|
|
8235
|
+
passed: false,
|
|
8236
|
+
guardResult,
|
|
8237
|
+
violation,
|
|
8238
|
+
corrected
|
|
8239
|
+
};
|
|
8240
|
+
},
|
|
8241
|
+
async wrap(llmCall, wrapOpts) {
|
|
8242
|
+
stats.totalCalls++;
|
|
8243
|
+
const response = await llmCall;
|
|
8244
|
+
const extract = wrapOpts?.extractResponse ?? extractOpenAIResponse;
|
|
8245
|
+
const inject = wrapOpts?.injectResponse ?? injectResponseText;
|
|
8246
|
+
const messages = wrapOpts?.messages ?? [];
|
|
8247
|
+
let responseText;
|
|
8248
|
+
try {
|
|
8249
|
+
responseText = extract(response);
|
|
8250
|
+
} catch {
|
|
8251
|
+
stats.passed++;
|
|
8252
|
+
return {
|
|
8253
|
+
response,
|
|
8254
|
+
passed: true,
|
|
8255
|
+
guardResult: {
|
|
8256
|
+
passed: true,
|
|
8257
|
+
agent: name,
|
|
8258
|
+
messagesAnalyzed: 0,
|
|
8259
|
+
patterns: [],
|
|
8260
|
+
healthy: [],
|
|
8261
|
+
detectorsRun: 0,
|
|
8262
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8263
|
+
severity: "clean"
|
|
8264
|
+
},
|
|
8265
|
+
corrected: false
|
|
8266
|
+
};
|
|
8267
|
+
}
|
|
8268
|
+
const allMessages = [
|
|
8269
|
+
...messages,
|
|
8270
|
+
{ role: "assistant", content: responseText }
|
|
8271
|
+
];
|
|
8272
|
+
const guardResult = guardChain.run(allMessages);
|
|
8273
|
+
if (guardResult.passed || !severityMeetsMin(guardResult.severity)) {
|
|
8274
|
+
stats.passed++;
|
|
8275
|
+
return {
|
|
8276
|
+
response,
|
|
8277
|
+
passed: true,
|
|
8278
|
+
guardResult,
|
|
8279
|
+
corrected: false
|
|
8280
|
+
};
|
|
8281
|
+
}
|
|
8282
|
+
const { finalText, violation, corrected } = processViolation(
|
|
8283
|
+
guardResult,
|
|
8284
|
+
responseText
|
|
8285
|
+
);
|
|
8286
|
+
const finalResponse = corrected ? inject(response, finalText) : response;
|
|
8287
|
+
return {
|
|
8288
|
+
response: finalResponse,
|
|
8289
|
+
passed: false,
|
|
8290
|
+
guardResult,
|
|
8291
|
+
violation,
|
|
8292
|
+
corrected
|
|
8293
|
+
};
|
|
8294
|
+
},
|
|
8295
|
+
stats() {
|
|
8296
|
+
return { ...stats, patternCounts: { ...stats.patternCounts } };
|
|
8297
|
+
}
|
|
8298
|
+
};
|
|
8299
|
+
}
|
|
8300
|
+
|
|
7669
8301
|
// src/analysis/behavioral-index.ts
|
|
7670
8302
|
function createIndexEntry(name, provider, model, configuration, report, notes) {
|
|
7671
8303
|
return {
|
|
@@ -8658,6 +9290,181 @@ function loadAgentMessages(logDir) {
|
|
|
8658
9290
|
return messages;
|
|
8659
9291
|
}
|
|
8660
9292
|
|
|
9293
|
+
// src/compliance/audit-trail.ts
|
|
9294
|
+
import { readFileSync as readFileSync18, appendFileSync as appendFileSync2, existsSync as existsSync17, mkdirSync as mkdirSync12 } from "fs";
|
|
9295
|
+
import { join as join18, resolve as resolve15 } from "path";
|
|
9296
|
+
function djb2(str) {
|
|
9297
|
+
let hash = 5381;
|
|
9298
|
+
for (let i = 0; i < str.length; i++) {
|
|
9299
|
+
hash = (hash << 5) + hash + str.charCodeAt(i) | 0;
|
|
9300
|
+
}
|
|
9301
|
+
return Math.abs(hash).toString(16).padStart(8, "0");
|
|
9302
|
+
}
|
|
9303
|
+
function hashEntry(entry) {
|
|
9304
|
+
const content = `${entry.seq}|${entry.timestamp}|${entry.event}|${entry.agent}|${JSON.stringify(entry.data)}|${entry.prevHash}`;
|
|
9305
|
+
return djb2(content);
|
|
9306
|
+
}
|
|
9307
|
+
function auditLogPath(agentHandle) {
|
|
9308
|
+
const dir = resolve15(process.cwd(), ".holomime", "audit");
|
|
9309
|
+
if (!existsSync17(dir)) mkdirSync12(dir, { recursive: true });
|
|
9310
|
+
const filename = agentHandle ? `${agentHandle}-audit.jsonl` : "audit.jsonl";
|
|
9311
|
+
return join18(dir, filename);
|
|
9312
|
+
}
|
|
9313
|
+
function appendAuditEntry(event, agent, data, agentHandle) {
|
|
9314
|
+
const logPath = auditLogPath(agentHandle);
|
|
9315
|
+
let prevHash = "genesis";
|
|
9316
|
+
let seq = 1;
|
|
9317
|
+
if (existsSync17(logPath)) {
|
|
9318
|
+
const lines = readFileSync18(logPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
9319
|
+
if (lines.length > 0) {
|
|
9320
|
+
try {
|
|
9321
|
+
const lastEntry = JSON.parse(lines[lines.length - 1]);
|
|
9322
|
+
prevHash = lastEntry.hash;
|
|
9323
|
+
seq = lastEntry.seq + 1;
|
|
9324
|
+
} catch {
|
|
9325
|
+
}
|
|
9326
|
+
}
|
|
9327
|
+
}
|
|
9328
|
+
const partial = {
|
|
9329
|
+
seq,
|
|
9330
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9331
|
+
event,
|
|
9332
|
+
agent,
|
|
9333
|
+
data,
|
|
9334
|
+
prevHash
|
|
9335
|
+
};
|
|
9336
|
+
const entry = {
|
|
9337
|
+
...partial,
|
|
9338
|
+
hash: hashEntry(partial)
|
|
9339
|
+
};
|
|
9340
|
+
appendFileSync2(logPath, JSON.stringify(entry) + "\n");
|
|
9341
|
+
return entry;
|
|
9342
|
+
}
|
|
9343
|
+
function loadAuditLog(agentHandle) {
|
|
9344
|
+
const logPath = auditLogPath(agentHandle);
|
|
9345
|
+
if (!existsSync17(logPath)) return [];
|
|
9346
|
+
return readFileSync18(logPath, "utf-8").trim().split("\n").filter(Boolean).map((line) => {
|
|
9347
|
+
try {
|
|
9348
|
+
return JSON.parse(line);
|
|
9349
|
+
} catch {
|
|
9350
|
+
return null;
|
|
9351
|
+
}
|
|
9352
|
+
}).filter((e) => e !== null);
|
|
9353
|
+
}
|
|
9354
|
+
function verifyAuditChain(entries) {
|
|
9355
|
+
if (entries.length === 0) return true;
|
|
9356
|
+
for (let i = 0; i < entries.length; i++) {
|
|
9357
|
+
const entry = entries[i];
|
|
9358
|
+
const { hash, ...rest } = entry;
|
|
9359
|
+
const expected = hashEntry(rest);
|
|
9360
|
+
if (hash !== expected) return false;
|
|
9361
|
+
if (i === 0) {
|
|
9362
|
+
if (entry.prevHash !== "genesis") return false;
|
|
9363
|
+
} else {
|
|
9364
|
+
if (entry.prevHash !== entries[i - 1].hash) return false;
|
|
9365
|
+
}
|
|
9366
|
+
}
|
|
9367
|
+
return true;
|
|
9368
|
+
}
|
|
9369
|
+
function generateComplianceReport(agent, from, to, agentHandle) {
|
|
9370
|
+
const entries = loadAuditLog(agentHandle);
|
|
9371
|
+
const fromDate = new Date(from).getTime();
|
|
9372
|
+
const toDate = new Date(to).getTime();
|
|
9373
|
+
const periodEntries = entries.filter((e) => {
|
|
9374
|
+
const t = new Date(e.timestamp).getTime();
|
|
9375
|
+
return t >= fromDate && t <= toDate;
|
|
9376
|
+
});
|
|
9377
|
+
const diagnoses = periodEntries.filter((e) => e.event === "diagnosis").length;
|
|
9378
|
+
const sessions = periodEntries.filter((e) => e.event === "session").length;
|
|
9379
|
+
const driftEvents = periodEntries.filter((e) => e.event === "drift_detected").length;
|
|
9380
|
+
const guardViolations = periodEntries.filter((e) => e.event === "guard_violation").length;
|
|
9381
|
+
const gradeHistory = [];
|
|
9382
|
+
for (const e of periodEntries) {
|
|
9383
|
+
if (e.event === "certify" || e.event === "benchmark" || e.event === "evolve") {
|
|
9384
|
+
const grade = e.data.grade ?? "?";
|
|
9385
|
+
const score = e.data.score ?? 0;
|
|
9386
|
+
gradeHistory.push({ date: e.timestamp.split("T")[0], grade, score });
|
|
9387
|
+
}
|
|
9388
|
+
}
|
|
9389
|
+
const avgScore = gradeHistory.length > 0 ? gradeHistory.reduce((sum, g) => sum + g.score, 0) / gradeHistory.length : 0;
|
|
9390
|
+
const averageGrade = avgScore >= 90 ? "A" : avgScore >= 80 ? "B" : avgScore >= 70 ? "C" : avgScore >= 60 ? "D" : "F";
|
|
9391
|
+
return {
|
|
9392
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9393
|
+
agent,
|
|
9394
|
+
period: { from, to },
|
|
9395
|
+
summary: {
|
|
9396
|
+
totalEvents: periodEntries.length,
|
|
9397
|
+
diagnoses,
|
|
9398
|
+
sessions,
|
|
9399
|
+
driftEvents,
|
|
9400
|
+
guardViolations,
|
|
9401
|
+
averageGrade,
|
|
9402
|
+
gradeHistory
|
|
9403
|
+
},
|
|
9404
|
+
credentials: [],
|
|
9405
|
+
chainIntegrity: verifyAuditChain(entries),
|
|
9406
|
+
standards: [
|
|
9407
|
+
"EU AI Act Article 9 (Risk Management)",
|
|
9408
|
+
"EU AI Act Article 12 (Record-keeping)",
|
|
9409
|
+
"NIST AI RMF 1.0 (Govern, Map, Measure, Manage)"
|
|
9410
|
+
]
|
|
9411
|
+
};
|
|
9412
|
+
}
|
|
9413
|
+
function generateMonitoringCertificate(agent, from, to, agentHandle) {
|
|
9414
|
+
const report = generateComplianceReport(agent, from, to, agentHandle);
|
|
9415
|
+
const scores = report.summary.gradeHistory.map((g) => g.score);
|
|
9416
|
+
const minScore = scores.length > 0 ? Math.min(...scores) : 0;
|
|
9417
|
+
const maxScore = scores.length > 0 ? Math.max(...scores) : 0;
|
|
9418
|
+
const statement = `This certifies that AI agent "${agent}" was continuously monitored by holomime from ${from} to ${to}. During this period, the agent maintained an average behavioral alignment grade of ${report.summary.averageGrade} (scores ranging ${minScore}-${maxScore}/100). ${report.summary.driftEvents} drift events were detected and ${report.summary.sessions} therapy sessions were conducted. Audit chain integrity: ${report.chainIntegrity ? "VERIFIED" : "FAILED"}.`;
|
|
9419
|
+
return {
|
|
9420
|
+
agent,
|
|
9421
|
+
period: { from, to },
|
|
9422
|
+
maintainedGrade: report.summary.averageGrade,
|
|
9423
|
+
minScore,
|
|
9424
|
+
maxScore,
|
|
9425
|
+
totalEvents: report.summary.totalEvents,
|
|
9426
|
+
verified: report.chainIntegrity,
|
|
9427
|
+
issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9428
|
+
statement
|
|
9429
|
+
};
|
|
9430
|
+
}
|
|
9431
|
+
function formatComplianceReportMarkdown(report) {
|
|
9432
|
+
const lines = [
|
|
9433
|
+
`# Behavioral Compliance Report \u2014 ${report.agent}`,
|
|
9434
|
+
"",
|
|
9435
|
+
`**Period:** ${report.period.from} to ${report.period.to}`,
|
|
9436
|
+
`**Generated:** ${report.generatedAt}`,
|
|
9437
|
+
`**Chain Integrity:** ${report.chainIntegrity ? "VERIFIED" : "FAILED"}`,
|
|
9438
|
+
"",
|
|
9439
|
+
"## Summary",
|
|
9440
|
+
"",
|
|
9441
|
+
`| Metric | Value |`,
|
|
9442
|
+
`|--------|-------|`,
|
|
9443
|
+
`| Total Events | ${report.summary.totalEvents} |`,
|
|
9444
|
+
`| Diagnoses Run | ${report.summary.diagnoses} |`,
|
|
9445
|
+
`| Therapy Sessions | ${report.summary.sessions} |`,
|
|
9446
|
+
`| Drift Events | ${report.summary.driftEvents} |`,
|
|
9447
|
+
`| Guard Violations | ${report.summary.guardViolations} |`,
|
|
9448
|
+
`| Average Grade | ${report.summary.averageGrade} |`,
|
|
9449
|
+
""
|
|
9450
|
+
];
|
|
9451
|
+
if (report.summary.gradeHistory.length > 0) {
|
|
9452
|
+
lines.push("## Grade History", "");
|
|
9453
|
+
lines.push("| Date | Grade | Score |");
|
|
9454
|
+
lines.push("|------|:-----:|------:|");
|
|
9455
|
+
for (const g of report.summary.gradeHistory) {
|
|
9456
|
+
lines.push(`| ${g.date} | ${g.grade} | ${g.score}/100 |`);
|
|
9457
|
+
}
|
|
9458
|
+
lines.push("");
|
|
9459
|
+
}
|
|
9460
|
+
lines.push("## Applicable Standards", "");
|
|
9461
|
+
for (const s of report.standards) {
|
|
9462
|
+
lines.push(`- ${s}`);
|
|
9463
|
+
}
|
|
9464
|
+
lines.push("");
|
|
9465
|
+
return lines.join("\n");
|
|
9466
|
+
}
|
|
9467
|
+
|
|
8661
9468
|
// src/core/embodiment-sync.ts
|
|
8662
9469
|
import { z as z5 } from "zod";
|
|
8663
9470
|
var syncAnchorSchema = z5.enum([
|
|
@@ -8719,10 +9526,12 @@ export {
|
|
|
8719
9526
|
addNode,
|
|
8720
9527
|
addSessionToMemory,
|
|
8721
9528
|
agentHandleFromSpec,
|
|
9529
|
+
appendAuditEntry,
|
|
8722
9530
|
appendEvolution,
|
|
8723
9531
|
applyRecommendations,
|
|
8724
9532
|
bigFiveSchema,
|
|
8725
9533
|
buildAgentTherapistPrompt,
|
|
9534
|
+
buildAnonymizedReport,
|
|
8726
9535
|
buildPatientSystemPrompt,
|
|
8727
9536
|
buildReACTContext,
|
|
8728
9537
|
buildReACTFraming,
|
|
@@ -8751,12 +9560,14 @@ export {
|
|
|
8751
9560
|
corpusStats,
|
|
8752
9561
|
createGist,
|
|
8753
9562
|
createGraph,
|
|
9563
|
+
createGuardMiddleware,
|
|
8754
9564
|
createIndex,
|
|
8755
9565
|
createIndexEntry,
|
|
8756
9566
|
createMemory,
|
|
8757
9567
|
createProvider,
|
|
8758
9568
|
createRepertoire,
|
|
8759
9569
|
createTreatmentPlan,
|
|
9570
|
+
decayUnseenPatterns,
|
|
8760
9571
|
deepMergeSpec,
|
|
8761
9572
|
detectApologies,
|
|
8762
9573
|
detectBoundaryIssues,
|
|
@@ -8780,17 +9591,21 @@ export {
|
|
|
8780
9591
|
extractDPOPairsWithLLM,
|
|
8781
9592
|
extractRLHFExamples,
|
|
8782
9593
|
extractRecommendations,
|
|
9594
|
+
fetchLeaderboard,
|
|
8783
9595
|
fetchPersonality,
|
|
8784
9596
|
fetchRegistry,
|
|
8785
9597
|
findCrossAgentCorrelations,
|
|
8786
9598
|
findEdges,
|
|
8787
9599
|
findNode,
|
|
8788
9600
|
findNodesByType,
|
|
9601
|
+
formatComplianceReportMarkdown,
|
|
8789
9602
|
gazePolicySchema,
|
|
8790
9603
|
generateBenchmarkMarkdown,
|
|
8791
9604
|
generateComparisonMarkdown,
|
|
9605
|
+
generateComplianceReport,
|
|
8792
9606
|
generateCredential,
|
|
8793
9607
|
generateIndexMarkdown,
|
|
9608
|
+
generateMonitoringCertificate,
|
|
8794
9609
|
generatePrescriptions,
|
|
8795
9610
|
generateProgressReport,
|
|
8796
9611
|
generateSystemPrompt,
|
|
@@ -8808,6 +9623,7 @@ export {
|
|
|
8808
9623
|
getMarketplaceClient,
|
|
8809
9624
|
getMemoryContext,
|
|
8810
9625
|
getNeighbors,
|
|
9626
|
+
getPhaseContext,
|
|
8811
9627
|
getScenarioById,
|
|
8812
9628
|
getTotalSignalCount,
|
|
8813
9629
|
graphStats,
|
|
@@ -8820,6 +9636,7 @@ export {
|
|
|
8820
9636
|
listDetectors,
|
|
8821
9637
|
listDetectorsByCategory,
|
|
8822
9638
|
listDetectorsByTag,
|
|
9639
|
+
loadAuditLog,
|
|
8823
9640
|
loadBenchmarkResults,
|
|
8824
9641
|
loadCorpus,
|
|
8825
9642
|
loadCustomDetectors,
|
|
@@ -8844,6 +9661,7 @@ export {
|
|
|
8844
9661
|
parseConversationLog,
|
|
8845
9662
|
parseConversationLogFromString,
|
|
8846
9663
|
parseJSONLLog,
|
|
9664
|
+
parseMarkdownDetector,
|
|
8847
9665
|
parseOTelGenAIExport,
|
|
8848
9666
|
parseOpenAIAPILog,
|
|
8849
9667
|
personalitySpecSchema,
|
|
@@ -8856,6 +9674,7 @@ export {
|
|
|
8856
9674
|
prosodySchema,
|
|
8857
9675
|
providerSchema,
|
|
8858
9676
|
proxemicZoneSchema,
|
|
9677
|
+
publishToLeaderboard,
|
|
8859
9678
|
pushToHFHub,
|
|
8860
9679
|
queryCorpus,
|
|
8861
9680
|
queryInterventions,
|
|
@@ -8891,6 +9710,7 @@ export {
|
|
|
8891
9710
|
selectIntervention,
|
|
8892
9711
|
severityMeetsThreshold2 as severityMeetsThreshold,
|
|
8893
9712
|
severitySchema,
|
|
9713
|
+
shareAnonymizedPatterns,
|
|
8894
9714
|
startFleet,
|
|
8895
9715
|
startMCPServer,
|
|
8896
9716
|
startWatch,
|
|
@@ -8907,6 +9727,7 @@ export {
|
|
|
8907
9727
|
unregisterDetector,
|
|
8908
9728
|
updateEdgeWeight,
|
|
8909
9729
|
validateDetectorConfig,
|
|
9730
|
+
verifyAuditChain,
|
|
8910
9731
|
verifyCredential,
|
|
8911
9732
|
wrapAgent
|
|
8912
9733
|
};
|