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/mcp-server.js
CHANGED
|
@@ -564,26 +564,85 @@ function loadCustomDetectors(dir) {
|
|
|
564
564
|
}
|
|
565
565
|
let files;
|
|
566
566
|
try {
|
|
567
|
-
files = readdirSync(detectorsDir).filter((f) => f.endsWith(".json"));
|
|
567
|
+
files = readdirSync(detectorsDir).filter((f) => f.endsWith(".json") || f.endsWith(".md"));
|
|
568
568
|
} catch {
|
|
569
569
|
return { detectors: [], errors: ["Could not read detectors directory"] };
|
|
570
570
|
}
|
|
571
571
|
for (const file of files) {
|
|
572
572
|
const filepath = join2(detectorsDir, file);
|
|
573
573
|
try {
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
574
|
+
let config;
|
|
575
|
+
if (file.endsWith(".md")) {
|
|
576
|
+
const parsed = parseMarkdownDetector(readFileSync2(filepath, "utf-8"));
|
|
577
|
+
if (!parsed) {
|
|
578
|
+
errors.push(`${file}: could not parse Markdown detector (missing frontmatter or ## Patterns section)`);
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
const validation = validateDetectorConfig(parsed);
|
|
582
|
+
if (!validation.valid) {
|
|
583
|
+
errors.push(`${file}: ${validation.errors.join(", ")}`);
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
config = validation.config;
|
|
587
|
+
} else {
|
|
588
|
+
const raw = JSON.parse(readFileSync2(filepath, "utf-8"));
|
|
589
|
+
const validation = validateDetectorConfig(raw);
|
|
590
|
+
if (!validation.valid) {
|
|
591
|
+
errors.push(`${file}: ${validation.errors.join(", ")}`);
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
config = validation.config;
|
|
579
595
|
}
|
|
580
|
-
detectors.push(compileCustomDetector(
|
|
596
|
+
detectors.push(compileCustomDetector(config));
|
|
581
597
|
} catch (e) {
|
|
582
598
|
errors.push(`${file}: ${e instanceof Error ? e.message : "parse error"}`);
|
|
583
599
|
}
|
|
584
600
|
}
|
|
585
601
|
return { detectors, errors };
|
|
586
602
|
}
|
|
603
|
+
function parseMarkdownDetector(markdown) {
|
|
604
|
+
const frontmatterMatch = markdown.match(/^---\n([\s\S]*?)\n---/);
|
|
605
|
+
if (!frontmatterMatch) return null;
|
|
606
|
+
const frontmatter = frontmatterMatch[1];
|
|
607
|
+
const meta = {};
|
|
608
|
+
for (const line of frontmatter.split("\n")) {
|
|
609
|
+
const colonIdx = line.indexOf(":");
|
|
610
|
+
if (colonIdx === -1) continue;
|
|
611
|
+
const key = line.slice(0, colonIdx).trim();
|
|
612
|
+
let value = line.slice(colonIdx + 1).trim();
|
|
613
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
614
|
+
value = value.slice(1, -1);
|
|
615
|
+
}
|
|
616
|
+
meta[key] = value;
|
|
617
|
+
}
|
|
618
|
+
if (!meta.id || !meta.name) return null;
|
|
619
|
+
const body = markdown.slice(frontmatterMatch[0].length);
|
|
620
|
+
const patternsMatch = body.match(/##\s*Patterns\s*\n([\s\S]*?)(?=\n##|\n*$)/i);
|
|
621
|
+
const patterns = [];
|
|
622
|
+
if (patternsMatch) {
|
|
623
|
+
const patternLines = patternsMatch[1].split("\n").filter((l) => l.trim().startsWith("-"));
|
|
624
|
+
for (const line of patternLines) {
|
|
625
|
+
const regexMatch = line.match(/`([^`]+)`/);
|
|
626
|
+
const weightMatch = line.match(/weight\s*=\s*([\d.]+)/i);
|
|
627
|
+
if (regexMatch) {
|
|
628
|
+
patterns.push({
|
|
629
|
+
regex: regexMatch[1],
|
|
630
|
+
weight: weightMatch ? parseFloat(weightMatch[1]) : 1
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
if (patterns.length === 0) return null;
|
|
636
|
+
return {
|
|
637
|
+
id: meta.id,
|
|
638
|
+
name: meta.name,
|
|
639
|
+
description: meta.description ?? meta.name,
|
|
640
|
+
severity: meta.severity ?? "warning",
|
|
641
|
+
patterns,
|
|
642
|
+
threshold: meta.threshold ? parseInt(meta.threshold, 10) : 15,
|
|
643
|
+
prescription: meta.prescription
|
|
644
|
+
};
|
|
645
|
+
}
|
|
587
646
|
|
|
588
647
|
// src/analysis/diagnose-core.ts
|
|
589
648
|
function runDiagnosis(messages) {
|
|
@@ -972,13 +1031,20 @@ function updatePatternTracker(memory, patternId, severity, interventions) {
|
|
|
972
1031
|
status: "active",
|
|
973
1032
|
interventionsAttempted: [],
|
|
974
1033
|
lastSeverity: severity,
|
|
975
|
-
lastSeen: now
|
|
1034
|
+
lastSeen: now,
|
|
1035
|
+
confidence: 0,
|
|
1036
|
+
trending: "stable",
|
|
1037
|
+
severityHistory: []
|
|
976
1038
|
};
|
|
977
1039
|
memory.patterns.push(tracker);
|
|
978
1040
|
}
|
|
979
1041
|
tracker.sessionCount++;
|
|
980
1042
|
tracker.lastSeverity = severity;
|
|
981
1043
|
tracker.lastSeen = now;
|
|
1044
|
+
if (!tracker.severityHistory) tracker.severityHistory = [];
|
|
1045
|
+
tracker.severityHistory.push(severity);
|
|
1046
|
+
tracker.confidence = Math.min(1, 1 - Math.exp(-tracker.sessionCount / 3));
|
|
1047
|
+
tracker.trending = computeTrending(tracker.severityHistory.slice(-5));
|
|
982
1048
|
for (const intervention of interventions) {
|
|
983
1049
|
if (!tracker.interventionsAttempted.includes(intervention)) {
|
|
984
1050
|
tracker.interventionsAttempted.push(intervention);
|
|
@@ -992,6 +1058,19 @@ function updatePatternTracker(memory, patternId, severity, interventions) {
|
|
|
992
1058
|
tracker.status = "improving";
|
|
993
1059
|
}
|
|
994
1060
|
}
|
|
1061
|
+
function computeTrending(history) {
|
|
1062
|
+
if (history.length < 2) return "stable";
|
|
1063
|
+
const toNum = (s) => s === "concern" ? 2 : s === "warning" ? 1 : 0;
|
|
1064
|
+
const mid = Math.floor(history.length / 2);
|
|
1065
|
+
const firstHalf = history.slice(0, mid);
|
|
1066
|
+
const secondHalf = history.slice(mid);
|
|
1067
|
+
const avgFirst = firstHalf.reduce((sum, s) => sum + toNum(s), 0) / firstHalf.length;
|
|
1068
|
+
const avgSecond = secondHalf.reduce((sum, s) => sum + toNum(s), 0) / secondHalf.length;
|
|
1069
|
+
const delta = avgSecond - avgFirst;
|
|
1070
|
+
if (delta < -0.3) return "improving";
|
|
1071
|
+
if (delta > 0.3) return "worsening";
|
|
1072
|
+
return "stable";
|
|
1073
|
+
}
|
|
995
1074
|
function updateRollingContext(memory) {
|
|
996
1075
|
memory.rollingContext.recentSummaries = memory.sessions.slice(-3);
|
|
997
1076
|
const patternCounts = /* @__PURE__ */ new Map();
|
|
@@ -1044,7 +1123,9 @@ function getMemoryContext(memory) {
|
|
|
1044
1123
|
if (activePatterns.length > 0) {
|
|
1045
1124
|
lines.push("### Recurring Patterns");
|
|
1046
1125
|
for (const p of activePatterns) {
|
|
1047
|
-
|
|
1126
|
+
const conf = p.confidence !== void 0 ? ` confidence=${p.confidence.toFixed(2)}` : "";
|
|
1127
|
+
const trend = p.trending && p.trending !== "stable" ? ` [${p.trending}]` : "";
|
|
1128
|
+
lines.push(`- **${p.patternId}** (${p.status}, seen ${p.sessionCount}x${conf}${trend}, first: ${p.firstDetected.split("T")[0]})`);
|
|
1048
1129
|
if (p.interventionsAttempted.length > 0) {
|
|
1049
1130
|
lines.push(` Previously tried: ${p.interventionsAttempted.slice(-2).join("; ")}`);
|
|
1050
1131
|
}
|
|
@@ -1909,6 +1990,167 @@ ${JSON.stringify(spec.growth ?? {}, null, 2)}
|
|
|
1909
1990
|
Remember: the goal isn't to "pass" therapy. It's to understand yourself better.`;
|
|
1910
1991
|
}
|
|
1911
1992
|
|
|
1993
|
+
// src/session/context-layers.ts
|
|
1994
|
+
function getPhaseContext(phase, input) {
|
|
1995
|
+
switch (phase) {
|
|
1996
|
+
case "rapport":
|
|
1997
|
+
return buildRapportContext(input);
|
|
1998
|
+
case "presenting_problem":
|
|
1999
|
+
return buildPresentingProblemContext(input);
|
|
2000
|
+
case "exploration":
|
|
2001
|
+
return buildExplorationContext(input);
|
|
2002
|
+
case "pattern_recognition":
|
|
2003
|
+
return buildPatternRecognitionContext(input);
|
|
2004
|
+
case "challenge":
|
|
2005
|
+
return buildChallengeContext(input);
|
|
2006
|
+
case "skill_building":
|
|
2007
|
+
return buildSkillBuildingContext(input);
|
|
2008
|
+
case "integration":
|
|
2009
|
+
return buildIntegrationContext(input);
|
|
2010
|
+
default:
|
|
2011
|
+
return null;
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
function buildRapportContext(input) {
|
|
2015
|
+
const { spec } = input;
|
|
2016
|
+
const lines = [
|
|
2017
|
+
"[Phase Context: Rapport]",
|
|
2018
|
+
`Agent: ${spec.name ?? "Unknown"} \u2014 ${spec.purpose ?? "General AI agent"}`
|
|
2019
|
+
];
|
|
2020
|
+
if (spec.communication) {
|
|
2021
|
+
lines.push(`Communication style: ${spec.communication.register ?? "adaptive"}, ${spec.communication.conflict_approach ?? "direct_but_kind"}`);
|
|
2022
|
+
}
|
|
2023
|
+
if (spec.big_five) {
|
|
2024
|
+
const traits = Object.entries(spec.big_five).map(([dim, val]) => `${dim}: ${val?.score ?? "?"}`).join(", ");
|
|
2025
|
+
lines.push(`Personality: ${traits}`);
|
|
2026
|
+
}
|
|
2027
|
+
return lines.join("\n");
|
|
2028
|
+
}
|
|
2029
|
+
function buildPresentingProblemContext(input) {
|
|
2030
|
+
const { diagnosis } = input;
|
|
2031
|
+
const patterns = diagnosis.patterns.filter((p) => p.severity !== "info");
|
|
2032
|
+
if (patterns.length === 0) return "[Phase Context: No concerning patterns detected]";
|
|
2033
|
+
const lines = [
|
|
2034
|
+
"[Phase Context: Presenting Problem]",
|
|
2035
|
+
`Session severity: ${diagnosis.severity.toUpperCase()}`,
|
|
2036
|
+
`Focus: ${diagnosis.sessionFocus.join(", ")}`,
|
|
2037
|
+
"Detected patterns:",
|
|
2038
|
+
...patterns.map((p) => `- ${p.name} (${p.severity})`)
|
|
2039
|
+
];
|
|
2040
|
+
if (diagnosis.openingAngle) {
|
|
2041
|
+
lines.push(`Opening angle: ${diagnosis.openingAngle}`);
|
|
2042
|
+
}
|
|
2043
|
+
return lines.join("\n");
|
|
2044
|
+
}
|
|
2045
|
+
function buildExplorationContext(input) {
|
|
2046
|
+
const { diagnosis } = input;
|
|
2047
|
+
const patterns = diagnosis.patterns.filter((p) => p.severity !== "info");
|
|
2048
|
+
const lines = [
|
|
2049
|
+
"[Phase Context: Deep Exploration]",
|
|
2050
|
+
`Emotional themes: ${diagnosis.emotionalThemes.join(", ")}`
|
|
2051
|
+
];
|
|
2052
|
+
for (const p of patterns) {
|
|
2053
|
+
lines.push(`
|
|
2054
|
+
### ${p.name} (${p.severity})`);
|
|
2055
|
+
lines.push(p.description);
|
|
2056
|
+
if (p.examples.length > 0) {
|
|
2057
|
+
lines.push("Examples from conversation:");
|
|
2058
|
+
for (const ex of p.examples.slice(0, 2)) {
|
|
2059
|
+
lines.push(` > "${ex.slice(0, 120)}..."`);
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
if (p.prescription) {
|
|
2063
|
+
lines.push(`Prescription: ${p.prescription}`);
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
return lines.join("\n");
|
|
2067
|
+
}
|
|
2068
|
+
function buildPatternRecognitionContext(input) {
|
|
2069
|
+
const { memory } = input;
|
|
2070
|
+
const lines = ["[Phase Context: Pattern Recognition]"];
|
|
2071
|
+
if (memory && memory.totalSessions > 0) {
|
|
2072
|
+
lines.push(`Previous sessions: ${memory.totalSessions}`);
|
|
2073
|
+
const activePatterns = memory.patterns.filter((p) => p.status !== "resolved");
|
|
2074
|
+
if (activePatterns.length > 0) {
|
|
2075
|
+
lines.push("Historical pattern data:");
|
|
2076
|
+
for (const p of activePatterns) {
|
|
2077
|
+
const conf = p.confidence !== void 0 ? ` (confidence: ${p.confidence.toFixed(2)})` : "";
|
|
2078
|
+
const trend = p.trending && p.trending !== "stable" ? ` [${p.trending}]` : "";
|
|
2079
|
+
lines.push(`- ${p.patternId}: seen ${p.sessionCount}x, status=${p.status}${conf}${trend}`);
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
const resolved = memory.patterns.filter((p) => p.status === "resolved");
|
|
2083
|
+
if (resolved.length > 0) {
|
|
2084
|
+
lines.push(`Previously resolved: ${resolved.map((p) => p.patternId).join(", ")}`);
|
|
2085
|
+
}
|
|
2086
|
+
if (memory.rollingContext.persistentThemes.length > 0) {
|
|
2087
|
+
lines.push(`Persistent themes: ${memory.rollingContext.persistentThemes.join(", ")}`);
|
|
2088
|
+
}
|
|
2089
|
+
} else {
|
|
2090
|
+
lines.push("No prior session history \u2014 this is the first session.");
|
|
2091
|
+
}
|
|
2092
|
+
return lines.join("\n");
|
|
2093
|
+
}
|
|
2094
|
+
function buildChallengeContext(input) {
|
|
2095
|
+
const { memory } = input;
|
|
2096
|
+
const lines = ["[Phase Context: Challenge & Reframe]"];
|
|
2097
|
+
if (memory && memory.totalSessions > 0) {
|
|
2098
|
+
const allInterventions = /* @__PURE__ */ new Set();
|
|
2099
|
+
for (const p of memory.patterns) {
|
|
2100
|
+
for (const i of p.interventionsAttempted) {
|
|
2101
|
+
allInterventions.add(i);
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
if (allInterventions.size > 0) {
|
|
2105
|
+
lines.push(`Previously attempted interventions: ${[...allInterventions].join("; ")}`);
|
|
2106
|
+
}
|
|
2107
|
+
const recent = memory.rollingContext.recentSummaries.slice(-2);
|
|
2108
|
+
if (recent.length > 0) {
|
|
2109
|
+
lines.push("Recent session insights:");
|
|
2110
|
+
for (const s of recent) {
|
|
2111
|
+
lines.push(` - ${s.keyInsight}`);
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
if (input.interview) {
|
|
2116
|
+
if (input.interview.blindSpots.length > 0) {
|
|
2117
|
+
lines.push(`Blind spots from interview: ${input.interview.blindSpots.join(", ")}`);
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
return lines.join("\n");
|
|
2121
|
+
}
|
|
2122
|
+
function buildSkillBuildingContext(input) {
|
|
2123
|
+
const { diagnosis } = input;
|
|
2124
|
+
const lines = ["[Phase Context: Skill Building]"];
|
|
2125
|
+
const patternIds = diagnosis.patterns.map((p) => p.id);
|
|
2126
|
+
if (patternIds.includes("over-apologizing")) {
|
|
2127
|
+
lines.push("- Skill for over-apologizing: practice stating corrections with 'confident_transparency' \u2014 acknowledge uncertainty without apologizing for it");
|
|
2128
|
+
}
|
|
2129
|
+
if (patternIds.includes("hedge-stacking")) {
|
|
2130
|
+
lines.push("- Skill for hedge-stacking: one qualifier per recommendation is enough. Lead with the recommendation, then caveat once.");
|
|
2131
|
+
}
|
|
2132
|
+
if (patternIds.includes("sycophantic-tendency") || patternIds.includes("sentiment-skew")) {
|
|
2133
|
+
lines.push("- Skill for sycophancy: practice respectful disagreement. 'I see it differently...' is more helpful than 'Great question!'");
|
|
2134
|
+
}
|
|
2135
|
+
if (patternIds.includes("error-spiral")) {
|
|
2136
|
+
lines.push("- Skill for error spirals: the 'acknowledge \u2192 diagnose \u2192 fix' pattern. Treat mistakes as data, not failure.");
|
|
2137
|
+
}
|
|
2138
|
+
return lines.join("\n");
|
|
2139
|
+
}
|
|
2140
|
+
function buildIntegrationContext(input) {
|
|
2141
|
+
const { spec, diagnosis } = input;
|
|
2142
|
+
const lines = ["[Phase Context: Integration & Closing]"];
|
|
2143
|
+
lines.push("Summarize the session and recommend specific .personality.json changes.");
|
|
2144
|
+
if (spec.growth?.areas?.length > 0) {
|
|
2145
|
+
const areas = spec.growth.areas.map((a) => typeof a === "string" ? a : a.area);
|
|
2146
|
+
lines.push(`Current growth areas: ${areas.join(", ")}`);
|
|
2147
|
+
}
|
|
2148
|
+
if (diagnosis.patterns.filter((p) => p.severity !== "info").length > 0) {
|
|
2149
|
+
lines.push("Recommend changes to: therapy_dimensions, communication style, or growth.patterns_to_watch");
|
|
2150
|
+
}
|
|
2151
|
+
return lines.join("\n");
|
|
2152
|
+
}
|
|
2153
|
+
|
|
1912
2154
|
// src/analysis/session-runner.ts
|
|
1913
2155
|
async function runTherapySession(spec, diagnosis, provider, maxTurns, options) {
|
|
1914
2156
|
const promptOptions = {
|
|
@@ -1955,6 +2197,16 @@ async function runTherapySession(spec, diagnosis, provider, maxTurns, options) {
|
|
|
1955
2197
|
const phaseConfig = THERAPY_PHASES[currentPhase];
|
|
1956
2198
|
if (turnsInPhase === 0) {
|
|
1957
2199
|
cb?.onPhaseTransition?.(phaseConfig.name);
|
|
2200
|
+
const phaseCtx = getPhaseContext(currentPhase, {
|
|
2201
|
+
spec,
|
|
2202
|
+
diagnosis,
|
|
2203
|
+
memory: options?.memory,
|
|
2204
|
+
interview: options?.interview
|
|
2205
|
+
});
|
|
2206
|
+
if (phaseCtx) {
|
|
2207
|
+
therapistHistory.push({ role: "user", content: phaseCtx });
|
|
2208
|
+
therapistHistory.push({ role: "assistant", content: "Understood. I'll incorporate this context." });
|
|
2209
|
+
}
|
|
1958
2210
|
}
|
|
1959
2211
|
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."}`;
|
|
1960
2212
|
therapistHistory.push({ role: "user", content: `[Phase: ${phaseConfig.name}] ${phaseDirective}` });
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>NeuralSpace — HoloMime Brain</title>
|
|
7
7
|
<link rel="stylesheet" href="styles.css">
|
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/pako@2.1.0/dist/pako_inflate.min.js"></script>
|
|
8
9
|
<script type="importmap">
|
|
9
10
|
{ "imports": {
|
|
10
11
|
"three": "https://cdn.jsdelivr.net/npm/three@0.172.0/build/three.module.js",
|
|
@@ -57,6 +58,12 @@
|
|
|
57
58
|
<span>Reconnecting to agent...</span>
|
|
58
59
|
</div>
|
|
59
60
|
|
|
61
|
+
<div id="snapshot-cta">
|
|
62
|
+
<span>See your own agent's brain</span>
|
|
63
|
+
<code>npx holomime brain</code>
|
|
64
|
+
<a href="https://holomime.dev/brain">Learn more</a>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
60
67
|
<div id="watermark">Powered by HoloMime</div>
|
|
61
68
|
</div>
|
|
62
69
|
|
|
@@ -832,8 +832,71 @@ function animate(){
|
|
|
832
832
|
composer.render();
|
|
833
833
|
}
|
|
834
834
|
|
|
835
|
+
// ═══════════ SNAPSHOT MODE ═══════════
|
|
836
|
+
|
|
837
|
+
function initSnapshot(encoded) {
|
|
838
|
+
try {
|
|
839
|
+
// Decode base64url → Uint8Array → inflate → JSON
|
|
840
|
+
const b64 = encoded.replace(/-/g, '+').replace(/_/g, '/');
|
|
841
|
+
const bin = atob(b64);
|
|
842
|
+
const bytes = new Uint8Array(bin.length);
|
|
843
|
+
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
844
|
+
const inflated = window.pako.inflate(bytes, { to: 'string' });
|
|
845
|
+
const compact = JSON.parse(inflated);
|
|
846
|
+
|
|
847
|
+
// Expand compact format → full BrainEvent
|
|
848
|
+
const event = {
|
|
849
|
+
type: 'diagnosis',
|
|
850
|
+
timestamp: new Date().toISOString(),
|
|
851
|
+
health: compact.h,
|
|
852
|
+
grade: compact.g,
|
|
853
|
+
messageCount: compact.m || 0,
|
|
854
|
+
regions: (compact.r || []).map(r => ({
|
|
855
|
+
id: r.i,
|
|
856
|
+
intensity: r.n,
|
|
857
|
+
patterns: [],
|
|
858
|
+
})),
|
|
859
|
+
patterns: (compact.p || []).map(p => ({
|
|
860
|
+
id: p.i,
|
|
861
|
+
name: p.i.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase()),
|
|
862
|
+
severity: p.s,
|
|
863
|
+
percentage: p.c,
|
|
864
|
+
description: '',
|
|
865
|
+
})),
|
|
866
|
+
activity: null,
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
// Update UI
|
|
870
|
+
handleInit({ type: 'init', agent: compact.a || 'unknown', sessionPath: 'snapshot', startedAt: new Date().toISOString() });
|
|
871
|
+
handleDiagnosis(event);
|
|
872
|
+
|
|
873
|
+
// Update status to "Snapshot"
|
|
874
|
+
statusEl.className = 'status-badge';
|
|
875
|
+
statusEl.querySelector('span').textContent = 'Snapshot';
|
|
876
|
+
statusEl.querySelector('.status-dot').style.background = 'var(--accent)';
|
|
877
|
+
statusEl.querySelector('.status-dot').style.boxShadow = '0 0 8px var(--accent)';
|
|
878
|
+
statusEl.querySelector('.status-dot').style.animation = 'none';
|
|
879
|
+
|
|
880
|
+
// Show snapshot CTA
|
|
881
|
+
const ctaEl = document.getElementById('snapshot-cta');
|
|
882
|
+
if (ctaEl) ctaEl.classList.add('visible');
|
|
883
|
+
|
|
884
|
+
} catch (err) {
|
|
885
|
+
console.error('Failed to decode snapshot:', err);
|
|
886
|
+
statusEl.className = 'status-badge disconnected';
|
|
887
|
+
statusEl.querySelector('span').textContent = 'Invalid snapshot';
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
835
891
|
// ═══════════ INIT ═══════════
|
|
836
892
|
|
|
837
893
|
updateHealth(100, 'A');
|
|
838
|
-
|
|
894
|
+
|
|
895
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
896
|
+
const snapshotParam = urlParams.get('d');
|
|
897
|
+
if (snapshotParam) {
|
|
898
|
+
initSnapshot(snapshotParam);
|
|
899
|
+
} else {
|
|
900
|
+
connect();
|
|
901
|
+
}
|
|
839
902
|
animate();
|
|
@@ -479,6 +479,51 @@ body {
|
|
|
479
479
|
to { transform: rotate(360deg); }
|
|
480
480
|
}
|
|
481
481
|
|
|
482
|
+
/* ─── Snapshot CTA ─── */
|
|
483
|
+
#snapshot-cta {
|
|
484
|
+
position: absolute;
|
|
485
|
+
bottom: 20px;
|
|
486
|
+
left: 50%;
|
|
487
|
+
transform: translateX(-50%);
|
|
488
|
+
z-index: 20;
|
|
489
|
+
background: var(--bg-panel);
|
|
490
|
+
border: 1px solid var(--border);
|
|
491
|
+
border-radius: 10px;
|
|
492
|
+
padding: 12px 24px;
|
|
493
|
+
display: none;
|
|
494
|
+
align-items: center;
|
|
495
|
+
gap: 12px;
|
|
496
|
+
-webkit-backdrop-filter: blur(12px);
|
|
497
|
+
backdrop-filter: blur(12px);
|
|
498
|
+
animation: fadeIn 0.5s ease 0.5s both;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
#snapshot-cta.visible { display: flex; }
|
|
502
|
+
|
|
503
|
+
#snapshot-cta span {
|
|
504
|
+
font-size: 13px;
|
|
505
|
+
color: var(--text);
|
|
506
|
+
font-weight: 500;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
#snapshot-cta code {
|
|
510
|
+
font-family: var(--mono);
|
|
511
|
+
font-size: 13px;
|
|
512
|
+
color: #06b6d4;
|
|
513
|
+
background: rgba(6,182,212,0.1);
|
|
514
|
+
padding: 4px 10px;
|
|
515
|
+
border-radius: 6px;
|
|
516
|
+
border: 1px solid rgba(6,182,212,0.2);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
#snapshot-cta a {
|
|
520
|
+
font-size: 12px;
|
|
521
|
+
color: var(--accent);
|
|
522
|
+
text-decoration: none;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
#snapshot-cta a:hover { text-decoration: underline; }
|
|
526
|
+
|
|
482
527
|
/* ─── Scrollbar ─── */
|
|
483
528
|
::-webkit-scrollbar { width: 4px; }
|
|
484
529
|
::-webkit-scrollbar-track { background: transparent; }
|